@adhdev/daemon-core 0.7.46 → 0.8.1

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