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