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