@adhdev/daemon-core 0.7.46 → 0.8.1
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 +1634 -191
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1634 -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/agent-stream/poller.ts +9 -0
- 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];
|
|
@@ -12258,6 +12710,15 @@ var AgentStreamPoller = class {
|
|
|
12258
12710
|
cdpManagerKey: ideType,
|
|
12259
12711
|
instanceKey: `ide:${ideType}`
|
|
12260
12712
|
});
|
|
12713
|
+
const activeSessionId2 = agentStreamManager.getActiveSessionId(parentSessionId);
|
|
12714
|
+
if (!activeSessionId2 || enabledExtTypes.size === 1) {
|
|
12715
|
+
await agentStreamManager.setActiveSession(
|
|
12716
|
+
cdp,
|
|
12717
|
+
parentSessionId,
|
|
12718
|
+
extInstance.getInstanceId()
|
|
12719
|
+
);
|
|
12720
|
+
LOG.info("AgentStream", `Auto-activated enabled extension: ${extType} (${ideType})`);
|
|
12721
|
+
}
|
|
12261
12722
|
}
|
|
12262
12723
|
LOG.info("AgentStream", `Extension added: ${extType} (enabled for ${ideType})`);
|
|
12263
12724
|
}
|
|
@@ -12716,8 +13177,8 @@ async function detectAllVersions(loader, archive) {
|
|
|
12716
13177
|
|
|
12717
13178
|
// src/daemon/dev-server.ts
|
|
12718
13179
|
var http2 = __toESM(require("http"));
|
|
12719
|
-
var
|
|
12720
|
-
var
|
|
13180
|
+
var fs14 = __toESM(require("fs"));
|
|
13181
|
+
var path18 = __toESM(require("path"));
|
|
12721
13182
|
|
|
12722
13183
|
// src/daemon/scaffold-template.ts
|
|
12723
13184
|
function generateFiles(type, name, category, opts = {}) {
|
|
@@ -14060,6 +14521,162 @@ async function handleDomContext(ctx, type, req, res) {
|
|
|
14060
14521
|
}
|
|
14061
14522
|
|
|
14062
14523
|
// src/daemon/dev-cli-debug.ts
|
|
14524
|
+
var fs12 = __toESM(require("fs"));
|
|
14525
|
+
var path16 = __toESM(require("path"));
|
|
14526
|
+
function slugifyFixtureName(value) {
|
|
14527
|
+
const normalized = String(value || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
14528
|
+
return normalized || `fixture-${Date.now()}`;
|
|
14529
|
+
}
|
|
14530
|
+
function getCliFixtureDir(ctx, type) {
|
|
14531
|
+
const providerDir = ctx.providerLoader.findProviderDir(type);
|
|
14532
|
+
if (!providerDir) {
|
|
14533
|
+
throw new Error(`Provider directory not found for '${type}'`);
|
|
14534
|
+
}
|
|
14535
|
+
return path16.join(providerDir, "fixtures");
|
|
14536
|
+
}
|
|
14537
|
+
function readCliFixture(ctx, type, name) {
|
|
14538
|
+
const fixtureDir = getCliFixtureDir(ctx, type);
|
|
14539
|
+
const filePath = path16.join(fixtureDir, `${name}.json`);
|
|
14540
|
+
if (!fs12.existsSync(filePath)) {
|
|
14541
|
+
throw new Error(`Fixture not found: ${filePath}`);
|
|
14542
|
+
}
|
|
14543
|
+
return JSON.parse(fs12.readFileSync(filePath, "utf-8"));
|
|
14544
|
+
}
|
|
14545
|
+
function getExerciseTranscriptText(result) {
|
|
14546
|
+
const parts = [];
|
|
14547
|
+
const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
|
|
14548
|
+
const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
|
|
14549
|
+
for (const message of [...debugMessages, ...traceMessages]) {
|
|
14550
|
+
if (!message || typeof message.content !== "string") continue;
|
|
14551
|
+
parts.push(message.content);
|
|
14552
|
+
}
|
|
14553
|
+
if (typeof result?.debug?.partialResponse === "string") parts.push(result.debug.partialResponse);
|
|
14554
|
+
if (typeof result?.trace?.responseBuffer === "string") parts.push(result.trace.responseBuffer);
|
|
14555
|
+
return parts.join("\n");
|
|
14556
|
+
}
|
|
14557
|
+
function getExerciseLastAssistant(result) {
|
|
14558
|
+
const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
|
|
14559
|
+
const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
|
|
14560
|
+
for (const messages of [debugMessages, traceMessages]) {
|
|
14561
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
14562
|
+
const message = messages[i];
|
|
14563
|
+
if (message?.role === "assistant" && typeof message.content === "string" && message.content.trim()) {
|
|
14564
|
+
return message.content;
|
|
14565
|
+
}
|
|
14566
|
+
}
|
|
14567
|
+
}
|
|
14568
|
+
return "";
|
|
14569
|
+
}
|
|
14570
|
+
function getExerciseMessageCount(result) {
|
|
14571
|
+
const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
|
|
14572
|
+
const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
|
|
14573
|
+
return Math.max(debugMessages.length, traceMessages.length);
|
|
14574
|
+
}
|
|
14575
|
+
function compileFixtureRegex(source) {
|
|
14576
|
+
const value = String(source || "").trim();
|
|
14577
|
+
if (!value) return null;
|
|
14578
|
+
const delimited = value.match(/^\/([\s\S]+)\/([dgimsuvy]*)$/);
|
|
14579
|
+
try {
|
|
14580
|
+
if (delimited) {
|
|
14581
|
+
return new RegExp(delimited[1], delimited[2]);
|
|
14582
|
+
}
|
|
14583
|
+
return new RegExp(value, "m");
|
|
14584
|
+
} catch {
|
|
14585
|
+
return null;
|
|
14586
|
+
}
|
|
14587
|
+
}
|
|
14588
|
+
function statusesContainSequence(actual, expected) {
|
|
14589
|
+
if (!expected.length) return true;
|
|
14590
|
+
let index = 0;
|
|
14591
|
+
for (const status of actual) {
|
|
14592
|
+
if (status === expected[index]) index += 1;
|
|
14593
|
+
if (index >= expected.length) return true;
|
|
14594
|
+
}
|
|
14595
|
+
return false;
|
|
14596
|
+
}
|
|
14597
|
+
function validateCliFixtureResult(result, assertions) {
|
|
14598
|
+
const failures = [];
|
|
14599
|
+
const transcriptText = getExerciseTranscriptText(result);
|
|
14600
|
+
const lastAssistant = getExerciseLastAssistant(result);
|
|
14601
|
+
const mustContainAny = assertions.mustContainAny || [];
|
|
14602
|
+
const mustNotContainAny = assertions.mustNotContainAny || [];
|
|
14603
|
+
const mustMatchAny = assertions.mustMatchAny || [];
|
|
14604
|
+
const mustNotMatchAny = assertions.mustNotMatchAny || [];
|
|
14605
|
+
const lastAssistantMustContainAny = assertions.lastAssistantMustContainAny || [];
|
|
14606
|
+
const lastAssistantMustNotContainAny = assertions.lastAssistantMustNotContainAny || [];
|
|
14607
|
+
const lastAssistantMustMatchAny = assertions.lastAssistantMustMatchAny || [];
|
|
14608
|
+
const lastAssistantMustNotMatchAny = assertions.lastAssistantMustNotMatchAny || [];
|
|
14609
|
+
const statusesSeen = Array.isArray(result?.statusesSeen) ? result.statusesSeen.map((value) => String(value)) : [];
|
|
14610
|
+
if (assertions.requireNotTimedOut !== false && result?.timedOut) {
|
|
14611
|
+
failures.push("Exercise timed out");
|
|
14612
|
+
}
|
|
14613
|
+
const missingRequired = mustContainAny.filter((value) => !transcriptText.includes(value));
|
|
14614
|
+
if (missingRequired.length > 0) {
|
|
14615
|
+
failures.push(`Missing required substrings: ${missingRequired.join(", ")}`);
|
|
14616
|
+
}
|
|
14617
|
+
const presentBanned = mustNotContainAny.filter((value) => transcriptText.includes(value));
|
|
14618
|
+
if (presentBanned.length > 0) {
|
|
14619
|
+
failures.push(`Found banned substrings: ${presentBanned.join(", ")}`);
|
|
14620
|
+
}
|
|
14621
|
+
const missingRegex = mustMatchAny.filter((value) => {
|
|
14622
|
+
const regex = compileFixtureRegex(value);
|
|
14623
|
+
return !regex || !regex.test(transcriptText);
|
|
14624
|
+
});
|
|
14625
|
+
if (missingRegex.length > 0) {
|
|
14626
|
+
failures.push(`Missing required regex matches: ${missingRegex.join(", ")}`);
|
|
14627
|
+
}
|
|
14628
|
+
const presentBannedRegex = mustNotMatchAny.filter((value) => {
|
|
14629
|
+
const regex = compileFixtureRegex(value);
|
|
14630
|
+
return !!regex && regex.test(transcriptText);
|
|
14631
|
+
});
|
|
14632
|
+
if (presentBannedRegex.length > 0) {
|
|
14633
|
+
failures.push(`Found banned regex matches: ${presentBannedRegex.join(", ")}`);
|
|
14634
|
+
}
|
|
14635
|
+
const missingLastAssistant = lastAssistantMustContainAny.filter((value) => !lastAssistant.includes(value));
|
|
14636
|
+
if (missingLastAssistant.length > 0) {
|
|
14637
|
+
failures.push(`Missing required lastAssistant substrings: ${missingLastAssistant.join(", ")}`);
|
|
14638
|
+
}
|
|
14639
|
+
const presentBannedLastAssistant = lastAssistantMustNotContainAny.filter((value) => lastAssistant.includes(value));
|
|
14640
|
+
if (presentBannedLastAssistant.length > 0) {
|
|
14641
|
+
failures.push(`Found banned lastAssistant substrings: ${presentBannedLastAssistant.join(", ")}`);
|
|
14642
|
+
}
|
|
14643
|
+
const missingLastAssistantRegex = lastAssistantMustMatchAny.filter((value) => {
|
|
14644
|
+
const regex = compileFixtureRegex(value);
|
|
14645
|
+
return !regex || !regex.test(lastAssistant);
|
|
14646
|
+
});
|
|
14647
|
+
if (missingLastAssistantRegex.length > 0) {
|
|
14648
|
+
failures.push(`Missing required lastAssistant regex matches: ${missingLastAssistantRegex.join(", ")}`);
|
|
14649
|
+
}
|
|
14650
|
+
const presentBannedLastAssistantRegex = lastAssistantMustNotMatchAny.filter((value) => {
|
|
14651
|
+
const regex = compileFixtureRegex(value);
|
|
14652
|
+
return !!regex && regex.test(lastAssistant);
|
|
14653
|
+
});
|
|
14654
|
+
if (presentBannedLastAssistantRegex.length > 0) {
|
|
14655
|
+
failures.push(`Found banned lastAssistant regex matches: ${presentBannedLastAssistantRegex.join(", ")}`);
|
|
14656
|
+
}
|
|
14657
|
+
if (assertions.statusesSeen?.length && !statusesContainSequence(statusesSeen, assertions.statusesSeen)) {
|
|
14658
|
+
failures.push(`Expected statuses sequence not observed: ${assertions.statusesSeen.join(" -> ")}`);
|
|
14659
|
+
}
|
|
14660
|
+
if (result && typeof result === "object") {
|
|
14661
|
+
result.lastAssistant = lastAssistant;
|
|
14662
|
+
}
|
|
14663
|
+
return failures;
|
|
14664
|
+
}
|
|
14665
|
+
function getCliProviderResolutionMeta(ctx, type, adapter) {
|
|
14666
|
+
const adapterMeta = typeof adapter?.getProviderResolutionMeta === "function" ? adapter.getProviderResolutionMeta() : adapter?.getDebugState?.()?.providerResolution || null;
|
|
14667
|
+
const resolvedProvider = ctx.providerLoader.resolve(type);
|
|
14668
|
+
if (!adapterMeta && !resolvedProvider) return null;
|
|
14669
|
+
return {
|
|
14670
|
+
type,
|
|
14671
|
+
providerDir: adapterMeta?.providerDir || resolvedProvider?._resolvedProviderDir || ctx.providerLoader.findProviderDir(type),
|
|
14672
|
+
scriptDir: adapterMeta?.scriptDir || resolvedProvider?._resolvedScriptDir || null,
|
|
14673
|
+
scriptsPath: adapterMeta?.scriptsPath || resolvedProvider?._resolvedScriptsPath || null,
|
|
14674
|
+
scriptsSource: adapterMeta?.scriptsSource || resolvedProvider?._resolvedScriptsSource || null,
|
|
14675
|
+
resolvedVersion: adapterMeta?.resolvedVersion || resolvedProvider?._resolvedVersion || null,
|
|
14676
|
+
resolvedOs: adapterMeta?.resolvedOs || resolvedProvider?._resolvedOs || null,
|
|
14677
|
+
versionWarning: adapterMeta?.versionWarning || resolvedProvider?._versionWarning || null
|
|
14678
|
+
};
|
|
14679
|
+
}
|
|
14063
14680
|
function findCliTarget(ctx, type, instanceId) {
|
|
14064
14681
|
if (!ctx.instanceManager) return null;
|
|
14065
14682
|
const cliStates = ctx.instanceManager.collectAllStates().filter((s) => s.category === "cli" || s.category === "acp");
|
|
@@ -14068,6 +14685,331 @@ function findCliTarget(ctx, type, instanceId) {
|
|
|
14068
14685
|
const matches = cliStates.filter((s) => s.type === type);
|
|
14069
14686
|
return matches[matches.length - 1] || null;
|
|
14070
14687
|
}
|
|
14688
|
+
function getCliTargetBundle(ctx, type, instanceId) {
|
|
14689
|
+
if (!ctx.instanceManager) return null;
|
|
14690
|
+
const target = findCliTarget(ctx, type, instanceId);
|
|
14691
|
+
if (!target) return null;
|
|
14692
|
+
const instance = ctx.instanceManager.getInstance(target.instanceId);
|
|
14693
|
+
if (!instance) return null;
|
|
14694
|
+
const adapter = instance.getAdapter?.() || instance.adapter;
|
|
14695
|
+
if (!adapter) return null;
|
|
14696
|
+
return { target, instance, adapter };
|
|
14697
|
+
}
|
|
14698
|
+
function sleep(ms) {
|
|
14699
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
14700
|
+
}
|
|
14701
|
+
async function waitForCliReady(ctx, type, instanceId, timeoutMs) {
|
|
14702
|
+
const startedAt = Date.now();
|
|
14703
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
14704
|
+
const bundle = getCliTargetBundle(ctx, type, instanceId);
|
|
14705
|
+
if (bundle) {
|
|
14706
|
+
const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
|
|
14707
|
+
const startupParseGate = !!debug?.startupParseGate;
|
|
14708
|
+
const adapterReady = !!debug?.ready;
|
|
14709
|
+
const visibleStatusReady = bundle.target.status === "generating" || bundle.target.status === "waiting_approval";
|
|
14710
|
+
const idleReady = bundle.target.status === "idle" && !startupParseGate;
|
|
14711
|
+
if (adapterReady || visibleStatusReady || idleReady) {
|
|
14712
|
+
return bundle;
|
|
14713
|
+
}
|
|
14714
|
+
}
|
|
14715
|
+
await sleep(100);
|
|
14716
|
+
}
|
|
14717
|
+
return getCliTargetBundle(ctx, type, instanceId);
|
|
14718
|
+
}
|
|
14719
|
+
async function runCliExerciseInternal(ctx, body) {
|
|
14720
|
+
if (!ctx.cliManager) {
|
|
14721
|
+
throw new Error("CliManager not available");
|
|
14722
|
+
}
|
|
14723
|
+
if (!ctx.instanceManager) {
|
|
14724
|
+
throw new Error("InstanceManager not available");
|
|
14725
|
+
}
|
|
14726
|
+
const {
|
|
14727
|
+
type,
|
|
14728
|
+
text,
|
|
14729
|
+
instanceId: requestedInstanceId,
|
|
14730
|
+
workingDir,
|
|
14731
|
+
args,
|
|
14732
|
+
autoLaunch = true,
|
|
14733
|
+
freshSession = true,
|
|
14734
|
+
autoResolveApprovals = true,
|
|
14735
|
+
approvalButtonIndex = 0,
|
|
14736
|
+
timeoutMs = 45e3,
|
|
14737
|
+
readyTimeoutMs = 15e3,
|
|
14738
|
+
idleSettledMs = 1200,
|
|
14739
|
+
traceLimit = 160,
|
|
14740
|
+
stopWhenDone = false
|
|
14741
|
+
} = body || {};
|
|
14742
|
+
if (!type) {
|
|
14743
|
+
throw new Error("type required (e.g. claude-cli, codex-cli)");
|
|
14744
|
+
}
|
|
14745
|
+
if (!text || typeof text !== "string") {
|
|
14746
|
+
throw new Error("text required (prompt to send to the CLI)");
|
|
14747
|
+
}
|
|
14748
|
+
let resolvedInstanceId = requestedInstanceId;
|
|
14749
|
+
if (freshSession) {
|
|
14750
|
+
const staleTargets = ctx.instanceManager.collectAllStates().filter((state) => (state.category === "cli" || state.category === "acp") && state.type === type).map((state) => state.instanceId);
|
|
14751
|
+
for (const staleId of staleTargets) {
|
|
14752
|
+
ctx.instanceManager.removeInstance(staleId);
|
|
14753
|
+
}
|
|
14754
|
+
resolvedInstanceId = void 0;
|
|
14755
|
+
}
|
|
14756
|
+
let bundle = getCliTargetBundle(ctx, type, resolvedInstanceId);
|
|
14757
|
+
if (!bundle && autoLaunch) {
|
|
14758
|
+
const launchArgs = [type, workingDir || process.cwd(), Array.isArray(args) ? args : []];
|
|
14759
|
+
let launched = null;
|
|
14760
|
+
let lastLaunchError = null;
|
|
14761
|
+
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
14762
|
+
try {
|
|
14763
|
+
launched = await ctx.cliManager.startSession(...launchArgs);
|
|
14764
|
+
lastLaunchError = null;
|
|
14765
|
+
break;
|
|
14766
|
+
} catch (error) {
|
|
14767
|
+
lastLaunchError = error instanceof Error ? error : new Error(String(error?.message || error));
|
|
14768
|
+
const message = String(lastLaunchError.message || "");
|
|
14769
|
+
const retryable = /ECONNREFUSED|session-host|Session host/i.test(message);
|
|
14770
|
+
if (!retryable || attempt === 2) break;
|
|
14771
|
+
await sleep(1e3);
|
|
14772
|
+
}
|
|
14773
|
+
}
|
|
14774
|
+
if (!launched) {
|
|
14775
|
+
throw lastLaunchError || new Error(`Failed to start ${type}`);
|
|
14776
|
+
}
|
|
14777
|
+
resolvedInstanceId = launched.runtimeSessionId;
|
|
14778
|
+
bundle = await waitForCliReady(ctx, type, resolvedInstanceId, Math.max(1e3, readyTimeoutMs));
|
|
14779
|
+
}
|
|
14780
|
+
if (!bundle) {
|
|
14781
|
+
throw new Error(`No running instance found for: ${resolvedInstanceId || type}`);
|
|
14782
|
+
}
|
|
14783
|
+
const initialDebug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
|
|
14784
|
+
const initialTrace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
|
|
14785
|
+
const providerResolution = getCliProviderResolutionMeta(ctx, bundle.target.type, bundle.adapter);
|
|
14786
|
+
const preTraceCount = Number(initialTrace?.entryCount || 0);
|
|
14787
|
+
const startAt = Date.now();
|
|
14788
|
+
const statusesSeen = [];
|
|
14789
|
+
const approvalsResolved = [];
|
|
14790
|
+
let lastStatus = "";
|
|
14791
|
+
let lastModalKey = "";
|
|
14792
|
+
let idleSince = 0;
|
|
14793
|
+
let sawBusy = false;
|
|
14794
|
+
ctx.instanceManager.sendEvent(bundle.target.instanceId, "send_message", { text });
|
|
14795
|
+
while (Date.now() - startAt < Math.max(1e3, timeoutMs)) {
|
|
14796
|
+
await sleep(150);
|
|
14797
|
+
bundle = getCliTargetBundle(ctx, type, bundle.target.instanceId);
|
|
14798
|
+
if (!bundle) {
|
|
14799
|
+
throw new Error("CLI instance disappeared during exercise");
|
|
14800
|
+
}
|
|
14801
|
+
const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
|
|
14802
|
+
const trace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
|
|
14803
|
+
const status = String(debug?.status || bundle.target.status || "unknown");
|
|
14804
|
+
const traceEntries = Array.isArray(trace?.entries) ? trace.entries : [];
|
|
14805
|
+
const sawSendMessage = traceEntries.some((entry) => entry?.type === "send_message");
|
|
14806
|
+
const sawSubmitWrite = traceEntries.some((entry) => entry?.type === "submit_write");
|
|
14807
|
+
const hasTurnStarted = sawSendMessage || sawSubmitWrite || !!debug?.currentTurnScope;
|
|
14808
|
+
if (status !== lastStatus) {
|
|
14809
|
+
statusesSeen.push(status);
|
|
14810
|
+
lastStatus = status;
|
|
14811
|
+
}
|
|
14812
|
+
if (status === "generating" || status === "waiting_approval") {
|
|
14813
|
+
sawBusy = true;
|
|
14814
|
+
idleSince = 0;
|
|
14815
|
+
}
|
|
14816
|
+
const modal = debug?.activeModal || trace?.activeModal || null;
|
|
14817
|
+
if (autoResolveApprovals && status === "waiting_approval" && modal && Array.isArray(modal.buttons) && modal.buttons.length > 0) {
|
|
14818
|
+
const clampedIndex = Math.max(0, Math.min(Number(approvalButtonIndex) || 0, modal.buttons.length - 1));
|
|
14819
|
+
const modalKey = JSON.stringify({
|
|
14820
|
+
message: modal.message || "",
|
|
14821
|
+
buttons: modal.buttons,
|
|
14822
|
+
index: clampedIndex
|
|
14823
|
+
});
|
|
14824
|
+
if (modalKey !== lastModalKey && typeof bundle.adapter.resolveModal === "function") {
|
|
14825
|
+
lastModalKey = modalKey;
|
|
14826
|
+
approvalsResolved.push({
|
|
14827
|
+
at: Date.now(),
|
|
14828
|
+
buttonIndex: clampedIndex,
|
|
14829
|
+
label: modal.buttons[clampedIndex] || null
|
|
14830
|
+
});
|
|
14831
|
+
bundle.adapter.resolveModal(clampedIndex);
|
|
14832
|
+
continue;
|
|
14833
|
+
}
|
|
14834
|
+
}
|
|
14835
|
+
const traceCount = Number(trace?.entryCount || 0);
|
|
14836
|
+
const hasProgress = hasTurnStarted && (traceCount > preTraceCount || statusesSeen.length > 1 || approvalsResolved.length > 0);
|
|
14837
|
+
if (status === "idle" && hasProgress && sawBusy) {
|
|
14838
|
+
if (!idleSince) idleSince = Date.now();
|
|
14839
|
+
if (Date.now() - idleSince >= Math.max(200, idleSettledMs)) {
|
|
14840
|
+
const payload2 = {
|
|
14841
|
+
exercised: true,
|
|
14842
|
+
instanceId: bundle.target.instanceId,
|
|
14843
|
+
providerState: {
|
|
14844
|
+
type: bundle.target.type,
|
|
14845
|
+
name: bundle.target.name,
|
|
14846
|
+
status: bundle.target.status,
|
|
14847
|
+
mode: "mode" in bundle.target ? bundle.target.mode : void 0
|
|
14848
|
+
},
|
|
14849
|
+
providerResolution,
|
|
14850
|
+
initialDebug,
|
|
14851
|
+
initialTrace,
|
|
14852
|
+
debug,
|
|
14853
|
+
trace,
|
|
14854
|
+
statusesSeen,
|
|
14855
|
+
approvalsResolved,
|
|
14856
|
+
elapsedMs: Date.now() - startAt,
|
|
14857
|
+
timedOut: false
|
|
14858
|
+
};
|
|
14859
|
+
payload2.lastAssistant = getExerciseLastAssistant(payload2);
|
|
14860
|
+
payload2.messageCount = getExerciseMessageCount(payload2);
|
|
14861
|
+
if (stopWhenDone) {
|
|
14862
|
+
ctx.instanceManager.removeInstance(bundle.target.instanceId);
|
|
14863
|
+
}
|
|
14864
|
+
return payload2;
|
|
14865
|
+
}
|
|
14866
|
+
} else if (status === "idle" && hasProgress) {
|
|
14867
|
+
if (!idleSince) idleSince = Date.now();
|
|
14868
|
+
if (Date.now() - idleSince >= Math.max(500, idleSettledMs) && Date.now() - startAt >= 750) {
|
|
14869
|
+
const payload2 = {
|
|
14870
|
+
exercised: true,
|
|
14871
|
+
instanceId: bundle.target.instanceId,
|
|
14872
|
+
providerState: {
|
|
14873
|
+
type: bundle.target.type,
|
|
14874
|
+
name: bundle.target.name,
|
|
14875
|
+
status: bundle.target.status,
|
|
14876
|
+
mode: "mode" in bundle.target ? bundle.target.mode : void 0
|
|
14877
|
+
},
|
|
14878
|
+
providerResolution,
|
|
14879
|
+
initialDebug,
|
|
14880
|
+
initialTrace,
|
|
14881
|
+
debug,
|
|
14882
|
+
trace,
|
|
14883
|
+
statusesSeen,
|
|
14884
|
+
approvalsResolved,
|
|
14885
|
+
elapsedMs: Date.now() - startAt,
|
|
14886
|
+
timedOut: false
|
|
14887
|
+
};
|
|
14888
|
+
payload2.lastAssistant = getExerciseLastAssistant(payload2);
|
|
14889
|
+
payload2.messageCount = getExerciseMessageCount(payload2);
|
|
14890
|
+
if (stopWhenDone) {
|
|
14891
|
+
ctx.instanceManager.removeInstance(bundle.target.instanceId);
|
|
14892
|
+
}
|
|
14893
|
+
return payload2;
|
|
14894
|
+
}
|
|
14895
|
+
} else {
|
|
14896
|
+
idleSince = 0;
|
|
14897
|
+
}
|
|
14898
|
+
}
|
|
14899
|
+
const finalBundle = getCliTargetBundle(ctx, type, bundle.target.instanceId) || bundle;
|
|
14900
|
+
const finalDebug = typeof finalBundle.adapter.getDebugState === "function" ? finalBundle.adapter.getDebugState() : null;
|
|
14901
|
+
const finalTrace = typeof finalBundle.adapter.getTraceState === "function" ? finalBundle.adapter.getTraceState(traceLimit) : null;
|
|
14902
|
+
if (stopWhenDone) {
|
|
14903
|
+
ctx.instanceManager.removeInstance(finalBundle.target.instanceId);
|
|
14904
|
+
}
|
|
14905
|
+
const payload = {
|
|
14906
|
+
exercised: true,
|
|
14907
|
+
instanceId: finalBundle.target.instanceId,
|
|
14908
|
+
providerState: {
|
|
14909
|
+
type: finalBundle.target.type,
|
|
14910
|
+
name: finalBundle.target.name,
|
|
14911
|
+
status: finalBundle.target.status,
|
|
14912
|
+
mode: "mode" in finalBundle.target ? finalBundle.target.mode : void 0
|
|
14913
|
+
},
|
|
14914
|
+
providerResolution: getCliProviderResolutionMeta(ctx, finalBundle.target.type, finalBundle.adapter),
|
|
14915
|
+
initialDebug,
|
|
14916
|
+
initialTrace,
|
|
14917
|
+
debug: finalDebug,
|
|
14918
|
+
trace: finalTrace,
|
|
14919
|
+
statusesSeen,
|
|
14920
|
+
approvalsResolved,
|
|
14921
|
+
elapsedMs: Date.now() - startAt,
|
|
14922
|
+
timedOut: true
|
|
14923
|
+
};
|
|
14924
|
+
payload.lastAssistant = getExerciseLastAssistant(payload);
|
|
14925
|
+
payload.messageCount = getExerciseMessageCount(payload);
|
|
14926
|
+
return payload;
|
|
14927
|
+
}
|
|
14928
|
+
async function runCliAutoImplVerification(ctx, type, verification) {
|
|
14929
|
+
const assertions = {
|
|
14930
|
+
mustContainAny: verification?.mustContainAny || [],
|
|
14931
|
+
mustNotContainAny: verification?.mustNotContainAny || [],
|
|
14932
|
+
mustMatchAny: verification?.mustMatchAny || [],
|
|
14933
|
+
mustNotMatchAny: verification?.mustNotMatchAny || [],
|
|
14934
|
+
lastAssistantMustContainAny: verification?.lastAssistantMustContainAny || [],
|
|
14935
|
+
lastAssistantMustNotContainAny: verification?.lastAssistantMustNotContainAny || [],
|
|
14936
|
+
lastAssistantMustMatchAny: verification?.lastAssistantMustMatchAny || [],
|
|
14937
|
+
lastAssistantMustNotMatchAny: verification?.lastAssistantMustNotMatchAny || [],
|
|
14938
|
+
requireNotTimedOut: true
|
|
14939
|
+
};
|
|
14940
|
+
const rawFixtureNames = Array.isArray(verification?.fixtureNames) ? verification.fixtureNames.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
14941
|
+
if (rawFixtureNames.length > 0) {
|
|
14942
|
+
const results = [];
|
|
14943
|
+
for (const rawFixtureName2 of rawFixtureNames) {
|
|
14944
|
+
const name = slugifyFixtureName(rawFixtureName2);
|
|
14945
|
+
const fixture = readCliFixture(ctx, type, name);
|
|
14946
|
+
const mergedAssertions = {
|
|
14947
|
+
...fixture.assertions,
|
|
14948
|
+
...assertions
|
|
14949
|
+
};
|
|
14950
|
+
const result2 = await runCliExerciseInternal(ctx, {
|
|
14951
|
+
...fixture.request,
|
|
14952
|
+
type
|
|
14953
|
+
});
|
|
14954
|
+
const failures2 = validateCliFixtureResult(result2, mergedAssertions);
|
|
14955
|
+
results.push({
|
|
14956
|
+
fixtureName: name,
|
|
14957
|
+
pass: failures2.length === 0,
|
|
14958
|
+
failures: failures2,
|
|
14959
|
+
result: result2,
|
|
14960
|
+
assertions: mergedAssertions,
|
|
14961
|
+
fixture
|
|
14962
|
+
});
|
|
14963
|
+
}
|
|
14964
|
+
const firstFailure = results.find((item) => !item.pass) || results[results.length - 1];
|
|
14965
|
+
return {
|
|
14966
|
+
mode: "fixture_replay_suite",
|
|
14967
|
+
pass: results.every((item) => item.pass),
|
|
14968
|
+
failures: results.flatMap((item) => item.failures.map((failure) => `${item.fixtureName}: ${failure}`)),
|
|
14969
|
+
result: firstFailure.result,
|
|
14970
|
+
assertions: firstFailure.assertions,
|
|
14971
|
+
fixture: firstFailure.fixture,
|
|
14972
|
+
results
|
|
14973
|
+
};
|
|
14974
|
+
}
|
|
14975
|
+
const rawFixtureName = String(verification?.fixtureName || "").trim();
|
|
14976
|
+
if (rawFixtureName) {
|
|
14977
|
+
const name = slugifyFixtureName(rawFixtureName);
|
|
14978
|
+
try {
|
|
14979
|
+
const fixture = readCliFixture(ctx, type, name);
|
|
14980
|
+
const mergedAssertions = {
|
|
14981
|
+
...fixture.assertions,
|
|
14982
|
+
...assertions
|
|
14983
|
+
};
|
|
14984
|
+
const result2 = await runCliExerciseInternal(ctx, {
|
|
14985
|
+
...fixture.request,
|
|
14986
|
+
type
|
|
14987
|
+
});
|
|
14988
|
+
const failures2 = validateCliFixtureResult(result2, mergedAssertions);
|
|
14989
|
+
return {
|
|
14990
|
+
mode: "fixture_replay",
|
|
14991
|
+
pass: failures2.length === 0,
|
|
14992
|
+
failures: failures2,
|
|
14993
|
+
result: result2,
|
|
14994
|
+
assertions: mergedAssertions,
|
|
14995
|
+
fixture
|
|
14996
|
+
};
|
|
14997
|
+
} catch {
|
|
14998
|
+
}
|
|
14999
|
+
}
|
|
15000
|
+
const result = await runCliExerciseInternal(ctx, {
|
|
15001
|
+
...verification?.request || {},
|
|
15002
|
+
type
|
|
15003
|
+
});
|
|
15004
|
+
const failures = validateCliFixtureResult(result, assertions);
|
|
15005
|
+
return {
|
|
15006
|
+
mode: "exercise",
|
|
15007
|
+
pass: failures.length === 0,
|
|
15008
|
+
failures,
|
|
15009
|
+
result,
|
|
15010
|
+
assertions
|
|
15011
|
+
};
|
|
15012
|
+
}
|
|
14071
15013
|
async function handleCliStatus(ctx, _req, res) {
|
|
14072
15014
|
if (!ctx.instanceManager) {
|
|
14073
15015
|
ctx.json(res, 503, { error: "InstanceManager not available (daemon not fully initialized)" });
|
|
@@ -14206,12 +15148,14 @@ async function handleCliDebug(ctx, type, _req, res) {
|
|
|
14206
15148
|
status: target.status,
|
|
14207
15149
|
mode: "mode" in target ? target.mode : void 0
|
|
14208
15150
|
},
|
|
15151
|
+
providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
|
|
14209
15152
|
debug: debugState
|
|
14210
15153
|
});
|
|
14211
15154
|
} else {
|
|
14212
15155
|
ctx.json(res, 200, {
|
|
14213
15156
|
instanceId: target.instanceId,
|
|
14214
15157
|
providerState: target,
|
|
15158
|
+
providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
|
|
14215
15159
|
debug: null,
|
|
14216
15160
|
message: "No debug state available (adapter.getDebugState not found)"
|
|
14217
15161
|
});
|
|
@@ -14220,6 +15164,191 @@ async function handleCliDebug(ctx, type, _req, res) {
|
|
|
14220
15164
|
ctx.json(res, 500, { error: `Debug state failed: ${e.message}` });
|
|
14221
15165
|
}
|
|
14222
15166
|
}
|
|
15167
|
+
async function handleCliTrace(ctx, type, req, res) {
|
|
15168
|
+
if (!ctx.instanceManager) {
|
|
15169
|
+
ctx.json(res, 503, { error: "InstanceManager not available" });
|
|
15170
|
+
return;
|
|
15171
|
+
}
|
|
15172
|
+
const target = findCliTarget(ctx, type);
|
|
15173
|
+
if (!target) {
|
|
15174
|
+
const allStates = ctx.instanceManager.collectAllStates();
|
|
15175
|
+
ctx.json(res, 404, {
|
|
15176
|
+
error: `No running instance for: ${type}`,
|
|
15177
|
+
available: allStates.filter((s) => s.category === "cli" || s.category === "acp").map((s) => s.type)
|
|
15178
|
+
});
|
|
15179
|
+
return;
|
|
15180
|
+
}
|
|
15181
|
+
const instance = ctx.instanceManager.getInstance(target.instanceId);
|
|
15182
|
+
if (!instance) {
|
|
15183
|
+
ctx.json(res, 404, { error: `Instance not found: ${target.instanceId}` });
|
|
15184
|
+
return;
|
|
15185
|
+
}
|
|
15186
|
+
try {
|
|
15187
|
+
const adapter = instance.getAdapter?.() || instance.adapter;
|
|
15188
|
+
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
15189
|
+
const limit = parseInt(url.searchParams.get("limit") || "120", 10);
|
|
15190
|
+
if (adapter && typeof adapter.getTraceState === "function") {
|
|
15191
|
+
const trace = adapter.getTraceState(limit);
|
|
15192
|
+
const debug = typeof adapter.getDebugState === "function" ? adapter.getDebugState() : null;
|
|
15193
|
+
ctx.json(res, 200, {
|
|
15194
|
+
instanceId: target.instanceId,
|
|
15195
|
+
providerState: {
|
|
15196
|
+
type: target.type,
|
|
15197
|
+
name: target.name,
|
|
15198
|
+
status: target.status,
|
|
15199
|
+
mode: "mode" in target ? target.mode : void 0
|
|
15200
|
+
},
|
|
15201
|
+
providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
|
|
15202
|
+
debug,
|
|
15203
|
+
trace
|
|
15204
|
+
});
|
|
15205
|
+
} else {
|
|
15206
|
+
ctx.json(res, 200, {
|
|
15207
|
+
instanceId: target.instanceId,
|
|
15208
|
+
providerState: target,
|
|
15209
|
+
providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
|
|
15210
|
+
debug: typeof adapter?.getDebugState === "function" ? adapter.getDebugState() : null,
|
|
15211
|
+
trace: null,
|
|
15212
|
+
message: "No trace state available (adapter.getTraceState not found)"
|
|
15213
|
+
});
|
|
15214
|
+
}
|
|
15215
|
+
} catch (e) {
|
|
15216
|
+
ctx.json(res, 500, { error: `Trace state failed: ${e.message}` });
|
|
15217
|
+
}
|
|
15218
|
+
}
|
|
15219
|
+
async function handleCliExercise(ctx, req, res) {
|
|
15220
|
+
try {
|
|
15221
|
+
const body = await ctx.readBody(req);
|
|
15222
|
+
const result = await runCliExerciseInternal(ctx, body || {});
|
|
15223
|
+
ctx.json(res, 200, result);
|
|
15224
|
+
} catch (e) {
|
|
15225
|
+
ctx.json(res, 500, { error: `Exercise failed: ${e.message}` });
|
|
15226
|
+
}
|
|
15227
|
+
}
|
|
15228
|
+
async function handleCliFixtureCapture(ctx, req, res) {
|
|
15229
|
+
try {
|
|
15230
|
+
const body = await ctx.readBody(req);
|
|
15231
|
+
const type = String(body?.type || "");
|
|
15232
|
+
const request = body?.request || {};
|
|
15233
|
+
if (!type) {
|
|
15234
|
+
ctx.json(res, 400, { error: "type required" });
|
|
15235
|
+
return;
|
|
15236
|
+
}
|
|
15237
|
+
if (!request?.text) {
|
|
15238
|
+
ctx.json(res, 400, { error: "request.text required" });
|
|
15239
|
+
return;
|
|
15240
|
+
}
|
|
15241
|
+
const fixtureDir = getCliFixtureDir(ctx, type);
|
|
15242
|
+
fs12.mkdirSync(fixtureDir, { recursive: true });
|
|
15243
|
+
const name = slugifyFixtureName(String(body?.name || `${type}-${Date.now()}`));
|
|
15244
|
+
const result = await runCliExerciseInternal(ctx, { ...request, type });
|
|
15245
|
+
const fixture = {
|
|
15246
|
+
version: 1,
|
|
15247
|
+
kind: "cli-exercise-fixture",
|
|
15248
|
+
name,
|
|
15249
|
+
type,
|
|
15250
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15251
|
+
providerDir: ctx.providerLoader.findProviderDir(type),
|
|
15252
|
+
providerResolution: result?.providerResolution || null,
|
|
15253
|
+
request: { ...request, type },
|
|
15254
|
+
result,
|
|
15255
|
+
assertions: {
|
|
15256
|
+
mustContainAny: Array.isArray(body?.assertions?.mustContainAny) ? body.assertions.mustContainAny : [],
|
|
15257
|
+
mustNotContainAny: Array.isArray(body?.assertions?.mustNotContainAny) ? body.assertions.mustNotContainAny : [],
|
|
15258
|
+
mustMatchAny: Array.isArray(body?.assertions?.mustMatchAny) ? body.assertions.mustMatchAny : [],
|
|
15259
|
+
mustNotMatchAny: Array.isArray(body?.assertions?.mustNotMatchAny) ? body.assertions.mustNotMatchAny : [],
|
|
15260
|
+
lastAssistantMustContainAny: Array.isArray(body?.assertions?.lastAssistantMustContainAny) ? body.assertions.lastAssistantMustContainAny : [],
|
|
15261
|
+
lastAssistantMustNotContainAny: Array.isArray(body?.assertions?.lastAssistantMustNotContainAny) ? body.assertions.lastAssistantMustNotContainAny : [],
|
|
15262
|
+
lastAssistantMustMatchAny: Array.isArray(body?.assertions?.lastAssistantMustMatchAny) ? body.assertions.lastAssistantMustMatchAny : [],
|
|
15263
|
+
lastAssistantMustNotMatchAny: Array.isArray(body?.assertions?.lastAssistantMustNotMatchAny) ? body.assertions.lastAssistantMustNotMatchAny : [],
|
|
15264
|
+
statusesSeen: Array.isArray(body?.assertions?.statusesSeen) ? body.assertions.statusesSeen : void 0,
|
|
15265
|
+
requireNotTimedOut: body?.assertions?.requireNotTimedOut !== false
|
|
15266
|
+
},
|
|
15267
|
+
notes: typeof body?.notes === "string" ? body.notes : void 0
|
|
15268
|
+
};
|
|
15269
|
+
const filePath = path16.join(fixtureDir, `${name}.json`);
|
|
15270
|
+
fs12.writeFileSync(filePath, JSON.stringify(fixture, null, 2));
|
|
15271
|
+
ctx.json(res, 200, {
|
|
15272
|
+
saved: true,
|
|
15273
|
+
name,
|
|
15274
|
+
path: filePath,
|
|
15275
|
+
fixture,
|
|
15276
|
+
verification: {
|
|
15277
|
+
pass: validateCliFixtureResult(result, fixture.assertions).length === 0,
|
|
15278
|
+
failures: validateCliFixtureResult(result, fixture.assertions)
|
|
15279
|
+
}
|
|
15280
|
+
});
|
|
15281
|
+
} catch (e) {
|
|
15282
|
+
ctx.json(res, 500, { error: `Fixture capture failed: ${e.message}` });
|
|
15283
|
+
}
|
|
15284
|
+
}
|
|
15285
|
+
async function handleCliFixtureList(ctx, type, _req, res) {
|
|
15286
|
+
try {
|
|
15287
|
+
const fixtureDir = getCliFixtureDir(ctx, type);
|
|
15288
|
+
if (!fs12.existsSync(fixtureDir)) {
|
|
15289
|
+
ctx.json(res, 200, { fixtures: [], count: 0 });
|
|
15290
|
+
return;
|
|
15291
|
+
}
|
|
15292
|
+
const fixtures = fs12.readdirSync(fixtureDir).filter((file) => file.endsWith(".json")).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" })).map((file) => {
|
|
15293
|
+
const fullPath = path16.join(fixtureDir, file);
|
|
15294
|
+
try {
|
|
15295
|
+
const raw = JSON.parse(fs12.readFileSync(fullPath, "utf-8"));
|
|
15296
|
+
return {
|
|
15297
|
+
name: raw.name || file.replace(/\.json$/i, ""),
|
|
15298
|
+
path: fullPath,
|
|
15299
|
+
createdAt: raw.createdAt || null,
|
|
15300
|
+
notes: raw.notes || null,
|
|
15301
|
+
requestText: raw.request?.text || "",
|
|
15302
|
+
assertions: raw.assertions || {}
|
|
15303
|
+
};
|
|
15304
|
+
} catch {
|
|
15305
|
+
return {
|
|
15306
|
+
name: file.replace(/\.json$/i, ""),
|
|
15307
|
+
path: fullPath,
|
|
15308
|
+
createdAt: null,
|
|
15309
|
+
notes: "Unreadable fixture",
|
|
15310
|
+
requestText: "",
|
|
15311
|
+
assertions: {}
|
|
15312
|
+
};
|
|
15313
|
+
}
|
|
15314
|
+
});
|
|
15315
|
+
ctx.json(res, 200, { fixtures, count: fixtures.length });
|
|
15316
|
+
} catch (e) {
|
|
15317
|
+
ctx.json(res, 500, { error: `Fixture list failed: ${e.message}` });
|
|
15318
|
+
}
|
|
15319
|
+
}
|
|
15320
|
+
async function handleCliFixtureReplay(ctx, req, res) {
|
|
15321
|
+
try {
|
|
15322
|
+
const body = await ctx.readBody(req);
|
|
15323
|
+
const type = String(body?.type || "");
|
|
15324
|
+
const rawName = String(body?.name || "").trim();
|
|
15325
|
+
if (!type || !rawName) {
|
|
15326
|
+
ctx.json(res, 400, { error: "type and name required" });
|
|
15327
|
+
return;
|
|
15328
|
+
}
|
|
15329
|
+
const name = slugifyFixtureName(rawName);
|
|
15330
|
+
const fixture = readCliFixture(ctx, type, name);
|
|
15331
|
+
const result = await runCliExerciseInternal(ctx, {
|
|
15332
|
+
...fixture.request,
|
|
15333
|
+
type
|
|
15334
|
+
});
|
|
15335
|
+
const assertions = {
|
|
15336
|
+
...fixture.assertions,
|
|
15337
|
+
...body?.assertions || {}
|
|
15338
|
+
};
|
|
15339
|
+
const failures = validateCliFixtureResult(result, assertions);
|
|
15340
|
+
ctx.json(res, 200, {
|
|
15341
|
+
replayed: true,
|
|
15342
|
+
pass: failures.length === 0,
|
|
15343
|
+
failures,
|
|
15344
|
+
fixture,
|
|
15345
|
+
result,
|
|
15346
|
+
assertions
|
|
15347
|
+
});
|
|
15348
|
+
} catch (e) {
|
|
15349
|
+
ctx.json(res, 500, { error: `Fixture replay failed: ${e.message}` });
|
|
15350
|
+
}
|
|
15351
|
+
}
|
|
14223
15352
|
async function handleCliResolve(ctx, req, res) {
|
|
14224
15353
|
const body = await ctx.readBody(req);
|
|
14225
15354
|
const { type, buttonIndex, instanceId } = body;
|
|
@@ -14296,9 +15425,29 @@ async function handleCliRaw(ctx, req, res) {
|
|
|
14296
15425
|
}
|
|
14297
15426
|
|
|
14298
15427
|
// src/daemon/dev-auto-implement.ts
|
|
14299
|
-
var
|
|
14300
|
-
var
|
|
15428
|
+
var fs13 = __toESM(require("fs"));
|
|
15429
|
+
var path17 = __toESM(require("path"));
|
|
14301
15430
|
var os17 = __toESM(require("os"));
|
|
15431
|
+
function getAutoImplPid(ctx) {
|
|
15432
|
+
const proc = ctx.autoImplProcess;
|
|
15433
|
+
return proc && typeof proc.pid === "number" && proc.pid > 0 ? proc.pid : null;
|
|
15434
|
+
}
|
|
15435
|
+
function isPidAlive(pid) {
|
|
15436
|
+
try {
|
|
15437
|
+
process.kill(pid, 0);
|
|
15438
|
+
return true;
|
|
15439
|
+
} catch (error) {
|
|
15440
|
+
return error?.code === "EPERM";
|
|
15441
|
+
}
|
|
15442
|
+
}
|
|
15443
|
+
function clearStaleAutoImplState(ctx, reason) {
|
|
15444
|
+
if (!ctx.autoImplStatus.running && !ctx.autoImplProcess) return;
|
|
15445
|
+
const pid = getAutoImplPid(ctx);
|
|
15446
|
+
if (pid && isPidAlive(pid)) return;
|
|
15447
|
+
ctx.log(`Clearing stale auto-implement state: ${reason}${pid ? ` (pid ${pid})` : ""}`);
|
|
15448
|
+
ctx.autoImplProcess = null;
|
|
15449
|
+
ctx.autoImplStatus.running = false;
|
|
15450
|
+
}
|
|
14302
15451
|
function getDefaultAutoImplReference(ctx, category, type) {
|
|
14303
15452
|
if (category === "cli") {
|
|
14304
15453
|
return type === "codex-cli" ? "claude-cli" : "codex-cli";
|
|
@@ -14314,45 +15463,45 @@ function resolveAutoImplReference(ctx, category, requestedReference, targetType)
|
|
|
14314
15463
|
return fallback?.type || null;
|
|
14315
15464
|
}
|
|
14316
15465
|
function getLatestScriptVersionDir(scriptsDir) {
|
|
14317
|
-
if (!
|
|
14318
|
-
const versions =
|
|
15466
|
+
if (!fs13.existsSync(scriptsDir)) return null;
|
|
15467
|
+
const versions = fs13.readdirSync(scriptsDir).filter((d) => {
|
|
14319
15468
|
try {
|
|
14320
|
-
return
|
|
15469
|
+
return fs13.statSync(path17.join(scriptsDir, d)).isDirectory();
|
|
14321
15470
|
} catch {
|
|
14322
15471
|
return false;
|
|
14323
15472
|
}
|
|
14324
15473
|
}).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
|
|
14325
15474
|
if (versions.length === 0) return null;
|
|
14326
|
-
return
|
|
15475
|
+
return path17.join(scriptsDir, versions[0]);
|
|
14327
15476
|
}
|
|
14328
15477
|
function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
|
|
14329
|
-
const canonicalUserDir =
|
|
14330
|
-
const desiredDir = requestedDir ?
|
|
14331
|
-
const upstreamRoot =
|
|
14332
|
-
if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${
|
|
15478
|
+
const canonicalUserDir = path17.resolve(ctx.providerLoader.getUserProviderDir(category, type));
|
|
15479
|
+
const desiredDir = requestedDir ? path17.resolve(requestedDir) : canonicalUserDir;
|
|
15480
|
+
const upstreamRoot = path17.resolve(ctx.providerLoader.getUpstreamDir());
|
|
15481
|
+
if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path17.sep}`)) {
|
|
14333
15482
|
return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
|
|
14334
15483
|
}
|
|
14335
|
-
if (
|
|
15484
|
+
if (path17.basename(desiredDir) !== type) {
|
|
14336
15485
|
return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
|
|
14337
15486
|
}
|
|
14338
15487
|
const sourceDir = ctx.findProviderDir(type);
|
|
14339
15488
|
if (!sourceDir) {
|
|
14340
15489
|
return { dir: null, reason: `Provider source directory not found for '${type}'` };
|
|
14341
15490
|
}
|
|
14342
|
-
if (!
|
|
14343
|
-
|
|
14344
|
-
|
|
15491
|
+
if (!fs13.existsSync(desiredDir)) {
|
|
15492
|
+
fs13.mkdirSync(path17.dirname(desiredDir), { recursive: true });
|
|
15493
|
+
fs13.cpSync(sourceDir, desiredDir, { recursive: true });
|
|
14345
15494
|
ctx.log(`Auto-implement writable copy created: ${desiredDir}`);
|
|
14346
15495
|
}
|
|
14347
|
-
const providerJson =
|
|
14348
|
-
if (!
|
|
15496
|
+
const providerJson = path17.join(desiredDir, "provider.json");
|
|
15497
|
+
if (!fs13.existsSync(providerJson)) {
|
|
14349
15498
|
return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
|
|
14350
15499
|
}
|
|
14351
15500
|
try {
|
|
14352
|
-
const providerData = JSON.parse(
|
|
15501
|
+
const providerData = JSON.parse(fs13.readFileSync(providerJson, "utf-8"));
|
|
14353
15502
|
if (providerData.disableUpstream !== true) {
|
|
14354
15503
|
providerData.disableUpstream = true;
|
|
14355
|
-
|
|
15504
|
+
fs13.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
|
|
14356
15505
|
}
|
|
14357
15506
|
} catch (error) {
|
|
14358
15507
|
return {
|
|
@@ -14365,15 +15514,15 @@ function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
|
|
|
14365
15514
|
function loadAutoImplReferenceScripts(ctx, referenceType) {
|
|
14366
15515
|
if (!referenceType) return {};
|
|
14367
15516
|
const refDir = ctx.findProviderDir(referenceType);
|
|
14368
|
-
if (!refDir || !
|
|
15517
|
+
if (!refDir || !fs13.existsSync(refDir)) return {};
|
|
14369
15518
|
const referenceScripts = {};
|
|
14370
|
-
const scriptsDir =
|
|
15519
|
+
const scriptsDir = path17.join(refDir, "scripts");
|
|
14371
15520
|
const latestDir = getLatestScriptVersionDir(scriptsDir);
|
|
14372
15521
|
if (!latestDir) return referenceScripts;
|
|
14373
|
-
for (const file of
|
|
15522
|
+
for (const file of fs13.readdirSync(latestDir)) {
|
|
14374
15523
|
if (!file.endsWith(".js")) continue;
|
|
14375
15524
|
try {
|
|
14376
|
-
referenceScripts[file] =
|
|
15525
|
+
referenceScripts[file] = fs13.readFileSync(path17.join(latestDir, file), "utf-8");
|
|
14377
15526
|
} catch {
|
|
14378
15527
|
}
|
|
14379
15528
|
}
|
|
@@ -14381,11 +15530,20 @@ function loadAutoImplReferenceScripts(ctx, referenceType) {
|
|
|
14381
15530
|
}
|
|
14382
15531
|
async function handleAutoImplement(ctx, type, req, res) {
|
|
14383
15532
|
const body = await ctx.readBody(req);
|
|
14384
|
-
const {
|
|
15533
|
+
const {
|
|
15534
|
+
agent = "claude-cli",
|
|
15535
|
+
functions,
|
|
15536
|
+
reference,
|
|
15537
|
+
model,
|
|
15538
|
+
comment,
|
|
15539
|
+
providerDir: requestedProviderDir,
|
|
15540
|
+
verification
|
|
15541
|
+
} = body;
|
|
14385
15542
|
if (!functions || !Array.isArray(functions) || functions.length === 0) {
|
|
14386
15543
|
ctx.json(res, 400, { error: 'functions[] is required (e.g. ["readChat", "sendMessage"])' });
|
|
14387
15544
|
return;
|
|
14388
15545
|
}
|
|
15546
|
+
clearStaleAutoImplState(ctx, "new auto-implement request");
|
|
14389
15547
|
if (ctx.autoImplStatus.running) {
|
|
14390
15548
|
ctx.json(res, 409, { error: "Auto-implement already in progress", type: ctx.autoImplStatus.type });
|
|
14391
15549
|
return;
|
|
@@ -14403,7 +15561,55 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14403
15561
|
return;
|
|
14404
15562
|
}
|
|
14405
15563
|
const providerDir = writableProvider.dir;
|
|
15564
|
+
ctx.autoImplStatus = { running: false, type, progress: [] };
|
|
15565
|
+
if (provider.category === "cli" && verification && (verification.fixtureName || verification.fixtureNames && verification.fixtureNames.length > 0)) {
|
|
15566
|
+
sendAutoImplSSE(ctx, {
|
|
15567
|
+
event: "progress",
|
|
15568
|
+
data: {
|
|
15569
|
+
function: "_preflight",
|
|
15570
|
+
status: "verifying",
|
|
15571
|
+
message: "Running preflight verification before spawning agent..."
|
|
15572
|
+
}
|
|
15573
|
+
});
|
|
15574
|
+
try {
|
|
15575
|
+
const preflight = await runCliAutoImplVerification(ctx, type, verification);
|
|
15576
|
+
sendAutoImplSSE(ctx, { event: "verification", data: preflight });
|
|
15577
|
+
if (preflight.pass) {
|
|
15578
|
+
sendAutoImplSSE(ctx, {
|
|
15579
|
+
event: "complete",
|
|
15580
|
+
data: {
|
|
15581
|
+
success: true,
|
|
15582
|
+
exitCode: 0,
|
|
15583
|
+
functions,
|
|
15584
|
+
message: `\u2705 No-op: exact ${preflight.mode} already passes`,
|
|
15585
|
+
verification: preflight,
|
|
15586
|
+
skipped: true
|
|
15587
|
+
}
|
|
15588
|
+
});
|
|
15589
|
+
ctx.json(res, 200, {
|
|
15590
|
+
started: false,
|
|
15591
|
+
skipped: true,
|
|
15592
|
+
type,
|
|
15593
|
+
functions,
|
|
15594
|
+
providerDir,
|
|
15595
|
+
verification: preflight,
|
|
15596
|
+
message: "Preflight verification already passes. No auto-implement run needed."
|
|
15597
|
+
});
|
|
15598
|
+
return;
|
|
15599
|
+
}
|
|
15600
|
+
} catch (error) {
|
|
15601
|
+
sendAutoImplSSE(ctx, {
|
|
15602
|
+
event: "progress",
|
|
15603
|
+
data: {
|
|
15604
|
+
function: "_preflight",
|
|
15605
|
+
status: "verify_failed",
|
|
15606
|
+
message: `Preflight verification errored, continuing to agent run: ${error?.message || error}`
|
|
15607
|
+
}
|
|
15608
|
+
});
|
|
15609
|
+
}
|
|
15610
|
+
}
|
|
14406
15611
|
try {
|
|
15612
|
+
ctx.autoImplStatus = { running: true, type, progress: ctx.autoImplStatus.progress };
|
|
14407
15613
|
const resolvedReference = resolveAutoImplReference(ctx, provider.category, reference, type);
|
|
14408
15614
|
sendAutoImplSSE(ctx, {
|
|
14409
15615
|
event: "progress",
|
|
@@ -14423,17 +15629,17 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14423
15629
|
}
|
|
14424
15630
|
});
|
|
14425
15631
|
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
|
-
|
|
15632
|
+
const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference, verification);
|
|
15633
|
+
const tmpDir = path17.join(os17.tmpdir(), "adhdev-autoimpl");
|
|
15634
|
+
if (!fs13.existsSync(tmpDir)) fs13.mkdirSync(tmpDir, { recursive: true });
|
|
15635
|
+
const promptFile = path17.join(tmpDir, `prompt-${type}-${Date.now()}.md`);
|
|
15636
|
+
fs13.writeFileSync(promptFile, prompt, "utf-8");
|
|
14431
15637
|
ctx.log(`Auto-implement prompt written to ${promptFile} (${prompt.length} chars)`);
|
|
14432
15638
|
const agentProvider = ctx.providerLoader.resolve(agent) || ctx.providerLoader.getMeta(agent);
|
|
14433
15639
|
const spawn4 = agentProvider?.spawn;
|
|
14434
15640
|
if (!spawn4?.command) {
|
|
14435
15641
|
try {
|
|
14436
|
-
|
|
15642
|
+
fs13.unlinkSync(promptFile);
|
|
14437
15643
|
} catch {
|
|
14438
15644
|
}
|
|
14439
15645
|
ctx.json(res, 400, { error: `Agent '${agent}' has no spawn config. Select a CLI provider with a spawn configuration.` });
|
|
@@ -14442,7 +15648,8 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14442
15648
|
const agentCategory = agentProvider?.category;
|
|
14443
15649
|
if (agentCategory === "acp") {
|
|
14444
15650
|
sendAutoImplSSE(ctx, { event: "progress", data: { function: "_init", status: "spawning", message: `Spawning ACP agent: ${spawn4.command} ${(spawn4.args || []).join(" ")}` } });
|
|
14445
|
-
ctx.autoImplStatus =
|
|
15651
|
+
ctx.autoImplStatus.running = true;
|
|
15652
|
+
ctx.autoImplStatus.type = type;
|
|
14446
15653
|
const { ClientSideConnection: ClientSideConnection2, ndJsonStream: ndJsonStream2, PROTOCOL_VERSION: PROTOCOL_VERSION2 } = await import("@agentclientprotocol/sdk");
|
|
14447
15654
|
const { Readable: Readable2, Writable: Writable2 } = await import("stream");
|
|
14448
15655
|
const { spawn: spawnFn2 } = await import("child_process");
|
|
@@ -14534,7 +15741,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14534
15741
|
} catch {
|
|
14535
15742
|
}
|
|
14536
15743
|
try {
|
|
14537
|
-
|
|
15744
|
+
fs13.unlinkSync(promptFile);
|
|
14538
15745
|
} catch {
|
|
14539
15746
|
}
|
|
14540
15747
|
ctx.log(`Auto-implement (ACP) ${success ? "completed" : "failed"}: ${type} (exit: ${code})`);
|
|
@@ -14612,7 +15819,8 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14612
15819
|
}
|
|
14613
15820
|
}
|
|
14614
15821
|
sendAutoImplSSE(ctx, { event: "progress", data: { function: "_init", status: "spawning", message: `Spawning agent: ${shellCmd.substring(0, 200)}... (prompt: ${prompt.length} chars)` } });
|
|
14615
|
-
ctx.autoImplStatus =
|
|
15822
|
+
ctx.autoImplStatus.running = true;
|
|
15823
|
+
ctx.autoImplStatus.type = type;
|
|
14616
15824
|
const spawnedAt = Date.now();
|
|
14617
15825
|
let child;
|
|
14618
15826
|
let isPty = false;
|
|
@@ -14655,6 +15863,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14655
15863
|
let approvalKeys = { 0: "y\r" };
|
|
14656
15864
|
let approvalBuffer = "";
|
|
14657
15865
|
let lastApprovalTime = 0;
|
|
15866
|
+
let completionSignalSeen = false;
|
|
14658
15867
|
try {
|
|
14659
15868
|
const { normalizeCliProviderForRuntime: normalizeCliProviderForRuntime2 } = await Promise.resolve().then(() => (init_provider_cli_adapter(), provider_cli_adapter_exports));
|
|
14660
15869
|
const normalized = normalizeCliProviderForRuntime2(agentProvider);
|
|
@@ -14668,6 +15877,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14668
15877
|
approvalBuffer = (approvalBuffer + cleanData).slice(-1500);
|
|
14669
15878
|
const elapsed = Date.now() - spawnedAt;
|
|
14670
15879
|
if (elapsed > 15e3 && cleanData.includes("_PIPELINE_COMPLETE_SIGNAL_")) {
|
|
15880
|
+
completionSignalSeen = true;
|
|
14671
15881
|
ctx.log(`Agent finished task after ${Math.round(elapsed / 1e3)}s. Terminating interactive CLI session to unblock pipeline.`);
|
|
14672
15882
|
sendAutoImplSSE(ctx, { event: "output", data: { chunk: `
|
|
14673
15883
|
[\u{1F916} ADHDev Pipeline] Completion token detected. Proceeding...
|
|
@@ -14691,6 +15901,55 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14691
15901
|
lastApprovalTime = Date.now();
|
|
14692
15902
|
}
|
|
14693
15903
|
};
|
|
15904
|
+
const finalizeCliAutoImpl = async (code) => {
|
|
15905
|
+
ctx.autoImplProcess = null;
|
|
15906
|
+
let success = completionSignalSeen || code === 0;
|
|
15907
|
+
let message = success ? completionSignalSeen && code !== 0 ? "\u2705 Auto-implement complete (completion signal)" : "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})`;
|
|
15908
|
+
let verificationSummary = null;
|
|
15909
|
+
try {
|
|
15910
|
+
ctx.providerLoader.reload();
|
|
15911
|
+
} catch {
|
|
15912
|
+
}
|
|
15913
|
+
if (provider.category === "cli" && verification) {
|
|
15914
|
+
sendAutoImplSSE(ctx, {
|
|
15915
|
+
event: "progress",
|
|
15916
|
+
data: {
|
|
15917
|
+
function: "_verify",
|
|
15918
|
+
status: "running",
|
|
15919
|
+
message: "Running exact post-patch verification..."
|
|
15920
|
+
}
|
|
15921
|
+
});
|
|
15922
|
+
try {
|
|
15923
|
+
verificationSummary = await runCliAutoImplVerification(ctx, type, verification);
|
|
15924
|
+
sendAutoImplSSE(ctx, { event: "verification", data: verificationSummary });
|
|
15925
|
+
success = verificationSummary.pass;
|
|
15926
|
+
message = verificationSummary.pass ? `\u2705 Auto-implement complete (${verificationSummary.mode})` : `\u274C Post-patch verification failed (${verificationSummary.mode}): ${verificationSummary.failures.join("; ") || "unknown failure"}`;
|
|
15927
|
+
} catch (error) {
|
|
15928
|
+
success = false;
|
|
15929
|
+
message = `\u274C Post-patch verification error: ${error?.message || error}`;
|
|
15930
|
+
sendAutoImplSSE(ctx, {
|
|
15931
|
+
event: "verification",
|
|
15932
|
+
data: { pass: false, error: error?.message || String(error) }
|
|
15933
|
+
});
|
|
15934
|
+
}
|
|
15935
|
+
}
|
|
15936
|
+
ctx.autoImplStatus.running = false;
|
|
15937
|
+
sendAutoImplSSE(ctx, {
|
|
15938
|
+
event: "complete",
|
|
15939
|
+
data: {
|
|
15940
|
+
success,
|
|
15941
|
+
exitCode: code,
|
|
15942
|
+
functions,
|
|
15943
|
+
message,
|
|
15944
|
+
verification: verificationSummary
|
|
15945
|
+
}
|
|
15946
|
+
});
|
|
15947
|
+
try {
|
|
15948
|
+
fs13.unlinkSync(promptFile);
|
|
15949
|
+
} catch {
|
|
15950
|
+
}
|
|
15951
|
+
ctx.log(`Auto-implement ${success ? "completed" : "failed"}: ${type} (exit: ${code})${verificationSummary ? ` verify=${verificationSummary.pass ? "pass" : "fail"}` : ""}`);
|
|
15952
|
+
};
|
|
14694
15953
|
if (isPty) {
|
|
14695
15954
|
child.onData((data) => {
|
|
14696
15955
|
stdout += data;
|
|
@@ -14702,21 +15961,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14702
15961
|
sendAutoImplSSE(ctx, { event: "output", data: { chunk: data, stream: "stdout" } });
|
|
14703
15962
|
});
|
|
14704
15963
|
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
|
-
}
|
|
15964
|
+
void finalizeCliAutoImpl(code);
|
|
14720
15965
|
});
|
|
14721
15966
|
} else {
|
|
14722
15967
|
child.stdout?.on("data", (d) => {
|
|
@@ -14733,27 +15978,7 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14733
15978
|
sendAutoImplSSE(ctx, { event: "output", data: { chunk, stream: "stderr" } });
|
|
14734
15979
|
});
|
|
14735
15980
|
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})`);
|
|
15981
|
+
void finalizeCliAutoImpl(code);
|
|
14757
15982
|
});
|
|
14758
15983
|
}
|
|
14759
15984
|
ctx.json(res, 202, {
|
|
@@ -14770,9 +15995,9 @@ async function handleAutoImplement(ctx, type, req, res) {
|
|
|
14770
15995
|
ctx.json(res, 500, { error: `Auto-implement failed: ${e.message}` });
|
|
14771
15996
|
}
|
|
14772
15997
|
}
|
|
14773
|
-
function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, userComment, referenceType) {
|
|
15998
|
+
function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, userComment, referenceType, verification) {
|
|
14774
15999
|
if (provider.category === "cli") {
|
|
14775
|
-
return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType);
|
|
16000
|
+
return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification);
|
|
14776
16001
|
}
|
|
14777
16002
|
const lines = [];
|
|
14778
16003
|
lines.push("You are implementing browser automation scripts for an IDE provider.");
|
|
@@ -14797,7 +16022,7 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14797
16022
|
setMode: "set_mode.js"
|
|
14798
16023
|
};
|
|
14799
16024
|
const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
|
|
14800
|
-
const scriptsDir =
|
|
16025
|
+
const scriptsDir = path17.join(providerDir, "scripts");
|
|
14801
16026
|
const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
|
|
14802
16027
|
if (latestScriptsDir) {
|
|
14803
16028
|
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
@@ -14805,10 +16030,10 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14805
16030
|
lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
|
|
14806
16031
|
lines.push("These are the ONLY files you are allowed to modify. Replace the TODO stubs with working implementations.");
|
|
14807
16032
|
lines.push("");
|
|
14808
|
-
for (const file of
|
|
16033
|
+
for (const file of fs13.readdirSync(latestScriptsDir)) {
|
|
14809
16034
|
if (file.endsWith(".js") && targetFileNames.has(file)) {
|
|
14810
16035
|
try {
|
|
14811
|
-
const content =
|
|
16036
|
+
const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
|
|
14812
16037
|
lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
|
|
14813
16038
|
lines.push("```javascript");
|
|
14814
16039
|
lines.push(content);
|
|
@@ -14818,14 +16043,14 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14818
16043
|
}
|
|
14819
16044
|
}
|
|
14820
16045
|
}
|
|
14821
|
-
const refFiles =
|
|
16046
|
+
const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
|
|
14822
16047
|
if (refFiles.length > 0) {
|
|
14823
16048
|
lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
|
|
14824
16049
|
lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
|
|
14825
16050
|
lines.push("");
|
|
14826
16051
|
for (const file of refFiles) {
|
|
14827
16052
|
try {
|
|
14828
|
-
const content =
|
|
16053
|
+
const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
|
|
14829
16054
|
lines.push(`### \`${file}\` \u{1F512}`);
|
|
14830
16055
|
lines.push("```javascript");
|
|
14831
16056
|
lines.push(content);
|
|
@@ -14866,11 +16091,11 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
14866
16091
|
lines.push("");
|
|
14867
16092
|
}
|
|
14868
16093
|
}
|
|
14869
|
-
const docsDir =
|
|
16094
|
+
const docsDir = path17.join(providerDir, "../../docs");
|
|
14870
16095
|
const loadGuide = (name) => {
|
|
14871
16096
|
try {
|
|
14872
|
-
const p =
|
|
14873
|
-
if (
|
|
16097
|
+
const p = path17.join(docsDir, name);
|
|
16098
|
+
if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
|
|
14874
16099
|
} catch {
|
|
14875
16100
|
}
|
|
14876
16101
|
return null;
|
|
@@ -15028,8 +16253,69 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
|
|
|
15028
16253
|
lines.push("Start NOW. Do not ask for permission. Explore the DOM -> Code -> Test.");
|
|
15029
16254
|
return lines.join("\n");
|
|
15030
16255
|
}
|
|
15031
|
-
function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType) {
|
|
16256
|
+
function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification) {
|
|
15032
16257
|
const lines = [];
|
|
16258
|
+
const defaultExercisePayload = {
|
|
16259
|
+
type,
|
|
16260
|
+
workingDir: providerDir,
|
|
16261
|
+
freshSession: true,
|
|
16262
|
+
autoLaunch: true,
|
|
16263
|
+
autoResolveApprovals: true,
|
|
16264
|
+
approvalButtonIndex: 0,
|
|
16265
|
+
timeoutMs: 45e3,
|
|
16266
|
+
traceLimit: 200,
|
|
16267
|
+
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."
|
|
16268
|
+
};
|
|
16269
|
+
const exercisePayload = {
|
|
16270
|
+
...defaultExercisePayload,
|
|
16271
|
+
...verification?.request || {},
|
|
16272
|
+
type,
|
|
16273
|
+
workingDir: providerDir
|
|
16274
|
+
};
|
|
16275
|
+
const exerciseJson = JSON.stringify(exercisePayload).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
|
|
16276
|
+
const verificationInspectFields = verification?.inspectFields?.length ? verification.inspectFields : [
|
|
16277
|
+
"debug.messages",
|
|
16278
|
+
"trace.entries[].payload.parsedLastAssistant",
|
|
16279
|
+
"trace.entries[].payload.lastAssistant"
|
|
16280
|
+
];
|
|
16281
|
+
const verificationMustContainAny = verification?.mustContainAny || [];
|
|
16282
|
+
const verificationMustNotContainAny = verification?.mustNotContainAny || [];
|
|
16283
|
+
const verificationMustMatchAny = verification?.mustMatchAny || [];
|
|
16284
|
+
const verificationMustNotMatchAny = verification?.mustNotMatchAny || [];
|
|
16285
|
+
const verificationLastAssistantMustContainAny = verification?.lastAssistantMustContainAny || [];
|
|
16286
|
+
const verificationLastAssistantMustNotContainAny = verification?.lastAssistantMustNotContainAny || [];
|
|
16287
|
+
const verificationLastAssistantMustMatchAny = verification?.lastAssistantMustMatchAny || [];
|
|
16288
|
+
const verificationLastAssistantMustNotMatchAny = verification?.lastAssistantMustNotMatchAny || [];
|
|
16289
|
+
const quotedMustContain = verificationMustContainAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16290
|
+
const quotedMustNotContain = verificationMustNotContainAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16291
|
+
const quotedMustMatch = verificationMustMatchAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16292
|
+
const quotedMustNotMatch = verificationMustNotMatchAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16293
|
+
const quotedLastAssistantMustContain = verificationLastAssistantMustContainAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16294
|
+
const quotedLastAssistantMustNotContain = verificationLastAssistantMustNotContainAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16295
|
+
const quotedLastAssistantMustMatch = verificationLastAssistantMustMatchAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16296
|
+
const quotedLastAssistantMustNotMatch = verificationLastAssistantMustNotMatchAny.map((value) => JSON.stringify(value)).join(", ");
|
|
16297
|
+
const fixtureName = verification?.fixtureName || `${type}-provider-fix`;
|
|
16298
|
+
const fixtureNames = Array.isArray(verification?.fixtureNames) ? verification.fixtureNames.map((value) => String(value || "").trim()).filter(Boolean) : [];
|
|
16299
|
+
const fixtureCaptureJson = JSON.stringify({
|
|
16300
|
+
type,
|
|
16301
|
+
name: fixtureName,
|
|
16302
|
+
request: exercisePayload,
|
|
16303
|
+
assertions: {
|
|
16304
|
+
mustContainAny: verificationMustContainAny,
|
|
16305
|
+
mustNotContainAny: verificationMustNotContainAny,
|
|
16306
|
+
mustMatchAny: verificationMustMatchAny,
|
|
16307
|
+
mustNotMatchAny: verificationMustNotMatchAny,
|
|
16308
|
+
lastAssistantMustContainAny: verificationLastAssistantMustContainAny,
|
|
16309
|
+
lastAssistantMustNotContainAny: verificationLastAssistantMustNotContainAny,
|
|
16310
|
+
lastAssistantMustMatchAny: verificationLastAssistantMustMatchAny,
|
|
16311
|
+
lastAssistantMustNotMatchAny: verificationLastAssistantMustNotMatchAny,
|
|
16312
|
+
requireNotTimedOut: true
|
|
16313
|
+
}
|
|
16314
|
+
}).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
|
|
16315
|
+
const fixtureReplayJson = JSON.stringify({
|
|
16316
|
+
type,
|
|
16317
|
+
name: fixtureName
|
|
16318
|
+
}).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
|
|
15033
16319
|
lines.push("You are implementing PTY parsing scripts for a CLI provider.");
|
|
15034
16320
|
lines.push("Be concise. Do NOT explain your reasoning. Edit files directly and verify with the local DevServer.");
|
|
15035
16321
|
lines.push("");
|
|
@@ -15043,7 +16329,7 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15043
16329
|
parseApproval: "parse_approval.js"
|
|
15044
16330
|
};
|
|
15045
16331
|
const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
|
|
15046
|
-
const scriptsDir =
|
|
16332
|
+
const scriptsDir = path17.join(providerDir, "scripts");
|
|
15047
16333
|
const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
|
|
15048
16334
|
if (latestScriptsDir) {
|
|
15049
16335
|
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
@@ -15051,11 +16337,11 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15051
16337
|
lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
|
|
15052
16338
|
lines.push("These are the ONLY files you are allowed to modify. Replace TODO or heuristic-only logic with working PTY-aware implementations.");
|
|
15053
16339
|
lines.push("");
|
|
15054
|
-
for (const file of
|
|
16340
|
+
for (const file of fs13.readdirSync(latestScriptsDir)) {
|
|
15055
16341
|
if (!file.endsWith(".js")) continue;
|
|
15056
16342
|
if (!targetFileNames.has(file)) continue;
|
|
15057
16343
|
try {
|
|
15058
|
-
const content =
|
|
16344
|
+
const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
|
|
15059
16345
|
lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
|
|
15060
16346
|
lines.push("```javascript");
|
|
15061
16347
|
lines.push(content);
|
|
@@ -15064,14 +16350,14 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15064
16350
|
} catch {
|
|
15065
16351
|
}
|
|
15066
16352
|
}
|
|
15067
|
-
const refFiles =
|
|
16353
|
+
const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
|
|
15068
16354
|
if (refFiles.length > 0) {
|
|
15069
16355
|
lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
|
|
15070
16356
|
lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
|
|
15071
16357
|
lines.push("");
|
|
15072
16358
|
for (const file of refFiles) {
|
|
15073
16359
|
try {
|
|
15074
|
-
const content =
|
|
16360
|
+
const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
|
|
15075
16361
|
lines.push(`### \`${file}\` \u{1F512}`);
|
|
15076
16362
|
lines.push("```javascript");
|
|
15077
16363
|
lines.push(content);
|
|
@@ -15104,17 +16390,17 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15104
16390
|
lines.push("");
|
|
15105
16391
|
}
|
|
15106
16392
|
}
|
|
15107
|
-
const docsDir =
|
|
16393
|
+
const docsDir = path17.join(providerDir, "../../docs");
|
|
15108
16394
|
const loadGuide = (name) => {
|
|
15109
16395
|
try {
|
|
15110
|
-
const p =
|
|
15111
|
-
if (
|
|
16396
|
+
const p = path17.join(docsDir, name);
|
|
16397
|
+
if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
|
|
15112
16398
|
} catch {
|
|
15113
16399
|
}
|
|
15114
16400
|
return null;
|
|
15115
16401
|
};
|
|
15116
16402
|
const providerGuide = loadGuide("PROVIDER_GUIDE.md");
|
|
15117
|
-
if (providerGuide) {
|
|
16403
|
+
if (providerGuide && provider.category !== "cli") {
|
|
15118
16404
|
lines.push("## Documentation: PROVIDER_GUIDE.md");
|
|
15119
16405
|
lines.push("```markdown");
|
|
15120
16406
|
lines.push(providerGuide);
|
|
@@ -15156,6 +16442,11 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15156
16442
|
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
16443
|
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
16444
|
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.");
|
|
16445
|
+
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.');
|
|
16446
|
+
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.");
|
|
16447
|
+
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.');
|
|
16448
|
+
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.");
|
|
16449
|
+
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
16450
|
lines.push("");
|
|
15160
16451
|
lines.push("## Task");
|
|
15161
16452
|
lines.push(`Edit files in \`${providerDir}\` to implement: **${functions.join(", ")}**`);
|
|
@@ -15163,35 +16454,145 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15163
16454
|
lines.push("## Verification API");
|
|
15164
16455
|
lines.push("Use the DevServer CLI debug endpoints, not DOM/CDP routes.");
|
|
15165
16456
|
lines.push("");
|
|
15166
|
-
lines.push("### 1.
|
|
16457
|
+
lines.push("### 1. Preferred: run a full autonomous repro");
|
|
16458
|
+
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
16459
|
lines.push("```bash");
|
|
15168
|
-
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/
|
|
16460
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
|
|
15169
16461
|
lines.push(' -H "Content-Type: application/json" \\');
|
|
15170
|
-
lines.push(` -d '
|
|
16462
|
+
lines.push(` -d '${exerciseJson}'`);
|
|
15171
16463
|
lines.push("```");
|
|
15172
16464
|
lines.push("");
|
|
16465
|
+
if (verification?.description) {
|
|
16466
|
+
lines.push("Verification intent:");
|
|
16467
|
+
lines.push(verification.description);
|
|
16468
|
+
lines.push("");
|
|
16469
|
+
}
|
|
16470
|
+
lines.push("Read the JSON response carefully. It already includes:");
|
|
16471
|
+
lines.push("1. `instanceId`");
|
|
16472
|
+
lines.push("2. `statusesSeen` and `approvalsResolved`");
|
|
16473
|
+
lines.push("3. `debug` for the final settled state");
|
|
16474
|
+
lines.push("4. `trace.entries` for the repro turn");
|
|
16475
|
+
lines.push("");
|
|
16476
|
+
lines.push("Save the response to a temp file and inspect the exact parsed transcript fields before editing:");
|
|
16477
|
+
lines.push("```bash");
|
|
16478
|
+
lines.push(`EXERCISE_JSON=$(mktemp)`);
|
|
16479
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
|
|
16480
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16481
|
+
lines.push(` -d '${exerciseJson}' > "$EXERCISE_JSON"`);
|
|
16482
|
+
lines.push(`jq '{timedOut,statusesSeen,approvalsResolved,inspect:{${verificationInspectFields.map((field, index) => `f${index + 1}: .${field}`).join(", ")}}}' "$EXERCISE_JSON"`);
|
|
16483
|
+
lines.push("```");
|
|
16484
|
+
lines.push("");
|
|
16485
|
+
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) {
|
|
16486
|
+
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.");
|
|
16487
|
+
lines.push("```bash");
|
|
16488
|
+
if (verificationMustContainAny.length > 0) {
|
|
16489
|
+
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"`);
|
|
16490
|
+
}
|
|
16491
|
+
if (verificationMustNotContainAny.length > 0) {
|
|
16492
|
+
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"`);
|
|
16493
|
+
}
|
|
16494
|
+
if (verificationMustMatchAny.length > 0) {
|
|
16495
|
+
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"`);
|
|
16496
|
+
}
|
|
16497
|
+
if (verificationMustNotMatchAny.length > 0) {
|
|
16498
|
+
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"`);
|
|
16499
|
+
}
|
|
16500
|
+
if (verificationLastAssistantMustContainAny.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 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"`);
|
|
16502
|
+
}
|
|
16503
|
+
if (verificationLastAssistantMustNotContainAny.length > 0) {
|
|
16504
|
+
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"`);
|
|
16505
|
+
}
|
|
16506
|
+
if (verificationLastAssistantMustMatchAny.length > 0) {
|
|
16507
|
+
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"`);
|
|
16508
|
+
}
|
|
16509
|
+
if (verificationLastAssistantMustNotMatchAny.length > 0) {
|
|
16510
|
+
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"`);
|
|
16511
|
+
}
|
|
16512
|
+
lines.push("```");
|
|
16513
|
+
lines.push("");
|
|
16514
|
+
}
|
|
16515
|
+
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.");
|
|
16516
|
+
lines.push("");
|
|
16517
|
+
lines.push("### 1b. Persist or replay the exact repro as a reusable fixture");
|
|
16518
|
+
if (fixtureNames.length > 0) {
|
|
16519
|
+
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(", ")}.`);
|
|
16520
|
+
for (const name of fixtureNames) {
|
|
16521
|
+
const replayJson = JSON.stringify({ type, name }).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
|
|
16522
|
+
lines.push("```bash");
|
|
16523
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
|
|
16524
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16525
|
+
lines.push(` -d '${replayJson}'`);
|
|
16526
|
+
lines.push("```");
|
|
16527
|
+
lines.push("");
|
|
16528
|
+
}
|
|
16529
|
+
lines.push("Do not create new fixtures unless one of the listed fixtures is missing or stale.");
|
|
16530
|
+
} else if (verification?.fixtureName) {
|
|
16531
|
+
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.`);
|
|
16532
|
+
lines.push("```bash");
|
|
16533
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
|
|
16534
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16535
|
+
lines.push(` -d '${fixtureReplayJson}'`);
|
|
16536
|
+
lines.push("```");
|
|
16537
|
+
lines.push("");
|
|
16538
|
+
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.");
|
|
16539
|
+
lines.push("```bash");
|
|
16540
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
|
|
16541
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16542
|
+
lines.push(` -d '${fixtureCaptureJson}'`);
|
|
16543
|
+
lines.push("```");
|
|
16544
|
+
} else {
|
|
16545
|
+
lines.push("Capture the exact exercise once before editing. After patching, replay THIS fixture and do not declare success unless replay passes.");
|
|
16546
|
+
lines.push("```bash");
|
|
16547
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
|
|
16548
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16549
|
+
lines.push(` -d '${fixtureCaptureJson}'`);
|
|
16550
|
+
lines.push("");
|
|
16551
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
|
|
16552
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16553
|
+
lines.push(` -d '${fixtureReplayJson}'`);
|
|
16554
|
+
lines.push("```");
|
|
16555
|
+
}
|
|
16556
|
+
lines.push("");
|
|
16557
|
+
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.");
|
|
16558
|
+
lines.push("");
|
|
15173
16559
|
lines.push("### 2. Inspect parsed + raw adapter state");
|
|
15174
16560
|
lines.push("```bash");
|
|
16561
|
+
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/launch \\`);
|
|
16562
|
+
lines.push(' -H "Content-Type: application/json" \\');
|
|
16563
|
+
lines.push(` -d '{"type":"${type}","workingDir":"${providerDir.replace(/\\/g, "\\\\")}"}'`);
|
|
15175
16564
|
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
|
|
16565
|
+
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
|
|
15176
16566
|
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
|
|
15177
16567
|
lines.push("```");
|
|
15178
16568
|
lines.push("");
|
|
16569
|
+
lines.push("The CLI trace endpoint is the primary debugging source. Read it BEFORE editing any parser code.");
|
|
16570
|
+
lines.push("Use the trace timeline to find the latest `settled` or `commit_transcript` frame for the repro turn and inspect these fields first:");
|
|
16571
|
+
lines.push("1. `payload.screenText`");
|
|
16572
|
+
lines.push("2. `payload.detectStatus` and `payload.parsedStatus`");
|
|
16573
|
+
lines.push("3. `payload.parsedLastAssistant`");
|
|
16574
|
+
lines.push("4. `payload.approval` / `payload.parsedActiveModal`");
|
|
16575
|
+
lines.push("5. `payload.rawPreview` only when control-sequence residue matters");
|
|
16576
|
+
lines.push("");
|
|
15179
16577
|
lines.push("The debug payload should be read in this priority order:");
|
|
15180
16578
|
lines.push("1. `screenText` / current visible state");
|
|
15181
16579
|
lines.push("2. parsed `status`, `messages`, `activeModal`");
|
|
15182
16580
|
lines.push("3. `rawBuffer` only for style/control-sequence cues");
|
|
15183
16581
|
lines.push("4. `buffer` only when the current screen is insufficient");
|
|
15184
16582
|
lines.push("");
|
|
15185
|
-
lines.push("
|
|
16583
|
+
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.");
|
|
16584
|
+
lines.push("Do NOT guess based only on the final chat bubble or a truncated UI preview.");
|
|
16585
|
+
lines.push("");
|
|
16586
|
+
lines.push("Extract the current `instanceId` from the exercise, launch, or status response and keep using it below.");
|
|
15186
16587
|
lines.push("");
|
|
15187
|
-
lines.push("### 3.
|
|
16588
|
+
lines.push("### 3. Manual fallback only: send a realistic approval-triggering prompt");
|
|
15188
16589
|
lines.push("```bash");
|
|
15189
16590
|
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/send \\`);
|
|
15190
16591
|
lines.push(' -H "Content-Type: application/json" \\');
|
|
15191
16592
|
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
16593
|
lines.push("```");
|
|
15193
16594
|
lines.push("");
|
|
15194
|
-
lines.push("### 4.
|
|
16595
|
+
lines.push("### 4. Manual fallback only: if approval appears, resolve it until the CLI reaches idle");
|
|
15195
16596
|
lines.push("```bash");
|
|
15196
16597
|
lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/resolve \\`);
|
|
15197
16598
|
lines.push(' -H "Content-Type: application/json" \\');
|
|
@@ -15201,10 +16602,14 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15201
16602
|
lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","keys":"1"}'`);
|
|
15202
16603
|
lines.push("```");
|
|
15203
16604
|
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.");
|
|
16605
|
+
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
16606
|
lines.push("");
|
|
15206
16607
|
lines.push("### Patch Discipline");
|
|
15207
16608
|
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.");
|
|
16609
|
+
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.");
|
|
16610
|
+
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.");
|
|
16611
|
+
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.");
|
|
16612
|
+
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
16613
|
lines.push("");
|
|
15209
16614
|
lines.push("### 5. Verify the side effects outside the CLI");
|
|
15210
16615
|
lines.push("```bash");
|
|
@@ -15229,6 +16634,8 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15229
16634
|
lines.push("7. Re-run the debug endpoints after edits. Do NOT finish until the parsed result looks correct.");
|
|
15230
16635
|
lines.push("8. Confirm the parser still works after a redraw or scroll change without duplicating transcript history.");
|
|
15231
16636
|
lines.push("9. Confirm the implementation prefers current-screen signals over stale history when both are present.");
|
|
16637
|
+
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.");
|
|
16638
|
+
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
16639
|
lines.push("");
|
|
15233
16640
|
if (userComment) {
|
|
15234
16641
|
lines.push("## \u26A0\uFE0F User Instructions (HIGH PRIORITY)");
|
|
@@ -15237,10 +16644,11 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
|
|
|
15237
16644
|
lines.push(userComment);
|
|
15238
16645
|
lines.push("");
|
|
15239
16646
|
}
|
|
15240
|
-
lines.push("Start NOW. Launch the CLI, inspect PTY state, edit the scripts, and verify via the CLI debug endpoints.");
|
|
16647
|
+
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
16648
|
return lines.join("\n");
|
|
15242
16649
|
}
|
|
15243
16650
|
function handleAutoImplSSE(ctx, type, req, res) {
|
|
16651
|
+
clearStaleAutoImplState(ctx, "SSE connection opened");
|
|
15244
16652
|
res.writeHead(200, {
|
|
15245
16653
|
"Content-Type": "text/event-stream",
|
|
15246
16654
|
"Cache-Control": "no-cache",
|
|
@@ -15262,6 +16670,7 @@ data: ${JSON.stringify(p.data)}
|
|
|
15262
16670
|
});
|
|
15263
16671
|
}
|
|
15264
16672
|
function handleAutoImplCancel(ctx, _type, _req, res) {
|
|
16673
|
+
clearStaleAutoImplState(ctx, "cancel request");
|
|
15265
16674
|
if (ctx.autoImplProcess) {
|
|
15266
16675
|
ctx.autoImplProcess.kill("SIGTERM");
|
|
15267
16676
|
setTimeout(() => {
|
|
@@ -15345,11 +16754,16 @@ var DevServer = class _DevServer {
|
|
|
15345
16754
|
{ method: "GET", pattern: "/api/cli/status", handler: (q, s) => this.handleCliStatus(q, s) },
|
|
15346
16755
|
{ method: "POST", pattern: "/api/cli/launch", handler: (q, s) => this.handleCliLaunch(q, s) },
|
|
15347
16756
|
{ method: "POST", pattern: "/api/cli/send", handler: (q, s) => this.handleCliSend(q, s) },
|
|
16757
|
+
{ method: "POST", pattern: "/api/cli/exercise", handler: (q, s) => this.handleCliExercise(q, s) },
|
|
16758
|
+
{ method: "POST", pattern: "/api/cli/fixture/capture", handler: (q, s) => this.handleCliFixtureCapture(q, s) },
|
|
16759
|
+
{ method: "POST", pattern: "/api/cli/fixture/replay", handler: (q, s) => this.handleCliFixtureReplay(q, s) },
|
|
15348
16760
|
{ method: "POST", pattern: "/api/cli/resolve", handler: (q, s) => this.handleCliResolve(q, s) },
|
|
15349
16761
|
{ method: "POST", pattern: "/api/cli/raw", handler: (q, s) => this.handleCliRaw(q, s) },
|
|
15350
16762
|
{ method: "POST", pattern: "/api/cli/stop", handler: (q, s) => this.handleCliStop(q, s) },
|
|
15351
16763
|
{ method: "GET", pattern: "/api/cli/events", handler: (q, s) => this.handleCliSSE(q, s) },
|
|
15352
16764
|
{ method: "GET", pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q, s, p) => this.handleCliDebug(p[0], q, s) },
|
|
16765
|
+
{ method: "GET", pattern: /^\/api\/cli\/trace\/([^/]+)$/, handler: (q, s, p) => this.handleCliTrace(p[0], q, s) },
|
|
16766
|
+
{ method: "GET", pattern: /^\/api\/cli\/fixtures\/([^/]+)$/, handler: (q, s, p) => this.handleCliFixtureList(p[0], q, s) },
|
|
15353
16767
|
// Dynamic routes (provider :type param)
|
|
15354
16768
|
{ method: "POST", pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q, s, p) => this.handleRunScript(p[0], q, s) },
|
|
15355
16769
|
{ method: "GET", pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q, s, p) => this.handleListFiles(p[0], q, s) },
|
|
@@ -15383,8 +16797,8 @@ var DevServer = class _DevServer {
|
|
|
15383
16797
|
}
|
|
15384
16798
|
getEndpointList() {
|
|
15385
16799
|
return this.routes.map((r) => {
|
|
15386
|
-
const
|
|
15387
|
-
return `${r.method.padEnd(5)} ${
|
|
16800
|
+
const path19 = typeof r.pattern === "string" ? r.pattern : r.pattern.source.replace(/\\\//g, "/").replace(/\(\[.*?\]\+\)/g, ":type").replace(/[\^$]/g, "");
|
|
16801
|
+
return `${r.method.padEnd(5)} ${path19}`;
|
|
15388
16802
|
});
|
|
15389
16803
|
}
|
|
15390
16804
|
async start(port = DEV_SERVER_PORT) {
|
|
@@ -15666,12 +17080,12 @@ var DevServer = class _DevServer {
|
|
|
15666
17080
|
// ─── DevConsole SPA ───
|
|
15667
17081
|
getConsoleDistDir() {
|
|
15668
17082
|
const candidates = [
|
|
15669
|
-
|
|
15670
|
-
|
|
15671
|
-
|
|
17083
|
+
path18.resolve(__dirname, "../../web-devconsole/dist"),
|
|
17084
|
+
path18.resolve(__dirname, "../../../web-devconsole/dist"),
|
|
17085
|
+
path18.join(process.cwd(), "packages/web-devconsole/dist")
|
|
15672
17086
|
];
|
|
15673
17087
|
for (const dir of candidates) {
|
|
15674
|
-
if (
|
|
17088
|
+
if (fs14.existsSync(path18.join(dir, "index.html"))) return dir;
|
|
15675
17089
|
}
|
|
15676
17090
|
return null;
|
|
15677
17091
|
}
|
|
@@ -15681,9 +17095,9 @@ var DevServer = class _DevServer {
|
|
|
15681
17095
|
this.json(res, 500, { error: "DevConsole not found. Run: npm run build -w packages/web-devconsole" });
|
|
15682
17096
|
return;
|
|
15683
17097
|
}
|
|
15684
|
-
const htmlPath =
|
|
17098
|
+
const htmlPath = path18.join(distDir, "index.html");
|
|
15685
17099
|
try {
|
|
15686
|
-
const html =
|
|
17100
|
+
const html = fs14.readFileSync(htmlPath, "utf-8");
|
|
15687
17101
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
15688
17102
|
res.end(html);
|
|
15689
17103
|
} catch (e) {
|
|
@@ -15706,15 +17120,15 @@ var DevServer = class _DevServer {
|
|
|
15706
17120
|
this.json(res, 404, { error: "Not found" });
|
|
15707
17121
|
return;
|
|
15708
17122
|
}
|
|
15709
|
-
const safePath =
|
|
15710
|
-
const filePath =
|
|
17123
|
+
const safePath = path18.normalize(pathname).replace(/^\.\.\//, "");
|
|
17124
|
+
const filePath = path18.join(distDir, safePath);
|
|
15711
17125
|
if (!filePath.startsWith(distDir)) {
|
|
15712
17126
|
this.json(res, 403, { error: "Forbidden" });
|
|
15713
17127
|
return;
|
|
15714
17128
|
}
|
|
15715
17129
|
try {
|
|
15716
|
-
const content =
|
|
15717
|
-
const ext =
|
|
17130
|
+
const content = fs14.readFileSync(filePath);
|
|
17131
|
+
const ext = path18.extname(filePath);
|
|
15718
17132
|
const contentType = _DevServer.MIME_MAP[ext] || "application/octet-stream";
|
|
15719
17133
|
res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "public, max-age=31536000, immutable" });
|
|
15720
17134
|
res.end(content);
|
|
@@ -15822,14 +17236,14 @@ var DevServer = class _DevServer {
|
|
|
15822
17236
|
const files = [];
|
|
15823
17237
|
const scan = (d, prefix) => {
|
|
15824
17238
|
try {
|
|
15825
|
-
for (const entry of
|
|
17239
|
+
for (const entry of fs14.readdirSync(d, { withFileTypes: true })) {
|
|
15826
17240
|
if (entry.name.startsWith(".") || entry.name.endsWith(".bak")) continue;
|
|
15827
17241
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
15828
17242
|
if (entry.isDirectory()) {
|
|
15829
17243
|
files.push({ path: rel, size: 0, type: "dir" });
|
|
15830
|
-
scan(
|
|
17244
|
+
scan(path18.join(d, entry.name), rel);
|
|
15831
17245
|
} else {
|
|
15832
|
-
const stat =
|
|
17246
|
+
const stat = fs14.statSync(path18.join(d, entry.name));
|
|
15833
17247
|
files.push({ path: rel, size: stat.size, type: "file" });
|
|
15834
17248
|
}
|
|
15835
17249
|
}
|
|
@@ -15852,16 +17266,16 @@ var DevServer = class _DevServer {
|
|
|
15852
17266
|
this.json(res, 404, { error: `Provider directory not found: ${type}` });
|
|
15853
17267
|
return;
|
|
15854
17268
|
}
|
|
15855
|
-
const fullPath =
|
|
17269
|
+
const fullPath = path18.resolve(dir, path18.normalize(filePath));
|
|
15856
17270
|
if (!fullPath.startsWith(dir)) {
|
|
15857
17271
|
this.json(res, 403, { error: "Forbidden" });
|
|
15858
17272
|
return;
|
|
15859
17273
|
}
|
|
15860
|
-
if (!
|
|
17274
|
+
if (!fs14.existsSync(fullPath) || fs14.statSync(fullPath).isDirectory()) {
|
|
15861
17275
|
this.json(res, 404, { error: `File not found: ${filePath}` });
|
|
15862
17276
|
return;
|
|
15863
17277
|
}
|
|
15864
|
-
const content =
|
|
17278
|
+
const content = fs14.readFileSync(fullPath, "utf-8");
|
|
15865
17279
|
this.json(res, 200, { type, path: filePath, content, lines: content.split("\n").length });
|
|
15866
17280
|
}
|
|
15867
17281
|
/** POST /api/providers/:type/file — write a file { path, content } */
|
|
@@ -15877,15 +17291,15 @@ var DevServer = class _DevServer {
|
|
|
15877
17291
|
this.json(res, 404, { error: `Provider directory not found: ${type}` });
|
|
15878
17292
|
return;
|
|
15879
17293
|
}
|
|
15880
|
-
const fullPath =
|
|
17294
|
+
const fullPath = path18.resolve(dir, path18.normalize(filePath));
|
|
15881
17295
|
if (!fullPath.startsWith(dir)) {
|
|
15882
17296
|
this.json(res, 403, { error: "Forbidden" });
|
|
15883
17297
|
return;
|
|
15884
17298
|
}
|
|
15885
17299
|
try {
|
|
15886
|
-
if (
|
|
15887
|
-
|
|
15888
|
-
|
|
17300
|
+
if (fs14.existsSync(fullPath)) fs14.copyFileSync(fullPath, fullPath + ".bak");
|
|
17301
|
+
fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
|
|
17302
|
+
fs14.writeFileSync(fullPath, content, "utf-8");
|
|
15889
17303
|
this.log(`File saved: ${fullPath} (${content.length} chars)`);
|
|
15890
17304
|
this.providerLoader.reload();
|
|
15891
17305
|
this.json(res, 200, { saved: true, path: filePath, chars: content.length });
|
|
@@ -15901,9 +17315,9 @@ var DevServer = class _DevServer {
|
|
|
15901
17315
|
return;
|
|
15902
17316
|
}
|
|
15903
17317
|
for (const name of ["scripts.js", "provider.json"]) {
|
|
15904
|
-
const p =
|
|
15905
|
-
if (
|
|
15906
|
-
const source =
|
|
17318
|
+
const p = path18.join(dir, name);
|
|
17319
|
+
if (fs14.existsSync(p)) {
|
|
17320
|
+
const source = fs14.readFileSync(p, "utf-8");
|
|
15907
17321
|
this.json(res, 200, { type, path: p, source, lines: source.split("\n").length });
|
|
15908
17322
|
return;
|
|
15909
17323
|
}
|
|
@@ -15922,11 +17336,11 @@ var DevServer = class _DevServer {
|
|
|
15922
17336
|
this.json(res, 404, { error: `Provider not found: ${type}` });
|
|
15923
17337
|
return;
|
|
15924
17338
|
}
|
|
15925
|
-
const target =
|
|
15926
|
-
const targetPath =
|
|
17339
|
+
const target = fs14.existsSync(path18.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
|
|
17340
|
+
const targetPath = path18.join(dir, target);
|
|
15927
17341
|
try {
|
|
15928
|
-
if (
|
|
15929
|
-
|
|
17342
|
+
if (fs14.existsSync(targetPath)) fs14.copyFileSync(targetPath, targetPath + ".bak");
|
|
17343
|
+
fs14.writeFileSync(targetPath, source, "utf-8");
|
|
15930
17344
|
this.log(`Saved provider: ${targetPath} (${source.length} chars)`);
|
|
15931
17345
|
this.providerLoader.reload();
|
|
15932
17346
|
this.json(res, 200, { saved: true, path: targetPath, chars: source.length });
|
|
@@ -16083,21 +17497,21 @@ var DevServer = class _DevServer {
|
|
|
16083
17497
|
}
|
|
16084
17498
|
let targetDir;
|
|
16085
17499
|
targetDir = this.providerLoader.getUserProviderDir(category, type);
|
|
16086
|
-
const jsonPath =
|
|
16087
|
-
if (
|
|
17500
|
+
const jsonPath = path18.join(targetDir, "provider.json");
|
|
17501
|
+
if (fs14.existsSync(jsonPath)) {
|
|
16088
17502
|
this.json(res, 409, { error: `Provider already exists at ${targetDir}`, path: targetDir });
|
|
16089
17503
|
return;
|
|
16090
17504
|
}
|
|
16091
17505
|
try {
|
|
16092
17506
|
const result = generateFiles(type, name, category, { cdpPorts, cli, processName, installPath, binary, extensionId, version, osPaths, processNames });
|
|
16093
|
-
|
|
16094
|
-
|
|
17507
|
+
fs14.mkdirSync(targetDir, { recursive: true });
|
|
17508
|
+
fs14.writeFileSync(jsonPath, result["provider.json"], "utf-8");
|
|
16095
17509
|
const createdFiles = ["provider.json"];
|
|
16096
17510
|
if (result.files) {
|
|
16097
17511
|
for (const [relPath, content] of Object.entries(result.files)) {
|
|
16098
|
-
const fullPath =
|
|
16099
|
-
|
|
16100
|
-
|
|
17512
|
+
const fullPath = path18.join(targetDir, relPath);
|
|
17513
|
+
fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
|
|
17514
|
+
fs14.writeFileSync(fullPath, content, "utf-8");
|
|
16101
17515
|
createdFiles.push(relPath);
|
|
16102
17516
|
}
|
|
16103
17517
|
}
|
|
@@ -16146,45 +17560,45 @@ var DevServer = class _DevServer {
|
|
|
16146
17560
|
}
|
|
16147
17561
|
// ─── Phase 2: Auto-Implement Backend ───
|
|
16148
17562
|
getLatestScriptVersionDir(scriptsDir) {
|
|
16149
|
-
if (!
|
|
16150
|
-
const versions =
|
|
17563
|
+
if (!fs14.existsSync(scriptsDir)) return null;
|
|
17564
|
+
const versions = fs14.readdirSync(scriptsDir).filter((d) => {
|
|
16151
17565
|
try {
|
|
16152
|
-
return
|
|
17566
|
+
return fs14.statSync(path18.join(scriptsDir, d)).isDirectory();
|
|
16153
17567
|
} catch {
|
|
16154
17568
|
return false;
|
|
16155
17569
|
}
|
|
16156
17570
|
}).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
|
|
16157
17571
|
if (versions.length === 0) return null;
|
|
16158
|
-
return
|
|
17572
|
+
return path18.join(scriptsDir, versions[0]);
|
|
16159
17573
|
}
|
|
16160
17574
|
resolveAutoImplWritableProviderDir(category, type, requestedDir) {
|
|
16161
|
-
const canonicalUserDir =
|
|
16162
|
-
const desiredDir = requestedDir ?
|
|
16163
|
-
const upstreamRoot =
|
|
16164
|
-
if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${
|
|
17575
|
+
const canonicalUserDir = path18.resolve(this.providerLoader.getUserProviderDir(category, type));
|
|
17576
|
+
const desiredDir = requestedDir ? path18.resolve(requestedDir) : canonicalUserDir;
|
|
17577
|
+
const upstreamRoot = path18.resolve(this.providerLoader.getUpstreamDir());
|
|
17578
|
+
if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path18.sep}`)) {
|
|
16165
17579
|
return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
|
|
16166
17580
|
}
|
|
16167
|
-
if (
|
|
17581
|
+
if (path18.basename(desiredDir) !== type) {
|
|
16168
17582
|
return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
|
|
16169
17583
|
}
|
|
16170
17584
|
const sourceDir = this.findProviderDir(type);
|
|
16171
17585
|
if (!sourceDir) {
|
|
16172
17586
|
return { dir: null, reason: `Provider source directory not found for '${type}'` };
|
|
16173
17587
|
}
|
|
16174
|
-
if (!
|
|
16175
|
-
|
|
16176
|
-
|
|
17588
|
+
if (!fs14.existsSync(desiredDir)) {
|
|
17589
|
+
fs14.mkdirSync(path18.dirname(desiredDir), { recursive: true });
|
|
17590
|
+
fs14.cpSync(sourceDir, desiredDir, { recursive: true });
|
|
16177
17591
|
this.log(`Auto-implement writable copy created: ${desiredDir}`);
|
|
16178
17592
|
}
|
|
16179
|
-
const providerJson =
|
|
16180
|
-
if (!
|
|
17593
|
+
const providerJson = path18.join(desiredDir, "provider.json");
|
|
17594
|
+
if (!fs14.existsSync(providerJson)) {
|
|
16181
17595
|
return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
|
|
16182
17596
|
}
|
|
16183
17597
|
try {
|
|
16184
|
-
const providerData = JSON.parse(
|
|
17598
|
+
const providerData = JSON.parse(fs14.readFileSync(providerJson, "utf-8"));
|
|
16185
17599
|
if (providerData.disableUpstream !== true) {
|
|
16186
17600
|
providerData.disableUpstream = true;
|
|
16187
|
-
|
|
17601
|
+
fs14.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
|
|
16188
17602
|
}
|
|
16189
17603
|
} catch (error) {
|
|
16190
17604
|
return {
|
|
@@ -16224,7 +17638,7 @@ var DevServer = class _DevServer {
|
|
|
16224
17638
|
setMode: "set_mode.js"
|
|
16225
17639
|
};
|
|
16226
17640
|
const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
|
|
16227
|
-
const scriptsDir =
|
|
17641
|
+
const scriptsDir = path18.join(providerDir, "scripts");
|
|
16228
17642
|
const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
|
|
16229
17643
|
if (latestScriptsDir) {
|
|
16230
17644
|
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
@@ -16232,10 +17646,10 @@ var DevServer = class _DevServer {
|
|
|
16232
17646
|
lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
|
|
16233
17647
|
lines.push("These are the ONLY files you are allowed to modify. Replace the TODO stubs with working implementations.");
|
|
16234
17648
|
lines.push("");
|
|
16235
|
-
for (const file of
|
|
17649
|
+
for (const file of fs14.readdirSync(latestScriptsDir)) {
|
|
16236
17650
|
if (file.endsWith(".js") && targetFileNames.has(file)) {
|
|
16237
17651
|
try {
|
|
16238
|
-
const content =
|
|
17652
|
+
const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
|
|
16239
17653
|
lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
|
|
16240
17654
|
lines.push("```javascript");
|
|
16241
17655
|
lines.push(content);
|
|
@@ -16245,14 +17659,14 @@ var DevServer = class _DevServer {
|
|
|
16245
17659
|
}
|
|
16246
17660
|
}
|
|
16247
17661
|
}
|
|
16248
|
-
const refFiles =
|
|
17662
|
+
const refFiles = fs14.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
|
|
16249
17663
|
if (refFiles.length > 0) {
|
|
16250
17664
|
lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
|
|
16251
17665
|
lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
|
|
16252
17666
|
lines.push("");
|
|
16253
17667
|
for (const file of refFiles) {
|
|
16254
17668
|
try {
|
|
16255
|
-
const content =
|
|
17669
|
+
const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
|
|
16256
17670
|
lines.push(`### \`${file}\` \u{1F512}`);
|
|
16257
17671
|
lines.push("```javascript");
|
|
16258
17672
|
lines.push(content);
|
|
@@ -16293,11 +17707,11 @@ var DevServer = class _DevServer {
|
|
|
16293
17707
|
lines.push("");
|
|
16294
17708
|
}
|
|
16295
17709
|
}
|
|
16296
|
-
const docsDir =
|
|
17710
|
+
const docsDir = path18.join(providerDir, "../../docs");
|
|
16297
17711
|
const loadGuide = (name) => {
|
|
16298
17712
|
try {
|
|
16299
|
-
const p =
|
|
16300
|
-
if (
|
|
17713
|
+
const p = path18.join(docsDir, name);
|
|
17714
|
+
if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
|
|
16301
17715
|
} catch {
|
|
16302
17716
|
}
|
|
16303
17717
|
return null;
|
|
@@ -16470,7 +17884,7 @@ var DevServer = class _DevServer {
|
|
|
16470
17884
|
parseApproval: "parse_approval.js"
|
|
16471
17885
|
};
|
|
16472
17886
|
const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
|
|
16473
|
-
const scriptsDir =
|
|
17887
|
+
const scriptsDir = path18.join(providerDir, "scripts");
|
|
16474
17888
|
const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
|
|
16475
17889
|
if (latestScriptsDir) {
|
|
16476
17890
|
lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
|
|
@@ -16478,11 +17892,11 @@ var DevServer = class _DevServer {
|
|
|
16478
17892
|
lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
|
|
16479
17893
|
lines.push("These are the ONLY files you are allowed to modify. Replace TODO or heuristic-only logic with working PTY-aware implementations.");
|
|
16480
17894
|
lines.push("");
|
|
16481
|
-
for (const file of
|
|
17895
|
+
for (const file of fs14.readdirSync(latestScriptsDir)) {
|
|
16482
17896
|
if (!file.endsWith(".js")) continue;
|
|
16483
17897
|
if (!targetFileNames.has(file)) continue;
|
|
16484
17898
|
try {
|
|
16485
|
-
const content =
|
|
17899
|
+
const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
|
|
16486
17900
|
lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
|
|
16487
17901
|
lines.push("```javascript");
|
|
16488
17902
|
lines.push(content);
|
|
@@ -16491,14 +17905,14 @@ var DevServer = class _DevServer {
|
|
|
16491
17905
|
} catch {
|
|
16492
17906
|
}
|
|
16493
17907
|
}
|
|
16494
|
-
const refFiles =
|
|
17908
|
+
const refFiles = fs14.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
|
|
16495
17909
|
if (refFiles.length > 0) {
|
|
16496
17910
|
lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
|
|
16497
17911
|
lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
|
|
16498
17912
|
lines.push("");
|
|
16499
17913
|
for (const file of refFiles) {
|
|
16500
17914
|
try {
|
|
16501
|
-
const content =
|
|
17915
|
+
const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
|
|
16502
17916
|
lines.push(`### \`${file}\` \u{1F512}`);
|
|
16503
17917
|
lines.push("```javascript");
|
|
16504
17918
|
lines.push(content);
|
|
@@ -16531,11 +17945,11 @@ var DevServer = class _DevServer {
|
|
|
16531
17945
|
lines.push("");
|
|
16532
17946
|
}
|
|
16533
17947
|
}
|
|
16534
|
-
const docsDir =
|
|
17948
|
+
const docsDir = path18.join(providerDir, "../../docs");
|
|
16535
17949
|
const loadGuide = (name) => {
|
|
16536
17950
|
try {
|
|
16537
|
-
const p =
|
|
16538
|
-
if (
|
|
17951
|
+
const p = path18.join(docsDir, name);
|
|
17952
|
+
if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
|
|
16539
17953
|
} catch {
|
|
16540
17954
|
}
|
|
16541
17955
|
return null;
|
|
@@ -16600,6 +18014,7 @@ var DevServer = class _DevServer {
|
|
|
16600
18014
|
lines.push("### 2. Inspect parsed + raw adapter state");
|
|
16601
18015
|
lines.push("```bash");
|
|
16602
18016
|
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
|
|
18017
|
+
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
|
|
16603
18018
|
lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
|
|
16604
18019
|
lines.push("```");
|
|
16605
18020
|
lines.push("");
|
|
@@ -16734,6 +18149,16 @@ data: ${JSON.stringify(msg.data)}
|
|
|
16734
18149
|
async handleCliSend(req, res) {
|
|
16735
18150
|
return handleCliSend(this, req, res);
|
|
16736
18151
|
}
|
|
18152
|
+
/** POST /api/cli/exercise — launch/send/approve/wait helper for provider-fix loops */
|
|
18153
|
+
async handleCliExercise(req, res) {
|
|
18154
|
+
return handleCliExercise(this, req, res);
|
|
18155
|
+
}
|
|
18156
|
+
async handleCliFixtureCapture(req, res) {
|
|
18157
|
+
return handleCliFixtureCapture(this, req, res);
|
|
18158
|
+
}
|
|
18159
|
+
async handleCliFixtureReplay(req, res) {
|
|
18160
|
+
return handleCliFixtureReplay(this, req, res);
|
|
18161
|
+
}
|
|
16737
18162
|
/** POST /api/cli/stop — stop a running CLI { type } */
|
|
16738
18163
|
async handleCliStop(req, res) {
|
|
16739
18164
|
return handleCliStop(this, req, res);
|
|
@@ -16757,6 +18182,13 @@ data: ${JSON.stringify(msg.data)}
|
|
|
16757
18182
|
async handleCliDebug(type, _req, res) {
|
|
16758
18183
|
return handleCliDebug(this, type, _req, res);
|
|
16759
18184
|
}
|
|
18185
|
+
/** GET /api/cli/trace/:type — recent CLI trace timeline plus current debug snapshot */
|
|
18186
|
+
async handleCliTrace(type, _req, res) {
|
|
18187
|
+
return handleCliTrace(this, type, _req, res);
|
|
18188
|
+
}
|
|
18189
|
+
async handleCliFixtureList(type, _req, res) {
|
|
18190
|
+
return handleCliFixtureList(this, type, _req, res);
|
|
18191
|
+
}
|
|
16760
18192
|
/** POST /api/cli/resolve — resolve an approval modal { type, buttonIndex } */
|
|
16761
18193
|
async handleCliResolve(req, res) {
|
|
16762
18194
|
return handleCliResolve(this, req, res);
|
|
@@ -16927,7 +18359,18 @@ var SessionHostRuntimeTransport = class {
|
|
|
16927
18359
|
});
|
|
16928
18360
|
}
|
|
16929
18361
|
async boot() {
|
|
16930
|
-
|
|
18362
|
+
if (typeof this.options.ensureReady === "function") {
|
|
18363
|
+
await this.options.ensureReady();
|
|
18364
|
+
}
|
|
18365
|
+
try {
|
|
18366
|
+
await this.client.connect();
|
|
18367
|
+
} catch (error) {
|
|
18368
|
+
if (typeof this.options.ensureReady !== "function") {
|
|
18369
|
+
throw error;
|
|
18370
|
+
}
|
|
18371
|
+
await this.options.ensureReady();
|
|
18372
|
+
await this.client.connect();
|
|
18373
|
+
}
|
|
16931
18374
|
this.unsubscribe = this.client.onEvent((event) => this.handleEvent(event));
|
|
16932
18375
|
let record = null;
|
|
16933
18376
|
if (this.options.attachExisting) {
|
|
@@ -17306,8 +18749,8 @@ async function installExtension(ide, extension) {
|
|
|
17306
18749
|
const res = await fetch(extension.vsixUrl);
|
|
17307
18750
|
if (res.ok) {
|
|
17308
18751
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
17309
|
-
const
|
|
17310
|
-
|
|
18752
|
+
const fs15 = await import("fs");
|
|
18753
|
+
fs15.writeFileSync(vsixPath, buffer);
|
|
17311
18754
|
return new Promise((resolve10) => {
|
|
17312
18755
|
const cmd = `"${ide.cliCommand}" --install-extension "${vsixPath}" --force`;
|
|
17313
18756
|
(0, import_child_process10.exec)(cmd, { timeout: 6e4 }, (error, _stdout, stderr) => {
|