@adhdev/daemon-core 0.7.46 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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\[\d*[A-HJKSTfG]/g, " ").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/\x1B\][^\x07]*\x07/g, "").replace(/\x1B\][^\x1B]*\x1B\\/g, "").replace(/ +/g, " ");
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 fs14 = __require("fs");
802
- const resolved = fs14.realpathSync(binaryPath);
801
+ const fs15 = __require("fs");
802
+ const resolved = fs15.realpathSync(binaryPath);
803
803
  const head = Buffer.alloc(8);
804
- const fd = fs14.openSync(resolved, "r");
805
- fs14.readSync(fd, head, 0, 8, 0);
806
- fs14.closeSync(fd);
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 fs14 = __require("fs");
818
- const resolved = fs14.realpathSync(filePath);
817
+ const fs15 = __require("fs");
818
+ const resolved = fs15.realpathSync(filePath);
819
819
  const buf = Buffer.alloc(8);
820
- const fd = fs14.openSync(resolved, "r");
821
- fs14.readSync(fd, buf, 0, 8, 0);
822
- fs14.closeSync(fd);
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 fs14 = __require("fs");
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 (fs14.existsSync(helper)) {
915
- const stat = fs14.statSync(helper);
917
+ if (fs15.existsSync(helper)) {
918
+ const stat = fs15.statSync(helper);
916
919
  if (!(stat.mode & 73)) {
917
- fs14.chmodSync(helper, stat.mode | 493);
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 isReady = scriptStatus === "idle" || scriptStatus === "waiting_approval" || elapsed > 8e3 || this.startupBuffer.length > 12e3;
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("CLI", `[${this.cliType}] Startup ready (${elapsed}ms, scriptStatus=${scriptStatus})`);
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: parsedMessages,
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 fs14 = __require("fs");
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 (fs14.existsSync(storagePath)) {
10450
- const data = JSON.parse(fs14.readFileSync(storagePath, "utf-8"));
10901
+ if (fs15.existsSync(storagePath)) {
10902
+ const data = JSON.parse(fs15.readFileSync(storagePath, "utf-8"));
10451
10903
  const workspaces = data?.openedPathsList?.workspaces3 || data?.openedPathsList?.entries || [];
10452
10904
  if (workspaces.length > 0) {
10453
10905
  const recent = workspaces[0];
@@ -12639,8 +13091,8 @@ async function detectAllVersions(loader, archive) {
12639
13091
 
12640
13092
  // src/daemon/dev-server.ts
12641
13093
  import * as http2 from "http";
12642
- import * as fs13 from "fs";
12643
- import * as path17 from "path";
13094
+ import * as fs14 from "fs";
13095
+ import * as path18 from "path";
12644
13096
 
12645
13097
  // src/daemon/scaffold-template.ts
12646
13098
  function generateFiles(type, name, category, opts = {}) {
@@ -13983,6 +14435,162 @@ async function handleDomContext(ctx, type, req, res) {
13983
14435
  }
13984
14436
 
13985
14437
  // src/daemon/dev-cli-debug.ts
14438
+ import * as fs12 from "fs";
14439
+ import * as path16 from "path";
14440
+ function slugifyFixtureName(value) {
14441
+ const normalized = String(value || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
14442
+ return normalized || `fixture-${Date.now()}`;
14443
+ }
14444
+ function getCliFixtureDir(ctx, type) {
14445
+ const providerDir = ctx.providerLoader.findProviderDir(type);
14446
+ if (!providerDir) {
14447
+ throw new Error(`Provider directory not found for '${type}'`);
14448
+ }
14449
+ return path16.join(providerDir, "fixtures");
14450
+ }
14451
+ function readCliFixture(ctx, type, name) {
14452
+ const fixtureDir = getCliFixtureDir(ctx, type);
14453
+ const filePath = path16.join(fixtureDir, `${name}.json`);
14454
+ if (!fs12.existsSync(filePath)) {
14455
+ throw new Error(`Fixture not found: ${filePath}`);
14456
+ }
14457
+ return JSON.parse(fs12.readFileSync(filePath, "utf-8"));
14458
+ }
14459
+ function getExerciseTranscriptText(result) {
14460
+ const parts = [];
14461
+ const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
14462
+ const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
14463
+ for (const message of [...debugMessages, ...traceMessages]) {
14464
+ if (!message || typeof message.content !== "string") continue;
14465
+ parts.push(message.content);
14466
+ }
14467
+ if (typeof result?.debug?.partialResponse === "string") parts.push(result.debug.partialResponse);
14468
+ if (typeof result?.trace?.responseBuffer === "string") parts.push(result.trace.responseBuffer);
14469
+ return parts.join("\n");
14470
+ }
14471
+ function getExerciseLastAssistant(result) {
14472
+ const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
14473
+ const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
14474
+ for (const messages of [debugMessages, traceMessages]) {
14475
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
14476
+ const message = messages[i];
14477
+ if (message?.role === "assistant" && typeof message.content === "string" && message.content.trim()) {
14478
+ return message.content;
14479
+ }
14480
+ }
14481
+ }
14482
+ return "";
14483
+ }
14484
+ function getExerciseMessageCount(result) {
14485
+ const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
14486
+ const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
14487
+ return Math.max(debugMessages.length, traceMessages.length);
14488
+ }
14489
+ function compileFixtureRegex(source) {
14490
+ const value = String(source || "").trim();
14491
+ if (!value) return null;
14492
+ const delimited = value.match(/^\/([\s\S]+)\/([dgimsuvy]*)$/);
14493
+ try {
14494
+ if (delimited) {
14495
+ return new RegExp(delimited[1], delimited[2]);
14496
+ }
14497
+ return new RegExp(value, "m");
14498
+ } catch {
14499
+ return null;
14500
+ }
14501
+ }
14502
+ function statusesContainSequence(actual, expected) {
14503
+ if (!expected.length) return true;
14504
+ let index = 0;
14505
+ for (const status of actual) {
14506
+ if (status === expected[index]) index += 1;
14507
+ if (index >= expected.length) return true;
14508
+ }
14509
+ return false;
14510
+ }
14511
+ function validateCliFixtureResult(result, assertions) {
14512
+ const failures = [];
14513
+ const transcriptText = getExerciseTranscriptText(result);
14514
+ const lastAssistant = getExerciseLastAssistant(result);
14515
+ const mustContainAny = assertions.mustContainAny || [];
14516
+ const mustNotContainAny = assertions.mustNotContainAny || [];
14517
+ const mustMatchAny = assertions.mustMatchAny || [];
14518
+ const mustNotMatchAny = assertions.mustNotMatchAny || [];
14519
+ const lastAssistantMustContainAny = assertions.lastAssistantMustContainAny || [];
14520
+ const lastAssistantMustNotContainAny = assertions.lastAssistantMustNotContainAny || [];
14521
+ const lastAssistantMustMatchAny = assertions.lastAssistantMustMatchAny || [];
14522
+ const lastAssistantMustNotMatchAny = assertions.lastAssistantMustNotMatchAny || [];
14523
+ const statusesSeen = Array.isArray(result?.statusesSeen) ? result.statusesSeen.map((value) => String(value)) : [];
14524
+ if (assertions.requireNotTimedOut !== false && result?.timedOut) {
14525
+ failures.push("Exercise timed out");
14526
+ }
14527
+ const missingRequired = mustContainAny.filter((value) => !transcriptText.includes(value));
14528
+ if (missingRequired.length > 0) {
14529
+ failures.push(`Missing required substrings: ${missingRequired.join(", ")}`);
14530
+ }
14531
+ const presentBanned = mustNotContainAny.filter((value) => transcriptText.includes(value));
14532
+ if (presentBanned.length > 0) {
14533
+ failures.push(`Found banned substrings: ${presentBanned.join(", ")}`);
14534
+ }
14535
+ const missingRegex = mustMatchAny.filter((value) => {
14536
+ const regex = compileFixtureRegex(value);
14537
+ return !regex || !regex.test(transcriptText);
14538
+ });
14539
+ if (missingRegex.length > 0) {
14540
+ failures.push(`Missing required regex matches: ${missingRegex.join(", ")}`);
14541
+ }
14542
+ const presentBannedRegex = mustNotMatchAny.filter((value) => {
14543
+ const regex = compileFixtureRegex(value);
14544
+ return !!regex && regex.test(transcriptText);
14545
+ });
14546
+ if (presentBannedRegex.length > 0) {
14547
+ failures.push(`Found banned regex matches: ${presentBannedRegex.join(", ")}`);
14548
+ }
14549
+ const missingLastAssistant = lastAssistantMustContainAny.filter((value) => !lastAssistant.includes(value));
14550
+ if (missingLastAssistant.length > 0) {
14551
+ failures.push(`Missing required lastAssistant substrings: ${missingLastAssistant.join(", ")}`);
14552
+ }
14553
+ const presentBannedLastAssistant = lastAssistantMustNotContainAny.filter((value) => lastAssistant.includes(value));
14554
+ if (presentBannedLastAssistant.length > 0) {
14555
+ failures.push(`Found banned lastAssistant substrings: ${presentBannedLastAssistant.join(", ")}`);
14556
+ }
14557
+ const missingLastAssistantRegex = lastAssistantMustMatchAny.filter((value) => {
14558
+ const regex = compileFixtureRegex(value);
14559
+ return !regex || !regex.test(lastAssistant);
14560
+ });
14561
+ if (missingLastAssistantRegex.length > 0) {
14562
+ failures.push(`Missing required lastAssistant regex matches: ${missingLastAssistantRegex.join(", ")}`);
14563
+ }
14564
+ const presentBannedLastAssistantRegex = lastAssistantMustNotMatchAny.filter((value) => {
14565
+ const regex = compileFixtureRegex(value);
14566
+ return !!regex && regex.test(lastAssistant);
14567
+ });
14568
+ if (presentBannedLastAssistantRegex.length > 0) {
14569
+ failures.push(`Found banned lastAssistant regex matches: ${presentBannedLastAssistantRegex.join(", ")}`);
14570
+ }
14571
+ if (assertions.statusesSeen?.length && !statusesContainSequence(statusesSeen, assertions.statusesSeen)) {
14572
+ failures.push(`Expected statuses sequence not observed: ${assertions.statusesSeen.join(" -> ")}`);
14573
+ }
14574
+ if (result && typeof result === "object") {
14575
+ result.lastAssistant = lastAssistant;
14576
+ }
14577
+ return failures;
14578
+ }
14579
+ function getCliProviderResolutionMeta(ctx, type, adapter) {
14580
+ const adapterMeta = typeof adapter?.getProviderResolutionMeta === "function" ? adapter.getProviderResolutionMeta() : adapter?.getDebugState?.()?.providerResolution || null;
14581
+ const resolvedProvider = ctx.providerLoader.resolve(type);
14582
+ if (!adapterMeta && !resolvedProvider) return null;
14583
+ return {
14584
+ type,
14585
+ providerDir: adapterMeta?.providerDir || resolvedProvider?._resolvedProviderDir || ctx.providerLoader.findProviderDir(type),
14586
+ scriptDir: adapterMeta?.scriptDir || resolvedProvider?._resolvedScriptDir || null,
14587
+ scriptsPath: adapterMeta?.scriptsPath || resolvedProvider?._resolvedScriptsPath || null,
14588
+ scriptsSource: adapterMeta?.scriptsSource || resolvedProvider?._resolvedScriptsSource || null,
14589
+ resolvedVersion: adapterMeta?.resolvedVersion || resolvedProvider?._resolvedVersion || null,
14590
+ resolvedOs: adapterMeta?.resolvedOs || resolvedProvider?._resolvedOs || null,
14591
+ versionWarning: adapterMeta?.versionWarning || resolvedProvider?._versionWarning || null
14592
+ };
14593
+ }
13986
14594
  function findCliTarget(ctx, type, instanceId) {
13987
14595
  if (!ctx.instanceManager) return null;
13988
14596
  const cliStates = ctx.instanceManager.collectAllStates().filter((s) => s.category === "cli" || s.category === "acp");
@@ -13991,6 +14599,331 @@ function findCliTarget(ctx, type, instanceId) {
13991
14599
  const matches = cliStates.filter((s) => s.type === type);
13992
14600
  return matches[matches.length - 1] || null;
13993
14601
  }
14602
+ function getCliTargetBundle(ctx, type, instanceId) {
14603
+ if (!ctx.instanceManager) return null;
14604
+ const target = findCliTarget(ctx, type, instanceId);
14605
+ if (!target) return null;
14606
+ const instance = ctx.instanceManager.getInstance(target.instanceId);
14607
+ if (!instance) return null;
14608
+ const adapter = instance.getAdapter?.() || instance.adapter;
14609
+ if (!adapter) return null;
14610
+ return { target, instance, adapter };
14611
+ }
14612
+ function sleep(ms) {
14613
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
14614
+ }
14615
+ async function waitForCliReady(ctx, type, instanceId, timeoutMs) {
14616
+ const startedAt = Date.now();
14617
+ while (Date.now() - startedAt < timeoutMs) {
14618
+ const bundle = getCliTargetBundle(ctx, type, instanceId);
14619
+ if (bundle) {
14620
+ const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
14621
+ const startupParseGate = !!debug?.startupParseGate;
14622
+ const adapterReady = !!debug?.ready;
14623
+ const visibleStatusReady = bundle.target.status === "generating" || bundle.target.status === "waiting_approval";
14624
+ const idleReady = bundle.target.status === "idle" && !startupParseGate;
14625
+ if (adapterReady || visibleStatusReady || idleReady) {
14626
+ return bundle;
14627
+ }
14628
+ }
14629
+ await sleep(100);
14630
+ }
14631
+ return getCliTargetBundle(ctx, type, instanceId);
14632
+ }
14633
+ async function runCliExerciseInternal(ctx, body) {
14634
+ if (!ctx.cliManager) {
14635
+ throw new Error("CliManager not available");
14636
+ }
14637
+ if (!ctx.instanceManager) {
14638
+ throw new Error("InstanceManager not available");
14639
+ }
14640
+ const {
14641
+ type,
14642
+ text,
14643
+ instanceId: requestedInstanceId,
14644
+ workingDir,
14645
+ args,
14646
+ autoLaunch = true,
14647
+ freshSession = true,
14648
+ autoResolveApprovals = true,
14649
+ approvalButtonIndex = 0,
14650
+ timeoutMs = 45e3,
14651
+ readyTimeoutMs = 15e3,
14652
+ idleSettledMs = 1200,
14653
+ traceLimit = 160,
14654
+ stopWhenDone = false
14655
+ } = body || {};
14656
+ if (!type) {
14657
+ throw new Error("type required (e.g. claude-cli, codex-cli)");
14658
+ }
14659
+ if (!text || typeof text !== "string") {
14660
+ throw new Error("text required (prompt to send to the CLI)");
14661
+ }
14662
+ let resolvedInstanceId = requestedInstanceId;
14663
+ if (freshSession) {
14664
+ const staleTargets = ctx.instanceManager.collectAllStates().filter((state) => (state.category === "cli" || state.category === "acp") && state.type === type).map((state) => state.instanceId);
14665
+ for (const staleId of staleTargets) {
14666
+ ctx.instanceManager.removeInstance(staleId);
14667
+ }
14668
+ resolvedInstanceId = void 0;
14669
+ }
14670
+ let bundle = getCliTargetBundle(ctx, type, resolvedInstanceId);
14671
+ if (!bundle && autoLaunch) {
14672
+ const launchArgs = [type, workingDir || process.cwd(), Array.isArray(args) ? args : []];
14673
+ let launched = null;
14674
+ let lastLaunchError = null;
14675
+ for (let attempt = 1; attempt <= 2; attempt += 1) {
14676
+ try {
14677
+ launched = await ctx.cliManager.startSession(...launchArgs);
14678
+ lastLaunchError = null;
14679
+ break;
14680
+ } catch (error) {
14681
+ lastLaunchError = error instanceof Error ? error : new Error(String(error?.message || error));
14682
+ const message = String(lastLaunchError.message || "");
14683
+ const retryable = /ECONNREFUSED|session-host|Session host/i.test(message);
14684
+ if (!retryable || attempt === 2) break;
14685
+ await sleep(1e3);
14686
+ }
14687
+ }
14688
+ if (!launched) {
14689
+ throw lastLaunchError || new Error(`Failed to start ${type}`);
14690
+ }
14691
+ resolvedInstanceId = launched.runtimeSessionId;
14692
+ bundle = await waitForCliReady(ctx, type, resolvedInstanceId, Math.max(1e3, readyTimeoutMs));
14693
+ }
14694
+ if (!bundle) {
14695
+ throw new Error(`No running instance found for: ${resolvedInstanceId || type}`);
14696
+ }
14697
+ const initialDebug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
14698
+ const initialTrace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
14699
+ const providerResolution = getCliProviderResolutionMeta(ctx, bundle.target.type, bundle.adapter);
14700
+ const preTraceCount = Number(initialTrace?.entryCount || 0);
14701
+ const startAt = Date.now();
14702
+ const statusesSeen = [];
14703
+ const approvalsResolved = [];
14704
+ let lastStatus = "";
14705
+ let lastModalKey = "";
14706
+ let idleSince = 0;
14707
+ let sawBusy = false;
14708
+ ctx.instanceManager.sendEvent(bundle.target.instanceId, "send_message", { text });
14709
+ while (Date.now() - startAt < Math.max(1e3, timeoutMs)) {
14710
+ await sleep(150);
14711
+ bundle = getCliTargetBundle(ctx, type, bundle.target.instanceId);
14712
+ if (!bundle) {
14713
+ throw new Error("CLI instance disappeared during exercise");
14714
+ }
14715
+ const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
14716
+ const trace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
14717
+ const status = String(debug?.status || bundle.target.status || "unknown");
14718
+ const traceEntries = Array.isArray(trace?.entries) ? trace.entries : [];
14719
+ const sawSendMessage = traceEntries.some((entry) => entry?.type === "send_message");
14720
+ const sawSubmitWrite = traceEntries.some((entry) => entry?.type === "submit_write");
14721
+ const hasTurnStarted = sawSendMessage || sawSubmitWrite || !!debug?.currentTurnScope;
14722
+ if (status !== lastStatus) {
14723
+ statusesSeen.push(status);
14724
+ lastStatus = status;
14725
+ }
14726
+ if (status === "generating" || status === "waiting_approval") {
14727
+ sawBusy = true;
14728
+ idleSince = 0;
14729
+ }
14730
+ const modal = debug?.activeModal || trace?.activeModal || null;
14731
+ if (autoResolveApprovals && status === "waiting_approval" && modal && Array.isArray(modal.buttons) && modal.buttons.length > 0) {
14732
+ const clampedIndex = Math.max(0, Math.min(Number(approvalButtonIndex) || 0, modal.buttons.length - 1));
14733
+ const modalKey = JSON.stringify({
14734
+ message: modal.message || "",
14735
+ buttons: modal.buttons,
14736
+ index: clampedIndex
14737
+ });
14738
+ if (modalKey !== lastModalKey && typeof bundle.adapter.resolveModal === "function") {
14739
+ lastModalKey = modalKey;
14740
+ approvalsResolved.push({
14741
+ at: Date.now(),
14742
+ buttonIndex: clampedIndex,
14743
+ label: modal.buttons[clampedIndex] || null
14744
+ });
14745
+ bundle.adapter.resolveModal(clampedIndex);
14746
+ continue;
14747
+ }
14748
+ }
14749
+ const traceCount = Number(trace?.entryCount || 0);
14750
+ const hasProgress = hasTurnStarted && (traceCount > preTraceCount || statusesSeen.length > 1 || approvalsResolved.length > 0);
14751
+ if (status === "idle" && hasProgress && sawBusy) {
14752
+ if (!idleSince) idleSince = Date.now();
14753
+ if (Date.now() - idleSince >= Math.max(200, idleSettledMs)) {
14754
+ const payload2 = {
14755
+ exercised: true,
14756
+ instanceId: bundle.target.instanceId,
14757
+ providerState: {
14758
+ type: bundle.target.type,
14759
+ name: bundle.target.name,
14760
+ status: bundle.target.status,
14761
+ mode: "mode" in bundle.target ? bundle.target.mode : void 0
14762
+ },
14763
+ providerResolution,
14764
+ initialDebug,
14765
+ initialTrace,
14766
+ debug,
14767
+ trace,
14768
+ statusesSeen,
14769
+ approvalsResolved,
14770
+ elapsedMs: Date.now() - startAt,
14771
+ timedOut: false
14772
+ };
14773
+ payload2.lastAssistant = getExerciseLastAssistant(payload2);
14774
+ payload2.messageCount = getExerciseMessageCount(payload2);
14775
+ if (stopWhenDone) {
14776
+ ctx.instanceManager.removeInstance(bundle.target.instanceId);
14777
+ }
14778
+ return payload2;
14779
+ }
14780
+ } else if (status === "idle" && hasProgress) {
14781
+ if (!idleSince) idleSince = Date.now();
14782
+ if (Date.now() - idleSince >= Math.max(500, idleSettledMs) && Date.now() - startAt >= 750) {
14783
+ const payload2 = {
14784
+ exercised: true,
14785
+ instanceId: bundle.target.instanceId,
14786
+ providerState: {
14787
+ type: bundle.target.type,
14788
+ name: bundle.target.name,
14789
+ status: bundle.target.status,
14790
+ mode: "mode" in bundle.target ? bundle.target.mode : void 0
14791
+ },
14792
+ providerResolution,
14793
+ initialDebug,
14794
+ initialTrace,
14795
+ debug,
14796
+ trace,
14797
+ statusesSeen,
14798
+ approvalsResolved,
14799
+ elapsedMs: Date.now() - startAt,
14800
+ timedOut: false
14801
+ };
14802
+ payload2.lastAssistant = getExerciseLastAssistant(payload2);
14803
+ payload2.messageCount = getExerciseMessageCount(payload2);
14804
+ if (stopWhenDone) {
14805
+ ctx.instanceManager.removeInstance(bundle.target.instanceId);
14806
+ }
14807
+ return payload2;
14808
+ }
14809
+ } else {
14810
+ idleSince = 0;
14811
+ }
14812
+ }
14813
+ const finalBundle = getCliTargetBundle(ctx, type, bundle.target.instanceId) || bundle;
14814
+ const finalDebug = typeof finalBundle.adapter.getDebugState === "function" ? finalBundle.adapter.getDebugState() : null;
14815
+ const finalTrace = typeof finalBundle.adapter.getTraceState === "function" ? finalBundle.adapter.getTraceState(traceLimit) : null;
14816
+ if (stopWhenDone) {
14817
+ ctx.instanceManager.removeInstance(finalBundle.target.instanceId);
14818
+ }
14819
+ const payload = {
14820
+ exercised: true,
14821
+ instanceId: finalBundle.target.instanceId,
14822
+ providerState: {
14823
+ type: finalBundle.target.type,
14824
+ name: finalBundle.target.name,
14825
+ status: finalBundle.target.status,
14826
+ mode: "mode" in finalBundle.target ? finalBundle.target.mode : void 0
14827
+ },
14828
+ providerResolution: getCliProviderResolutionMeta(ctx, finalBundle.target.type, finalBundle.adapter),
14829
+ initialDebug,
14830
+ initialTrace,
14831
+ debug: finalDebug,
14832
+ trace: finalTrace,
14833
+ statusesSeen,
14834
+ approvalsResolved,
14835
+ elapsedMs: Date.now() - startAt,
14836
+ timedOut: true
14837
+ };
14838
+ payload.lastAssistant = getExerciseLastAssistant(payload);
14839
+ payload.messageCount = getExerciseMessageCount(payload);
14840
+ return payload;
14841
+ }
14842
+ async function runCliAutoImplVerification(ctx, type, verification) {
14843
+ const assertions = {
14844
+ mustContainAny: verification?.mustContainAny || [],
14845
+ mustNotContainAny: verification?.mustNotContainAny || [],
14846
+ mustMatchAny: verification?.mustMatchAny || [],
14847
+ mustNotMatchAny: verification?.mustNotMatchAny || [],
14848
+ lastAssistantMustContainAny: verification?.lastAssistantMustContainAny || [],
14849
+ lastAssistantMustNotContainAny: verification?.lastAssistantMustNotContainAny || [],
14850
+ lastAssistantMustMatchAny: verification?.lastAssistantMustMatchAny || [],
14851
+ lastAssistantMustNotMatchAny: verification?.lastAssistantMustNotMatchAny || [],
14852
+ requireNotTimedOut: true
14853
+ };
14854
+ const rawFixtureNames = Array.isArray(verification?.fixtureNames) ? verification.fixtureNames.map((value) => String(value || "").trim()).filter(Boolean) : [];
14855
+ if (rawFixtureNames.length > 0) {
14856
+ const results = [];
14857
+ for (const rawFixtureName2 of rawFixtureNames) {
14858
+ const name = slugifyFixtureName(rawFixtureName2);
14859
+ const fixture = readCliFixture(ctx, type, name);
14860
+ const mergedAssertions = {
14861
+ ...fixture.assertions,
14862
+ ...assertions
14863
+ };
14864
+ const result2 = await runCliExerciseInternal(ctx, {
14865
+ ...fixture.request,
14866
+ type
14867
+ });
14868
+ const failures2 = validateCliFixtureResult(result2, mergedAssertions);
14869
+ results.push({
14870
+ fixtureName: name,
14871
+ pass: failures2.length === 0,
14872
+ failures: failures2,
14873
+ result: result2,
14874
+ assertions: mergedAssertions,
14875
+ fixture
14876
+ });
14877
+ }
14878
+ const firstFailure = results.find((item) => !item.pass) || results[results.length - 1];
14879
+ return {
14880
+ mode: "fixture_replay_suite",
14881
+ pass: results.every((item) => item.pass),
14882
+ failures: results.flatMap((item) => item.failures.map((failure) => `${item.fixtureName}: ${failure}`)),
14883
+ result: firstFailure.result,
14884
+ assertions: firstFailure.assertions,
14885
+ fixture: firstFailure.fixture,
14886
+ results
14887
+ };
14888
+ }
14889
+ const rawFixtureName = String(verification?.fixtureName || "").trim();
14890
+ if (rawFixtureName) {
14891
+ const name = slugifyFixtureName(rawFixtureName);
14892
+ try {
14893
+ const fixture = readCliFixture(ctx, type, name);
14894
+ const mergedAssertions = {
14895
+ ...fixture.assertions,
14896
+ ...assertions
14897
+ };
14898
+ const result2 = await runCliExerciseInternal(ctx, {
14899
+ ...fixture.request,
14900
+ type
14901
+ });
14902
+ const failures2 = validateCliFixtureResult(result2, mergedAssertions);
14903
+ return {
14904
+ mode: "fixture_replay",
14905
+ pass: failures2.length === 0,
14906
+ failures: failures2,
14907
+ result: result2,
14908
+ assertions: mergedAssertions,
14909
+ fixture
14910
+ };
14911
+ } catch {
14912
+ }
14913
+ }
14914
+ const result = await runCliExerciseInternal(ctx, {
14915
+ ...verification?.request || {},
14916
+ type
14917
+ });
14918
+ const failures = validateCliFixtureResult(result, assertions);
14919
+ return {
14920
+ mode: "exercise",
14921
+ pass: failures.length === 0,
14922
+ failures,
14923
+ result,
14924
+ assertions
14925
+ };
14926
+ }
13994
14927
  async function handleCliStatus(ctx, _req, res) {
13995
14928
  if (!ctx.instanceManager) {
13996
14929
  ctx.json(res, 503, { error: "InstanceManager not available (daemon not fully initialized)" });
@@ -14129,12 +15062,14 @@ async function handleCliDebug(ctx, type, _req, res) {
14129
15062
  status: target.status,
14130
15063
  mode: "mode" in target ? target.mode : void 0
14131
15064
  },
15065
+ providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
14132
15066
  debug: debugState
14133
15067
  });
14134
15068
  } else {
14135
15069
  ctx.json(res, 200, {
14136
15070
  instanceId: target.instanceId,
14137
15071
  providerState: target,
15072
+ providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
14138
15073
  debug: null,
14139
15074
  message: "No debug state available (adapter.getDebugState not found)"
14140
15075
  });
@@ -14143,6 +15078,191 @@ async function handleCliDebug(ctx, type, _req, res) {
14143
15078
  ctx.json(res, 500, { error: `Debug state failed: ${e.message}` });
14144
15079
  }
14145
15080
  }
15081
+ async function handleCliTrace(ctx, type, req, res) {
15082
+ if (!ctx.instanceManager) {
15083
+ ctx.json(res, 503, { error: "InstanceManager not available" });
15084
+ return;
15085
+ }
15086
+ const target = findCliTarget(ctx, type);
15087
+ if (!target) {
15088
+ const allStates = ctx.instanceManager.collectAllStates();
15089
+ ctx.json(res, 404, {
15090
+ error: `No running instance for: ${type}`,
15091
+ available: allStates.filter((s) => s.category === "cli" || s.category === "acp").map((s) => s.type)
15092
+ });
15093
+ return;
15094
+ }
15095
+ const instance = ctx.instanceManager.getInstance(target.instanceId);
15096
+ if (!instance) {
15097
+ ctx.json(res, 404, { error: `Instance not found: ${target.instanceId}` });
15098
+ return;
15099
+ }
15100
+ try {
15101
+ const adapter = instance.getAdapter?.() || instance.adapter;
15102
+ const url = new URL(req.url || "/", "http://127.0.0.1");
15103
+ const limit = parseInt(url.searchParams.get("limit") || "120", 10);
15104
+ if (adapter && typeof adapter.getTraceState === "function") {
15105
+ const trace = adapter.getTraceState(limit);
15106
+ const debug = typeof adapter.getDebugState === "function" ? adapter.getDebugState() : null;
15107
+ ctx.json(res, 200, {
15108
+ instanceId: target.instanceId,
15109
+ providerState: {
15110
+ type: target.type,
15111
+ name: target.name,
15112
+ status: target.status,
15113
+ mode: "mode" in target ? target.mode : void 0
15114
+ },
15115
+ providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
15116
+ debug,
15117
+ trace
15118
+ });
15119
+ } else {
15120
+ ctx.json(res, 200, {
15121
+ instanceId: target.instanceId,
15122
+ providerState: target,
15123
+ providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
15124
+ debug: typeof adapter?.getDebugState === "function" ? adapter.getDebugState() : null,
15125
+ trace: null,
15126
+ message: "No trace state available (adapter.getTraceState not found)"
15127
+ });
15128
+ }
15129
+ } catch (e) {
15130
+ ctx.json(res, 500, { error: `Trace state failed: ${e.message}` });
15131
+ }
15132
+ }
15133
+ async function handleCliExercise(ctx, req, res) {
15134
+ try {
15135
+ const body = await ctx.readBody(req);
15136
+ const result = await runCliExerciseInternal(ctx, body || {});
15137
+ ctx.json(res, 200, result);
15138
+ } catch (e) {
15139
+ ctx.json(res, 500, { error: `Exercise failed: ${e.message}` });
15140
+ }
15141
+ }
15142
+ async function handleCliFixtureCapture(ctx, req, res) {
15143
+ try {
15144
+ const body = await ctx.readBody(req);
15145
+ const type = String(body?.type || "");
15146
+ const request = body?.request || {};
15147
+ if (!type) {
15148
+ ctx.json(res, 400, { error: "type required" });
15149
+ return;
15150
+ }
15151
+ if (!request?.text) {
15152
+ ctx.json(res, 400, { error: "request.text required" });
15153
+ return;
15154
+ }
15155
+ const fixtureDir = getCliFixtureDir(ctx, type);
15156
+ fs12.mkdirSync(fixtureDir, { recursive: true });
15157
+ const name = slugifyFixtureName(String(body?.name || `${type}-${Date.now()}`));
15158
+ const result = await runCliExerciseInternal(ctx, { ...request, type });
15159
+ const fixture = {
15160
+ version: 1,
15161
+ kind: "cli-exercise-fixture",
15162
+ name,
15163
+ type,
15164
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
15165
+ providerDir: ctx.providerLoader.findProviderDir(type),
15166
+ providerResolution: result?.providerResolution || null,
15167
+ request: { ...request, type },
15168
+ result,
15169
+ assertions: {
15170
+ mustContainAny: Array.isArray(body?.assertions?.mustContainAny) ? body.assertions.mustContainAny : [],
15171
+ mustNotContainAny: Array.isArray(body?.assertions?.mustNotContainAny) ? body.assertions.mustNotContainAny : [],
15172
+ mustMatchAny: Array.isArray(body?.assertions?.mustMatchAny) ? body.assertions.mustMatchAny : [],
15173
+ mustNotMatchAny: Array.isArray(body?.assertions?.mustNotMatchAny) ? body.assertions.mustNotMatchAny : [],
15174
+ lastAssistantMustContainAny: Array.isArray(body?.assertions?.lastAssistantMustContainAny) ? body.assertions.lastAssistantMustContainAny : [],
15175
+ lastAssistantMustNotContainAny: Array.isArray(body?.assertions?.lastAssistantMustNotContainAny) ? body.assertions.lastAssistantMustNotContainAny : [],
15176
+ lastAssistantMustMatchAny: Array.isArray(body?.assertions?.lastAssistantMustMatchAny) ? body.assertions.lastAssistantMustMatchAny : [],
15177
+ lastAssistantMustNotMatchAny: Array.isArray(body?.assertions?.lastAssistantMustNotMatchAny) ? body.assertions.lastAssistantMustNotMatchAny : [],
15178
+ statusesSeen: Array.isArray(body?.assertions?.statusesSeen) ? body.assertions.statusesSeen : void 0,
15179
+ requireNotTimedOut: body?.assertions?.requireNotTimedOut !== false
15180
+ },
15181
+ notes: typeof body?.notes === "string" ? body.notes : void 0
15182
+ };
15183
+ const filePath = path16.join(fixtureDir, `${name}.json`);
15184
+ fs12.writeFileSync(filePath, JSON.stringify(fixture, null, 2));
15185
+ ctx.json(res, 200, {
15186
+ saved: true,
15187
+ name,
15188
+ path: filePath,
15189
+ fixture,
15190
+ verification: {
15191
+ pass: validateCliFixtureResult(result, fixture.assertions).length === 0,
15192
+ failures: validateCliFixtureResult(result, fixture.assertions)
15193
+ }
15194
+ });
15195
+ } catch (e) {
15196
+ ctx.json(res, 500, { error: `Fixture capture failed: ${e.message}` });
15197
+ }
15198
+ }
15199
+ async function handleCliFixtureList(ctx, type, _req, res) {
15200
+ try {
15201
+ const fixtureDir = getCliFixtureDir(ctx, type);
15202
+ if (!fs12.existsSync(fixtureDir)) {
15203
+ ctx.json(res, 200, { fixtures: [], count: 0 });
15204
+ return;
15205
+ }
15206
+ const fixtures = fs12.readdirSync(fixtureDir).filter((file) => file.endsWith(".json")).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" })).map((file) => {
15207
+ const fullPath = path16.join(fixtureDir, file);
15208
+ try {
15209
+ const raw = JSON.parse(fs12.readFileSync(fullPath, "utf-8"));
15210
+ return {
15211
+ name: raw.name || file.replace(/\.json$/i, ""),
15212
+ path: fullPath,
15213
+ createdAt: raw.createdAt || null,
15214
+ notes: raw.notes || null,
15215
+ requestText: raw.request?.text || "",
15216
+ assertions: raw.assertions || {}
15217
+ };
15218
+ } catch {
15219
+ return {
15220
+ name: file.replace(/\.json$/i, ""),
15221
+ path: fullPath,
15222
+ createdAt: null,
15223
+ notes: "Unreadable fixture",
15224
+ requestText: "",
15225
+ assertions: {}
15226
+ };
15227
+ }
15228
+ });
15229
+ ctx.json(res, 200, { fixtures, count: fixtures.length });
15230
+ } catch (e) {
15231
+ ctx.json(res, 500, { error: `Fixture list failed: ${e.message}` });
15232
+ }
15233
+ }
15234
+ async function handleCliFixtureReplay(ctx, req, res) {
15235
+ try {
15236
+ const body = await ctx.readBody(req);
15237
+ const type = String(body?.type || "");
15238
+ const rawName = String(body?.name || "").trim();
15239
+ if (!type || !rawName) {
15240
+ ctx.json(res, 400, { error: "type and name required" });
15241
+ return;
15242
+ }
15243
+ const name = slugifyFixtureName(rawName);
15244
+ const fixture = readCliFixture(ctx, type, name);
15245
+ const result = await runCliExerciseInternal(ctx, {
15246
+ ...fixture.request,
15247
+ type
15248
+ });
15249
+ const assertions = {
15250
+ ...fixture.assertions,
15251
+ ...body?.assertions || {}
15252
+ };
15253
+ const failures = validateCliFixtureResult(result, assertions);
15254
+ ctx.json(res, 200, {
15255
+ replayed: true,
15256
+ pass: failures.length === 0,
15257
+ failures,
15258
+ fixture,
15259
+ result,
15260
+ assertions
15261
+ });
15262
+ } catch (e) {
15263
+ ctx.json(res, 500, { error: `Fixture replay failed: ${e.message}` });
15264
+ }
15265
+ }
14146
15266
  async function handleCliResolve(ctx, req, res) {
14147
15267
  const body = await ctx.readBody(req);
14148
15268
  const { type, buttonIndex, instanceId } = body;
@@ -14219,9 +15339,29 @@ async function handleCliRaw(ctx, req, res) {
14219
15339
  }
14220
15340
 
14221
15341
  // src/daemon/dev-auto-implement.ts
14222
- import * as fs12 from "fs";
14223
- import * as path16 from "path";
15342
+ import * as fs13 from "fs";
15343
+ import * as path17 from "path";
14224
15344
  import * as os17 from "os";
15345
+ function getAutoImplPid(ctx) {
15346
+ const proc = ctx.autoImplProcess;
15347
+ return proc && typeof proc.pid === "number" && proc.pid > 0 ? proc.pid : null;
15348
+ }
15349
+ function isPidAlive(pid) {
15350
+ try {
15351
+ process.kill(pid, 0);
15352
+ return true;
15353
+ } catch (error) {
15354
+ return error?.code === "EPERM";
15355
+ }
15356
+ }
15357
+ function clearStaleAutoImplState(ctx, reason) {
15358
+ if (!ctx.autoImplStatus.running && !ctx.autoImplProcess) return;
15359
+ const pid = getAutoImplPid(ctx);
15360
+ if (pid && isPidAlive(pid)) return;
15361
+ ctx.log(`Clearing stale auto-implement state: ${reason}${pid ? ` (pid ${pid})` : ""}`);
15362
+ ctx.autoImplProcess = null;
15363
+ ctx.autoImplStatus.running = false;
15364
+ }
14225
15365
  function getDefaultAutoImplReference(ctx, category, type) {
14226
15366
  if (category === "cli") {
14227
15367
  return type === "codex-cli" ? "claude-cli" : "codex-cli";
@@ -14237,45 +15377,45 @@ function resolveAutoImplReference(ctx, category, requestedReference, targetType)
14237
15377
  return fallback?.type || null;
14238
15378
  }
14239
15379
  function getLatestScriptVersionDir(scriptsDir) {
14240
- if (!fs12.existsSync(scriptsDir)) return null;
14241
- const versions = fs12.readdirSync(scriptsDir).filter((d) => {
15380
+ if (!fs13.existsSync(scriptsDir)) return null;
15381
+ const versions = fs13.readdirSync(scriptsDir).filter((d) => {
14242
15382
  try {
14243
- return fs12.statSync(path16.join(scriptsDir, d)).isDirectory();
15383
+ return fs13.statSync(path17.join(scriptsDir, d)).isDirectory();
14244
15384
  } catch {
14245
15385
  return false;
14246
15386
  }
14247
15387
  }).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
14248
15388
  if (versions.length === 0) return null;
14249
- return path16.join(scriptsDir, versions[0]);
15389
+ return path17.join(scriptsDir, versions[0]);
14250
15390
  }
14251
15391
  function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
14252
- const canonicalUserDir = path16.resolve(ctx.providerLoader.getUserProviderDir(category, type));
14253
- const desiredDir = requestedDir ? path16.resolve(requestedDir) : canonicalUserDir;
14254
- const upstreamRoot = path16.resolve(ctx.providerLoader.getUpstreamDir());
14255
- if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path16.sep}`)) {
15392
+ const canonicalUserDir = path17.resolve(ctx.providerLoader.getUserProviderDir(category, type));
15393
+ const desiredDir = requestedDir ? path17.resolve(requestedDir) : canonicalUserDir;
15394
+ const upstreamRoot = path17.resolve(ctx.providerLoader.getUpstreamDir());
15395
+ if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path17.sep}`)) {
14256
15396
  return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
14257
15397
  }
14258
- if (path16.basename(desiredDir) !== type) {
15398
+ if (path17.basename(desiredDir) !== type) {
14259
15399
  return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
14260
15400
  }
14261
15401
  const sourceDir = ctx.findProviderDir(type);
14262
15402
  if (!sourceDir) {
14263
15403
  return { dir: null, reason: `Provider source directory not found for '${type}'` };
14264
15404
  }
14265
- if (!fs12.existsSync(desiredDir)) {
14266
- fs12.mkdirSync(path16.dirname(desiredDir), { recursive: true });
14267
- fs12.cpSync(sourceDir, desiredDir, { recursive: true });
15405
+ if (!fs13.existsSync(desiredDir)) {
15406
+ fs13.mkdirSync(path17.dirname(desiredDir), { recursive: true });
15407
+ fs13.cpSync(sourceDir, desiredDir, { recursive: true });
14268
15408
  ctx.log(`Auto-implement writable copy created: ${desiredDir}`);
14269
15409
  }
14270
- const providerJson = path16.join(desiredDir, "provider.json");
14271
- if (!fs12.existsSync(providerJson)) {
15410
+ const providerJson = path17.join(desiredDir, "provider.json");
15411
+ if (!fs13.existsSync(providerJson)) {
14272
15412
  return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
14273
15413
  }
14274
15414
  try {
14275
- const providerData = JSON.parse(fs12.readFileSync(providerJson, "utf-8"));
15415
+ const providerData = JSON.parse(fs13.readFileSync(providerJson, "utf-8"));
14276
15416
  if (providerData.disableUpstream !== true) {
14277
15417
  providerData.disableUpstream = true;
14278
- fs12.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
15418
+ fs13.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
14279
15419
  }
14280
15420
  } catch (error) {
14281
15421
  return {
@@ -14288,15 +15428,15 @@ function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
14288
15428
  function loadAutoImplReferenceScripts(ctx, referenceType) {
14289
15429
  if (!referenceType) return {};
14290
15430
  const refDir = ctx.findProviderDir(referenceType);
14291
- if (!refDir || !fs12.existsSync(refDir)) return {};
15431
+ if (!refDir || !fs13.existsSync(refDir)) return {};
14292
15432
  const referenceScripts = {};
14293
- const scriptsDir = path16.join(refDir, "scripts");
15433
+ const scriptsDir = path17.join(refDir, "scripts");
14294
15434
  const latestDir = getLatestScriptVersionDir(scriptsDir);
14295
15435
  if (!latestDir) return referenceScripts;
14296
- for (const file of fs12.readdirSync(latestDir)) {
15436
+ for (const file of fs13.readdirSync(latestDir)) {
14297
15437
  if (!file.endsWith(".js")) continue;
14298
15438
  try {
14299
- referenceScripts[file] = fs12.readFileSync(path16.join(latestDir, file), "utf-8");
15439
+ referenceScripts[file] = fs13.readFileSync(path17.join(latestDir, file), "utf-8");
14300
15440
  } catch {
14301
15441
  }
14302
15442
  }
@@ -14304,11 +15444,20 @@ function loadAutoImplReferenceScripts(ctx, referenceType) {
14304
15444
  }
14305
15445
  async function handleAutoImplement(ctx, type, req, res) {
14306
15446
  const body = await ctx.readBody(req);
14307
- const { agent = "claude-cli", functions, reference, model, comment, providerDir: requestedProviderDir } = body;
15447
+ const {
15448
+ agent = "claude-cli",
15449
+ functions,
15450
+ reference,
15451
+ model,
15452
+ comment,
15453
+ providerDir: requestedProviderDir,
15454
+ verification
15455
+ } = body;
14308
15456
  if (!functions || !Array.isArray(functions) || functions.length === 0) {
14309
15457
  ctx.json(res, 400, { error: 'functions[] is required (e.g. ["readChat", "sendMessage"])' });
14310
15458
  return;
14311
15459
  }
15460
+ clearStaleAutoImplState(ctx, "new auto-implement request");
14312
15461
  if (ctx.autoImplStatus.running) {
14313
15462
  ctx.json(res, 409, { error: "Auto-implement already in progress", type: ctx.autoImplStatus.type });
14314
15463
  return;
@@ -14326,7 +15475,55 @@ async function handleAutoImplement(ctx, type, req, res) {
14326
15475
  return;
14327
15476
  }
14328
15477
  const providerDir = writableProvider.dir;
15478
+ ctx.autoImplStatus = { running: false, type, progress: [] };
15479
+ if (provider.category === "cli" && verification && (verification.fixtureName || verification.fixtureNames && verification.fixtureNames.length > 0)) {
15480
+ sendAutoImplSSE(ctx, {
15481
+ event: "progress",
15482
+ data: {
15483
+ function: "_preflight",
15484
+ status: "verifying",
15485
+ message: "Running preflight verification before spawning agent..."
15486
+ }
15487
+ });
15488
+ try {
15489
+ const preflight = await runCliAutoImplVerification(ctx, type, verification);
15490
+ sendAutoImplSSE(ctx, { event: "verification", data: preflight });
15491
+ if (preflight.pass) {
15492
+ sendAutoImplSSE(ctx, {
15493
+ event: "complete",
15494
+ data: {
15495
+ success: true,
15496
+ exitCode: 0,
15497
+ functions,
15498
+ message: `\u2705 No-op: exact ${preflight.mode} already passes`,
15499
+ verification: preflight,
15500
+ skipped: true
15501
+ }
15502
+ });
15503
+ ctx.json(res, 200, {
15504
+ started: false,
15505
+ skipped: true,
15506
+ type,
15507
+ functions,
15508
+ providerDir,
15509
+ verification: preflight,
15510
+ message: "Preflight verification already passes. No auto-implement run needed."
15511
+ });
15512
+ return;
15513
+ }
15514
+ } catch (error) {
15515
+ sendAutoImplSSE(ctx, {
15516
+ event: "progress",
15517
+ data: {
15518
+ function: "_preflight",
15519
+ status: "verify_failed",
15520
+ message: `Preflight verification errored, continuing to agent run: ${error?.message || error}`
15521
+ }
15522
+ });
15523
+ }
15524
+ }
14329
15525
  try {
15526
+ ctx.autoImplStatus = { running: true, type, progress: ctx.autoImplStatus.progress };
14330
15527
  const resolvedReference = resolveAutoImplReference(ctx, provider.category, reference, type);
14331
15528
  sendAutoImplSSE(ctx, {
14332
15529
  event: "progress",
@@ -14346,17 +15543,17 @@ async function handleAutoImplement(ctx, type, req, res) {
14346
15543
  }
14347
15544
  });
14348
15545
  const referenceScripts = loadAutoImplReferenceScripts(ctx, resolvedReference);
14349
- const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference);
14350
- const tmpDir = path16.join(os17.tmpdir(), "adhdev-autoimpl");
14351
- if (!fs12.existsSync(tmpDir)) fs12.mkdirSync(tmpDir, { recursive: true });
14352
- const promptFile = path16.join(tmpDir, `prompt-${type}-${Date.now()}.md`);
14353
- fs12.writeFileSync(promptFile, prompt, "utf-8");
15546
+ const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference, verification);
15547
+ const tmpDir = path17.join(os17.tmpdir(), "adhdev-autoimpl");
15548
+ if (!fs13.existsSync(tmpDir)) fs13.mkdirSync(tmpDir, { recursive: true });
15549
+ const promptFile = path17.join(tmpDir, `prompt-${type}-${Date.now()}.md`);
15550
+ fs13.writeFileSync(promptFile, prompt, "utf-8");
14354
15551
  ctx.log(`Auto-implement prompt written to ${promptFile} (${prompt.length} chars)`);
14355
15552
  const agentProvider = ctx.providerLoader.resolve(agent) || ctx.providerLoader.getMeta(agent);
14356
15553
  const spawn4 = agentProvider?.spawn;
14357
15554
  if (!spawn4?.command) {
14358
15555
  try {
14359
- fs12.unlinkSync(promptFile);
15556
+ fs13.unlinkSync(promptFile);
14360
15557
  } catch {
14361
15558
  }
14362
15559
  ctx.json(res, 400, { error: `Agent '${agent}' has no spawn config. Select a CLI provider with a spawn configuration.` });
@@ -14365,7 +15562,8 @@ async function handleAutoImplement(ctx, type, req, res) {
14365
15562
  const agentCategory = agentProvider?.category;
14366
15563
  if (agentCategory === "acp") {
14367
15564
  sendAutoImplSSE(ctx, { event: "progress", data: { function: "_init", status: "spawning", message: `Spawning ACP agent: ${spawn4.command} ${(spawn4.args || []).join(" ")}` } });
14368
- ctx.autoImplStatus = { running: true, type, progress: [] };
15565
+ ctx.autoImplStatus.running = true;
15566
+ ctx.autoImplStatus.type = type;
14369
15567
  const { ClientSideConnection: ClientSideConnection2, ndJsonStream: ndJsonStream2, PROTOCOL_VERSION: PROTOCOL_VERSION2 } = await import("@agentclientprotocol/sdk");
14370
15568
  const { Readable: Readable2, Writable: Writable2 } = await import("stream");
14371
15569
  const { spawn: spawnFn2 } = await import("child_process");
@@ -14457,7 +15655,7 @@ async function handleAutoImplement(ctx, type, req, res) {
14457
15655
  } catch {
14458
15656
  }
14459
15657
  try {
14460
- fs12.unlinkSync(promptFile);
15658
+ fs13.unlinkSync(promptFile);
14461
15659
  } catch {
14462
15660
  }
14463
15661
  ctx.log(`Auto-implement (ACP) ${success ? "completed" : "failed"}: ${type} (exit: ${code})`);
@@ -14535,7 +15733,8 @@ async function handleAutoImplement(ctx, type, req, res) {
14535
15733
  }
14536
15734
  }
14537
15735
  sendAutoImplSSE(ctx, { event: "progress", data: { function: "_init", status: "spawning", message: `Spawning agent: ${shellCmd.substring(0, 200)}... (prompt: ${prompt.length} chars)` } });
14538
- ctx.autoImplStatus = { running: true, type, progress: [] };
15736
+ ctx.autoImplStatus.running = true;
15737
+ ctx.autoImplStatus.type = type;
14539
15738
  const spawnedAt = Date.now();
14540
15739
  let child;
14541
15740
  let isPty = false;
@@ -14578,6 +15777,7 @@ async function handleAutoImplement(ctx, type, req, res) {
14578
15777
  let approvalKeys = { 0: "y\r" };
14579
15778
  let approvalBuffer = "";
14580
15779
  let lastApprovalTime = 0;
15780
+ let completionSignalSeen = false;
14581
15781
  try {
14582
15782
  const { normalizeCliProviderForRuntime: normalizeCliProviderForRuntime2 } = await Promise.resolve().then(() => (init_provider_cli_adapter(), provider_cli_adapter_exports));
14583
15783
  const normalized = normalizeCliProviderForRuntime2(agentProvider);
@@ -14591,6 +15791,7 @@ async function handleAutoImplement(ctx, type, req, res) {
14591
15791
  approvalBuffer = (approvalBuffer + cleanData).slice(-1500);
14592
15792
  const elapsed = Date.now() - spawnedAt;
14593
15793
  if (elapsed > 15e3 && cleanData.includes("_PIPELINE_COMPLETE_SIGNAL_")) {
15794
+ completionSignalSeen = true;
14594
15795
  ctx.log(`Agent finished task after ${Math.round(elapsed / 1e3)}s. Terminating interactive CLI session to unblock pipeline.`);
14595
15796
  sendAutoImplSSE(ctx, { event: "output", data: { chunk: `
14596
15797
  [\u{1F916} ADHDev Pipeline] Completion token detected. Proceeding...
@@ -14614,6 +15815,55 @@ async function handleAutoImplement(ctx, type, req, res) {
14614
15815
  lastApprovalTime = Date.now();
14615
15816
  }
14616
15817
  };
15818
+ const finalizeCliAutoImpl = async (code) => {
15819
+ ctx.autoImplProcess = null;
15820
+ let success = completionSignalSeen || code === 0;
15821
+ let message = success ? completionSignalSeen && code !== 0 ? "\u2705 Auto-implement complete (completion signal)" : "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})`;
15822
+ let verificationSummary = null;
15823
+ try {
15824
+ ctx.providerLoader.reload();
15825
+ } catch {
15826
+ }
15827
+ if (provider.category === "cli" && verification) {
15828
+ sendAutoImplSSE(ctx, {
15829
+ event: "progress",
15830
+ data: {
15831
+ function: "_verify",
15832
+ status: "running",
15833
+ message: "Running exact post-patch verification..."
15834
+ }
15835
+ });
15836
+ try {
15837
+ verificationSummary = await runCliAutoImplVerification(ctx, type, verification);
15838
+ sendAutoImplSSE(ctx, { event: "verification", data: verificationSummary });
15839
+ success = verificationSummary.pass;
15840
+ message = verificationSummary.pass ? `\u2705 Auto-implement complete (${verificationSummary.mode})` : `\u274C Post-patch verification failed (${verificationSummary.mode}): ${verificationSummary.failures.join("; ") || "unknown failure"}`;
15841
+ } catch (error) {
15842
+ success = false;
15843
+ message = `\u274C Post-patch verification error: ${error?.message || error}`;
15844
+ sendAutoImplSSE(ctx, {
15845
+ event: "verification",
15846
+ data: { pass: false, error: error?.message || String(error) }
15847
+ });
15848
+ }
15849
+ }
15850
+ ctx.autoImplStatus.running = false;
15851
+ sendAutoImplSSE(ctx, {
15852
+ event: "complete",
15853
+ data: {
15854
+ success,
15855
+ exitCode: code,
15856
+ functions,
15857
+ message,
15858
+ verification: verificationSummary
15859
+ }
15860
+ });
15861
+ try {
15862
+ fs13.unlinkSync(promptFile);
15863
+ } catch {
15864
+ }
15865
+ ctx.log(`Auto-implement ${success ? "completed" : "failed"}: ${type} (exit: ${code})${verificationSummary ? ` verify=${verificationSummary.pass ? "pass" : "fail"}` : ""}`);
15866
+ };
14617
15867
  if (isPty) {
14618
15868
  child.onData((data) => {
14619
15869
  stdout += data;
@@ -14625,21 +15875,7 @@ async function handleAutoImplement(ctx, type, req, res) {
14625
15875
  sendAutoImplSSE(ctx, { event: "output", data: { chunk: data, stream: "stdout" } });
14626
15876
  });
14627
15877
  child.onExit(({ exitCode: code }) => {
14628
- ctx.autoImplProcess = null;
14629
- ctx.autoImplStatus.running = false;
14630
- const success = code === 0;
14631
- sendAutoImplSSE(ctx, {
14632
- event: "complete",
14633
- data: { success, exitCode: code, functions, message: success ? "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})` }
14634
- });
14635
- try {
14636
- ctx.providerLoader.reload();
14637
- } catch {
14638
- }
14639
- try {
14640
- fs12.unlinkSync(promptFile);
14641
- } catch {
14642
- }
15878
+ void finalizeCliAutoImpl(code);
14643
15879
  });
14644
15880
  } else {
14645
15881
  child.stdout?.on("data", (d) => {
@@ -14656,27 +15892,7 @@ async function handleAutoImplement(ctx, type, req, res) {
14656
15892
  sendAutoImplSSE(ctx, { event: "output", data: { chunk, stream: "stderr" } });
14657
15893
  });
14658
15894
  child.on("exit", (code) => {
14659
- ctx.autoImplProcess = null;
14660
- ctx.autoImplStatus.running = false;
14661
- const success = code === 0;
14662
- sendAutoImplSSE(ctx, {
14663
- event: "complete",
14664
- data: {
14665
- success,
14666
- exitCode: code,
14667
- functions,
14668
- message: success ? "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})`
14669
- }
14670
- });
14671
- try {
14672
- ctx.providerLoader.reload();
14673
- } catch {
14674
- }
14675
- try {
14676
- fs12.unlinkSync(promptFile);
14677
- } catch {
14678
- }
14679
- ctx.log(`Auto-implement ${success ? "completed" : "failed"}: ${type} (exit: ${code})`);
15895
+ void finalizeCliAutoImpl(code);
14680
15896
  });
14681
15897
  }
14682
15898
  ctx.json(res, 202, {
@@ -14693,9 +15909,9 @@ async function handleAutoImplement(ctx, type, req, res) {
14693
15909
  ctx.json(res, 500, { error: `Auto-implement failed: ${e.message}` });
14694
15910
  }
14695
15911
  }
14696
- function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, userComment, referenceType) {
15912
+ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, userComment, referenceType, verification) {
14697
15913
  if (provider.category === "cli") {
14698
- return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType);
15914
+ return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification);
14699
15915
  }
14700
15916
  const lines = [];
14701
15917
  lines.push("You are implementing browser automation scripts for an IDE provider.");
@@ -14720,7 +15936,7 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
14720
15936
  setMode: "set_mode.js"
14721
15937
  };
14722
15938
  const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
14723
- const scriptsDir = path16.join(providerDir, "scripts");
15939
+ const scriptsDir = path17.join(providerDir, "scripts");
14724
15940
  const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
14725
15941
  if (latestScriptsDir) {
14726
15942
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -14728,10 +15944,10 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
14728
15944
  lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
14729
15945
  lines.push("These are the ONLY files you are allowed to modify. Replace the TODO stubs with working implementations.");
14730
15946
  lines.push("");
14731
- for (const file of fs12.readdirSync(latestScriptsDir)) {
15947
+ for (const file of fs13.readdirSync(latestScriptsDir)) {
14732
15948
  if (file.endsWith(".js") && targetFileNames.has(file)) {
14733
15949
  try {
14734
- const content = fs12.readFileSync(path16.join(latestScriptsDir, file), "utf-8");
15950
+ const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
14735
15951
  lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
14736
15952
  lines.push("```javascript");
14737
15953
  lines.push(content);
@@ -14741,14 +15957,14 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
14741
15957
  }
14742
15958
  }
14743
15959
  }
14744
- const refFiles = fs12.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
15960
+ const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
14745
15961
  if (refFiles.length > 0) {
14746
15962
  lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
14747
15963
  lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
14748
15964
  lines.push("");
14749
15965
  for (const file of refFiles) {
14750
15966
  try {
14751
- const content = fs12.readFileSync(path16.join(latestScriptsDir, file), "utf-8");
15967
+ const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
14752
15968
  lines.push(`### \`${file}\` \u{1F512}`);
14753
15969
  lines.push("```javascript");
14754
15970
  lines.push(content);
@@ -14789,11 +16005,11 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
14789
16005
  lines.push("");
14790
16006
  }
14791
16007
  }
14792
- const docsDir = path16.join(providerDir, "../../docs");
16008
+ const docsDir = path17.join(providerDir, "../../docs");
14793
16009
  const loadGuide = (name) => {
14794
16010
  try {
14795
- const p = path16.join(docsDir, name);
14796
- if (fs12.existsSync(p)) return fs12.readFileSync(p, "utf-8");
16011
+ const p = path17.join(docsDir, name);
16012
+ if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
14797
16013
  } catch {
14798
16014
  }
14799
16015
  return null;
@@ -14951,8 +16167,69 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
14951
16167
  lines.push("Start NOW. Do not ask for permission. Explore the DOM -> Code -> Test.");
14952
16168
  return lines.join("\n");
14953
16169
  }
14954
- function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType) {
16170
+ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification) {
14955
16171
  const lines = [];
16172
+ const defaultExercisePayload = {
16173
+ type,
16174
+ workingDir: providerDir,
16175
+ freshSession: true,
16176
+ autoLaunch: true,
16177
+ autoResolveApprovals: true,
16178
+ approvalButtonIndex: 0,
16179
+ timeoutMs: 45e3,
16180
+ traceLimit: 200,
16181
+ text: "Create a file at tmp/adhdev_provider_fix_test.py that prints the current working directory and the squares of 1 through 5, then run python3 tmp/adhdev_provider_fix_test.py and tell me the exact output."
16182
+ };
16183
+ const exercisePayload = {
16184
+ ...defaultExercisePayload,
16185
+ ...verification?.request || {},
16186
+ type,
16187
+ workingDir: providerDir
16188
+ };
16189
+ const exerciseJson = JSON.stringify(exercisePayload).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
16190
+ const verificationInspectFields = verification?.inspectFields?.length ? verification.inspectFields : [
16191
+ "debug.messages",
16192
+ "trace.entries[].payload.parsedLastAssistant",
16193
+ "trace.entries[].payload.lastAssistant"
16194
+ ];
16195
+ const verificationMustContainAny = verification?.mustContainAny || [];
16196
+ const verificationMustNotContainAny = verification?.mustNotContainAny || [];
16197
+ const verificationMustMatchAny = verification?.mustMatchAny || [];
16198
+ const verificationMustNotMatchAny = verification?.mustNotMatchAny || [];
16199
+ const verificationLastAssistantMustContainAny = verification?.lastAssistantMustContainAny || [];
16200
+ const verificationLastAssistantMustNotContainAny = verification?.lastAssistantMustNotContainAny || [];
16201
+ const verificationLastAssistantMustMatchAny = verification?.lastAssistantMustMatchAny || [];
16202
+ const verificationLastAssistantMustNotMatchAny = verification?.lastAssistantMustNotMatchAny || [];
16203
+ const quotedMustContain = verificationMustContainAny.map((value) => JSON.stringify(value)).join(", ");
16204
+ const quotedMustNotContain = verificationMustNotContainAny.map((value) => JSON.stringify(value)).join(", ");
16205
+ const quotedMustMatch = verificationMustMatchAny.map((value) => JSON.stringify(value)).join(", ");
16206
+ const quotedMustNotMatch = verificationMustNotMatchAny.map((value) => JSON.stringify(value)).join(", ");
16207
+ const quotedLastAssistantMustContain = verificationLastAssistantMustContainAny.map((value) => JSON.stringify(value)).join(", ");
16208
+ const quotedLastAssistantMustNotContain = verificationLastAssistantMustNotContainAny.map((value) => JSON.stringify(value)).join(", ");
16209
+ const quotedLastAssistantMustMatch = verificationLastAssistantMustMatchAny.map((value) => JSON.stringify(value)).join(", ");
16210
+ const quotedLastAssistantMustNotMatch = verificationLastAssistantMustNotMatchAny.map((value) => JSON.stringify(value)).join(", ");
16211
+ const fixtureName = verification?.fixtureName || `${type}-provider-fix`;
16212
+ const fixtureNames = Array.isArray(verification?.fixtureNames) ? verification.fixtureNames.map((value) => String(value || "").trim()).filter(Boolean) : [];
16213
+ const fixtureCaptureJson = JSON.stringify({
16214
+ type,
16215
+ name: fixtureName,
16216
+ request: exercisePayload,
16217
+ assertions: {
16218
+ mustContainAny: verificationMustContainAny,
16219
+ mustNotContainAny: verificationMustNotContainAny,
16220
+ mustMatchAny: verificationMustMatchAny,
16221
+ mustNotMatchAny: verificationMustNotMatchAny,
16222
+ lastAssistantMustContainAny: verificationLastAssistantMustContainAny,
16223
+ lastAssistantMustNotContainAny: verificationLastAssistantMustNotContainAny,
16224
+ lastAssistantMustMatchAny: verificationLastAssistantMustMatchAny,
16225
+ lastAssistantMustNotMatchAny: verificationLastAssistantMustNotMatchAny,
16226
+ requireNotTimedOut: true
16227
+ }
16228
+ }).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
16229
+ const fixtureReplayJson = JSON.stringify({
16230
+ type,
16231
+ name: fixtureName
16232
+ }).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
14956
16233
  lines.push("You are implementing PTY parsing scripts for a CLI provider.");
14957
16234
  lines.push("Be concise. Do NOT explain your reasoning. Edit files directly and verify with the local DevServer.");
14958
16235
  lines.push("");
@@ -14966,7 +16243,7 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
14966
16243
  parseApproval: "parse_approval.js"
14967
16244
  };
14968
16245
  const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
14969
- const scriptsDir = path16.join(providerDir, "scripts");
16246
+ const scriptsDir = path17.join(providerDir, "scripts");
14970
16247
  const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
14971
16248
  if (latestScriptsDir) {
14972
16249
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -14974,11 +16251,11 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
14974
16251
  lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
14975
16252
  lines.push("These are the ONLY files you are allowed to modify. Replace TODO or heuristic-only logic with working PTY-aware implementations.");
14976
16253
  lines.push("");
14977
- for (const file of fs12.readdirSync(latestScriptsDir)) {
16254
+ for (const file of fs13.readdirSync(latestScriptsDir)) {
14978
16255
  if (!file.endsWith(".js")) continue;
14979
16256
  if (!targetFileNames.has(file)) continue;
14980
16257
  try {
14981
- const content = fs12.readFileSync(path16.join(latestScriptsDir, file), "utf-8");
16258
+ const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
14982
16259
  lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
14983
16260
  lines.push("```javascript");
14984
16261
  lines.push(content);
@@ -14987,14 +16264,14 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
14987
16264
  } catch {
14988
16265
  }
14989
16266
  }
14990
- const refFiles = fs12.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
16267
+ const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
14991
16268
  if (refFiles.length > 0) {
14992
16269
  lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
14993
16270
  lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
14994
16271
  lines.push("");
14995
16272
  for (const file of refFiles) {
14996
16273
  try {
14997
- const content = fs12.readFileSync(path16.join(latestScriptsDir, file), "utf-8");
16274
+ const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
14998
16275
  lines.push(`### \`${file}\` \u{1F512}`);
14999
16276
  lines.push("```javascript");
15000
16277
  lines.push(content);
@@ -15027,17 +16304,17 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
15027
16304
  lines.push("");
15028
16305
  }
15029
16306
  }
15030
- const docsDir = path16.join(providerDir, "../../docs");
16307
+ const docsDir = path17.join(providerDir, "../../docs");
15031
16308
  const loadGuide = (name) => {
15032
16309
  try {
15033
- const p = path16.join(docsDir, name);
15034
- if (fs12.existsSync(p)) return fs12.readFileSync(p, "utf-8");
16310
+ const p = path17.join(docsDir, name);
16311
+ if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
15035
16312
  } catch {
15036
16313
  }
15037
16314
  return null;
15038
16315
  };
15039
16316
  const providerGuide = loadGuide("PROVIDER_GUIDE.md");
15040
- if (providerGuide) {
16317
+ if (providerGuide && provider.category !== "cli") {
15041
16318
  lines.push("## Documentation: PROVIDER_GUIDE.md");
15042
16319
  lines.push("```markdown");
15043
16320
  lines.push(providerGuide);
@@ -15079,6 +16356,11 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
15079
16356
  lines.push("13. After the first successful live repro, stop broad diagnosis. Edit the scripts, reload, and verify. Do not burn tokens on repeated re-inspection without code changes.");
15080
16357
  lines.push("14. If the visible current screen is clean and sufficient, do NOT fall back to complex buffer heuristics. Simpler current-screen parsing is preferred.");
15081
16358
  lines.push("15. Before changing parser logic, verify whether `provider.json` submit/approval behavior (`sendDelayMs`, `approvalKeys`, submit strategy) is the simpler and more correct fix.");
16359
+ lines.push('16. Do NOT patch transcript bugs by piling up one-off literal string exceptions (`includes("foo")`, `=== "bar"`, ad hoc allowlists/denylists) for every observed variant. Model the UI as PATTERN FAMILIES using reusable regex classifiers and normalization first.');
16360
+ lines.push("17. If you find yourself adding a second or third near-duplicate literal check for spinner words, tool headers, approval prompts, footer chrome, or OSC residue, STOP and replace them with a broader regex or helper classifier.");
16361
+ lines.push('18. Prefer a small number of named classifiers such as "status line", "tool header", "tool detail", "footer chrome", "approval cue", "prompt line", and "OSC residue" over a long chain of unrelated string checks.');
16362
+ lines.push("19. Literal string checks are allowed only for stable proper nouns or exact product chrome that cannot be expressed safely as a broader pattern. Everything else should generalize.");
16363
+ lines.push("20. When a bug comes from noisy PTY text, first normalize and classify the line family; do NOT just append another special-case substring to the parser.");
15082
16364
  lines.push("");
15083
16365
  lines.push("## Task");
15084
16366
  lines.push(`Edit files in \`${providerDir}\` to implement: **${functions.join(", ")}**`);
@@ -15086,35 +16368,145 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
15086
16368
  lines.push("## Verification API");
15087
16369
  lines.push("Use the DevServer CLI debug endpoints, not DOM/CDP routes.");
15088
16370
  lines.push("");
15089
- lines.push("### 1. Launch the target CLI");
16371
+ lines.push("### 1. Preferred: run a full autonomous repro");
16372
+ lines.push("Use the exercise endpoint first. It launches a fresh CLI session, sends the repro prompt, auto-resolves approvals, waits for the session to settle, and returns the final debug + trace payload in one response.");
15090
16373
  lines.push("```bash");
15091
- lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/launch \\`);
16374
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
15092
16375
  lines.push(' -H "Content-Type: application/json" \\');
15093
- lines.push(` -d '{"type":"${type}","workingDir":"${providerDir.replace(/\\/g, "\\\\")}"}'`);
16376
+ lines.push(` -d '${exerciseJson}'`);
15094
16377
  lines.push("```");
15095
16378
  lines.push("");
16379
+ if (verification?.description) {
16380
+ lines.push("Verification intent:");
16381
+ lines.push(verification.description);
16382
+ lines.push("");
16383
+ }
16384
+ lines.push("Read the JSON response carefully. It already includes:");
16385
+ lines.push("1. `instanceId`");
16386
+ lines.push("2. `statusesSeen` and `approvalsResolved`");
16387
+ lines.push("3. `debug` for the final settled state");
16388
+ lines.push("4. `trace.entries` for the repro turn");
16389
+ lines.push("");
16390
+ lines.push("Save the response to a temp file and inspect the exact parsed transcript fields before editing:");
16391
+ lines.push("```bash");
16392
+ lines.push(`EXERCISE_JSON=$(mktemp)`);
16393
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
16394
+ lines.push(' -H "Content-Type: application/json" \\');
16395
+ lines.push(` -d '${exerciseJson}' > "$EXERCISE_JSON"`);
16396
+ lines.push(`jq '{timedOut,statusesSeen,approvalsResolved,inspect:{${verificationInspectFields.map((field, index) => `f${index + 1}: .${field}`).join(", ")}}}' "$EXERCISE_JSON"`);
16397
+ lines.push("```");
16398
+ lines.push("");
16399
+ if (verificationMustContainAny.length > 0 || verificationMustNotContainAny.length > 0 || verificationMustMatchAny.length > 0 || verificationMustNotMatchAny.length > 0 || verificationLastAssistantMustContainAny.length > 0 || verificationLastAssistantMustNotContainAny.length > 0 || verificationLastAssistantMustMatchAny.length > 0 || verificationLastAssistantMustNotMatchAny.length > 0) {
16400
+ lines.push("The exact repro below is mandatory. Do NOT declare success unless these transcript assertions pass on the exercise JSON from the PATCHED provider.");
16401
+ lines.push("```bash");
16402
+ if (verificationMustContainAny.length > 0) {
16403
+ lines.push(`node -e 'const fs=require("fs");const text=fs.readFileSync(process.argv[1],"utf8");const required=[${quotedMustContain}];const missing=required.filter(v=>!text.includes(v));if(missing.length){console.error("Missing required substrings:\\n"+missing.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
16404
+ }
16405
+ if (verificationMustNotContainAny.length > 0) {
16406
+ lines.push(`node -e 'const fs=require("fs");const text=fs.readFileSync(process.argv[1],"utf8");const banned=[${quotedMustNotContain}];const hits=banned.filter(v=>text.includes(v));if(hits.length){console.error("Found banned substrings:\\n"+hits.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
16407
+ }
16408
+ if (verificationMustMatchAny.length > 0) {
16409
+ lines.push(`node -e 'const fs=require("fs");const text=fs.readFileSync(process.argv[1],"utf8");const required=[${quotedMustMatch}].map(v=>new RegExp(v,"m"));const missing=required.filter(v=>!v.test(text)).map(v=>String(v));if(missing.length){console.error("Missing required regex matches:\\n"+missing.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
16410
+ }
16411
+ if (verificationMustNotMatchAny.length > 0) {
16412
+ lines.push(`node -e 'const fs=require("fs");const text=fs.readFileSync(process.argv[1],"utf8");const banned=[${quotedMustNotMatch}].map(v=>new RegExp(v,"m"));const hits=banned.filter(v=>v.test(text)).map(v=>String(v));if(hits.length){console.error("Found banned regex matches:\\n"+hits.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
16413
+ }
16414
+ if (verificationLastAssistantMustContainAny.length > 0) {
16415
+ lines.push(`node -e 'const fs=require("fs");const payload=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));const text=String(payload.lastAssistant||"");const required=[${quotedLastAssistantMustContain}];const missing=required.filter(v=>!text.includes(v));if(missing.length){console.error("Missing required lastAssistant substrings:\\n"+missing.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
16416
+ }
16417
+ if (verificationLastAssistantMustNotContainAny.length > 0) {
16418
+ lines.push(`node -e 'const fs=require("fs");const payload=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));const text=String(payload.lastAssistant||"");const banned=[${quotedLastAssistantMustNotContain}];const hits=banned.filter(v=>text.includes(v));if(hits.length){console.error("Found banned lastAssistant substrings:\\n"+hits.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
16419
+ }
16420
+ if (verificationLastAssistantMustMatchAny.length > 0) {
16421
+ lines.push(`node -e 'const fs=require("fs");const payload=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));const text=String(payload.lastAssistant||"");const required=[${quotedLastAssistantMustMatch}].map(v=>new RegExp(v,"m"));const missing=required.filter(v=>!v.test(text)).map(v=>String(v));if(missing.length){console.error("Missing required lastAssistant regex matches:\\n"+missing.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
16422
+ }
16423
+ if (verificationLastAssistantMustNotMatchAny.length > 0) {
16424
+ lines.push(`node -e 'const fs=require("fs");const payload=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));const text=String(payload.lastAssistant||"");const banned=[${quotedLastAssistantMustNotMatch}].map(v=>new RegExp(v,"m"));const hits=banned.filter(v=>v.test(text)).map(v=>String(v));if(hits.length){console.error("Found banned lastAssistant regex matches:\\n"+hits.join("\\n"));process.exit(1);}' "$EXERCISE_JSON"`);
16425
+ }
16426
+ lines.push("```");
16427
+ lines.push("");
16428
+ }
16429
+ lines.push("If you need a manual follow-up repro after patching, use the SAME endpoint again with the SAME prompt and compare the new trace to the previous one.");
16430
+ lines.push("");
16431
+ lines.push("### 1b. Persist or replay the exact repro as a reusable fixture");
16432
+ if (fixtureNames.length > 0) {
16433
+ lines.push(`Replay this exact fixture suite before editing, and replay the SAME suite again after patching. Do not declare success unless EVERY fixture passes: ${fixtureNames.map((name) => `\`${name}\``).join(", ")}.`);
16434
+ for (const name of fixtureNames) {
16435
+ const replayJson = JSON.stringify({ type, name }).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
16436
+ lines.push("```bash");
16437
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
16438
+ lines.push(' -H "Content-Type: application/json" \\');
16439
+ lines.push(` -d '${replayJson}'`);
16440
+ lines.push("```");
16441
+ lines.push("");
16442
+ }
16443
+ lines.push("Do not create new fixtures unless one of the listed fixtures is missing or stale.");
16444
+ } else if (verification?.fixtureName) {
16445
+ lines.push(`Replay the EXISTING saved fixture \`${fixtureName}\` before editing, and replay the SAME fixture again after patching. Do not declare success unless that exact fixture passes.`);
16446
+ lines.push("```bash");
16447
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
16448
+ lines.push(' -H "Content-Type: application/json" \\');
16449
+ lines.push(` -d '${fixtureReplayJson}'`);
16450
+ lines.push("```");
16451
+ lines.push("");
16452
+ lines.push("Only if the named fixture is missing or outdated should you recapture it. Prefer replaying the existing failing fixture over creating a new one.");
16453
+ lines.push("```bash");
16454
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
16455
+ lines.push(' -H "Content-Type: application/json" \\');
16456
+ lines.push(` -d '${fixtureCaptureJson}'`);
16457
+ lines.push("```");
16458
+ } else {
16459
+ lines.push("Capture the exact exercise once before editing. After patching, replay THIS fixture and do not declare success unless replay passes.");
16460
+ lines.push("```bash");
16461
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
16462
+ lines.push(' -H "Content-Type: application/json" \\');
16463
+ lines.push(` -d '${fixtureCaptureJson}'`);
16464
+ lines.push("");
16465
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
16466
+ lines.push(' -H "Content-Type: application/json" \\');
16467
+ lines.push(` -d '${fixtureReplayJson}'`);
16468
+ lines.push("```");
16469
+ }
16470
+ lines.push("");
16471
+ lines.push("The capture endpoint saves the exact request, initial result, and transcript assertions into the provider directory. The replay endpoint reruns the SAME exercise against your patched scripts and returns pass/fail.");
16472
+ lines.push("");
15096
16473
  lines.push("### 2. Inspect parsed + raw adapter state");
15097
16474
  lines.push("```bash");
16475
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/launch \\`);
16476
+ lines.push(' -H "Content-Type: application/json" \\');
16477
+ lines.push(` -d '{"type":"${type}","workingDir":"${providerDir.replace(/\\/g, "\\\\")}"}'`);
15098
16478
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
16479
+ lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
15099
16480
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
15100
16481
  lines.push("```");
15101
16482
  lines.push("");
16483
+ lines.push("The CLI trace endpoint is the primary debugging source. Read it BEFORE editing any parser code.");
16484
+ lines.push("Use the trace timeline to find the latest `settled` or `commit_transcript` frame for the repro turn and inspect these fields first:");
16485
+ lines.push("1. `payload.screenText`");
16486
+ lines.push("2. `payload.detectStatus` and `payload.parsedStatus`");
16487
+ lines.push("3. `payload.parsedLastAssistant`");
16488
+ lines.push("4. `payload.approval` / `payload.parsedActiveModal`");
16489
+ lines.push("5. `payload.rawPreview` only when control-sequence residue matters");
16490
+ lines.push("");
15102
16491
  lines.push("The debug payload should be read in this priority order:");
15103
16492
  lines.push("1. `screenText` / current visible state");
15104
16493
  lines.push("2. parsed `status`, `messages`, `activeModal`");
15105
16494
  lines.push("3. `rawBuffer` only for style/control-sequence cues");
15106
16495
  lines.push("4. `buffer` only when the current screen is insufficient");
15107
16496
  lines.push("");
15108
- lines.push("Extract the current `instanceId` from the launch or status response and keep using it below.");
16497
+ lines.push("If the bug is transcript corruption, quote the exact bad `parsedLastAssistant` or bad committed assistant message from the trace and patch against that concrete failure.");
16498
+ lines.push("Do NOT guess based only on the final chat bubble or a truncated UI preview.");
16499
+ lines.push("");
16500
+ lines.push("Extract the current `instanceId` from the exercise, launch, or status response and keep using it below.");
15109
16501
  lines.push("");
15110
- lines.push("### 3. Send a realistic approval-triggering prompt");
16502
+ lines.push("### 3. Manual fallback only: send a realistic approval-triggering prompt");
15111
16503
  lines.push("```bash");
15112
16504
  lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/send \\`);
15113
16505
  lines.push(' -H "Content-Type: application/json" \\');
15114
16506
  lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","text":"Create a file at tmp/adhdev_provider_fix_test.py that prints the current working directory and the squares of 1 through 5, then run python3 tmp/adhdev_provider_fix_test.py and tell me the exact output."}'`);
15115
16507
  lines.push("```");
15116
16508
  lines.push("");
15117
- lines.push("### 4. If approval appears, resolve it until the CLI reaches idle");
16509
+ lines.push("### 4. Manual fallback only: if approval appears, resolve it until the CLI reaches idle");
15118
16510
  lines.push("```bash");
15119
16511
  lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/resolve \\`);
15120
16512
  lines.push(' -H "Content-Type: application/json" \\');
@@ -15124,10 +16516,14 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
15124
16516
  lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","keys":"1"}'`);
15125
16517
  lines.push("```");
15126
16518
  lines.push("");
15127
- lines.push("Use `resolve` when the parsed modal buttons are correct. Use `raw` when the CLI expects a literal keystroke like `1`, `y`, or Enter. Repeat until idle.");
16519
+ lines.push("Use `resolve` when the parsed modal buttons are correct. Use `raw` when the CLI expects a literal keystroke like `1`, `y`, or Enter. Repeat until idle. Prefer the exercise endpoint instead of doing this by hand.");
15128
16520
  lines.push("");
15129
16521
  lines.push("### Patch Discipline");
15130
16522
  lines.push("Once the repro is confirmed, immediately edit the target files. Avoid loops where you keep re-reading long files or re-running the same debug commands without changing code.");
16523
+ lines.push("For CLI transcript bugs, reproduce once with the exercise endpoint, inspect the returned trace once, patch immediately, then re-run the SAME exercise and compare the new `commit_transcript` frame.");
16524
+ lines.push("If the patched run still fails the exact required/banned substring checks above, the task is NOT complete even if the CLI exits normally.");
16525
+ lines.push("When you patch, write down the pattern family you are fixing: e.g. spinner/status, tool block, approval modal, footer chrome, OSC/control residue, prompt echo, or long-output continuation. Patch that family once instead of adding case-by-case literals.");
16526
+ lines.push('Bad fix pattern: add another `includes("Drizzling")` or `includes("Show more (")` check. Good fix pattern: broaden the regex/helper that recognizes spinner words, collapsed tool overflow lines, or footer chrome as a family.');
15131
16527
  lines.push("");
15132
16528
  lines.push("### 5. Verify the side effects outside the CLI");
15133
16529
  lines.push("```bash");
@@ -15152,6 +16548,8 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
15152
16548
  lines.push("7. Re-run the debug endpoints after edits. Do NOT finish until the parsed result looks correct.");
15153
16549
  lines.push("8. Confirm the parser still works after a redraw or scroll change without duplicating transcript history.");
15154
16550
  lines.push("9. Confirm the implementation prefers current-screen signals over stale history when both are present.");
16551
+ lines.push("10. For transcript-cleanliness bugs, confirm the latest `commit_transcript` trace frame no longer contains tool headers, approval prompts, OSC residue like `0;`, or footer chrome unless they are truly user-facing answer content.");
16552
+ lines.push("11. Confirm the implementation uses generalized pattern classifiers or regexes for noisy UI families instead of accumulating one-off literal string exceptions for each observed sample.");
15155
16553
  lines.push("");
15156
16554
  if (userComment) {
15157
16555
  lines.push("## \u26A0\uFE0F User Instructions (HIGH PRIORITY)");
@@ -15160,10 +16558,11 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
15160
16558
  lines.push(userComment);
15161
16559
  lines.push("");
15162
16560
  }
15163
- lines.push("Start NOW. Launch the CLI, inspect PTY state, edit the scripts, and verify via the CLI debug endpoints.");
16561
+ lines.push("Start NOW. Launch the CLI, inspect the trace and PTY state, edit the scripts, and verify via the CLI debug + trace endpoints.");
15164
16562
  return lines.join("\n");
15165
16563
  }
15166
16564
  function handleAutoImplSSE(ctx, type, req, res) {
16565
+ clearStaleAutoImplState(ctx, "SSE connection opened");
15167
16566
  res.writeHead(200, {
15168
16567
  "Content-Type": "text/event-stream",
15169
16568
  "Cache-Control": "no-cache",
@@ -15185,6 +16584,7 @@ data: ${JSON.stringify(p.data)}
15185
16584
  });
15186
16585
  }
15187
16586
  function handleAutoImplCancel(ctx, _type, _req, res) {
16587
+ clearStaleAutoImplState(ctx, "cancel request");
15188
16588
  if (ctx.autoImplProcess) {
15189
16589
  ctx.autoImplProcess.kill("SIGTERM");
15190
16590
  setTimeout(() => {
@@ -15268,11 +16668,16 @@ var DevServer = class _DevServer {
15268
16668
  { method: "GET", pattern: "/api/cli/status", handler: (q, s) => this.handleCliStatus(q, s) },
15269
16669
  { method: "POST", pattern: "/api/cli/launch", handler: (q, s) => this.handleCliLaunch(q, s) },
15270
16670
  { method: "POST", pattern: "/api/cli/send", handler: (q, s) => this.handleCliSend(q, s) },
16671
+ { method: "POST", pattern: "/api/cli/exercise", handler: (q, s) => this.handleCliExercise(q, s) },
16672
+ { method: "POST", pattern: "/api/cli/fixture/capture", handler: (q, s) => this.handleCliFixtureCapture(q, s) },
16673
+ { method: "POST", pattern: "/api/cli/fixture/replay", handler: (q, s) => this.handleCliFixtureReplay(q, s) },
15271
16674
  { method: "POST", pattern: "/api/cli/resolve", handler: (q, s) => this.handleCliResolve(q, s) },
15272
16675
  { method: "POST", pattern: "/api/cli/raw", handler: (q, s) => this.handleCliRaw(q, s) },
15273
16676
  { method: "POST", pattern: "/api/cli/stop", handler: (q, s) => this.handleCliStop(q, s) },
15274
16677
  { method: "GET", pattern: "/api/cli/events", handler: (q, s) => this.handleCliSSE(q, s) },
15275
16678
  { method: "GET", pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q, s, p) => this.handleCliDebug(p[0], q, s) },
16679
+ { method: "GET", pattern: /^\/api\/cli\/trace\/([^/]+)$/, handler: (q, s, p) => this.handleCliTrace(p[0], q, s) },
16680
+ { method: "GET", pattern: /^\/api\/cli\/fixtures\/([^/]+)$/, handler: (q, s, p) => this.handleCliFixtureList(p[0], q, s) },
15276
16681
  // Dynamic routes (provider :type param)
15277
16682
  { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q, s, p) => this.handleRunScript(p[0], q, s) },
15278
16683
  { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q, s, p) => this.handleListFiles(p[0], q, s) },
@@ -15306,8 +16711,8 @@ var DevServer = class _DevServer {
15306
16711
  }
15307
16712
  getEndpointList() {
15308
16713
  return this.routes.map((r) => {
15309
- const path18 = typeof r.pattern === "string" ? r.pattern : r.pattern.source.replace(/\\\//g, "/").replace(/\(\[.*?\]\+\)/g, ":type").replace(/[\^$]/g, "");
15310
- return `${r.method.padEnd(5)} ${path18}`;
16714
+ const path19 = typeof r.pattern === "string" ? r.pattern : r.pattern.source.replace(/\\\//g, "/").replace(/\(\[.*?\]\+\)/g, ":type").replace(/[\^$]/g, "");
16715
+ return `${r.method.padEnd(5)} ${path19}`;
15311
16716
  });
15312
16717
  }
15313
16718
  async start(port = DEV_SERVER_PORT) {
@@ -15589,12 +16994,12 @@ var DevServer = class _DevServer {
15589
16994
  // ─── DevConsole SPA ───
15590
16995
  getConsoleDistDir() {
15591
16996
  const candidates = [
15592
- path17.resolve(__dirname, "../../web-devconsole/dist"),
15593
- path17.resolve(__dirname, "../../../web-devconsole/dist"),
15594
- path17.join(process.cwd(), "packages/web-devconsole/dist")
16997
+ path18.resolve(__dirname, "../../web-devconsole/dist"),
16998
+ path18.resolve(__dirname, "../../../web-devconsole/dist"),
16999
+ path18.join(process.cwd(), "packages/web-devconsole/dist")
15595
17000
  ];
15596
17001
  for (const dir of candidates) {
15597
- if (fs13.existsSync(path17.join(dir, "index.html"))) return dir;
17002
+ if (fs14.existsSync(path18.join(dir, "index.html"))) return dir;
15598
17003
  }
15599
17004
  return null;
15600
17005
  }
@@ -15604,9 +17009,9 @@ var DevServer = class _DevServer {
15604
17009
  this.json(res, 500, { error: "DevConsole not found. Run: npm run build -w packages/web-devconsole" });
15605
17010
  return;
15606
17011
  }
15607
- const htmlPath = path17.join(distDir, "index.html");
17012
+ const htmlPath = path18.join(distDir, "index.html");
15608
17013
  try {
15609
- const html = fs13.readFileSync(htmlPath, "utf-8");
17014
+ const html = fs14.readFileSync(htmlPath, "utf-8");
15610
17015
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
15611
17016
  res.end(html);
15612
17017
  } catch (e) {
@@ -15629,15 +17034,15 @@ var DevServer = class _DevServer {
15629
17034
  this.json(res, 404, { error: "Not found" });
15630
17035
  return;
15631
17036
  }
15632
- const safePath = path17.normalize(pathname).replace(/^\.\.\//, "");
15633
- const filePath = path17.join(distDir, safePath);
17037
+ const safePath = path18.normalize(pathname).replace(/^\.\.\//, "");
17038
+ const filePath = path18.join(distDir, safePath);
15634
17039
  if (!filePath.startsWith(distDir)) {
15635
17040
  this.json(res, 403, { error: "Forbidden" });
15636
17041
  return;
15637
17042
  }
15638
17043
  try {
15639
- const content = fs13.readFileSync(filePath);
15640
- const ext = path17.extname(filePath);
17044
+ const content = fs14.readFileSync(filePath);
17045
+ const ext = path18.extname(filePath);
15641
17046
  const contentType = _DevServer.MIME_MAP[ext] || "application/octet-stream";
15642
17047
  res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "public, max-age=31536000, immutable" });
15643
17048
  res.end(content);
@@ -15745,14 +17150,14 @@ var DevServer = class _DevServer {
15745
17150
  const files = [];
15746
17151
  const scan = (d, prefix) => {
15747
17152
  try {
15748
- for (const entry of fs13.readdirSync(d, { withFileTypes: true })) {
17153
+ for (const entry of fs14.readdirSync(d, { withFileTypes: true })) {
15749
17154
  if (entry.name.startsWith(".") || entry.name.endsWith(".bak")) continue;
15750
17155
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
15751
17156
  if (entry.isDirectory()) {
15752
17157
  files.push({ path: rel, size: 0, type: "dir" });
15753
- scan(path17.join(d, entry.name), rel);
17158
+ scan(path18.join(d, entry.name), rel);
15754
17159
  } else {
15755
- const stat = fs13.statSync(path17.join(d, entry.name));
17160
+ const stat = fs14.statSync(path18.join(d, entry.name));
15756
17161
  files.push({ path: rel, size: stat.size, type: "file" });
15757
17162
  }
15758
17163
  }
@@ -15775,16 +17180,16 @@ var DevServer = class _DevServer {
15775
17180
  this.json(res, 404, { error: `Provider directory not found: ${type}` });
15776
17181
  return;
15777
17182
  }
15778
- const fullPath = path17.resolve(dir, path17.normalize(filePath));
17183
+ const fullPath = path18.resolve(dir, path18.normalize(filePath));
15779
17184
  if (!fullPath.startsWith(dir)) {
15780
17185
  this.json(res, 403, { error: "Forbidden" });
15781
17186
  return;
15782
17187
  }
15783
- if (!fs13.existsSync(fullPath) || fs13.statSync(fullPath).isDirectory()) {
17188
+ if (!fs14.existsSync(fullPath) || fs14.statSync(fullPath).isDirectory()) {
15784
17189
  this.json(res, 404, { error: `File not found: ${filePath}` });
15785
17190
  return;
15786
17191
  }
15787
- const content = fs13.readFileSync(fullPath, "utf-8");
17192
+ const content = fs14.readFileSync(fullPath, "utf-8");
15788
17193
  this.json(res, 200, { type, path: filePath, content, lines: content.split("\n").length });
15789
17194
  }
15790
17195
  /** POST /api/providers/:type/file — write a file { path, content } */
@@ -15800,15 +17205,15 @@ var DevServer = class _DevServer {
15800
17205
  this.json(res, 404, { error: `Provider directory not found: ${type}` });
15801
17206
  return;
15802
17207
  }
15803
- const fullPath = path17.resolve(dir, path17.normalize(filePath));
17208
+ const fullPath = path18.resolve(dir, path18.normalize(filePath));
15804
17209
  if (!fullPath.startsWith(dir)) {
15805
17210
  this.json(res, 403, { error: "Forbidden" });
15806
17211
  return;
15807
17212
  }
15808
17213
  try {
15809
- if (fs13.existsSync(fullPath)) fs13.copyFileSync(fullPath, fullPath + ".bak");
15810
- fs13.mkdirSync(path17.dirname(fullPath), { recursive: true });
15811
- fs13.writeFileSync(fullPath, content, "utf-8");
17214
+ if (fs14.existsSync(fullPath)) fs14.copyFileSync(fullPath, fullPath + ".bak");
17215
+ fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
17216
+ fs14.writeFileSync(fullPath, content, "utf-8");
15812
17217
  this.log(`File saved: ${fullPath} (${content.length} chars)`);
15813
17218
  this.providerLoader.reload();
15814
17219
  this.json(res, 200, { saved: true, path: filePath, chars: content.length });
@@ -15824,9 +17229,9 @@ var DevServer = class _DevServer {
15824
17229
  return;
15825
17230
  }
15826
17231
  for (const name of ["scripts.js", "provider.json"]) {
15827
- const p = path17.join(dir, name);
15828
- if (fs13.existsSync(p)) {
15829
- const source = fs13.readFileSync(p, "utf-8");
17232
+ const p = path18.join(dir, name);
17233
+ if (fs14.existsSync(p)) {
17234
+ const source = fs14.readFileSync(p, "utf-8");
15830
17235
  this.json(res, 200, { type, path: p, source, lines: source.split("\n").length });
15831
17236
  return;
15832
17237
  }
@@ -15845,11 +17250,11 @@ var DevServer = class _DevServer {
15845
17250
  this.json(res, 404, { error: `Provider not found: ${type}` });
15846
17251
  return;
15847
17252
  }
15848
- const target = fs13.existsSync(path17.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
15849
- const targetPath = path17.join(dir, target);
17253
+ const target = fs14.existsSync(path18.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
17254
+ const targetPath = path18.join(dir, target);
15850
17255
  try {
15851
- if (fs13.existsSync(targetPath)) fs13.copyFileSync(targetPath, targetPath + ".bak");
15852
- fs13.writeFileSync(targetPath, source, "utf-8");
17256
+ if (fs14.existsSync(targetPath)) fs14.copyFileSync(targetPath, targetPath + ".bak");
17257
+ fs14.writeFileSync(targetPath, source, "utf-8");
15853
17258
  this.log(`Saved provider: ${targetPath} (${source.length} chars)`);
15854
17259
  this.providerLoader.reload();
15855
17260
  this.json(res, 200, { saved: true, path: targetPath, chars: source.length });
@@ -16006,21 +17411,21 @@ var DevServer = class _DevServer {
16006
17411
  }
16007
17412
  let targetDir;
16008
17413
  targetDir = this.providerLoader.getUserProviderDir(category, type);
16009
- const jsonPath = path17.join(targetDir, "provider.json");
16010
- if (fs13.existsSync(jsonPath)) {
17414
+ const jsonPath = path18.join(targetDir, "provider.json");
17415
+ if (fs14.existsSync(jsonPath)) {
16011
17416
  this.json(res, 409, { error: `Provider already exists at ${targetDir}`, path: targetDir });
16012
17417
  return;
16013
17418
  }
16014
17419
  try {
16015
17420
  const result = generateFiles(type, name, category, { cdpPorts, cli, processName, installPath, binary, extensionId, version, osPaths, processNames });
16016
- fs13.mkdirSync(targetDir, { recursive: true });
16017
- fs13.writeFileSync(jsonPath, result["provider.json"], "utf-8");
17421
+ fs14.mkdirSync(targetDir, { recursive: true });
17422
+ fs14.writeFileSync(jsonPath, result["provider.json"], "utf-8");
16018
17423
  const createdFiles = ["provider.json"];
16019
17424
  if (result.files) {
16020
17425
  for (const [relPath, content] of Object.entries(result.files)) {
16021
- const fullPath = path17.join(targetDir, relPath);
16022
- fs13.mkdirSync(path17.dirname(fullPath), { recursive: true });
16023
- fs13.writeFileSync(fullPath, content, "utf-8");
17426
+ const fullPath = path18.join(targetDir, relPath);
17427
+ fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
17428
+ fs14.writeFileSync(fullPath, content, "utf-8");
16024
17429
  createdFiles.push(relPath);
16025
17430
  }
16026
17431
  }
@@ -16069,45 +17474,45 @@ var DevServer = class _DevServer {
16069
17474
  }
16070
17475
  // ─── Phase 2: Auto-Implement Backend ───
16071
17476
  getLatestScriptVersionDir(scriptsDir) {
16072
- if (!fs13.existsSync(scriptsDir)) return null;
16073
- const versions = fs13.readdirSync(scriptsDir).filter((d) => {
17477
+ if (!fs14.existsSync(scriptsDir)) return null;
17478
+ const versions = fs14.readdirSync(scriptsDir).filter((d) => {
16074
17479
  try {
16075
- return fs13.statSync(path17.join(scriptsDir, d)).isDirectory();
17480
+ return fs14.statSync(path18.join(scriptsDir, d)).isDirectory();
16076
17481
  } catch {
16077
17482
  return false;
16078
17483
  }
16079
17484
  }).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
16080
17485
  if (versions.length === 0) return null;
16081
- return path17.join(scriptsDir, versions[0]);
17486
+ return path18.join(scriptsDir, versions[0]);
16082
17487
  }
16083
17488
  resolveAutoImplWritableProviderDir(category, type, requestedDir) {
16084
- const canonicalUserDir = path17.resolve(this.providerLoader.getUserProviderDir(category, type));
16085
- const desiredDir = requestedDir ? path17.resolve(requestedDir) : canonicalUserDir;
16086
- const upstreamRoot = path17.resolve(this.providerLoader.getUpstreamDir());
16087
- if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path17.sep}`)) {
17489
+ const canonicalUserDir = path18.resolve(this.providerLoader.getUserProviderDir(category, type));
17490
+ const desiredDir = requestedDir ? path18.resolve(requestedDir) : canonicalUserDir;
17491
+ const upstreamRoot = path18.resolve(this.providerLoader.getUpstreamDir());
17492
+ if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path18.sep}`)) {
16088
17493
  return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
16089
17494
  }
16090
- if (path17.basename(desiredDir) !== type) {
17495
+ if (path18.basename(desiredDir) !== type) {
16091
17496
  return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
16092
17497
  }
16093
17498
  const sourceDir = this.findProviderDir(type);
16094
17499
  if (!sourceDir) {
16095
17500
  return { dir: null, reason: `Provider source directory not found for '${type}'` };
16096
17501
  }
16097
- if (!fs13.existsSync(desiredDir)) {
16098
- fs13.mkdirSync(path17.dirname(desiredDir), { recursive: true });
16099
- fs13.cpSync(sourceDir, desiredDir, { recursive: true });
17502
+ if (!fs14.existsSync(desiredDir)) {
17503
+ fs14.mkdirSync(path18.dirname(desiredDir), { recursive: true });
17504
+ fs14.cpSync(sourceDir, desiredDir, { recursive: true });
16100
17505
  this.log(`Auto-implement writable copy created: ${desiredDir}`);
16101
17506
  }
16102
- const providerJson = path17.join(desiredDir, "provider.json");
16103
- if (!fs13.existsSync(providerJson)) {
17507
+ const providerJson = path18.join(desiredDir, "provider.json");
17508
+ if (!fs14.existsSync(providerJson)) {
16104
17509
  return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
16105
17510
  }
16106
17511
  try {
16107
- const providerData = JSON.parse(fs13.readFileSync(providerJson, "utf-8"));
17512
+ const providerData = JSON.parse(fs14.readFileSync(providerJson, "utf-8"));
16108
17513
  if (providerData.disableUpstream !== true) {
16109
17514
  providerData.disableUpstream = true;
16110
- fs13.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
17515
+ fs14.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
16111
17516
  }
16112
17517
  } catch (error) {
16113
17518
  return {
@@ -16147,7 +17552,7 @@ var DevServer = class _DevServer {
16147
17552
  setMode: "set_mode.js"
16148
17553
  };
16149
17554
  const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
16150
- const scriptsDir = path17.join(providerDir, "scripts");
17555
+ const scriptsDir = path18.join(providerDir, "scripts");
16151
17556
  const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
16152
17557
  if (latestScriptsDir) {
16153
17558
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -16155,10 +17560,10 @@ var DevServer = class _DevServer {
16155
17560
  lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
16156
17561
  lines.push("These are the ONLY files you are allowed to modify. Replace the TODO stubs with working implementations.");
16157
17562
  lines.push("");
16158
- for (const file of fs13.readdirSync(latestScriptsDir)) {
17563
+ for (const file of fs14.readdirSync(latestScriptsDir)) {
16159
17564
  if (file.endsWith(".js") && targetFileNames.has(file)) {
16160
17565
  try {
16161
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
17566
+ const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
16162
17567
  lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
16163
17568
  lines.push("```javascript");
16164
17569
  lines.push(content);
@@ -16168,14 +17573,14 @@ var DevServer = class _DevServer {
16168
17573
  }
16169
17574
  }
16170
17575
  }
16171
- const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
17576
+ const refFiles = fs14.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
16172
17577
  if (refFiles.length > 0) {
16173
17578
  lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
16174
17579
  lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
16175
17580
  lines.push("");
16176
17581
  for (const file of refFiles) {
16177
17582
  try {
16178
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
17583
+ const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
16179
17584
  lines.push(`### \`${file}\` \u{1F512}`);
16180
17585
  lines.push("```javascript");
16181
17586
  lines.push(content);
@@ -16216,11 +17621,11 @@ var DevServer = class _DevServer {
16216
17621
  lines.push("");
16217
17622
  }
16218
17623
  }
16219
- const docsDir = path17.join(providerDir, "../../docs");
17624
+ const docsDir = path18.join(providerDir, "../../docs");
16220
17625
  const loadGuide = (name) => {
16221
17626
  try {
16222
- const p = path17.join(docsDir, name);
16223
- if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
17627
+ const p = path18.join(docsDir, name);
17628
+ if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
16224
17629
  } catch {
16225
17630
  }
16226
17631
  return null;
@@ -16393,7 +17798,7 @@ var DevServer = class _DevServer {
16393
17798
  parseApproval: "parse_approval.js"
16394
17799
  };
16395
17800
  const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
16396
- const scriptsDir = path17.join(providerDir, "scripts");
17801
+ const scriptsDir = path18.join(providerDir, "scripts");
16397
17802
  const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
16398
17803
  if (latestScriptsDir) {
16399
17804
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -16401,11 +17806,11 @@ var DevServer = class _DevServer {
16401
17806
  lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
16402
17807
  lines.push("These are the ONLY files you are allowed to modify. Replace TODO or heuristic-only logic with working PTY-aware implementations.");
16403
17808
  lines.push("");
16404
- for (const file of fs13.readdirSync(latestScriptsDir)) {
17809
+ for (const file of fs14.readdirSync(latestScriptsDir)) {
16405
17810
  if (!file.endsWith(".js")) continue;
16406
17811
  if (!targetFileNames.has(file)) continue;
16407
17812
  try {
16408
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
17813
+ const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
16409
17814
  lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
16410
17815
  lines.push("```javascript");
16411
17816
  lines.push(content);
@@ -16414,14 +17819,14 @@ var DevServer = class _DevServer {
16414
17819
  } catch {
16415
17820
  }
16416
17821
  }
16417
- const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
17822
+ const refFiles = fs14.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
16418
17823
  if (refFiles.length > 0) {
16419
17824
  lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
16420
17825
  lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
16421
17826
  lines.push("");
16422
17827
  for (const file of refFiles) {
16423
17828
  try {
16424
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
17829
+ const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
16425
17830
  lines.push(`### \`${file}\` \u{1F512}`);
16426
17831
  lines.push("```javascript");
16427
17832
  lines.push(content);
@@ -16454,11 +17859,11 @@ var DevServer = class _DevServer {
16454
17859
  lines.push("");
16455
17860
  }
16456
17861
  }
16457
- const docsDir = path17.join(providerDir, "../../docs");
17862
+ const docsDir = path18.join(providerDir, "../../docs");
16458
17863
  const loadGuide = (name) => {
16459
17864
  try {
16460
- const p = path17.join(docsDir, name);
16461
- if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
17865
+ const p = path18.join(docsDir, name);
17866
+ if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
16462
17867
  } catch {
16463
17868
  }
16464
17869
  return null;
@@ -16523,6 +17928,7 @@ var DevServer = class _DevServer {
16523
17928
  lines.push("### 2. Inspect parsed + raw adapter state");
16524
17929
  lines.push("```bash");
16525
17930
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
17931
+ lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
16526
17932
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
16527
17933
  lines.push("```");
16528
17934
  lines.push("");
@@ -16657,6 +18063,16 @@ data: ${JSON.stringify(msg.data)}
16657
18063
  async handleCliSend(req, res) {
16658
18064
  return handleCliSend(this, req, res);
16659
18065
  }
18066
+ /** POST /api/cli/exercise — launch/send/approve/wait helper for provider-fix loops */
18067
+ async handleCliExercise(req, res) {
18068
+ return handleCliExercise(this, req, res);
18069
+ }
18070
+ async handleCliFixtureCapture(req, res) {
18071
+ return handleCliFixtureCapture(this, req, res);
18072
+ }
18073
+ async handleCliFixtureReplay(req, res) {
18074
+ return handleCliFixtureReplay(this, req, res);
18075
+ }
16660
18076
  /** POST /api/cli/stop — stop a running CLI { type } */
16661
18077
  async handleCliStop(req, res) {
16662
18078
  return handleCliStop(this, req, res);
@@ -16680,6 +18096,13 @@ data: ${JSON.stringify(msg.data)}
16680
18096
  async handleCliDebug(type, _req, res) {
16681
18097
  return handleCliDebug(this, type, _req, res);
16682
18098
  }
18099
+ /** GET /api/cli/trace/:type — recent CLI trace timeline plus current debug snapshot */
18100
+ async handleCliTrace(type, _req, res) {
18101
+ return handleCliTrace(this, type, _req, res);
18102
+ }
18103
+ async handleCliFixtureList(type, _req, res) {
18104
+ return handleCliFixtureList(this, type, _req, res);
18105
+ }
16683
18106
  /** POST /api/cli/resolve — resolve an approval modal { type, buttonIndex } */
16684
18107
  async handleCliResolve(req, res) {
16685
18108
  return handleCliResolve(this, req, res);
@@ -16852,7 +18275,18 @@ var SessionHostRuntimeTransport = class {
16852
18275
  });
16853
18276
  }
16854
18277
  async boot() {
16855
- await this.client.connect();
18278
+ if (typeof this.options.ensureReady === "function") {
18279
+ await this.options.ensureReady();
18280
+ }
18281
+ try {
18282
+ await this.client.connect();
18283
+ } catch (error) {
18284
+ if (typeof this.options.ensureReady !== "function") {
18285
+ throw error;
18286
+ }
18287
+ await this.options.ensureReady();
18288
+ await this.client.connect();
18289
+ }
16856
18290
  this.unsubscribe = this.client.onEvent((event) => this.handleEvent(event));
16857
18291
  let record = null;
16858
18292
  if (this.options.attachExisting) {
@@ -17234,8 +18668,8 @@ async function installExtension(ide, extension) {
17234
18668
  const res = await fetch(extension.vsixUrl);
17235
18669
  if (res.ok) {
17236
18670
  const buffer = Buffer.from(await res.arrayBuffer());
17237
- const fs14 = await import("fs");
17238
- fs14.writeFileSync(vsixPath, buffer);
18671
+ const fs15 = await import("fs");
18672
+ fs15.writeFileSync(vsixPath, buffer);
17239
18673
  return new Promise((resolve10) => {
17240
18674
  const cmd = `"${ide.cliCommand}" --install-extension "${vsixPath}" --force`;
17241
18675
  exec2(cmd, { timeout: 6e4 }, (error, _stdout, stderr) => {