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