@adhdev/daemon-standalone 0.7.46 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -27570,7 +27570,7 @@ var require_dist = __commonJS({
27570
27570
  };
27571
27571
  }
27572
27572
  };
27573
- var os5 = __toESM2(require("os"));
27573
+ var os6 = __toESM2(require("os"));
27574
27574
  var path22 = __toESM2(require("path"));
27575
27575
  var net3 = __toESM2(require("net"));
27576
27576
  var import_crypto22 = require("crypto");
@@ -27583,7 +27583,7 @@ var require_dist = __commonJS({
27583
27583
  }
27584
27584
  return {
27585
27585
  kind: "unix",
27586
- path: path22.join(os5.tmpdir(), `${appName}-session-host.sock`)
27586
+ path: path22.join(os6.tmpdir(), `${appName}-session-host.sock`)
27587
27587
  };
27588
27588
  }
27589
27589
  function serializeEnvelope3(envelope) {
@@ -28488,10 +28488,10 @@ var require_dist2 = __commonJS({
28488
28488
  normalizeCliProviderForRuntime: () => normalizeCliProviderForRuntime
28489
28489
  });
28490
28490
  function stripAnsi(str) {
28491
- 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, " ");
28491
+ 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, " ");
28492
28492
  }
28493
28493
  function stripTerminalNoise(str) {
28494
- 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, " ");
28494
+ 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, " ");
28495
28495
  }
28496
28496
  function sanitizeTerminalText(str) {
28497
28497
  return stripTerminalNoise(stripAnsi(str));
@@ -28534,12 +28534,12 @@ var require_dist2 = __commonJS({
28534
28534
  function isScriptBinary(binaryPath) {
28535
28535
  if (!path7.isAbsolute(binaryPath)) return false;
28536
28536
  try {
28537
- const fs14 = require("fs");
28538
- const resolved = fs14.realpathSync(binaryPath);
28537
+ const fs15 = require("fs");
28538
+ const resolved = fs15.realpathSync(binaryPath);
28539
28539
  const head = Buffer.alloc(8);
28540
- const fd = fs14.openSync(resolved, "r");
28541
- fs14.readSync(fd, head, 0, 8, 0);
28542
- fs14.closeSync(fd);
28540
+ const fd = fs15.openSync(resolved, "r");
28541
+ fs15.readSync(fd, head, 0, 8, 0);
28542
+ fs15.closeSync(fd);
28543
28543
  let i = 0;
28544
28544
  if (head[0] === 239 && head[1] === 187 && head[2] === 191) i = 3;
28545
28545
  return head[i] === 35 && head[i + 1] === 33;
@@ -28550,12 +28550,12 @@ var require_dist2 = __commonJS({
28550
28550
  function looksLikeMachOOrElf(filePath) {
28551
28551
  if (!path7.isAbsolute(filePath)) return false;
28552
28552
  try {
28553
- const fs14 = require("fs");
28554
- const resolved = fs14.realpathSync(filePath);
28553
+ const fs15 = require("fs");
28554
+ const resolved = fs15.realpathSync(filePath);
28555
28555
  const buf = Buffer.alloc(8);
28556
- const fd = fs14.openSync(resolved, "r");
28557
- fs14.readSync(fd, buf, 0, 8, 0);
28558
- fs14.closeSync(fd);
28556
+ const fd = fs15.openSync(resolved, "r");
28557
+ fs15.readSync(fd, buf, 0, 8, 0);
28558
+ fs15.closeSync(fd);
28559
28559
  let i = 0;
28560
28560
  if (buf[0] === 239 && buf[1] === 187 && buf[2] === 191) i = 3;
28561
28561
  const b2 = buf.subarray(i);
@@ -28608,6 +28608,9 @@ var require_dist2 = __commonJS({
28608
28608
  ).length;
28609
28609
  return matched >= required2;
28610
28610
  }
28611
+ function normalizeScreenSnapshot(text) {
28612
+ return sanitizeTerminalText(String(text || "")).replace(/\s+/g, " ").trim();
28613
+ }
28611
28614
  function parsePatternEntry(x) {
28612
28615
  if (x instanceof RegExp) return x;
28613
28616
  if (x && typeof x === "object" && typeof x.source === "string") {
@@ -28650,14 +28653,14 @@ var require_dist2 = __commonJS({
28650
28653
  pty2 = require("node-pty");
28651
28654
  if (os8.platform() !== "win32") {
28652
28655
  try {
28653
- const fs14 = require("fs");
28656
+ const fs15 = require("fs");
28654
28657
  const ptyDir = path7.resolve(path7.dirname(require.resolve("node-pty")), "..");
28655
28658
  const platformArch = `${os8.platform()}-${os8.arch()}`;
28656
28659
  const helper = path7.join(ptyDir, "prebuilds", platformArch, "spawn-helper");
28657
- if (fs14.existsSync(helper)) {
28658
- const stat4 = fs14.statSync(helper);
28660
+ if (fs15.existsSync(helper)) {
28661
+ const stat4 = fs15.statSync(helper);
28659
28662
  if (!(stat4.mode & 73)) {
28660
- fs14.chmodSync(helper, stat4.mode | 493);
28663
+ fs15.chmodSync(helper, stat4.mode | 493);
28661
28664
  LOG2.info("CLI", "[node-pty] Fixed spawn-helper permissions");
28662
28665
  }
28663
28666
  }
@@ -28691,10 +28694,25 @@ var require_dist2 = __commonJS({
28691
28694
  this.sendDelayMs = typeof provider.sendDelayMs === "number" ? Math.max(0, provider.sendDelayMs) : 0;
28692
28695
  this.sendKey = typeof provider.sendKey === "string" && provider.sendKey.length > 0 ? provider.sendKey : "\r";
28693
28696
  this.submitStrategy = provider.submitStrategy === "immediate" ? "immediate" : "wait_for_echo";
28697
+ this.providerResolutionMeta = {
28698
+ type: provider.type,
28699
+ name: provider.name,
28700
+ resolvedVersion: provider._resolvedVersion || null,
28701
+ resolvedOs: provider._resolvedOs || null,
28702
+ providerDir: provider._resolvedProviderDir || null,
28703
+ scriptDir: provider._resolvedScriptDir || null,
28704
+ scriptsPath: provider._resolvedScriptsPath || null,
28705
+ scriptsSource: provider._resolvedScriptsSource || null,
28706
+ versionWarning: provider._versionWarning || null
28707
+ };
28694
28708
  this.cliScripts = provider.scripts || {};
28695
28709
  const scriptNames = Object.keys(this.cliScripts).filter((k) => typeof this.cliScripts[k] === "function");
28696
28710
  if (scriptNames.length > 0) {
28697
28711
  LOG2.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
28712
+ LOG2.info(
28713
+ "CLI",
28714
+ `[${this.cliType}] Provider resolution: providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"} source=${this.providerResolutionMeta.scriptsSource || "-"} version=${this.providerResolutionMeta.resolvedVersion || "-"}`
28715
+ );
28698
28716
  } else {
28699
28717
  LOG2.warn("CLI", `[${this.cliType}] \u26A0 No CLI scripts loaded! Provider needs scripts/{version}/scripts.js`);
28700
28718
  }
@@ -28727,6 +28745,10 @@ var require_dist2 = __commonJS({
28727
28745
  ptyOutputBuffer = "";
28728
28746
  ptyOutputFlushTimer = null;
28729
28747
  pendingTerminalQueryTail = "";
28748
+ lastOutputAt = 0;
28749
+ lastNonEmptyOutputAt = 0;
28750
+ lastScreenChangeAt = 0;
28751
+ lastScreenSnapshot = "";
28730
28752
  // Server log forwarding
28731
28753
  serverConn = null;
28732
28754
  logBuffer = [];
@@ -28747,6 +28769,7 @@ var require_dist2 = __commonJS({
28747
28769
  submitRetryTimer = null;
28748
28770
  submitRetryUsed = false;
28749
28771
  submitRetryPromptSnippet = "";
28772
+ idleFinishCandidate = null;
28750
28773
  // Resize redraw suppression
28751
28774
  resizeSuppressUntil = 0;
28752
28775
  // Debug: status transition history
@@ -28762,6 +28785,12 @@ var require_dist2 = __commonJS({
28762
28785
  /** Max accumulated buffer size (last 50KB) */
28763
28786
  static MAX_ACCUMULATED_BUFFER = 5e4;
28764
28787
  currentTurnScope = null;
28788
+ traceEntries = [];
28789
+ traceSeq = 0;
28790
+ traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
28791
+ static MAX_TRACE_ENTRIES = 250;
28792
+ providerResolutionMeta;
28793
+ static IDLE_FINISH_CONFIRM_MS = 900;
28765
28794
  syncMessageViews() {
28766
28795
  this.messages = [...this.committedMessages];
28767
28796
  this.structuredMessages = [...this.committedMessages];
@@ -28797,8 +28826,89 @@ var require_dist2 = __commonJS({
28797
28826
  this.currentStatus = status;
28798
28827
  this.statusHistory.push({ status, at: Date.now(), trigger });
28799
28828
  if (this.statusHistory.length > 50) this.statusHistory.shift();
28829
+ this.recordTrace("status", {
28830
+ previousStatus: prev,
28831
+ trigger: trigger || null
28832
+ });
28800
28833
  LOG2.info("CLI", `[${this.cliType}] status: ${prev} \u2192 ${status}${trigger ? ` (${trigger})` : ""}`);
28801
28834
  }
28835
+ clearIdleFinishCandidate(reason) {
28836
+ if (!this.idleFinishCandidate) return;
28837
+ this.recordTrace("idle_candidate_reset", {
28838
+ reason,
28839
+ candidate: this.idleFinishCandidate
28840
+ });
28841
+ this.idleFinishCandidate = null;
28842
+ }
28843
+ armIdleFinishCandidate(assistantLength) {
28844
+ const now = Date.now();
28845
+ this.idleFinishCandidate = {
28846
+ armedAt: now,
28847
+ lastOutputAt: this.lastOutputAt,
28848
+ lastScreenChangeAt: this.lastScreenChangeAt,
28849
+ responseEpoch: this.responseEpoch,
28850
+ assistantLength
28851
+ };
28852
+ this.recordTrace("idle_candidate_armed", {
28853
+ confirmMs: _ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS,
28854
+ candidate: this.idleFinishCandidate,
28855
+ ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
28856
+ });
28857
+ if (this.settleTimer) clearTimeout(this.settleTimer);
28858
+ this.settleTimer = setTimeout(() => {
28859
+ this.settleTimer = null;
28860
+ this.settledBuffer = this.recentOutputBuffer;
28861
+ this.evaluateSettled();
28862
+ }, _ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS);
28863
+ }
28864
+ summarizeTraceText(text, max = 800) {
28865
+ const value = sanitizeTerminalText(String(text || ""));
28866
+ if (value.length <= max) return value;
28867
+ return `\u2026${value.slice(-max)}`;
28868
+ }
28869
+ summarizeTraceMessages(messages, limit = 3) {
28870
+ return messages.slice(-limit).map((message) => ({
28871
+ role: message.role,
28872
+ content: this.summarizeTraceText(message.content, 240),
28873
+ timestamp: message.timestamp
28874
+ }));
28875
+ }
28876
+ buildTraceParseSnapshot(scope, partialResponse = "") {
28877
+ const scopedBuffer = scope ? this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart) || this.accumulatedBuffer : this.accumulatedBuffer;
28878
+ const scopedRawBuffer = scope ? this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer : this.accumulatedRawBuffer;
28879
+ return {
28880
+ currentTurnScope: scope || null,
28881
+ responseBuffer: this.summarizeTraceText(this.responseBuffer, 1200),
28882
+ partialResponse: this.summarizeTraceText(partialResponse || this.responseBuffer, 1200),
28883
+ turnBuffer: this.summarizeTraceText(scopedBuffer, 1600),
28884
+ turnRawPreview: this.summarizeTraceText(scopedRawBuffer, 1600),
28885
+ turnSanitizedRawPreview: this.summarizeTraceText(sanitizeTerminalText(scopedRawBuffer), 1600)
28886
+ };
28887
+ }
28888
+ recordTrace(type, payload = {}) {
28889
+ const entry = {
28890
+ id: ++this.traceSeq,
28891
+ at: Date.now(),
28892
+ type,
28893
+ status: this.currentStatus,
28894
+ isWaitingForResponse: this.isWaitingForResponse,
28895
+ activeModal: this.activeModal ? { message: this.activeModal.message, buttons: [...this.activeModal.buttons] } : null,
28896
+ payload
28897
+ };
28898
+ this.traceEntries.push(entry);
28899
+ if (this.traceEntries.length > _ProviderCliAdapter.MAX_TRACE_ENTRIES) {
28900
+ this.traceEntries.splice(0, this.traceEntries.length - _ProviderCliAdapter.MAX_TRACE_ENTRIES);
28901
+ }
28902
+ }
28903
+ resetTraceSession() {
28904
+ this.traceEntries = [];
28905
+ this.traceSeq = 0;
28906
+ this.traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
28907
+ this.recordTrace("session_start", {
28908
+ providerType: this.cliType,
28909
+ workingDir: this.workingDir
28910
+ });
28911
+ }
28802
28912
  // Resolved timeouts
28803
28913
  timeouts;
28804
28914
  // Provider approval key mapping
@@ -28844,6 +28954,7 @@ var require_dist2 = __commonJS({
28844
28954
  const isWin = os8.platform() === "win32";
28845
28955
  const allArgs = [...spawnConfig.args, ...this.extraArgs];
28846
28956
  LOG2.info("CLI", `[${this.cliType}] Spawning in ${this.workingDir}`);
28957
+ this.resetTraceSession();
28847
28958
  let shellCmd;
28848
28959
  let shellArgs;
28849
28960
  const useShellUnix = !isWin && (!!spawnConfig.shell || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath) || !looksLikeMachOOrElf(binaryPath));
@@ -28869,6 +28980,14 @@ var require_dist2 = __commonJS({
28869
28980
  cwd: this.workingDir,
28870
28981
  env: buildCliSpawnEnv(process.env, spawnConfig.env)
28871
28982
  };
28983
+ this.recordTrace("spawn", {
28984
+ shellCommand: shellCmd,
28985
+ shellArgs,
28986
+ cwd: ptyOpts.cwd,
28987
+ cols: ptyOpts.cols,
28988
+ rows: ptyOpts.rows,
28989
+ providerResolution: this.providerResolutionMeta
28990
+ });
28872
28991
  try {
28873
28992
  this.ptyProcess = this.transportFactory.spawn(shellCmd, shellArgs, ptyOpts);
28874
28993
  } catch (err) {
@@ -28911,6 +29030,7 @@ var require_dist2 = __commonJS({
28911
29030
  this.ptyProcess.onExit(({ exitCode }) => {
28912
29031
  LOG2.info("CLI", `[${this.cliType}] Exit code ${exitCode}`);
28913
29032
  this.flushPendingOutputParse();
29033
+ this.recordTrace("exit", { exitCode });
28914
29034
  this.ptyProcess = null;
28915
29035
  this.setStatus("stopped", "pty_exit");
28916
29036
  this.ready = false;
@@ -28926,6 +29046,9 @@ var require_dist2 = __commonJS({
28926
29046
  this.currentTurnScope = null;
28927
29047
  this.ready = false;
28928
29048
  await this.ptyProcess.ready;
29049
+ this.recordTrace("ready", {
29050
+ runtimeMeta: this.getRuntimeMetadata()
29051
+ });
28929
29052
  this.setStatus("idle", "pty_ready");
28930
29053
  this.onStatusChange?.();
28931
29054
  }
@@ -28933,6 +29056,24 @@ var require_dist2 = __commonJS({
28933
29056
  handleOutput(rawData) {
28934
29057
  this.terminalScreen.write(rawData);
28935
29058
  const cleanData = sanitizeTerminalText(rawData);
29059
+ const now = Date.now();
29060
+ const normalizedScreenSnapshot = normalizeScreenSnapshot(this.terminalScreen.getText());
29061
+ this.lastOutputAt = now;
29062
+ if (cleanData.trim()) this.lastNonEmptyOutputAt = now;
29063
+ if (normalizedScreenSnapshot !== this.lastScreenSnapshot) {
29064
+ this.lastScreenSnapshot = normalizedScreenSnapshot;
29065
+ this.lastScreenChangeAt = now;
29066
+ }
29067
+ if (this.idleFinishCandidate && (rawData.length > 0 || cleanData.length > 0)) {
29068
+ this.clearIdleFinishCandidate("new_output");
29069
+ }
29070
+ this.recordTrace("output", {
29071
+ rawLength: rawData.length,
29072
+ cleanLength: cleanData.length,
29073
+ rawPreview: this.summarizeTraceText(rawData, 300),
29074
+ cleanPreview: this.summarizeTraceText(cleanData, 300),
29075
+ screenText: this.summarizeTraceText(this.terminalScreen.getText(), 1200)
29076
+ });
28936
29077
  if (this.isWaitingForResponse && cleanData) {
28937
29078
  this.responseBuffer = (this.responseBuffer + cleanData).slice(-8e3);
28938
29079
  }
@@ -28950,11 +29091,17 @@ var require_dist2 = __commonJS({
28950
29091
  this.startupBuffer += cleanData;
28951
29092
  const elapsed = Date.now() - this.spawnAt;
28952
29093
  const scriptStatus = this.runDetectStatus(this.startupBuffer);
28953
- const isReady = scriptStatus === "idle" || scriptStatus === "waiting_approval" || elapsed > 8e3 || this.startupBuffer.length > 12e3;
29094
+ const screenText = this.terminalScreen.getText() || "";
29095
+ const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
29096
+ const startupStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
29097
+ const isReady = (scriptStatus === "idle" || scriptStatus === "waiting_approval") && hasInteractivePrompt && startupStableMs >= 700 || elapsed > 8e3 || this.startupBuffer.length > 12e3;
28954
29098
  if (isReady) {
28955
29099
  this.startupParseGate = false;
28956
29100
  this.ready = true;
28957
- LOG2.info("CLI", `[${this.cliType}] Startup ready (${elapsed}ms, scriptStatus=${scriptStatus})`);
29101
+ LOG2.info(
29102
+ "CLI",
29103
+ `[${this.cliType}] Startup ready (${elapsed}ms, scriptStatus=${scriptStatus}, prompt=${hasInteractivePrompt}, stableMs=${startupStableMs}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
29104
+ );
28958
29105
  this.onStatusChange?.();
28959
29106
  }
28960
29107
  }
@@ -28999,6 +29146,41 @@ var require_dist2 = __commonJS({
28999
29146
  if (!text.trim()) return false;
29000
29147
  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);
29001
29148
  }
29149
+ async waitForInteractivePrompt(maxWaitMs = 5e3) {
29150
+ const startedAt = Date.now();
29151
+ let loggedWait = false;
29152
+ while (Date.now() - startedAt < maxWaitMs) {
29153
+ const screenText = this.terminalScreen.getText() || "";
29154
+ const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
29155
+ const stableMs = this.lastScreenChangeAt ? Date.now() - this.lastScreenChangeAt : 0;
29156
+ const recentlyOutput = this.lastNonEmptyOutputAt ? Date.now() - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
29157
+ const status = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
29158
+ const startupLikelyActive = /Welcome back|Tips for getting|Recent activity|Claude Code v\d/i.test(screenText);
29159
+ const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "starting" && status !== "generating";
29160
+ if (interactiveReady) {
29161
+ if (loggedWait) {
29162
+ LOG2.info(
29163
+ "CLI",
29164
+ `[${this.cliType}] Interactive prompt ready after ${Date.now() - startedAt}ms (stableMs=${stableMs}, recentOutputMs=${recentlyOutput}, startup=${startupLikelyActive})`
29165
+ );
29166
+ }
29167
+ return;
29168
+ }
29169
+ if (!loggedWait && Date.now() - startedAt >= 400) {
29170
+ loggedWait = true;
29171
+ LOG2.info(
29172
+ "CLI",
29173
+ `[${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)}`
29174
+ );
29175
+ }
29176
+ await new Promise((resolve10) => setTimeout(resolve10, 50));
29177
+ }
29178
+ const finalScreenText = this.terminalScreen.getText() || "";
29179
+ LOG2.warn(
29180
+ "CLI",
29181
+ `[${this.cliType}] Interactive prompt wait timed out after ${maxWaitMs}ms; proceeding with screen=${JSON.stringify(this.summarizeTraceText(finalScreenText, 240)).slice(0, 280)}`
29182
+ );
29183
+ }
29002
29184
  evaluateSettled() {
29003
29185
  const now = Date.now();
29004
29186
  if (this.submitPendingUntil > now || this.responseSettleIgnoreUntil > now) {
@@ -29016,6 +29198,30 @@ var require_dist2 = __commonJS({
29016
29198
  const modal = this.runParseApproval(tail);
29017
29199
  const rawScriptStatus = this.runDetectStatus(tail);
29018
29200
  const scriptStatus = rawScriptStatus;
29201
+ const parsedTranscript = this.parseCurrentTranscript(
29202
+ this.committedMessages,
29203
+ this.responseBuffer,
29204
+ this.currentTurnScope
29205
+ );
29206
+ const parsedMessages = Array.isArray(parsedTranscript?.messages) ? this.normalizeParsedMessages(parsedTranscript.messages) : [];
29207
+ const lastParsedAssistant = [...parsedMessages].reverse().find((message) => message.role === "assistant");
29208
+ this.recordTrace("settled", {
29209
+ tail: this.summarizeTraceText(tail, 500),
29210
+ screenText: this.summarizeTraceText(screenText, 1200),
29211
+ detectStatus: scriptStatus,
29212
+ parsedStatus: parsedTranscript?.status || null,
29213
+ parsedMessageCount: parsedMessages.length,
29214
+ parsedLastAssistant: lastParsedAssistant ? this.summarizeTraceText(lastParsedAssistant.content, 280) : "",
29215
+ parsedActiveModal: parsedTranscript?.activeModal ?? null,
29216
+ approval: modal,
29217
+ ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
29218
+ });
29219
+ if (this.currentTurnScope && !lastParsedAssistant) {
29220
+ LOG2.info(
29221
+ "CLI",
29222
+ `[${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 || "-"}`
29223
+ );
29224
+ }
29019
29225
  if (!scriptStatus) return;
29020
29226
  const prevStatus = this.currentStatus;
29021
29227
  const clearPendingScriptStatus = () => {
@@ -29051,6 +29257,7 @@ var require_dist2 = __commonJS({
29051
29257
  clearPendingScriptStatus();
29052
29258
  }
29053
29259
  if (scriptStatus === "waiting_approval") {
29260
+ this.clearIdleFinishCandidate("waiting_approval");
29054
29261
  const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
29055
29262
  const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
29056
29263
  if ((inCooldown || visibleIdlePrompt) && !modal) {
@@ -29084,6 +29291,7 @@ var require_dist2 = __commonJS({
29084
29291
  }
29085
29292
  }
29086
29293
  if (scriptStatus === "generating") {
29294
+ this.clearIdleFinishCandidate("generating");
29087
29295
  const effectiveScreenText = screenText || this.accumulatedBuffer;
29088
29296
  const noActiveTurn = !this.currentTurnScope;
29089
29297
  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));
@@ -29120,13 +29328,58 @@ var require_dist2 = __commonJS({
29120
29328
  this.lastApprovalResolvedAt = Date.now();
29121
29329
  }
29122
29330
  if (this.isWaitingForResponse) {
29331
+ const visibleIdlePrompt = this.looksLikeVisibleIdlePrompt(screenText);
29332
+ const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
29333
+ const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
29334
+ const hasAssistantTurn = !!lastParsedAssistant;
29335
+ const assistantLength = lastParsedAssistant?.content?.length || 0;
29336
+ const idleQuietThresholdMs = Math.max(220, this.timeouts.outputSettle);
29337
+ const idleStableThresholdMs = Math.max(120, Math.min(220, this.timeouts.outputSettle));
29338
+ const idleReady = visibleIdlePrompt && !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleStableThresholdMs;
29339
+ const candidate = this.idleFinishCandidate;
29340
+ 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;
29341
+ const canFinishImmediately = idleReady && candidateQuiet;
29342
+ this.recordTrace("idle_decision", {
29343
+ visibleIdlePrompt,
29344
+ quietForMs,
29345
+ screenStableMs,
29346
+ hasAssistantTurn,
29347
+ assistantLength,
29348
+ hasModal: !!modal,
29349
+ idleQuietThresholdMs,
29350
+ idleStableThresholdMs,
29351
+ idleReady,
29352
+ idleFinishConfirmMs: _ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS,
29353
+ idleFinishCandidate: candidate,
29354
+ candidateQuiet,
29355
+ canFinishImmediately,
29356
+ submitPendingUntil: this.submitPendingUntil,
29357
+ responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
29358
+ ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
29359
+ });
29360
+ if (canFinishImmediately) {
29361
+ this.clearIdleFinishCandidate("finish_response");
29362
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
29363
+ this.finishResponse();
29364
+ return;
29365
+ }
29366
+ if (idleReady) {
29367
+ if (!candidate) {
29368
+ this.armIdleFinishCandidate(assistantLength);
29369
+ return;
29370
+ }
29371
+ } else {
29372
+ this.clearIdleFinishCandidate("idle_not_ready");
29373
+ }
29123
29374
  if (this.idleTimeout) clearTimeout(this.idleTimeout);
29124
29375
  this.idleTimeout = setTimeout(() => {
29125
29376
  if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
29377
+ this.clearIdleFinishCandidate("idle_timeout_finish");
29126
29378
  this.finishResponse();
29127
29379
  }
29128
29380
  }, this.timeouts.idleFinish);
29129
29381
  } else if (prevStatus !== "idle") {
29382
+ this.clearIdleFinishCandidate("idle_without_response");
29130
29383
  this.setStatus("idle", "script_detect");
29131
29384
  this.onStatusChange?.();
29132
29385
  }
@@ -29135,6 +29388,10 @@ var require_dist2 = __commonJS({
29135
29388
  finishResponse() {
29136
29389
  if (this.submitPendingUntil > Date.now()) return;
29137
29390
  if (this.responseSettleIgnoreUntil > Date.now()) return;
29391
+ this.clearIdleFinishCandidate("finish_response_enter");
29392
+ this.recordTrace("finish_response", {
29393
+ ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
29394
+ });
29138
29395
  this.commitCurrentTranscript();
29139
29396
  if (this.responseTimeout) {
29140
29397
  clearTimeout(this.responseTimeout);
@@ -29171,6 +29428,20 @@ var require_dist2 = __commonJS({
29171
29428
  if (parsed && Array.isArray(parsed.messages)) {
29172
29429
  this.committedMessages = this.normalizeParsedMessages(parsed.messages);
29173
29430
  this.syncMessageViews();
29431
+ const lastAssistant = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
29432
+ this.recordTrace("commit_transcript", {
29433
+ parsedStatus: parsed.status || null,
29434
+ messageCount: this.committedMessages.length,
29435
+ lastAssistant: lastAssistant ? this.summarizeTraceText(lastAssistant.content, 320) : "",
29436
+ messages: this.summarizeTraceMessages(this.committedMessages),
29437
+ ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
29438
+ });
29439
+ if (!lastAssistant && this.currentTurnScope) {
29440
+ LOG2.warn(
29441
+ "CLI",
29442
+ `[${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 || "-"}`
29443
+ );
29444
+ }
29174
29445
  }
29175
29446
  }
29176
29447
  // ─── Script Execution ──────────────────────────
@@ -29294,16 +29565,23 @@ ${data.message || ""}`.trim();
29294
29565
  }
29295
29566
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
29296
29567
  if (this.isWaitingForResponse) return;
29568
+ await this.waitForInteractivePrompt();
29297
29569
  this.committedMessages.push({ role: "user", content: text, timestamp: Date.now() });
29298
29570
  this.syncMessageViews();
29299
29571
  this.isWaitingForResponse = true;
29300
29572
  this.responseBuffer = "";
29573
+ this.clearIdleFinishCandidate("send_message");
29301
29574
  this.currentTurnScope = {
29302
29575
  prompt: text,
29303
29576
  startedAt: Date.now(),
29304
29577
  bufferStart: this.accumulatedBuffer.length,
29305
29578
  rawBufferStart: this.accumulatedRawBuffer.length
29306
29579
  };
29580
+ this.recordTrace("send_message", {
29581
+ text: this.summarizeTraceText(text, 500),
29582
+ estimatedLines: estimatePromptDisplayLines(text),
29583
+ turnScope: this.currentTurnScope
29584
+ });
29307
29585
  LOG2.info("CLI", `[${this.cliType}] sendMessage turn scope buffer=${this.currentTurnScope.bufferStart} raw=${this.currentTurnScope.rawBufferStart} prompt=${JSON.stringify(text).slice(0, 120)}`);
29308
29586
  this.submitRetryUsed = false;
29309
29587
  this.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
@@ -29333,6 +29611,11 @@ ${data.message || ""}`.trim();
29333
29611
  const submit = () => {
29334
29612
  if (!this.ptyProcess) return;
29335
29613
  this.submitPendingUntil = 0;
29614
+ this.recordTrace("submit_write", {
29615
+ mode: "submit_key",
29616
+ sendKey: this.sendKey,
29617
+ screenText: this.summarizeTraceText(this.terminalScreen.getText(), 500)
29618
+ });
29336
29619
  this.ptyProcess.write(this.sendKey);
29337
29620
  const retrySubmitIfStuck = (attempt) => {
29338
29621
  this.submitRetryTimer = null;
@@ -29344,6 +29627,12 @@ ${data.message || ""}`.trim();
29344
29627
  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;
29345
29628
  this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
29346
29629
  LOG2.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt ${attempt})`);
29630
+ this.recordTrace("submit_write", {
29631
+ mode: "submit_retry",
29632
+ attempt,
29633
+ sendKey: this.sendKey,
29634
+ screenText: this.summarizeTraceText(screenText, 500)
29635
+ });
29347
29636
  this.ptyProcess.write(this.sendKey);
29348
29637
  if (attempt >= 3) {
29349
29638
  this.submitRetryUsed = true;
@@ -29356,6 +29645,12 @@ ${data.message || ""}`.trim();
29356
29645
  };
29357
29646
  if (this.submitStrategy === "immediate") {
29358
29647
  this.submitPendingUntil = 0;
29648
+ this.recordTrace("submit_write", {
29649
+ mode: "immediate",
29650
+ text: this.summarizeTraceText(text, 500),
29651
+ sendKey: this.sendKey,
29652
+ screenText: this.summarizeTraceText(this.terminalScreen.getText(), 500)
29653
+ });
29359
29654
  this.ptyProcess.write(text + this.sendKey);
29360
29655
  this.submitRetryTimer = setTimeout(() => {
29361
29656
  this.submitRetryTimer = null;
@@ -29366,6 +29661,12 @@ ${data.message || ""}`.trim();
29366
29661
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
29367
29662
  LOG2.info("CLI", `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
29368
29663
  this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
29664
+ this.recordTrace("submit_write", {
29665
+ mode: "immediate_retry",
29666
+ attempt: 1,
29667
+ sendKey: this.sendKey,
29668
+ screenText: this.summarizeTraceText(screenText, 500)
29669
+ });
29369
29670
  this.ptyProcess.write(this.sendKey);
29370
29671
  this.submitRetryUsed = true;
29371
29672
  }, retryDelayMs);
@@ -29376,6 +29677,12 @@ ${data.message || ""}`.trim();
29376
29677
  this.submitPendingUntil = Date.now() + submitDelayMs;
29377
29678
  }
29378
29679
  this.ptyProcess.write(text);
29680
+ this.recordTrace("submit_write", {
29681
+ mode: "type_then_submit",
29682
+ text: this.summarizeTraceText(text, 500),
29683
+ sendKey: this.sendKey,
29684
+ screenText: this.summarizeTraceText(this.terminalScreen.getText(), 500)
29685
+ });
29379
29686
  const submitStartedAt = Date.now();
29380
29687
  let lastNormalizedScreen = "";
29381
29688
  let lastScreenChangeAt = submitStartedAt;
@@ -29476,6 +29783,7 @@ ${data.message || ""}`.trim();
29476
29783
  });
29477
29784
  }
29478
29785
  shutdown() {
29786
+ this.clearIdleFinishCandidate("shutdown");
29479
29787
  if (this.settleTimer) {
29480
29788
  clearTimeout(this.settleTimer);
29481
29789
  this.settleTimer = null;
@@ -29516,6 +29824,7 @@ ${data.message || ""}`.trim();
29516
29824
  }
29517
29825
  }
29518
29826
  detach() {
29827
+ this.clearIdleFinishCandidate("detach");
29519
29828
  if (this.settleTimer) {
29520
29829
  clearTimeout(this.settleTimer);
29521
29830
  this.settleTimer = null;
@@ -29556,6 +29865,7 @@ ${data.message || ""}`.trim();
29556
29865
  this.onStatusChange?.();
29557
29866
  }
29558
29867
  clearHistory() {
29868
+ this.clearIdleFinishCandidate("clear_history");
29559
29869
  this.committedMessages = [];
29560
29870
  this.syncMessageViews();
29561
29871
  this.accumulatedBuffer = "";
@@ -29585,10 +29895,19 @@ ${data.message || ""}`.trim();
29585
29895
  return this.ready;
29586
29896
  }
29587
29897
  writeRaw(data) {
29898
+ this.recordTrace("write_raw", {
29899
+ keys: JSON.stringify(data),
29900
+ length: data.length
29901
+ });
29588
29902
  this.ptyProcess?.write(data);
29589
29903
  }
29590
29904
  resolveModal(buttonIndex) {
29591
29905
  if (!this.ptyProcess || this.currentStatus !== "waiting_approval" && !this.activeModal) return;
29906
+ this.clearIdleFinishCandidate("resolve_modal");
29907
+ this.recordTrace("resolve_modal", {
29908
+ buttonIndex,
29909
+ activeModal: this.activeModal
29910
+ });
29592
29911
  this.activeModal = null;
29593
29912
  this.lastApprovalResolvedAt = Date.now();
29594
29913
  this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
@@ -29620,6 +29939,7 @@ ${data.message || ""}`.trim();
29620
29939
  return {
29621
29940
  type: this.cliType,
29622
29941
  name: this.cliName,
29942
+ providerResolution: this.providerResolutionMeta,
29623
29943
  status: this.currentStatus,
29624
29944
  ready: this.ready,
29625
29945
  startupParseGate: this.startupParseGate,
@@ -29639,6 +29959,10 @@ ${data.message || ""}`.trim();
29639
29959
  rawBufferPreview: this.accumulatedRawBuffer.slice(-1e3),
29640
29960
  sanitizedRawPreview: sanitizeTerminalText(this.accumulatedRawBuffer).slice(-1e3),
29641
29961
  responseBuffer: this.responseBuffer.slice(-1e3),
29962
+ lastOutputAt: this.lastOutputAt,
29963
+ lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
29964
+ lastScreenChangeAt: this.lastScreenChangeAt,
29965
+ lastScreenSnapshot: this.lastScreenSnapshot.slice(-500),
29642
29966
  isWaitingForResponse: this.isWaitingForResponse,
29643
29967
  activeModal: this.activeModal,
29644
29968
  lastApprovalResolvedAt: this.lastApprovalResolvedAt,
@@ -29650,6 +29974,8 @@ ${data.message || ""}`.trim();
29650
29974
  resizeSuppressUntil: this.resizeSuppressUntil,
29651
29975
  hasCliScripts: this.hasCliScripts(),
29652
29976
  scriptNames: Object.keys(this.cliScripts).filter((k) => typeof this.cliScripts[k] === "function"),
29977
+ traceSessionId: this.traceSessionId,
29978
+ traceEntryCount: this.traceEntries.length,
29653
29979
  statusHistory: this.statusHistory.slice(-30),
29654
29980
  timeouts: this.timeouts,
29655
29981
  pendingOutputParseBufferLength: this.pendingOutputParseBuffer.length,
@@ -29657,6 +29983,25 @@ ${data.message || ""}`.trim();
29657
29983
  ptyAlive: !!this.ptyProcess
29658
29984
  };
29659
29985
  }
29986
+ getTraceState(limit = 120) {
29987
+ const cappedLimit = Math.max(1, Math.min(500, Number.isFinite(limit) ? Math.floor(limit) : 120));
29988
+ return {
29989
+ sessionId: this.traceSessionId,
29990
+ providerResolution: this.providerResolutionMeta,
29991
+ entryCount: this.traceEntries.length,
29992
+ entries: this.traceEntries.slice(-cappedLimit),
29993
+ screenText: this.summarizeTraceText(this.terminalScreen.getText(), 4e3),
29994
+ recentOutputBuffer: this.summarizeTraceText(this.recentOutputBuffer, 1e3),
29995
+ responseBuffer: this.summarizeTraceText(this.responseBuffer, 1200),
29996
+ status: this.currentStatus,
29997
+ activeModal: this.activeModal,
29998
+ currentTurnScope: this.currentTurnScope,
29999
+ messages: this.summarizeTraceMessages(this.committedMessages, 5)
30000
+ };
30001
+ }
30002
+ getProviderResolutionMeta() {
30003
+ return { ...this.providerResolutionMeta };
30004
+ }
29660
30005
  respondToTerminalQueries(data) {
29661
30006
  if (!this.ptyProcess || !data) return;
29662
30007
  const combined = this.pendingTerminalQueryTail + data;
@@ -29752,22 +30097,22 @@ ${data.message || ""}`.trim();
29752
30097
  });
29753
30098
  module2.exports = __toCommonJS2(index_exports);
29754
30099
  init_config();
29755
- var fs3 = __toESM2(require("fs"));
29756
- var os5 = __toESM2(require("os"));
30100
+ var fs4 = __toESM2(require("fs"));
30101
+ var os6 = __toESM2(require("os"));
29757
30102
  var path5 = __toESM2(require("path"));
29758
30103
  var import_crypto22 = require("crypto");
29759
30104
  var MAX_WORKSPACES = 50;
29760
30105
  function expandPath(p) {
29761
30106
  const t = (p || "").trim();
29762
30107
  if (!t) return "";
29763
- if (t.startsWith("~")) return path5.join(os5.homedir(), t.slice(1).replace(/^\//, ""));
30108
+ if (t.startsWith("~")) return path5.join(os6.homedir(), t.slice(1).replace(/^\//, ""));
29764
30109
  return path5.resolve(t);
29765
30110
  }
29766
30111
  function validateWorkspacePath(absPath) {
29767
30112
  try {
29768
30113
  if (!absPath) return { ok: false, error: "Path required" };
29769
- if (!fs3.existsSync(absPath)) return { ok: false, error: "Path does not exist" };
29770
- const st2 = fs3.statSync(absPath);
30114
+ if (!fs4.existsSync(absPath)) return { ok: false, error: "Path does not exist" };
30115
+ const st2 = fs4.statSync(absPath);
29771
30116
  if (!st2.isDirectory()) return { ok: false, error: "Not a directory" };
29772
30117
  return { ok: true };
29773
30118
  } catch (e) {
@@ -29833,7 +30178,7 @@ ${data.message || ""}`.trim();
29833
30178
  };
29834
30179
  }
29835
30180
  if (a.useHome === true) {
29836
- return { ok: true, path: os5.homedir(), source: "home" };
30181
+ return { ok: true, path: os6.homedir(), source: "home" };
29837
30182
  }
29838
30183
  return {
29839
30184
  ok: false,
@@ -29874,9 +30219,9 @@ ${data.message || ""}`.trim();
29874
30219
  const abs = expandPath(rawPath);
29875
30220
  const createIfMissing = options?.createIfMissing === true;
29876
30221
  if (!abs) return { error: "Path required" };
29877
- if (!fs3.existsSync(abs) && createIfMissing) {
30222
+ if (!fs4.existsSync(abs) && createIfMissing) {
29878
30223
  try {
29879
- fs3.mkdirSync(abs, { recursive: true });
30224
+ fs4.mkdirSync(abs, { recursive: true });
29880
30225
  } catch (e) {
29881
30226
  return { error: e?.message || "Could not create directory" };
29882
30227
  }
@@ -31824,6 +32169,7 @@ ${data.message || ""}`.trim();
31824
32169
  role: msg.role,
31825
32170
  content: msg.content || "",
31826
32171
  kind: typeof msg.kind === "string" ? msg.kind : void 0,
32172
+ senderName: typeof msg.senderName === "string" ? msg.senderName : void 0,
31827
32173
  agent: agentType,
31828
32174
  instanceId,
31829
32175
  historySessionId: effectiveHistoryKey,
@@ -31862,6 +32208,7 @@ ${data.message || ""}`.trim();
31862
32208
  kind: "system",
31863
32209
  content,
31864
32210
  receivedAt: options.receivedAt,
32211
+ senderName: options.senderName,
31865
32212
  historyDedupKey: options.dedupKey
31866
32213
  }],
31867
32214
  options.sessionTitle,
@@ -33192,6 +33539,12 @@ ${data.message || ""}`.trim();
33192
33539
  function getTargetedCliAdapter(h, args, providerType) {
33193
33540
  return h.getCliAdapter(args?.targetSessionId || providerType || h.currentSession?.providerType || h.currentManagerKey);
33194
33541
  }
33542
+ function getTargetInstance(h, args) {
33543
+ const targetSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
33544
+ const sessionId = targetSessionId || h.currentSession?.sessionId || "";
33545
+ if (!sessionId) return null;
33546
+ return h.ctx.instanceManager?.getInstance(sessionId);
33547
+ }
33195
33548
  function getTargetTransport(h, provider) {
33196
33549
  if (h.currentSession?.transport) return h.currentSession.transport;
33197
33550
  switch (provider?.category) {
@@ -33934,6 +34287,7 @@ ${data.message || ""}`.trim();
33934
34287
  adapter.writeRaw?.(keys);
33935
34288
  }
33936
34289
  LOG2.info("Command", `[resolveAction] CLI PTY \u2192 buttonIndex=${buttonIndex} "${buttons[buttonIndex] ?? "?"}"`);
34290
+ getTargetInstance(h, args)?.recordApprovalSelection?.(buttons[buttonIndex] ?? button);
33937
34291
  return { success: true, buttonIndex, button: buttons[buttonIndex] ?? button };
33938
34292
  }
33939
34293
  if (isExtensionTransport(transport) && h.agentStream && h.getCdp() && h.currentSession?.sessionId) {
@@ -34015,9 +34369,9 @@ ${data.message || ""}`.trim();
34015
34369
  }
34016
34370
  return { success: false, error: "resolveAction script not available for this provider" };
34017
34371
  }
34018
- var fs4 = __toESM2(require("fs"));
34372
+ var fs42 = __toESM2(require("fs"));
34019
34373
  var path6 = __toESM2(require("path"));
34020
- var os6 = __toESM2(require("os"));
34374
+ var os62 = __toESM2(require("os"));
34021
34375
  var KEY_TO_VK = {
34022
34376
  Backspace: 8,
34023
34377
  Tab: 9,
@@ -34244,7 +34598,7 @@ ${data.message || ""}`.trim();
34244
34598
  return { success: true, agents };
34245
34599
  }
34246
34600
  function resolveSafePath(requestedPath) {
34247
- const home = os6.homedir();
34601
+ const home = os62.homedir();
34248
34602
  let resolved;
34249
34603
  if (requestedPath.startsWith("~")) {
34250
34604
  resolved = path6.join(home, requestedPath.slice(1));
@@ -34258,7 +34612,7 @@ ${data.message || ""}`.trim();
34258
34612
  async function handleFileRead(h, args) {
34259
34613
  try {
34260
34614
  const filePath = resolveSafePath(args?.path);
34261
- const content = fs4.readFileSync(filePath, "utf-8");
34615
+ const content = fs42.readFileSync(filePath, "utf-8");
34262
34616
  return { success: true, content, path: filePath };
34263
34617
  } catch (e) {
34264
34618
  return { success: false, error: e.message };
@@ -34267,8 +34621,8 @@ ${data.message || ""}`.trim();
34267
34621
  async function handleFileWrite(h, args) {
34268
34622
  try {
34269
34623
  const filePath = resolveSafePath(args?.path);
34270
- fs4.mkdirSync(path6.dirname(filePath), { recursive: true });
34271
- fs4.writeFileSync(filePath, args?.content || "", "utf-8");
34624
+ fs42.mkdirSync(path6.dirname(filePath), { recursive: true });
34625
+ fs42.writeFileSync(filePath, args?.content || "", "utf-8");
34272
34626
  return { success: true, path: filePath };
34273
34627
  } catch (e) {
34274
34628
  return { success: false, error: e.message };
@@ -34277,11 +34631,11 @@ ${data.message || ""}`.trim();
34277
34631
  async function handleFileList(h, args) {
34278
34632
  try {
34279
34633
  const dirPath = resolveSafePath(args?.path || ".");
34280
- const entries = fs4.readdirSync(dirPath, { withFileTypes: true });
34634
+ const entries = fs42.readdirSync(dirPath, { withFileTypes: true });
34281
34635
  const files = entries.map((e) => ({
34282
34636
  name: e.name,
34283
34637
  type: e.isDirectory() ? "directory" : "file",
34284
- size: e.isFile() ? fs4.statSync(path6.join(dirPath, e.name)).size : void 0
34638
+ size: e.isFile() ? fs42.statSync(path6.join(dirPath, e.name)).size : void 0
34285
34639
  }));
34286
34640
  return { success: true, files, path: dirPath };
34287
34641
  } catch (e) {
@@ -35095,6 +35449,7 @@ ${data.message || ""}`.trim();
35095
35449
  generatingDebouncePending = null;
35096
35450
  lastApprovalEventAt = 0;
35097
35451
  historyWriter;
35452
+ runtimeMessages = [];
35098
35453
  instanceId;
35099
35454
  presentationMode;
35100
35455
  providerSessionId;
@@ -35157,6 +35512,7 @@ ${data.message || ""}`.trim();
35157
35512
  }
35158
35513
  const runtime = this.adapter.getRuntimeMetadata();
35159
35514
  const parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
35515
+ const mergedMessages = this.mergeConversationMessages(parsedMessages);
35160
35516
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
35161
35517
  if (parsedMessages.length > 0) {
35162
35518
  let messagesToSave = parsedMessages;
@@ -35186,7 +35542,7 @@ ${data.message || ""}`.trim();
35186
35542
  id: `${this.type}_${this.workingDir}`,
35187
35543
  title: parsedStatus?.title || dirName,
35188
35544
  status: parsedStatus?.status || adapterStatus.status,
35189
- messages: parsedMessages,
35545
+ messages: mergedMessages,
35190
35546
  activeModal: parsedStatus?.activeModal ?? adapterStatus.activeModal,
35191
35547
  inputContent: ""
35192
35548
  },
@@ -35285,6 +35641,11 @@ ${data.message || ""}`.trim();
35285
35641
  const approvalCooldown = 5e3;
35286
35642
  if (this.lastStatus !== "waiting_approval" && (!this.lastApprovalEventAt || now - this.lastApprovalEventAt > approvalCooldown)) {
35287
35643
  this.lastApprovalEventAt = now;
35644
+ this.appendRuntimeSystemMessage(
35645
+ this.formatApprovalRequestMessage(modal?.message, modal?.buttons),
35646
+ `approval_request:${now}`,
35647
+ now
35648
+ );
35288
35649
  this.pushEvent({
35289
35650
  event: "agent:waiting_approval",
35290
35651
  chatTitle,
@@ -35356,11 +35717,71 @@ ${data.message || ""}`.trim();
35356
35717
  get cliName() {
35357
35718
  return this.provider.name;
35358
35719
  }
35720
+ recordApprovalSelection(buttonText) {
35721
+ const cleanButton = String(buttonText || "").trim();
35722
+ if (!cleanButton) return;
35723
+ const now = Date.now();
35724
+ this.appendRuntimeSystemMessage(
35725
+ `Approval selected: ${cleanButton}`,
35726
+ `approval_selection:${now}:${cleanButton}`,
35727
+ now
35728
+ );
35729
+ }
35359
35730
  formatMarkerTimestamp(timestamp) {
35360
35731
  const date5 = new Date(timestamp);
35361
35732
  const pad = (value) => String(value).padStart(2, "0");
35362
35733
  return `${date5.getFullYear()}-${pad(date5.getMonth() + 1)}-${pad(date5.getDate())} ${pad(date5.getHours())}:${pad(date5.getMinutes())}:${pad(date5.getSeconds())}`;
35363
35734
  }
35735
+ appendRuntimeSystemMessage(content, dedupKey, receivedAt = Date.now()) {
35736
+ const normalizedContent = String(content || "").trim();
35737
+ if (!normalizedContent) return;
35738
+ if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
35739
+ this.runtimeMessages.push({
35740
+ key: dedupKey,
35741
+ message: {
35742
+ role: "system",
35743
+ senderName: "System",
35744
+ content: normalizedContent,
35745
+ receivedAt,
35746
+ timestamp: receivedAt
35747
+ }
35748
+ });
35749
+ if (this.runtimeMessages.length > 50) {
35750
+ this.runtimeMessages = this.runtimeMessages.slice(-50);
35751
+ }
35752
+ this.historyWriter.appendNewMessages(
35753
+ this.type,
35754
+ [{
35755
+ role: "system",
35756
+ senderName: "System",
35757
+ content: normalizedContent,
35758
+ receivedAt,
35759
+ historyDedupKey: dedupKey
35760
+ }],
35761
+ this.adapter.getScriptParsedStatus?.()?.title || this.workingDir.split("/").filter(Boolean).pop() || "session",
35762
+ this.instanceId,
35763
+ this.providerSessionId
35764
+ );
35765
+ }
35766
+ mergeConversationMessages(parsedMessages) {
35767
+ if (this.runtimeMessages.length === 0) return parsedMessages;
35768
+ return [...parsedMessages, ...this.runtimeMessages.map((entry) => entry.message)].map((message, index) => ({ message, index })).sort((a, b2) => {
35769
+ const aTime = a.message.receivedAt || a.message.timestamp || 0;
35770
+ const bTime = b2.message.receivedAt || b2.message.timestamp || 0;
35771
+ if (aTime !== bTime) return aTime - bTime;
35772
+ return a.index - b2.index;
35773
+ }).map((entry) => entry.message);
35774
+ }
35775
+ formatApprovalRequestMessage(modalMessage, buttons) {
35776
+ const lines = ["Approval requested"];
35777
+ const cleanMessage = String(modalMessage || "").trim();
35778
+ if (cleanMessage) lines.push(cleanMessage);
35779
+ const labels = (buttons || []).map((button) => String(button || "").trim()).filter(Boolean);
35780
+ if (labels.length > 0) {
35781
+ lines.push(labels.map((label) => `[${label}]`).join(" "));
35782
+ }
35783
+ return lines.join("\n");
35784
+ }
35364
35785
  promoteProviderSessionId(sessionId) {
35365
35786
  const nextSessionId = String(sessionId || "").trim();
35366
35787
  if (!nextSessionId || nextSessionId === this.providerSessionId) return;
@@ -37364,6 +37785,7 @@ ${installInfo}`
37364
37785
  resolve(type, context) {
37365
37786
  const base = this.providers.get(type);
37366
37787
  if (!base) return void 0;
37788
+ const providerDir = this.findProviderDirInternal(type) || void 0;
37367
37789
  const currentOs = context?.os || process.platform;
37368
37790
  const currentVersion = context?.version ?? this.versionArchive?.getLatest(type) ?? void 0;
37369
37791
  const resolved = JSON.parse(JSON.stringify(base));
@@ -37373,6 +37795,9 @@ ${installInfo}`
37373
37795
  if (base.scripts) {
37374
37796
  resolved.scripts = { ...base.scripts };
37375
37797
  }
37798
+ if (providerDir) {
37799
+ resolved._resolvedProviderDir = providerDir;
37800
+ }
37376
37801
  if (base.os?.[currentOs]) {
37377
37802
  const osOverride = base.os[currentOs];
37378
37803
  if (osOverride.scripts) {
@@ -37393,6 +37818,12 @@ ${installInfo}`
37393
37818
  if (loaded) {
37394
37819
  resolved.scripts = loaded;
37395
37820
  this.log(` [compatibility] ${type} v${currentVersion} \u2192 ${entry.scriptDir}`);
37821
+ resolved._resolvedScriptDir = entry.scriptDir;
37822
+ resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
37823
+ if (providerDir) {
37824
+ const fullDir = path10.join(providerDir, entry.scriptDir);
37825
+ resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
37826
+ }
37396
37827
  matched = true;
37397
37828
  }
37398
37829
  break;
@@ -37403,6 +37834,12 @@ ${installInfo}`
37403
37834
  if (loaded) {
37404
37835
  resolved.scripts = loaded;
37405
37836
  this.log(` [compatibility] ${type} v${currentVersion} \u2192 default: ${base.defaultScriptDir}`);
37837
+ resolved._resolvedScriptDir = base.defaultScriptDir;
37838
+ resolved._resolvedScriptsSource = "defaultScriptDir:version_miss";
37839
+ if (providerDir) {
37840
+ const fullDir = path10.join(providerDir, base.defaultScriptDir);
37841
+ resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
37842
+ }
37406
37843
  }
37407
37844
  resolved._versionWarning = `Version ${currentVersion} not in compatibility matrix. Using default scripts.`;
37408
37845
  }
@@ -37415,6 +37852,12 @@ ${installInfo}`
37415
37852
  if (loaded) {
37416
37853
  resolved.scripts = loaded;
37417
37854
  this.log(` [version override] ${type} ${range} \u2192 ${dirOverride}`);
37855
+ resolved._resolvedScriptDir = dirOverride;
37856
+ resolved._resolvedScriptsSource = `versions:${range}`;
37857
+ if (providerDir) {
37858
+ const fullDir = path10.join(providerDir, dirOverride);
37859
+ resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
37860
+ }
37418
37861
  }
37419
37862
  } else if (override.scripts) {
37420
37863
  resolved.scripts = { ...resolved.scripts, ...override.scripts };
@@ -37426,6 +37869,12 @@ ${installInfo}`
37426
37869
  if (loaded) {
37427
37870
  resolved.scripts = loaded;
37428
37871
  this.log(` [compatibility] ${type} no version detected \u2192 default: ${base.defaultScriptDir}`);
37872
+ resolved._resolvedScriptDir = base.defaultScriptDir;
37873
+ resolved._resolvedScriptsSource = "defaultScriptDir:no_version";
37874
+ if (providerDir) {
37875
+ const fullDir = path10.join(providerDir, base.defaultScriptDir);
37876
+ resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
37877
+ }
37429
37878
  }
37430
37879
  }
37431
37880
  if (base.overrides) {
@@ -37494,6 +37943,9 @@ ${installInfo}`
37494
37943
  awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
37495
37944
  });
37496
37945
  const handleChange = (filePath) => {
37946
+ if (/[\/\\]fixtures[\/\\]/.test(filePath)) {
37947
+ return;
37948
+ }
37497
37949
  if (filePath.endsWith(".js") || filePath.endsWith(".json")) {
37498
37950
  this.log(`File changed: ${path10.basename(filePath)}, reloading...`);
37499
37951
  this.reload();
@@ -38184,7 +38636,7 @@ ${installInfo}`
38184
38636
  }
38185
38637
  } else if (plat === "win32") {
38186
38638
  try {
38187
- const fs14 = require("fs");
38639
+ const fs15 = require("fs");
38188
38640
  const appNameMap = getMacAppIdentifiers();
38189
38641
  const appName = appNameMap[ideId];
38190
38642
  if (appName) {
@@ -38193,8 +38645,8 @@ ${installInfo}`
38193
38645
  appName,
38194
38646
  "storage.json"
38195
38647
  );
38196
- if (fs14.existsSync(storagePath)) {
38197
- const data = JSON.parse(fs14.readFileSync(storagePath, "utf-8"));
38648
+ if (fs15.existsSync(storagePath)) {
38649
+ const data = JSON.parse(fs15.readFileSync(storagePath, "utf-8"));
38198
38650
  const workspaces = data?.openedPathsList?.workspaces3 || data?.openedPathsList?.entries || [];
38199
38651
  if (workspaces.length > 0) {
38200
38652
  const recent = workspaces[0];
@@ -38654,7 +39106,7 @@ ${installInfo}`
38654
39106
  function getNpmExecutable() {
38655
39107
  return process.platform === "win32" ? "npm.cmd" : "npm";
38656
39108
  }
38657
- function killPid(pid) {
39109
+ function killPid2(pid) {
38658
39110
  try {
38659
39111
  if (process.platform === "win32") {
38660
39112
  (0, import_child_process7.execFileSync)("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore" });
@@ -38683,7 +39135,7 @@ ${installInfo}`
38683
39135
  if (fs8.existsSync(pidFile)) {
38684
39136
  const pid = Number.parseInt(fs8.readFileSync(pidFile, "utf8").trim(), 10);
38685
39137
  if (Number.isFinite(pid)) {
38686
- killPid(pid);
39138
+ killPid2(pid);
38687
39139
  }
38688
39140
  }
38689
39141
  } catch {
@@ -38699,7 +39151,7 @@ ${installInfo}`
38699
39151
  for (const line of raw.split("\n")) {
38700
39152
  const pid = Number.parseInt(line.trim(), 10);
38701
39153
  if (Number.isFinite(pid)) {
38702
- killPid(pid);
39154
+ killPid2(pid);
38703
39155
  }
38704
39156
  }
38705
39157
  } catch {
@@ -39904,6 +40356,15 @@ ${installInfo}`
39904
40356
  cdpManagerKey: ideType,
39905
40357
  instanceKey: `ide:${ideType}`
39906
40358
  });
40359
+ const activeSessionId2 = agentStreamManager.getActiveSessionId(parentSessionId);
40360
+ if (!activeSessionId2 || enabledExtTypes.size === 1) {
40361
+ await agentStreamManager.setActiveSession(
40362
+ cdp,
40363
+ parentSessionId,
40364
+ extInstance.getInstanceId()
40365
+ );
40366
+ LOG2.info("AgentStream", `Auto-activated enabled extension: ${extType} (${ideType})`);
40367
+ }
39907
40368
  }
39908
40369
  LOG2.info("AgentStream", `Extension added: ${extType} (enabled for ${ideType})`);
39909
40370
  }
@@ -40354,8 +40815,8 @@ ${installInfo}`
40354
40815
  return results;
40355
40816
  }
40356
40817
  var http2 = __toESM2(require("http"));
40357
- var fs13 = __toESM2(require("fs"));
40358
- var path17 = __toESM2(require("path"));
40818
+ var fs14 = __toESM2(require("fs"));
40819
+ var path18 = __toESM2(require("path"));
40359
40820
  function generateFiles(type, name, category, opts = {}) {
40360
40821
  const { cdpPorts, cli, processName, installPath, binary, extensionId, version: version2 = "0.1" } = opts;
40361
40822
  if (category === "cli" || category === "acp") {
@@ -41690,6 +42151,162 @@ async (params) => {
41690
42151
  ctx.json(res, 500, { error: `DOM context collection failed: ${e.message}` });
41691
42152
  }
41692
42153
  }
42154
+ var fs12 = __toESM2(require("fs"));
42155
+ var path16 = __toESM2(require("path"));
42156
+ function slugifyFixtureName(value) {
42157
+ const normalized = String(value || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
42158
+ return normalized || `fixture-${Date.now()}`;
42159
+ }
42160
+ function getCliFixtureDir(ctx, type) {
42161
+ const providerDir = ctx.providerLoader.findProviderDir(type);
42162
+ if (!providerDir) {
42163
+ throw new Error(`Provider directory not found for '${type}'`);
42164
+ }
42165
+ return path16.join(providerDir, "fixtures");
42166
+ }
42167
+ function readCliFixture(ctx, type, name) {
42168
+ const fixtureDir = getCliFixtureDir(ctx, type);
42169
+ const filePath = path16.join(fixtureDir, `${name}.json`);
42170
+ if (!fs12.existsSync(filePath)) {
42171
+ throw new Error(`Fixture not found: ${filePath}`);
42172
+ }
42173
+ return JSON.parse(fs12.readFileSync(filePath, "utf-8"));
42174
+ }
42175
+ function getExerciseTranscriptText(result) {
42176
+ const parts = [];
42177
+ const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
42178
+ const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
42179
+ for (const message of [...debugMessages, ...traceMessages]) {
42180
+ if (!message || typeof message.content !== "string") continue;
42181
+ parts.push(message.content);
42182
+ }
42183
+ if (typeof result?.debug?.partialResponse === "string") parts.push(result.debug.partialResponse);
42184
+ if (typeof result?.trace?.responseBuffer === "string") parts.push(result.trace.responseBuffer);
42185
+ return parts.join("\n");
42186
+ }
42187
+ function getExerciseLastAssistant(result) {
42188
+ const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
42189
+ const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
42190
+ for (const messages of [debugMessages, traceMessages]) {
42191
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
42192
+ const message = messages[i];
42193
+ if (message?.role === "assistant" && typeof message.content === "string" && message.content.trim()) {
42194
+ return message.content;
42195
+ }
42196
+ }
42197
+ }
42198
+ return "";
42199
+ }
42200
+ function getExerciseMessageCount(result) {
42201
+ const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
42202
+ const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
42203
+ return Math.max(debugMessages.length, traceMessages.length);
42204
+ }
42205
+ function compileFixtureRegex(source) {
42206
+ const value = String(source || "").trim();
42207
+ if (!value) return null;
42208
+ const delimited = value.match(/^\/([\s\S]+)\/([dgimsuvy]*)$/);
42209
+ try {
42210
+ if (delimited) {
42211
+ return new RegExp(delimited[1], delimited[2]);
42212
+ }
42213
+ return new RegExp(value, "m");
42214
+ } catch {
42215
+ return null;
42216
+ }
42217
+ }
42218
+ function statusesContainSequence(actual, expected) {
42219
+ if (!expected.length) return true;
42220
+ let index = 0;
42221
+ for (const status of actual) {
42222
+ if (status === expected[index]) index += 1;
42223
+ if (index >= expected.length) return true;
42224
+ }
42225
+ return false;
42226
+ }
42227
+ function validateCliFixtureResult(result, assertions) {
42228
+ const failures = [];
42229
+ const transcriptText = getExerciseTranscriptText(result);
42230
+ const lastAssistant = getExerciseLastAssistant(result);
42231
+ const mustContainAny = assertions.mustContainAny || [];
42232
+ const mustNotContainAny = assertions.mustNotContainAny || [];
42233
+ const mustMatchAny = assertions.mustMatchAny || [];
42234
+ const mustNotMatchAny = assertions.mustNotMatchAny || [];
42235
+ const lastAssistantMustContainAny = assertions.lastAssistantMustContainAny || [];
42236
+ const lastAssistantMustNotContainAny = assertions.lastAssistantMustNotContainAny || [];
42237
+ const lastAssistantMustMatchAny = assertions.lastAssistantMustMatchAny || [];
42238
+ const lastAssistantMustNotMatchAny = assertions.lastAssistantMustNotMatchAny || [];
42239
+ const statusesSeen = Array.isArray(result?.statusesSeen) ? result.statusesSeen.map((value) => String(value)) : [];
42240
+ if (assertions.requireNotTimedOut !== false && result?.timedOut) {
42241
+ failures.push("Exercise timed out");
42242
+ }
42243
+ const missingRequired = mustContainAny.filter((value) => !transcriptText.includes(value));
42244
+ if (missingRequired.length > 0) {
42245
+ failures.push(`Missing required substrings: ${missingRequired.join(", ")}`);
42246
+ }
42247
+ const presentBanned = mustNotContainAny.filter((value) => transcriptText.includes(value));
42248
+ if (presentBanned.length > 0) {
42249
+ failures.push(`Found banned substrings: ${presentBanned.join(", ")}`);
42250
+ }
42251
+ const missingRegex = mustMatchAny.filter((value) => {
42252
+ const regex = compileFixtureRegex(value);
42253
+ return !regex || !regex.test(transcriptText);
42254
+ });
42255
+ if (missingRegex.length > 0) {
42256
+ failures.push(`Missing required regex matches: ${missingRegex.join(", ")}`);
42257
+ }
42258
+ const presentBannedRegex = mustNotMatchAny.filter((value) => {
42259
+ const regex = compileFixtureRegex(value);
42260
+ return !!regex && regex.test(transcriptText);
42261
+ });
42262
+ if (presentBannedRegex.length > 0) {
42263
+ failures.push(`Found banned regex matches: ${presentBannedRegex.join(", ")}`);
42264
+ }
42265
+ const missingLastAssistant = lastAssistantMustContainAny.filter((value) => !lastAssistant.includes(value));
42266
+ if (missingLastAssistant.length > 0) {
42267
+ failures.push(`Missing required lastAssistant substrings: ${missingLastAssistant.join(", ")}`);
42268
+ }
42269
+ const presentBannedLastAssistant = lastAssistantMustNotContainAny.filter((value) => lastAssistant.includes(value));
42270
+ if (presentBannedLastAssistant.length > 0) {
42271
+ failures.push(`Found banned lastAssistant substrings: ${presentBannedLastAssistant.join(", ")}`);
42272
+ }
42273
+ const missingLastAssistantRegex = lastAssistantMustMatchAny.filter((value) => {
42274
+ const regex = compileFixtureRegex(value);
42275
+ return !regex || !regex.test(lastAssistant);
42276
+ });
42277
+ if (missingLastAssistantRegex.length > 0) {
42278
+ failures.push(`Missing required lastAssistant regex matches: ${missingLastAssistantRegex.join(", ")}`);
42279
+ }
42280
+ const presentBannedLastAssistantRegex = lastAssistantMustNotMatchAny.filter((value) => {
42281
+ const regex = compileFixtureRegex(value);
42282
+ return !!regex && regex.test(lastAssistant);
42283
+ });
42284
+ if (presentBannedLastAssistantRegex.length > 0) {
42285
+ failures.push(`Found banned lastAssistant regex matches: ${presentBannedLastAssistantRegex.join(", ")}`);
42286
+ }
42287
+ if (assertions.statusesSeen?.length && !statusesContainSequence(statusesSeen, assertions.statusesSeen)) {
42288
+ failures.push(`Expected statuses sequence not observed: ${assertions.statusesSeen.join(" -> ")}`);
42289
+ }
42290
+ if (result && typeof result === "object") {
42291
+ result.lastAssistant = lastAssistant;
42292
+ }
42293
+ return failures;
42294
+ }
42295
+ function getCliProviderResolutionMeta(ctx, type, adapter) {
42296
+ const adapterMeta = typeof adapter?.getProviderResolutionMeta === "function" ? adapter.getProviderResolutionMeta() : adapter?.getDebugState?.()?.providerResolution || null;
42297
+ const resolvedProvider = ctx.providerLoader.resolve(type);
42298
+ if (!adapterMeta && !resolvedProvider) return null;
42299
+ return {
42300
+ type,
42301
+ providerDir: adapterMeta?.providerDir || resolvedProvider?._resolvedProviderDir || ctx.providerLoader.findProviderDir(type),
42302
+ scriptDir: adapterMeta?.scriptDir || resolvedProvider?._resolvedScriptDir || null,
42303
+ scriptsPath: adapterMeta?.scriptsPath || resolvedProvider?._resolvedScriptsPath || null,
42304
+ scriptsSource: adapterMeta?.scriptsSource || resolvedProvider?._resolvedScriptsSource || null,
42305
+ resolvedVersion: adapterMeta?.resolvedVersion || resolvedProvider?._resolvedVersion || null,
42306
+ resolvedOs: adapterMeta?.resolvedOs || resolvedProvider?._resolvedOs || null,
42307
+ versionWarning: adapterMeta?.versionWarning || resolvedProvider?._versionWarning || null
42308
+ };
42309
+ }
41693
42310
  function findCliTarget(ctx, type, instanceId) {
41694
42311
  if (!ctx.instanceManager) return null;
41695
42312
  const cliStates = ctx.instanceManager.collectAllStates().filter((s15) => s15.category === "cli" || s15.category === "acp");
@@ -41698,6 +42315,331 @@ async (params) => {
41698
42315
  const matches = cliStates.filter((s15) => s15.type === type);
41699
42316
  return matches[matches.length - 1] || null;
41700
42317
  }
42318
+ function getCliTargetBundle(ctx, type, instanceId) {
42319
+ if (!ctx.instanceManager) return null;
42320
+ const target = findCliTarget(ctx, type, instanceId);
42321
+ if (!target) return null;
42322
+ const instance = ctx.instanceManager.getInstance(target.instanceId);
42323
+ if (!instance) return null;
42324
+ const adapter = instance.getAdapter?.() || instance.adapter;
42325
+ if (!adapter) return null;
42326
+ return { target, instance, adapter };
42327
+ }
42328
+ function sleep(ms2) {
42329
+ return new Promise((resolve10) => setTimeout(resolve10, ms2));
42330
+ }
42331
+ async function waitForCliReady(ctx, type, instanceId, timeoutMs) {
42332
+ const startedAt = Date.now();
42333
+ while (Date.now() - startedAt < timeoutMs) {
42334
+ const bundle = getCliTargetBundle(ctx, type, instanceId);
42335
+ if (bundle) {
42336
+ const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
42337
+ const startupParseGate = !!debug?.startupParseGate;
42338
+ const adapterReady = !!debug?.ready;
42339
+ const visibleStatusReady = bundle.target.status === "generating" || bundle.target.status === "waiting_approval";
42340
+ const idleReady = bundle.target.status === "idle" && !startupParseGate;
42341
+ if (adapterReady || visibleStatusReady || idleReady) {
42342
+ return bundle;
42343
+ }
42344
+ }
42345
+ await sleep(100);
42346
+ }
42347
+ return getCliTargetBundle(ctx, type, instanceId);
42348
+ }
42349
+ async function runCliExerciseInternal(ctx, body) {
42350
+ if (!ctx.cliManager) {
42351
+ throw new Error("CliManager not available");
42352
+ }
42353
+ if (!ctx.instanceManager) {
42354
+ throw new Error("InstanceManager not available");
42355
+ }
42356
+ const {
42357
+ type,
42358
+ text,
42359
+ instanceId: requestedInstanceId,
42360
+ workingDir,
42361
+ args,
42362
+ autoLaunch = true,
42363
+ freshSession = true,
42364
+ autoResolveApprovals = true,
42365
+ approvalButtonIndex = 0,
42366
+ timeoutMs = 45e3,
42367
+ readyTimeoutMs = 15e3,
42368
+ idleSettledMs = 1200,
42369
+ traceLimit = 160,
42370
+ stopWhenDone = false
42371
+ } = body || {};
42372
+ if (!type) {
42373
+ throw new Error("type required (e.g. claude-cli, codex-cli)");
42374
+ }
42375
+ if (!text || typeof text !== "string") {
42376
+ throw new Error("text required (prompt to send to the CLI)");
42377
+ }
42378
+ let resolvedInstanceId = requestedInstanceId;
42379
+ if (freshSession) {
42380
+ const staleTargets = ctx.instanceManager.collectAllStates().filter((state) => (state.category === "cli" || state.category === "acp") && state.type === type).map((state) => state.instanceId);
42381
+ for (const staleId of staleTargets) {
42382
+ ctx.instanceManager.removeInstance(staleId);
42383
+ }
42384
+ resolvedInstanceId = void 0;
42385
+ }
42386
+ let bundle = getCliTargetBundle(ctx, type, resolvedInstanceId);
42387
+ if (!bundle && autoLaunch) {
42388
+ const launchArgs = [type, workingDir || process.cwd(), Array.isArray(args) ? args : []];
42389
+ let launched = null;
42390
+ let lastLaunchError = null;
42391
+ for (let attempt = 1; attempt <= 2; attempt += 1) {
42392
+ try {
42393
+ launched = await ctx.cliManager.startSession(...launchArgs);
42394
+ lastLaunchError = null;
42395
+ break;
42396
+ } catch (error48) {
42397
+ lastLaunchError = error48 instanceof Error ? error48 : new Error(String(error48?.message || error48));
42398
+ const message = String(lastLaunchError.message || "");
42399
+ const retryable = /ECONNREFUSED|session-host|Session host/i.test(message);
42400
+ if (!retryable || attempt === 2) break;
42401
+ await sleep(1e3);
42402
+ }
42403
+ }
42404
+ if (!launched) {
42405
+ throw lastLaunchError || new Error(`Failed to start ${type}`);
42406
+ }
42407
+ resolvedInstanceId = launched.runtimeSessionId;
42408
+ bundle = await waitForCliReady(ctx, type, resolvedInstanceId, Math.max(1e3, readyTimeoutMs));
42409
+ }
42410
+ if (!bundle) {
42411
+ throw new Error(`No running instance found for: ${resolvedInstanceId || type}`);
42412
+ }
42413
+ const initialDebug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
42414
+ const initialTrace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
42415
+ const providerResolution = getCliProviderResolutionMeta(ctx, bundle.target.type, bundle.adapter);
42416
+ const preTraceCount = Number(initialTrace?.entryCount || 0);
42417
+ const startAt = Date.now();
42418
+ const statusesSeen = [];
42419
+ const approvalsResolved = [];
42420
+ let lastStatus = "";
42421
+ let lastModalKey = "";
42422
+ let idleSince = 0;
42423
+ let sawBusy = false;
42424
+ ctx.instanceManager.sendEvent(bundle.target.instanceId, "send_message", { text });
42425
+ while (Date.now() - startAt < Math.max(1e3, timeoutMs)) {
42426
+ await sleep(150);
42427
+ bundle = getCliTargetBundle(ctx, type, bundle.target.instanceId);
42428
+ if (!bundle) {
42429
+ throw new Error("CLI instance disappeared during exercise");
42430
+ }
42431
+ const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
42432
+ const trace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
42433
+ const status = String(debug?.status || bundle.target.status || "unknown");
42434
+ const traceEntries = Array.isArray(trace?.entries) ? trace.entries : [];
42435
+ const sawSendMessage = traceEntries.some((entry) => entry?.type === "send_message");
42436
+ const sawSubmitWrite = traceEntries.some((entry) => entry?.type === "submit_write");
42437
+ const hasTurnStarted = sawSendMessage || sawSubmitWrite || !!debug?.currentTurnScope;
42438
+ if (status !== lastStatus) {
42439
+ statusesSeen.push(status);
42440
+ lastStatus = status;
42441
+ }
42442
+ if (status === "generating" || status === "waiting_approval") {
42443
+ sawBusy = true;
42444
+ idleSince = 0;
42445
+ }
42446
+ const modal = debug?.activeModal || trace?.activeModal || null;
42447
+ if (autoResolveApprovals && status === "waiting_approval" && modal && Array.isArray(modal.buttons) && modal.buttons.length > 0) {
42448
+ const clampedIndex = Math.max(0, Math.min(Number(approvalButtonIndex) || 0, modal.buttons.length - 1));
42449
+ const modalKey = JSON.stringify({
42450
+ message: modal.message || "",
42451
+ buttons: modal.buttons,
42452
+ index: clampedIndex
42453
+ });
42454
+ if (modalKey !== lastModalKey && typeof bundle.adapter.resolveModal === "function") {
42455
+ lastModalKey = modalKey;
42456
+ approvalsResolved.push({
42457
+ at: Date.now(),
42458
+ buttonIndex: clampedIndex,
42459
+ label: modal.buttons[clampedIndex] || null
42460
+ });
42461
+ bundle.adapter.resolveModal(clampedIndex);
42462
+ continue;
42463
+ }
42464
+ }
42465
+ const traceCount = Number(trace?.entryCount || 0);
42466
+ const hasProgress = hasTurnStarted && (traceCount > preTraceCount || statusesSeen.length > 1 || approvalsResolved.length > 0);
42467
+ if (status === "idle" && hasProgress && sawBusy) {
42468
+ if (!idleSince) idleSince = Date.now();
42469
+ if (Date.now() - idleSince >= Math.max(200, idleSettledMs)) {
42470
+ const payload2 = {
42471
+ exercised: true,
42472
+ instanceId: bundle.target.instanceId,
42473
+ providerState: {
42474
+ type: bundle.target.type,
42475
+ name: bundle.target.name,
42476
+ status: bundle.target.status,
42477
+ mode: "mode" in bundle.target ? bundle.target.mode : void 0
42478
+ },
42479
+ providerResolution,
42480
+ initialDebug,
42481
+ initialTrace,
42482
+ debug,
42483
+ trace,
42484
+ statusesSeen,
42485
+ approvalsResolved,
42486
+ elapsedMs: Date.now() - startAt,
42487
+ timedOut: false
42488
+ };
42489
+ payload2.lastAssistant = getExerciseLastAssistant(payload2);
42490
+ payload2.messageCount = getExerciseMessageCount(payload2);
42491
+ if (stopWhenDone) {
42492
+ ctx.instanceManager.removeInstance(bundle.target.instanceId);
42493
+ }
42494
+ return payload2;
42495
+ }
42496
+ } else if (status === "idle" && hasProgress) {
42497
+ if (!idleSince) idleSince = Date.now();
42498
+ if (Date.now() - idleSince >= Math.max(500, idleSettledMs) && Date.now() - startAt >= 750) {
42499
+ const payload2 = {
42500
+ exercised: true,
42501
+ instanceId: bundle.target.instanceId,
42502
+ providerState: {
42503
+ type: bundle.target.type,
42504
+ name: bundle.target.name,
42505
+ status: bundle.target.status,
42506
+ mode: "mode" in bundle.target ? bundle.target.mode : void 0
42507
+ },
42508
+ providerResolution,
42509
+ initialDebug,
42510
+ initialTrace,
42511
+ debug,
42512
+ trace,
42513
+ statusesSeen,
42514
+ approvalsResolved,
42515
+ elapsedMs: Date.now() - startAt,
42516
+ timedOut: false
42517
+ };
42518
+ payload2.lastAssistant = getExerciseLastAssistant(payload2);
42519
+ payload2.messageCount = getExerciseMessageCount(payload2);
42520
+ if (stopWhenDone) {
42521
+ ctx.instanceManager.removeInstance(bundle.target.instanceId);
42522
+ }
42523
+ return payload2;
42524
+ }
42525
+ } else {
42526
+ idleSince = 0;
42527
+ }
42528
+ }
42529
+ const finalBundle = getCliTargetBundle(ctx, type, bundle.target.instanceId) || bundle;
42530
+ const finalDebug = typeof finalBundle.adapter.getDebugState === "function" ? finalBundle.adapter.getDebugState() : null;
42531
+ const finalTrace = typeof finalBundle.adapter.getTraceState === "function" ? finalBundle.adapter.getTraceState(traceLimit) : null;
42532
+ if (stopWhenDone) {
42533
+ ctx.instanceManager.removeInstance(finalBundle.target.instanceId);
42534
+ }
42535
+ const payload = {
42536
+ exercised: true,
42537
+ instanceId: finalBundle.target.instanceId,
42538
+ providerState: {
42539
+ type: finalBundle.target.type,
42540
+ name: finalBundle.target.name,
42541
+ status: finalBundle.target.status,
42542
+ mode: "mode" in finalBundle.target ? finalBundle.target.mode : void 0
42543
+ },
42544
+ providerResolution: getCliProviderResolutionMeta(ctx, finalBundle.target.type, finalBundle.adapter),
42545
+ initialDebug,
42546
+ initialTrace,
42547
+ debug: finalDebug,
42548
+ trace: finalTrace,
42549
+ statusesSeen,
42550
+ approvalsResolved,
42551
+ elapsedMs: Date.now() - startAt,
42552
+ timedOut: true
42553
+ };
42554
+ payload.lastAssistant = getExerciseLastAssistant(payload);
42555
+ payload.messageCount = getExerciseMessageCount(payload);
42556
+ return payload;
42557
+ }
42558
+ async function runCliAutoImplVerification(ctx, type, verification) {
42559
+ const assertions = {
42560
+ mustContainAny: verification?.mustContainAny || [],
42561
+ mustNotContainAny: verification?.mustNotContainAny || [],
42562
+ mustMatchAny: verification?.mustMatchAny || [],
42563
+ mustNotMatchAny: verification?.mustNotMatchAny || [],
42564
+ lastAssistantMustContainAny: verification?.lastAssistantMustContainAny || [],
42565
+ lastAssistantMustNotContainAny: verification?.lastAssistantMustNotContainAny || [],
42566
+ lastAssistantMustMatchAny: verification?.lastAssistantMustMatchAny || [],
42567
+ lastAssistantMustNotMatchAny: verification?.lastAssistantMustNotMatchAny || [],
42568
+ requireNotTimedOut: true
42569
+ };
42570
+ const rawFixtureNames = Array.isArray(verification?.fixtureNames) ? verification.fixtureNames.map((value) => String(value || "").trim()).filter(Boolean) : [];
42571
+ if (rawFixtureNames.length > 0) {
42572
+ const results = [];
42573
+ for (const rawFixtureName2 of rawFixtureNames) {
42574
+ const name = slugifyFixtureName(rawFixtureName2);
42575
+ const fixture = readCliFixture(ctx, type, name);
42576
+ const mergedAssertions = {
42577
+ ...fixture.assertions,
42578
+ ...assertions
42579
+ };
42580
+ const result2 = await runCliExerciseInternal(ctx, {
42581
+ ...fixture.request,
42582
+ type
42583
+ });
42584
+ const failures2 = validateCliFixtureResult(result2, mergedAssertions);
42585
+ results.push({
42586
+ fixtureName: name,
42587
+ pass: failures2.length === 0,
42588
+ failures: failures2,
42589
+ result: result2,
42590
+ assertions: mergedAssertions,
42591
+ fixture
42592
+ });
42593
+ }
42594
+ const firstFailure = results.find((item) => !item.pass) || results[results.length - 1];
42595
+ return {
42596
+ mode: "fixture_replay_suite",
42597
+ pass: results.every((item) => item.pass),
42598
+ failures: results.flatMap((item) => item.failures.map((failure) => `${item.fixtureName}: ${failure}`)),
42599
+ result: firstFailure.result,
42600
+ assertions: firstFailure.assertions,
42601
+ fixture: firstFailure.fixture,
42602
+ results
42603
+ };
42604
+ }
42605
+ const rawFixtureName = String(verification?.fixtureName || "").trim();
42606
+ if (rawFixtureName) {
42607
+ const name = slugifyFixtureName(rawFixtureName);
42608
+ try {
42609
+ const fixture = readCliFixture(ctx, type, name);
42610
+ const mergedAssertions = {
42611
+ ...fixture.assertions,
42612
+ ...assertions
42613
+ };
42614
+ const result2 = await runCliExerciseInternal(ctx, {
42615
+ ...fixture.request,
42616
+ type
42617
+ });
42618
+ const failures2 = validateCliFixtureResult(result2, mergedAssertions);
42619
+ return {
42620
+ mode: "fixture_replay",
42621
+ pass: failures2.length === 0,
42622
+ failures: failures2,
42623
+ result: result2,
42624
+ assertions: mergedAssertions,
42625
+ fixture
42626
+ };
42627
+ } catch {
42628
+ }
42629
+ }
42630
+ const result = await runCliExerciseInternal(ctx, {
42631
+ ...verification?.request || {},
42632
+ type
42633
+ });
42634
+ const failures = validateCliFixtureResult(result, assertions);
42635
+ return {
42636
+ mode: "exercise",
42637
+ pass: failures.length === 0,
42638
+ failures,
42639
+ result,
42640
+ assertions
42641
+ };
42642
+ }
41701
42643
  async function handleCliStatus(ctx, _req, res) {
41702
42644
  if (!ctx.instanceManager) {
41703
42645
  ctx.json(res, 503, { error: "InstanceManager not available (daemon not fully initialized)" });
@@ -41836,12 +42778,14 @@ async (params) => {
41836
42778
  status: target.status,
41837
42779
  mode: "mode" in target ? target.mode : void 0
41838
42780
  },
42781
+ providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
41839
42782
  debug: debugState
41840
42783
  });
41841
42784
  } else {
41842
42785
  ctx.json(res, 200, {
41843
42786
  instanceId: target.instanceId,
41844
42787
  providerState: target,
42788
+ providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
41845
42789
  debug: null,
41846
42790
  message: "No debug state available (adapter.getDebugState not found)"
41847
42791
  });
@@ -41850,6 +42794,191 @@ async (params) => {
41850
42794
  ctx.json(res, 500, { error: `Debug state failed: ${e.message}` });
41851
42795
  }
41852
42796
  }
42797
+ async function handleCliTrace(ctx, type, req, res) {
42798
+ if (!ctx.instanceManager) {
42799
+ ctx.json(res, 503, { error: "InstanceManager not available" });
42800
+ return;
42801
+ }
42802
+ const target = findCliTarget(ctx, type);
42803
+ if (!target) {
42804
+ const allStates = ctx.instanceManager.collectAllStates();
42805
+ ctx.json(res, 404, {
42806
+ error: `No running instance for: ${type}`,
42807
+ available: allStates.filter((s15) => s15.category === "cli" || s15.category === "acp").map((s15) => s15.type)
42808
+ });
42809
+ return;
42810
+ }
42811
+ const instance = ctx.instanceManager.getInstance(target.instanceId);
42812
+ if (!instance) {
42813
+ ctx.json(res, 404, { error: `Instance not found: ${target.instanceId}` });
42814
+ return;
42815
+ }
42816
+ try {
42817
+ const adapter = instance.getAdapter?.() || instance.adapter;
42818
+ const url2 = new URL(req.url || "/", "http://127.0.0.1");
42819
+ const limit = parseInt(url2.searchParams.get("limit") || "120", 10);
42820
+ if (adapter && typeof adapter.getTraceState === "function") {
42821
+ const trace = adapter.getTraceState(limit);
42822
+ const debug = typeof adapter.getDebugState === "function" ? adapter.getDebugState() : null;
42823
+ ctx.json(res, 200, {
42824
+ instanceId: target.instanceId,
42825
+ providerState: {
42826
+ type: target.type,
42827
+ name: target.name,
42828
+ status: target.status,
42829
+ mode: "mode" in target ? target.mode : void 0
42830
+ },
42831
+ providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
42832
+ debug,
42833
+ trace
42834
+ });
42835
+ } else {
42836
+ ctx.json(res, 200, {
42837
+ instanceId: target.instanceId,
42838
+ providerState: target,
42839
+ providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
42840
+ debug: typeof adapter?.getDebugState === "function" ? adapter.getDebugState() : null,
42841
+ trace: null,
42842
+ message: "No trace state available (adapter.getTraceState not found)"
42843
+ });
42844
+ }
42845
+ } catch (e) {
42846
+ ctx.json(res, 500, { error: `Trace state failed: ${e.message}` });
42847
+ }
42848
+ }
42849
+ async function handleCliExercise(ctx, req, res) {
42850
+ try {
42851
+ const body = await ctx.readBody(req);
42852
+ const result = await runCliExerciseInternal(ctx, body || {});
42853
+ ctx.json(res, 200, result);
42854
+ } catch (e) {
42855
+ ctx.json(res, 500, { error: `Exercise failed: ${e.message}` });
42856
+ }
42857
+ }
42858
+ async function handleCliFixtureCapture(ctx, req, res) {
42859
+ try {
42860
+ const body = await ctx.readBody(req);
42861
+ const type = String(body?.type || "");
42862
+ const request = body?.request || {};
42863
+ if (!type) {
42864
+ ctx.json(res, 400, { error: "type required" });
42865
+ return;
42866
+ }
42867
+ if (!request?.text) {
42868
+ ctx.json(res, 400, { error: "request.text required" });
42869
+ return;
42870
+ }
42871
+ const fixtureDir = getCliFixtureDir(ctx, type);
42872
+ fs12.mkdirSync(fixtureDir, { recursive: true });
42873
+ const name = slugifyFixtureName(String(body?.name || `${type}-${Date.now()}`));
42874
+ const result = await runCliExerciseInternal(ctx, { ...request, type });
42875
+ const fixture = {
42876
+ version: 1,
42877
+ kind: "cli-exercise-fixture",
42878
+ name,
42879
+ type,
42880
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
42881
+ providerDir: ctx.providerLoader.findProviderDir(type),
42882
+ providerResolution: result?.providerResolution || null,
42883
+ request: { ...request, type },
42884
+ result,
42885
+ assertions: {
42886
+ mustContainAny: Array.isArray(body?.assertions?.mustContainAny) ? body.assertions.mustContainAny : [],
42887
+ mustNotContainAny: Array.isArray(body?.assertions?.mustNotContainAny) ? body.assertions.mustNotContainAny : [],
42888
+ mustMatchAny: Array.isArray(body?.assertions?.mustMatchAny) ? body.assertions.mustMatchAny : [],
42889
+ mustNotMatchAny: Array.isArray(body?.assertions?.mustNotMatchAny) ? body.assertions.mustNotMatchAny : [],
42890
+ lastAssistantMustContainAny: Array.isArray(body?.assertions?.lastAssistantMustContainAny) ? body.assertions.lastAssistantMustContainAny : [],
42891
+ lastAssistantMustNotContainAny: Array.isArray(body?.assertions?.lastAssistantMustNotContainAny) ? body.assertions.lastAssistantMustNotContainAny : [],
42892
+ lastAssistantMustMatchAny: Array.isArray(body?.assertions?.lastAssistantMustMatchAny) ? body.assertions.lastAssistantMustMatchAny : [],
42893
+ lastAssistantMustNotMatchAny: Array.isArray(body?.assertions?.lastAssistantMustNotMatchAny) ? body.assertions.lastAssistantMustNotMatchAny : [],
42894
+ statusesSeen: Array.isArray(body?.assertions?.statusesSeen) ? body.assertions.statusesSeen : void 0,
42895
+ requireNotTimedOut: body?.assertions?.requireNotTimedOut !== false
42896
+ },
42897
+ notes: typeof body?.notes === "string" ? body.notes : void 0
42898
+ };
42899
+ const filePath = path16.join(fixtureDir, `${name}.json`);
42900
+ fs12.writeFileSync(filePath, JSON.stringify(fixture, null, 2));
42901
+ ctx.json(res, 200, {
42902
+ saved: true,
42903
+ name,
42904
+ path: filePath,
42905
+ fixture,
42906
+ verification: {
42907
+ pass: validateCliFixtureResult(result, fixture.assertions).length === 0,
42908
+ failures: validateCliFixtureResult(result, fixture.assertions)
42909
+ }
42910
+ });
42911
+ } catch (e) {
42912
+ ctx.json(res, 500, { error: `Fixture capture failed: ${e.message}` });
42913
+ }
42914
+ }
42915
+ async function handleCliFixtureList(ctx, type, _req, res) {
42916
+ try {
42917
+ const fixtureDir = getCliFixtureDir(ctx, type);
42918
+ if (!fs12.existsSync(fixtureDir)) {
42919
+ ctx.json(res, 200, { fixtures: [], count: 0 });
42920
+ return;
42921
+ }
42922
+ const fixtures = fs12.readdirSync(fixtureDir).filter((file2) => file2.endsWith(".json")).sort((a, b2) => b2.localeCompare(a, void 0, { numeric: true, sensitivity: "base" })).map((file2) => {
42923
+ const fullPath = path16.join(fixtureDir, file2);
42924
+ try {
42925
+ const raw = JSON.parse(fs12.readFileSync(fullPath, "utf-8"));
42926
+ return {
42927
+ name: raw.name || file2.replace(/\.json$/i, ""),
42928
+ path: fullPath,
42929
+ createdAt: raw.createdAt || null,
42930
+ notes: raw.notes || null,
42931
+ requestText: raw.request?.text || "",
42932
+ assertions: raw.assertions || {}
42933
+ };
42934
+ } catch {
42935
+ return {
42936
+ name: file2.replace(/\.json$/i, ""),
42937
+ path: fullPath,
42938
+ createdAt: null,
42939
+ notes: "Unreadable fixture",
42940
+ requestText: "",
42941
+ assertions: {}
42942
+ };
42943
+ }
42944
+ });
42945
+ ctx.json(res, 200, { fixtures, count: fixtures.length });
42946
+ } catch (e) {
42947
+ ctx.json(res, 500, { error: `Fixture list failed: ${e.message}` });
42948
+ }
42949
+ }
42950
+ async function handleCliFixtureReplay(ctx, req, res) {
42951
+ try {
42952
+ const body = await ctx.readBody(req);
42953
+ const type = String(body?.type || "");
42954
+ const rawName = String(body?.name || "").trim();
42955
+ if (!type || !rawName) {
42956
+ ctx.json(res, 400, { error: "type and name required" });
42957
+ return;
42958
+ }
42959
+ const name = slugifyFixtureName(rawName);
42960
+ const fixture = readCliFixture(ctx, type, name);
42961
+ const result = await runCliExerciseInternal(ctx, {
42962
+ ...fixture.request,
42963
+ type
42964
+ });
42965
+ const assertions = {
42966
+ ...fixture.assertions,
42967
+ ...body?.assertions || {}
42968
+ };
42969
+ const failures = validateCliFixtureResult(result, assertions);
42970
+ ctx.json(res, 200, {
42971
+ replayed: true,
42972
+ pass: failures.length === 0,
42973
+ failures,
42974
+ fixture,
42975
+ result,
42976
+ assertions
42977
+ });
42978
+ } catch (e) {
42979
+ ctx.json(res, 500, { error: `Fixture replay failed: ${e.message}` });
42980
+ }
42981
+ }
41853
42982
  async function handleCliResolve(ctx, req, res) {
41854
42983
  const body = await ctx.readBody(req);
41855
42984
  const { type, buttonIndex, instanceId } = body;
@@ -41924,9 +43053,29 @@ async (params) => {
41924
43053
  ctx.json(res, 500, { error: `Raw send failed: ${e.message}` });
41925
43054
  }
41926
43055
  }
41927
- var fs12 = __toESM2(require("fs"));
41928
- var path16 = __toESM2(require("path"));
43056
+ var fs13 = __toESM2(require("fs"));
43057
+ var path17 = __toESM2(require("path"));
41929
43058
  var os17 = __toESM2(require("os"));
43059
+ function getAutoImplPid(ctx) {
43060
+ const proc = ctx.autoImplProcess;
43061
+ return proc && typeof proc.pid === "number" && proc.pid > 0 ? proc.pid : null;
43062
+ }
43063
+ function isPidAlive(pid) {
43064
+ try {
43065
+ process.kill(pid, 0);
43066
+ return true;
43067
+ } catch (error48) {
43068
+ return error48?.code === "EPERM";
43069
+ }
43070
+ }
43071
+ function clearStaleAutoImplState(ctx, reason) {
43072
+ if (!ctx.autoImplStatus.running && !ctx.autoImplProcess) return;
43073
+ const pid = getAutoImplPid(ctx);
43074
+ if (pid && isPidAlive(pid)) return;
43075
+ ctx.log(`Clearing stale auto-implement state: ${reason}${pid ? ` (pid ${pid})` : ""}`);
43076
+ ctx.autoImplProcess = null;
43077
+ ctx.autoImplStatus.running = false;
43078
+ }
41930
43079
  function getDefaultAutoImplReference(ctx, category, type) {
41931
43080
  if (category === "cli") {
41932
43081
  return type === "codex-cli" ? "claude-cli" : "codex-cli";
@@ -41942,45 +43091,45 @@ async (params) => {
41942
43091
  return fallback?.type || null;
41943
43092
  }
41944
43093
  function getLatestScriptVersionDir(scriptsDir) {
41945
- if (!fs12.existsSync(scriptsDir)) return null;
41946
- const versions = fs12.readdirSync(scriptsDir).filter((d) => {
43094
+ if (!fs13.existsSync(scriptsDir)) return null;
43095
+ const versions = fs13.readdirSync(scriptsDir).filter((d) => {
41947
43096
  try {
41948
- return fs12.statSync(path16.join(scriptsDir, d)).isDirectory();
43097
+ return fs13.statSync(path17.join(scriptsDir, d)).isDirectory();
41949
43098
  } catch {
41950
43099
  return false;
41951
43100
  }
41952
43101
  }).sort((a, b2) => b2.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
41953
43102
  if (versions.length === 0) return null;
41954
- return path16.join(scriptsDir, versions[0]);
43103
+ return path17.join(scriptsDir, versions[0]);
41955
43104
  }
41956
43105
  function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
41957
- const canonicalUserDir = path16.resolve(ctx.providerLoader.getUserProviderDir(category, type));
41958
- const desiredDir = requestedDir ? path16.resolve(requestedDir) : canonicalUserDir;
41959
- const upstreamRoot = path16.resolve(ctx.providerLoader.getUpstreamDir());
41960
- if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path16.sep}`)) {
43106
+ const canonicalUserDir = path17.resolve(ctx.providerLoader.getUserProviderDir(category, type));
43107
+ const desiredDir = requestedDir ? path17.resolve(requestedDir) : canonicalUserDir;
43108
+ const upstreamRoot = path17.resolve(ctx.providerLoader.getUpstreamDir());
43109
+ if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path17.sep}`)) {
41961
43110
  return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
41962
43111
  }
41963
- if (path16.basename(desiredDir) !== type) {
43112
+ if (path17.basename(desiredDir) !== type) {
41964
43113
  return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
41965
43114
  }
41966
43115
  const sourceDir = ctx.findProviderDir(type);
41967
43116
  if (!sourceDir) {
41968
43117
  return { dir: null, reason: `Provider source directory not found for '${type}'` };
41969
43118
  }
41970
- if (!fs12.existsSync(desiredDir)) {
41971
- fs12.mkdirSync(path16.dirname(desiredDir), { recursive: true });
41972
- fs12.cpSync(sourceDir, desiredDir, { recursive: true });
43119
+ if (!fs13.existsSync(desiredDir)) {
43120
+ fs13.mkdirSync(path17.dirname(desiredDir), { recursive: true });
43121
+ fs13.cpSync(sourceDir, desiredDir, { recursive: true });
41973
43122
  ctx.log(`Auto-implement writable copy created: ${desiredDir}`);
41974
43123
  }
41975
- const providerJson = path16.join(desiredDir, "provider.json");
41976
- if (!fs12.existsSync(providerJson)) {
43124
+ const providerJson = path17.join(desiredDir, "provider.json");
43125
+ if (!fs13.existsSync(providerJson)) {
41977
43126
  return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
41978
43127
  }
41979
43128
  try {
41980
- const providerData = JSON.parse(fs12.readFileSync(providerJson, "utf-8"));
43129
+ const providerData = JSON.parse(fs13.readFileSync(providerJson, "utf-8"));
41981
43130
  if (providerData.disableUpstream !== true) {
41982
43131
  providerData.disableUpstream = true;
41983
- fs12.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
43132
+ fs13.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
41984
43133
  }
41985
43134
  } catch (error48) {
41986
43135
  return {
@@ -41993,15 +43142,15 @@ async (params) => {
41993
43142
  function loadAutoImplReferenceScripts(ctx, referenceType) {
41994
43143
  if (!referenceType) return {};
41995
43144
  const refDir = ctx.findProviderDir(referenceType);
41996
- if (!refDir || !fs12.existsSync(refDir)) return {};
43145
+ if (!refDir || !fs13.existsSync(refDir)) return {};
41997
43146
  const referenceScripts = {};
41998
- const scriptsDir = path16.join(refDir, "scripts");
43147
+ const scriptsDir = path17.join(refDir, "scripts");
41999
43148
  const latestDir = getLatestScriptVersionDir(scriptsDir);
42000
43149
  if (!latestDir) return referenceScripts;
42001
- for (const file2 of fs12.readdirSync(latestDir)) {
43150
+ for (const file2 of fs13.readdirSync(latestDir)) {
42002
43151
  if (!file2.endsWith(".js")) continue;
42003
43152
  try {
42004
- referenceScripts[file2] = fs12.readFileSync(path16.join(latestDir, file2), "utf-8");
43153
+ referenceScripts[file2] = fs13.readFileSync(path17.join(latestDir, file2), "utf-8");
42005
43154
  } catch {
42006
43155
  }
42007
43156
  }
@@ -42009,11 +43158,20 @@ async (params) => {
42009
43158
  }
42010
43159
  async function handleAutoImplement(ctx, type, req, res) {
42011
43160
  const body = await ctx.readBody(req);
42012
- const { agent = "claude-cli", functions, reference, model, comment, providerDir: requestedProviderDir } = body;
43161
+ const {
43162
+ agent = "claude-cli",
43163
+ functions,
43164
+ reference,
43165
+ model,
43166
+ comment,
43167
+ providerDir: requestedProviderDir,
43168
+ verification
43169
+ } = body;
42013
43170
  if (!functions || !Array.isArray(functions) || functions.length === 0) {
42014
43171
  ctx.json(res, 400, { error: 'functions[] is required (e.g. ["readChat", "sendMessage"])' });
42015
43172
  return;
42016
43173
  }
43174
+ clearStaleAutoImplState(ctx, "new auto-implement request");
42017
43175
  if (ctx.autoImplStatus.running) {
42018
43176
  ctx.json(res, 409, { error: "Auto-implement already in progress", type: ctx.autoImplStatus.type });
42019
43177
  return;
@@ -42031,7 +43189,55 @@ async (params) => {
42031
43189
  return;
42032
43190
  }
42033
43191
  const providerDir = writableProvider.dir;
43192
+ ctx.autoImplStatus = { running: false, type, progress: [] };
43193
+ if (provider.category === "cli" && verification && (verification.fixtureName || verification.fixtureNames && verification.fixtureNames.length > 0)) {
43194
+ sendAutoImplSSE(ctx, {
43195
+ event: "progress",
43196
+ data: {
43197
+ function: "_preflight",
43198
+ status: "verifying",
43199
+ message: "Running preflight verification before spawning agent..."
43200
+ }
43201
+ });
43202
+ try {
43203
+ const preflight = await runCliAutoImplVerification(ctx, type, verification);
43204
+ sendAutoImplSSE(ctx, { event: "verification", data: preflight });
43205
+ if (preflight.pass) {
43206
+ sendAutoImplSSE(ctx, {
43207
+ event: "complete",
43208
+ data: {
43209
+ success: true,
43210
+ exitCode: 0,
43211
+ functions,
43212
+ message: `\u2705 No-op: exact ${preflight.mode} already passes`,
43213
+ verification: preflight,
43214
+ skipped: true
43215
+ }
43216
+ });
43217
+ ctx.json(res, 200, {
43218
+ started: false,
43219
+ skipped: true,
43220
+ type,
43221
+ functions,
43222
+ providerDir,
43223
+ verification: preflight,
43224
+ message: "Preflight verification already passes. No auto-implement run needed."
43225
+ });
43226
+ return;
43227
+ }
43228
+ } catch (error48) {
43229
+ sendAutoImplSSE(ctx, {
43230
+ event: "progress",
43231
+ data: {
43232
+ function: "_preflight",
43233
+ status: "verify_failed",
43234
+ message: `Preflight verification errored, continuing to agent run: ${error48?.message || error48}`
43235
+ }
43236
+ });
43237
+ }
43238
+ }
42034
43239
  try {
43240
+ ctx.autoImplStatus = { running: true, type, progress: ctx.autoImplStatus.progress };
42035
43241
  const resolvedReference = resolveAutoImplReference(ctx, provider.category, reference, type);
42036
43242
  sendAutoImplSSE(ctx, {
42037
43243
  event: "progress",
@@ -42051,17 +43257,17 @@ async (params) => {
42051
43257
  }
42052
43258
  });
42053
43259
  const referenceScripts = loadAutoImplReferenceScripts(ctx, resolvedReference);
42054
- const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference);
42055
- const tmpDir = path16.join(os17.tmpdir(), "adhdev-autoimpl");
42056
- if (!fs12.existsSync(tmpDir)) fs12.mkdirSync(tmpDir, { recursive: true });
42057
- const promptFile = path16.join(tmpDir, `prompt-${type}-${Date.now()}.md`);
42058
- fs12.writeFileSync(promptFile, prompt, "utf-8");
43260
+ const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference, verification);
43261
+ const tmpDir = path17.join(os17.tmpdir(), "adhdev-autoimpl");
43262
+ if (!fs13.existsSync(tmpDir)) fs13.mkdirSync(tmpDir, { recursive: true });
43263
+ const promptFile = path17.join(tmpDir, `prompt-${type}-${Date.now()}.md`);
43264
+ fs13.writeFileSync(promptFile, prompt, "utf-8");
42059
43265
  ctx.log(`Auto-implement prompt written to ${promptFile} (${prompt.length} chars)`);
42060
43266
  const agentProvider = ctx.providerLoader.resolve(agent) || ctx.providerLoader.getMeta(agent);
42061
43267
  const spawn4 = agentProvider?.spawn;
42062
43268
  if (!spawn4?.command) {
42063
43269
  try {
42064
- fs12.unlinkSync(promptFile);
43270
+ fs13.unlinkSync(promptFile);
42065
43271
  } catch {
42066
43272
  }
42067
43273
  ctx.json(res, 400, { error: `Agent '${agent}' has no spawn config. Select a CLI provider with a spawn configuration.` });
@@ -42070,7 +43276,8 @@ async (params) => {
42070
43276
  const agentCategory = agentProvider?.category;
42071
43277
  if (agentCategory === "acp") {
42072
43278
  sendAutoImplSSE(ctx, { event: "progress", data: { function: "_init", status: "spawning", message: `Spawning ACP agent: ${spawn4.command} ${(spawn4.args || []).join(" ")}` } });
42073
- ctx.autoImplStatus = { running: true, type, progress: [] };
43279
+ ctx.autoImplStatus.running = true;
43280
+ ctx.autoImplStatus.type = type;
42074
43281
  const { ClientSideConnection: ClientSideConnection2, ndJsonStream: ndJsonStream2, PROTOCOL_VERSION: PROTOCOL_VERSION2 } = await Promise.resolve().then(() => (init_acp(), acp_exports));
42075
43282
  const { Readable: Readable2, Writable: Writable2 } = await import("stream");
42076
43283
  const { spawn: spawnFn2 } = await import("child_process");
@@ -42162,7 +43369,7 @@ async (params) => {
42162
43369
  } catch {
42163
43370
  }
42164
43371
  try {
42165
- fs12.unlinkSync(promptFile);
43372
+ fs13.unlinkSync(promptFile);
42166
43373
  } catch {
42167
43374
  }
42168
43375
  ctx.log(`Auto-implement (ACP) ${success2 ? "completed" : "failed"}: ${type} (exit: ${code})`);
@@ -42240,7 +43447,8 @@ async (params) => {
42240
43447
  }
42241
43448
  }
42242
43449
  sendAutoImplSSE(ctx, { event: "progress", data: { function: "_init", status: "spawning", message: `Spawning agent: ${shellCmd.substring(0, 200)}... (prompt: ${prompt.length} chars)` } });
42243
- ctx.autoImplStatus = { running: true, type, progress: [] };
43450
+ ctx.autoImplStatus.running = true;
43451
+ ctx.autoImplStatus.type = type;
42244
43452
  const spawnedAt = Date.now();
42245
43453
  let child;
42246
43454
  let isPty = false;
@@ -42283,6 +43491,7 @@ async (params) => {
42283
43491
  let approvalKeys = { 0: "y\r" };
42284
43492
  let approvalBuffer = "";
42285
43493
  let lastApprovalTime = 0;
43494
+ let completionSignalSeen = false;
42286
43495
  try {
42287
43496
  const { normalizeCliProviderForRuntime: normalizeCliProviderForRuntime2 } = await Promise.resolve().then(() => (init_provider_cli_adapter(), provider_cli_adapter_exports));
42288
43497
  const normalized = normalizeCliProviderForRuntime2(agentProvider);
@@ -42296,6 +43505,7 @@ async (params) => {
42296
43505
  approvalBuffer = (approvalBuffer + cleanData).slice(-1500);
42297
43506
  const elapsed = Date.now() - spawnedAt;
42298
43507
  if (elapsed > 15e3 && cleanData.includes("_PIPELINE_COMPLETE_SIGNAL_")) {
43508
+ completionSignalSeen = true;
42299
43509
  ctx.log(`Agent finished task after ${Math.round(elapsed / 1e3)}s. Terminating interactive CLI session to unblock pipeline.`);
42300
43510
  sendAutoImplSSE(ctx, { event: "output", data: { chunk: `
42301
43511
  [\u{1F916} ADHDev Pipeline] Completion token detected. Proceeding...
@@ -42319,6 +43529,55 @@ async (params) => {
42319
43529
  lastApprovalTime = Date.now();
42320
43530
  }
42321
43531
  };
43532
+ const finalizeCliAutoImpl = async (code) => {
43533
+ ctx.autoImplProcess = null;
43534
+ let success2 = completionSignalSeen || code === 0;
43535
+ let message = success2 ? completionSignalSeen && code !== 0 ? "\u2705 Auto-implement complete (completion signal)" : "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})`;
43536
+ let verificationSummary = null;
43537
+ try {
43538
+ ctx.providerLoader.reload();
43539
+ } catch {
43540
+ }
43541
+ if (provider.category === "cli" && verification) {
43542
+ sendAutoImplSSE(ctx, {
43543
+ event: "progress",
43544
+ data: {
43545
+ function: "_verify",
43546
+ status: "running",
43547
+ message: "Running exact post-patch verification..."
43548
+ }
43549
+ });
43550
+ try {
43551
+ verificationSummary = await runCliAutoImplVerification(ctx, type, verification);
43552
+ sendAutoImplSSE(ctx, { event: "verification", data: verificationSummary });
43553
+ success2 = verificationSummary.pass;
43554
+ message = verificationSummary.pass ? `\u2705 Auto-implement complete (${verificationSummary.mode})` : `\u274C Post-patch verification failed (${verificationSummary.mode}): ${verificationSummary.failures.join("; ") || "unknown failure"}`;
43555
+ } catch (error48) {
43556
+ success2 = false;
43557
+ message = `\u274C Post-patch verification error: ${error48?.message || error48}`;
43558
+ sendAutoImplSSE(ctx, {
43559
+ event: "verification",
43560
+ data: { pass: false, error: error48?.message || String(error48) }
43561
+ });
43562
+ }
43563
+ }
43564
+ ctx.autoImplStatus.running = false;
43565
+ sendAutoImplSSE(ctx, {
43566
+ event: "complete",
43567
+ data: {
43568
+ success: success2,
43569
+ exitCode: code,
43570
+ functions,
43571
+ message,
43572
+ verification: verificationSummary
43573
+ }
43574
+ });
43575
+ try {
43576
+ fs13.unlinkSync(promptFile);
43577
+ } catch {
43578
+ }
43579
+ ctx.log(`Auto-implement ${success2 ? "completed" : "failed"}: ${type} (exit: ${code})${verificationSummary ? ` verify=${verificationSummary.pass ? "pass" : "fail"}` : ""}`);
43580
+ };
42322
43581
  if (isPty) {
42323
43582
  child.onData((data) => {
42324
43583
  stdout += data;
@@ -42330,21 +43589,7 @@ async (params) => {
42330
43589
  sendAutoImplSSE(ctx, { event: "output", data: { chunk: data, stream: "stdout" } });
42331
43590
  });
42332
43591
  child.onExit(({ exitCode: code }) => {
42333
- ctx.autoImplProcess = null;
42334
- ctx.autoImplStatus.running = false;
42335
- const success2 = code === 0;
42336
- sendAutoImplSSE(ctx, {
42337
- event: "complete",
42338
- data: { success: success2, exitCode: code, functions, message: success2 ? "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})` }
42339
- });
42340
- try {
42341
- ctx.providerLoader.reload();
42342
- } catch {
42343
- }
42344
- try {
42345
- fs12.unlinkSync(promptFile);
42346
- } catch {
42347
- }
43592
+ void finalizeCliAutoImpl(code);
42348
43593
  });
42349
43594
  } else {
42350
43595
  child.stdout?.on("data", (d) => {
@@ -42361,27 +43606,7 @@ async (params) => {
42361
43606
  sendAutoImplSSE(ctx, { event: "output", data: { chunk, stream: "stderr" } });
42362
43607
  });
42363
43608
  child.on("exit", (code) => {
42364
- ctx.autoImplProcess = null;
42365
- ctx.autoImplStatus.running = false;
42366
- const success2 = code === 0;
42367
- sendAutoImplSSE(ctx, {
42368
- event: "complete",
42369
- data: {
42370
- success: success2,
42371
- exitCode: code,
42372
- functions,
42373
- message: success2 ? "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})`
42374
- }
42375
- });
42376
- try {
42377
- ctx.providerLoader.reload();
42378
- } catch {
42379
- }
42380
- try {
42381
- fs12.unlinkSync(promptFile);
42382
- } catch {
42383
- }
42384
- ctx.log(`Auto-implement ${success2 ? "completed" : "failed"}: ${type} (exit: ${code})`);
43609
+ void finalizeCliAutoImpl(code);
42385
43610
  });
42386
43611
  }
42387
43612
  ctx.json(res, 202, {
@@ -42398,9 +43623,9 @@ async (params) => {
42398
43623
  ctx.json(res, 500, { error: `Auto-implement failed: ${e.message}` });
42399
43624
  }
42400
43625
  }
42401
- function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, userComment, referenceType) {
43626
+ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, userComment, referenceType, verification) {
42402
43627
  if (provider.category === "cli") {
42403
- return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType);
43628
+ return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification);
42404
43629
  }
42405
43630
  const lines = [];
42406
43631
  lines.push("You are implementing browser automation scripts for an IDE provider.");
@@ -42425,7 +43650,7 @@ async (params) => {
42425
43650
  setMode: "set_mode.js"
42426
43651
  };
42427
43652
  const targetFileNames = new Set(functions.map((fn2) => funcToFile[fn2]).filter(Boolean));
42428
- const scriptsDir = path16.join(providerDir, "scripts");
43653
+ const scriptsDir = path17.join(providerDir, "scripts");
42429
43654
  const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
42430
43655
  if (latestScriptsDir) {
42431
43656
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -42433,10 +43658,10 @@ async (params) => {
42433
43658
  lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
42434
43659
  lines.push("These are the ONLY files you are allowed to modify. Replace the TODO stubs with working implementations.");
42435
43660
  lines.push("");
42436
- for (const file2 of fs12.readdirSync(latestScriptsDir)) {
43661
+ for (const file2 of fs13.readdirSync(latestScriptsDir)) {
42437
43662
  if (file2.endsWith(".js") && targetFileNames.has(file2)) {
42438
43663
  try {
42439
- const content = fs12.readFileSync(path16.join(latestScriptsDir, file2), "utf-8");
43664
+ const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
42440
43665
  lines.push(`### \`${file2}\` \u270F\uFE0F EDIT`);
42441
43666
  lines.push("```javascript");
42442
43667
  lines.push(content);
@@ -42446,14 +43671,14 @@ async (params) => {
42446
43671
  }
42447
43672
  }
42448
43673
  }
42449
- const refFiles = fs12.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
43674
+ const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
42450
43675
  if (refFiles.length > 0) {
42451
43676
  lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
42452
43677
  lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
42453
43678
  lines.push("");
42454
43679
  for (const file2 of refFiles) {
42455
43680
  try {
42456
- const content = fs12.readFileSync(path16.join(latestScriptsDir, file2), "utf-8");
43681
+ const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
42457
43682
  lines.push(`### \`${file2}\` \u{1F512}`);
42458
43683
  lines.push("```javascript");
42459
43684
  lines.push(content);
@@ -42494,11 +43719,11 @@ async (params) => {
42494
43719
  lines.push("");
42495
43720
  }
42496
43721
  }
42497
- const docsDir = path16.join(providerDir, "../../docs");
43722
+ const docsDir = path17.join(providerDir, "../../docs");
42498
43723
  const loadGuide = (name) => {
42499
43724
  try {
42500
- const p = path16.join(docsDir, name);
42501
- if (fs12.existsSync(p)) return fs12.readFileSync(p, "utf-8");
43725
+ const p = path17.join(docsDir, name);
43726
+ if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
42502
43727
  } catch {
42503
43728
  }
42504
43729
  return null;
@@ -42656,8 +43881,69 @@ async (params) => {
42656
43881
  lines.push("Start NOW. Do not ask for permission. Explore the DOM -> Code -> Test.");
42657
43882
  return lines.join("\n");
42658
43883
  }
42659
- function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType) {
43884
+ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification) {
42660
43885
  const lines = [];
43886
+ const defaultExercisePayload = {
43887
+ type,
43888
+ workingDir: providerDir,
43889
+ freshSession: true,
43890
+ autoLaunch: true,
43891
+ autoResolveApprovals: true,
43892
+ approvalButtonIndex: 0,
43893
+ timeoutMs: 45e3,
43894
+ traceLimit: 200,
43895
+ 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."
43896
+ };
43897
+ const exercisePayload = {
43898
+ ...defaultExercisePayload,
43899
+ ...verification?.request || {},
43900
+ type,
43901
+ workingDir: providerDir
43902
+ };
43903
+ const exerciseJson = JSON.stringify(exercisePayload).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
43904
+ const verificationInspectFields = verification?.inspectFields?.length ? verification.inspectFields : [
43905
+ "debug.messages",
43906
+ "trace.entries[].payload.parsedLastAssistant",
43907
+ "trace.entries[].payload.lastAssistant"
43908
+ ];
43909
+ const verificationMustContainAny = verification?.mustContainAny || [];
43910
+ const verificationMustNotContainAny = verification?.mustNotContainAny || [];
43911
+ const verificationMustMatchAny = verification?.mustMatchAny || [];
43912
+ const verificationMustNotMatchAny = verification?.mustNotMatchAny || [];
43913
+ const verificationLastAssistantMustContainAny = verification?.lastAssistantMustContainAny || [];
43914
+ const verificationLastAssistantMustNotContainAny = verification?.lastAssistantMustNotContainAny || [];
43915
+ const verificationLastAssistantMustMatchAny = verification?.lastAssistantMustMatchAny || [];
43916
+ const verificationLastAssistantMustNotMatchAny = verification?.lastAssistantMustNotMatchAny || [];
43917
+ const quotedMustContain = verificationMustContainAny.map((value) => JSON.stringify(value)).join(", ");
43918
+ const quotedMustNotContain = verificationMustNotContainAny.map((value) => JSON.stringify(value)).join(", ");
43919
+ const quotedMustMatch = verificationMustMatchAny.map((value) => JSON.stringify(value)).join(", ");
43920
+ const quotedMustNotMatch = verificationMustNotMatchAny.map((value) => JSON.stringify(value)).join(", ");
43921
+ const quotedLastAssistantMustContain = verificationLastAssistantMustContainAny.map((value) => JSON.stringify(value)).join(", ");
43922
+ const quotedLastAssistantMustNotContain = verificationLastAssistantMustNotContainAny.map((value) => JSON.stringify(value)).join(", ");
43923
+ const quotedLastAssistantMustMatch = verificationLastAssistantMustMatchAny.map((value) => JSON.stringify(value)).join(", ");
43924
+ const quotedLastAssistantMustNotMatch = verificationLastAssistantMustNotMatchAny.map((value) => JSON.stringify(value)).join(", ");
43925
+ const fixtureName = verification?.fixtureName || `${type}-provider-fix`;
43926
+ const fixtureNames = Array.isArray(verification?.fixtureNames) ? verification.fixtureNames.map((value) => String(value || "").trim()).filter(Boolean) : [];
43927
+ const fixtureCaptureJson = JSON.stringify({
43928
+ type,
43929
+ name: fixtureName,
43930
+ request: exercisePayload,
43931
+ assertions: {
43932
+ mustContainAny: verificationMustContainAny,
43933
+ mustNotContainAny: verificationMustNotContainAny,
43934
+ mustMatchAny: verificationMustMatchAny,
43935
+ mustNotMatchAny: verificationMustNotMatchAny,
43936
+ lastAssistantMustContainAny: verificationLastAssistantMustContainAny,
43937
+ lastAssistantMustNotContainAny: verificationLastAssistantMustNotContainAny,
43938
+ lastAssistantMustMatchAny: verificationLastAssistantMustMatchAny,
43939
+ lastAssistantMustNotMatchAny: verificationLastAssistantMustNotMatchAny,
43940
+ requireNotTimedOut: true
43941
+ }
43942
+ }).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
43943
+ const fixtureReplayJson = JSON.stringify({
43944
+ type,
43945
+ name: fixtureName
43946
+ }).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
42661
43947
  lines.push("You are implementing PTY parsing scripts for a CLI provider.");
42662
43948
  lines.push("Be concise. Do NOT explain your reasoning. Edit files directly and verify with the local DevServer.");
42663
43949
  lines.push("");
@@ -42671,7 +43957,7 @@ async (params) => {
42671
43957
  parseApproval: "parse_approval.js"
42672
43958
  };
42673
43959
  const targetFileNames = new Set(functions.map((fn2) => funcToFile[fn2]).filter(Boolean));
42674
- const scriptsDir = path16.join(providerDir, "scripts");
43960
+ const scriptsDir = path17.join(providerDir, "scripts");
42675
43961
  const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
42676
43962
  if (latestScriptsDir) {
42677
43963
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -42679,11 +43965,11 @@ async (params) => {
42679
43965
  lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
42680
43966
  lines.push("These are the ONLY files you are allowed to modify. Replace TODO or heuristic-only logic with working PTY-aware implementations.");
42681
43967
  lines.push("");
42682
- for (const file2 of fs12.readdirSync(latestScriptsDir)) {
43968
+ for (const file2 of fs13.readdirSync(latestScriptsDir)) {
42683
43969
  if (!file2.endsWith(".js")) continue;
42684
43970
  if (!targetFileNames.has(file2)) continue;
42685
43971
  try {
42686
- const content = fs12.readFileSync(path16.join(latestScriptsDir, file2), "utf-8");
43972
+ const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
42687
43973
  lines.push(`### \`${file2}\` \u270F\uFE0F EDIT`);
42688
43974
  lines.push("```javascript");
42689
43975
  lines.push(content);
@@ -42692,14 +43978,14 @@ async (params) => {
42692
43978
  } catch {
42693
43979
  }
42694
43980
  }
42695
- const refFiles = fs12.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
43981
+ const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
42696
43982
  if (refFiles.length > 0) {
42697
43983
  lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
42698
43984
  lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
42699
43985
  lines.push("");
42700
43986
  for (const file2 of refFiles) {
42701
43987
  try {
42702
- const content = fs12.readFileSync(path16.join(latestScriptsDir, file2), "utf-8");
43988
+ const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
42703
43989
  lines.push(`### \`${file2}\` \u{1F512}`);
42704
43990
  lines.push("```javascript");
42705
43991
  lines.push(content);
@@ -42732,17 +44018,17 @@ async (params) => {
42732
44018
  lines.push("");
42733
44019
  }
42734
44020
  }
42735
- const docsDir = path16.join(providerDir, "../../docs");
44021
+ const docsDir = path17.join(providerDir, "../../docs");
42736
44022
  const loadGuide = (name) => {
42737
44023
  try {
42738
- const p = path16.join(docsDir, name);
42739
- if (fs12.existsSync(p)) return fs12.readFileSync(p, "utf-8");
44024
+ const p = path17.join(docsDir, name);
44025
+ if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
42740
44026
  } catch {
42741
44027
  }
42742
44028
  return null;
42743
44029
  };
42744
44030
  const providerGuide = loadGuide("PROVIDER_GUIDE.md");
42745
- if (providerGuide) {
44031
+ if (providerGuide && provider.category !== "cli") {
42746
44032
  lines.push("## Documentation: PROVIDER_GUIDE.md");
42747
44033
  lines.push("```markdown");
42748
44034
  lines.push(providerGuide);
@@ -42784,6 +44070,11 @@ async (params) => {
42784
44070
  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.");
42785
44071
  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.");
42786
44072
  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.");
44073
+ 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.');
44074
+ 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.");
44075
+ 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.');
44076
+ 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.");
44077
+ 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.");
42787
44078
  lines.push("");
42788
44079
  lines.push("## Task");
42789
44080
  lines.push(`Edit files in \`${providerDir}\` to implement: **${functions.join(", ")}**`);
@@ -42791,35 +44082,145 @@ async (params) => {
42791
44082
  lines.push("## Verification API");
42792
44083
  lines.push("Use the DevServer CLI debug endpoints, not DOM/CDP routes.");
42793
44084
  lines.push("");
42794
- lines.push("### 1. Launch the target CLI");
44085
+ lines.push("### 1. Preferred: run a full autonomous repro");
44086
+ 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.");
42795
44087
  lines.push("```bash");
42796
- lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/launch \\`);
44088
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
42797
44089
  lines.push(' -H "Content-Type: application/json" \\');
42798
- lines.push(` -d '{"type":"${type}","workingDir":"${providerDir.replace(/\\/g, "\\\\")}"}'`);
44090
+ lines.push(` -d '${exerciseJson}'`);
42799
44091
  lines.push("```");
42800
44092
  lines.push("");
44093
+ if (verification?.description) {
44094
+ lines.push("Verification intent:");
44095
+ lines.push(verification.description);
44096
+ lines.push("");
44097
+ }
44098
+ lines.push("Read the JSON response carefully. It already includes:");
44099
+ lines.push("1. `instanceId`");
44100
+ lines.push("2. `statusesSeen` and `approvalsResolved`");
44101
+ lines.push("3. `debug` for the final settled state");
44102
+ lines.push("4. `trace.entries` for the repro turn");
44103
+ lines.push("");
44104
+ lines.push("Save the response to a temp file and inspect the exact parsed transcript fields before editing:");
44105
+ lines.push("```bash");
44106
+ lines.push(`EXERCISE_JSON=$(mktemp)`);
44107
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
44108
+ lines.push(' -H "Content-Type: application/json" \\');
44109
+ lines.push(` -d '${exerciseJson}' > "$EXERCISE_JSON"`);
44110
+ lines.push(`jq '{timedOut,statusesSeen,approvalsResolved,inspect:{${verificationInspectFields.map((field, index) => `f${index + 1}: .${field}`).join(", ")}}}' "$EXERCISE_JSON"`);
44111
+ lines.push("```");
44112
+ lines.push("");
44113
+ 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) {
44114
+ 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.");
44115
+ lines.push("```bash");
44116
+ if (verificationMustContainAny.length > 0) {
44117
+ 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"`);
44118
+ }
44119
+ if (verificationMustNotContainAny.length > 0) {
44120
+ 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"`);
44121
+ }
44122
+ if (verificationMustMatchAny.length > 0) {
44123
+ 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"`);
44124
+ }
44125
+ if (verificationMustNotMatchAny.length > 0) {
44126
+ 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"`);
44127
+ }
44128
+ if (verificationLastAssistantMustContainAny.length > 0) {
44129
+ 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"`);
44130
+ }
44131
+ if (verificationLastAssistantMustNotContainAny.length > 0) {
44132
+ 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"`);
44133
+ }
44134
+ if (verificationLastAssistantMustMatchAny.length > 0) {
44135
+ 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"`);
44136
+ }
44137
+ if (verificationLastAssistantMustNotMatchAny.length > 0) {
44138
+ 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"`);
44139
+ }
44140
+ lines.push("```");
44141
+ lines.push("");
44142
+ }
44143
+ 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.");
44144
+ lines.push("");
44145
+ lines.push("### 1b. Persist or replay the exact repro as a reusable fixture");
44146
+ if (fixtureNames.length > 0) {
44147
+ 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(", ")}.`);
44148
+ for (const name of fixtureNames) {
44149
+ const replayJson = JSON.stringify({ type, name }).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
44150
+ lines.push("```bash");
44151
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
44152
+ lines.push(' -H "Content-Type: application/json" \\');
44153
+ lines.push(` -d '${replayJson}'`);
44154
+ lines.push("```");
44155
+ lines.push("");
44156
+ }
44157
+ lines.push("Do not create new fixtures unless one of the listed fixtures is missing or stale.");
44158
+ } else if (verification?.fixtureName) {
44159
+ 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.`);
44160
+ lines.push("```bash");
44161
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
44162
+ lines.push(' -H "Content-Type: application/json" \\');
44163
+ lines.push(` -d '${fixtureReplayJson}'`);
44164
+ lines.push("```");
44165
+ lines.push("");
44166
+ 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.");
44167
+ lines.push("```bash");
44168
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
44169
+ lines.push(' -H "Content-Type: application/json" \\');
44170
+ lines.push(` -d '${fixtureCaptureJson}'`);
44171
+ lines.push("```");
44172
+ } else {
44173
+ lines.push("Capture the exact exercise once before editing. After patching, replay THIS fixture and do not declare success unless replay passes.");
44174
+ lines.push("```bash");
44175
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
44176
+ lines.push(' -H "Content-Type: application/json" \\');
44177
+ lines.push(` -d '${fixtureCaptureJson}'`);
44178
+ lines.push("");
44179
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
44180
+ lines.push(' -H "Content-Type: application/json" \\');
44181
+ lines.push(` -d '${fixtureReplayJson}'`);
44182
+ lines.push("```");
44183
+ }
44184
+ lines.push("");
44185
+ 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.");
44186
+ lines.push("");
42801
44187
  lines.push("### 2. Inspect parsed + raw adapter state");
42802
44188
  lines.push("```bash");
44189
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/launch \\`);
44190
+ lines.push(' -H "Content-Type: application/json" \\');
44191
+ lines.push(` -d '{"type":"${type}","workingDir":"${providerDir.replace(/\\/g, "\\\\")}"}'`);
42803
44192
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
44193
+ lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
42804
44194
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
42805
44195
  lines.push("```");
42806
44196
  lines.push("");
44197
+ lines.push("The CLI trace endpoint is the primary debugging source. Read it BEFORE editing any parser code.");
44198
+ lines.push("Use the trace timeline to find the latest `settled` or `commit_transcript` frame for the repro turn and inspect these fields first:");
44199
+ lines.push("1. `payload.screenText`");
44200
+ lines.push("2. `payload.detectStatus` and `payload.parsedStatus`");
44201
+ lines.push("3. `payload.parsedLastAssistant`");
44202
+ lines.push("4. `payload.approval` / `payload.parsedActiveModal`");
44203
+ lines.push("5. `payload.rawPreview` only when control-sequence residue matters");
44204
+ lines.push("");
42807
44205
  lines.push("The debug payload should be read in this priority order:");
42808
44206
  lines.push("1. `screenText` / current visible state");
42809
44207
  lines.push("2. parsed `status`, `messages`, `activeModal`");
42810
44208
  lines.push("3. `rawBuffer` only for style/control-sequence cues");
42811
44209
  lines.push("4. `buffer` only when the current screen is insufficient");
42812
44210
  lines.push("");
42813
- lines.push("Extract the current `instanceId` from the launch or status response and keep using it below.");
44211
+ 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.");
44212
+ lines.push("Do NOT guess based only on the final chat bubble or a truncated UI preview.");
42814
44213
  lines.push("");
42815
- lines.push("### 3. Send a realistic approval-triggering prompt");
44214
+ lines.push("Extract the current `instanceId` from the exercise, launch, or status response and keep using it below.");
44215
+ lines.push("");
44216
+ lines.push("### 3. Manual fallback only: send a realistic approval-triggering prompt");
42816
44217
  lines.push("```bash");
42817
44218
  lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/send \\`);
42818
44219
  lines.push(' -H "Content-Type: application/json" \\');
42819
44220
  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."}'`);
42820
44221
  lines.push("```");
42821
44222
  lines.push("");
42822
- lines.push("### 4. If approval appears, resolve it until the CLI reaches idle");
44223
+ lines.push("### 4. Manual fallback only: if approval appears, resolve it until the CLI reaches idle");
42823
44224
  lines.push("```bash");
42824
44225
  lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/resolve \\`);
42825
44226
  lines.push(' -H "Content-Type: application/json" \\');
@@ -42829,10 +44230,14 @@ async (params) => {
42829
44230
  lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","keys":"1"}'`);
42830
44231
  lines.push("```");
42831
44232
  lines.push("");
42832
- 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.");
44233
+ 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.");
42833
44234
  lines.push("");
42834
44235
  lines.push("### Patch Discipline");
42835
44236
  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.");
44237
+ 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.");
44238
+ 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.");
44239
+ 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.");
44240
+ 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.');
42836
44241
  lines.push("");
42837
44242
  lines.push("### 5. Verify the side effects outside the CLI");
42838
44243
  lines.push("```bash");
@@ -42857,6 +44262,8 @@ async (params) => {
42857
44262
  lines.push("7. Re-run the debug endpoints after edits. Do NOT finish until the parsed result looks correct.");
42858
44263
  lines.push("8. Confirm the parser still works after a redraw or scroll change without duplicating transcript history.");
42859
44264
  lines.push("9. Confirm the implementation prefers current-screen signals over stale history when both are present.");
44265
+ 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.");
44266
+ 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.");
42860
44267
  lines.push("");
42861
44268
  if (userComment) {
42862
44269
  lines.push("## \u26A0\uFE0F User Instructions (HIGH PRIORITY)");
@@ -42865,10 +44272,11 @@ async (params) => {
42865
44272
  lines.push(userComment);
42866
44273
  lines.push("");
42867
44274
  }
42868
- lines.push("Start NOW. Launch the CLI, inspect PTY state, edit the scripts, and verify via the CLI debug endpoints.");
44275
+ lines.push("Start NOW. Launch the CLI, inspect the trace and PTY state, edit the scripts, and verify via the CLI debug + trace endpoints.");
42869
44276
  return lines.join("\n");
42870
44277
  }
42871
44278
  function handleAutoImplSSE(ctx, type, req, res) {
44279
+ clearStaleAutoImplState(ctx, "SSE connection opened");
42872
44280
  res.writeHead(200, {
42873
44281
  "Content-Type": "text/event-stream",
42874
44282
  "Cache-Control": "no-cache",
@@ -42890,6 +44298,7 @@ data: ${JSON.stringify(p.data)}
42890
44298
  });
42891
44299
  }
42892
44300
  function handleAutoImplCancel(ctx, _type, _req, res) {
44301
+ clearStaleAutoImplState(ctx, "cancel request");
42893
44302
  if (ctx.autoImplProcess) {
42894
44303
  ctx.autoImplProcess.kill("SIGTERM");
42895
44304
  setTimeout(() => {
@@ -42971,11 +44380,16 @@ data: ${JSON.stringify(msg.data)}
42971
44380
  { method: "GET", pattern: "/api/cli/status", handler: (q2, s15) => this.handleCliStatus(q2, s15) },
42972
44381
  { method: "POST", pattern: "/api/cli/launch", handler: (q2, s15) => this.handleCliLaunch(q2, s15) },
42973
44382
  { method: "POST", pattern: "/api/cli/send", handler: (q2, s15) => this.handleCliSend(q2, s15) },
44383
+ { method: "POST", pattern: "/api/cli/exercise", handler: (q2, s15) => this.handleCliExercise(q2, s15) },
44384
+ { method: "POST", pattern: "/api/cli/fixture/capture", handler: (q2, s15) => this.handleCliFixtureCapture(q2, s15) },
44385
+ { method: "POST", pattern: "/api/cli/fixture/replay", handler: (q2, s15) => this.handleCliFixtureReplay(q2, s15) },
42974
44386
  { method: "POST", pattern: "/api/cli/resolve", handler: (q2, s15) => this.handleCliResolve(q2, s15) },
42975
44387
  { method: "POST", pattern: "/api/cli/raw", handler: (q2, s15) => this.handleCliRaw(q2, s15) },
42976
44388
  { method: "POST", pattern: "/api/cli/stop", handler: (q2, s15) => this.handleCliStop(q2, s15) },
42977
44389
  { method: "GET", pattern: "/api/cli/events", handler: (q2, s15) => this.handleCliSSE(q2, s15) },
42978
44390
  { method: "GET", pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q2, s15, p) => this.handleCliDebug(p[0], q2, s15) },
44391
+ { method: "GET", pattern: /^\/api\/cli\/trace\/([^/]+)$/, handler: (q2, s15, p) => this.handleCliTrace(p[0], q2, s15) },
44392
+ { method: "GET", pattern: /^\/api\/cli\/fixtures\/([^/]+)$/, handler: (q2, s15, p) => this.handleCliFixtureList(p[0], q2, s15) },
42979
44393
  // Dynamic routes (provider :type param)
42980
44394
  { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q2, s15, p) => this.handleRunScript(p[0], q2, s15) },
42981
44395
  { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q2, s15, p) => this.handleListFiles(p[0], q2, s15) },
@@ -43009,8 +44423,8 @@ data: ${JSON.stringify(msg.data)}
43009
44423
  }
43010
44424
  getEndpointList() {
43011
44425
  return this.routes.map((r) => {
43012
- const path18 = typeof r.pattern === "string" ? r.pattern : r.pattern.source.replace(/\\\//g, "/").replace(/\(\[.*?\]\+\)/g, ":type").replace(/[\^$]/g, "");
43013
- return `${r.method.padEnd(5)} ${path18}`;
44426
+ const path19 = typeof r.pattern === "string" ? r.pattern : r.pattern.source.replace(/\\\//g, "/").replace(/\(\[.*?\]\+\)/g, ":type").replace(/[\^$]/g, "");
44427
+ return `${r.method.padEnd(5)} ${path19}`;
43014
44428
  });
43015
44429
  }
43016
44430
  async start(port = DEV_SERVER_PORT) {
@@ -43292,12 +44706,12 @@ data: ${JSON.stringify(msg.data)}
43292
44706
  // ─── DevConsole SPA ───
43293
44707
  getConsoleDistDir() {
43294
44708
  const candidates = [
43295
- path17.resolve(__dirname, "../../web-devconsole/dist"),
43296
- path17.resolve(__dirname, "../../../web-devconsole/dist"),
43297
- path17.join(process.cwd(), "packages/web-devconsole/dist")
44709
+ path18.resolve(__dirname, "../../web-devconsole/dist"),
44710
+ path18.resolve(__dirname, "../../../web-devconsole/dist"),
44711
+ path18.join(process.cwd(), "packages/web-devconsole/dist")
43298
44712
  ];
43299
44713
  for (const dir of candidates) {
43300
- if (fs13.existsSync(path17.join(dir, "index.html"))) return dir;
44714
+ if (fs14.existsSync(path18.join(dir, "index.html"))) return dir;
43301
44715
  }
43302
44716
  return null;
43303
44717
  }
@@ -43307,9 +44721,9 @@ data: ${JSON.stringify(msg.data)}
43307
44721
  this.json(res, 500, { error: "DevConsole not found. Run: npm run build -w packages/web-devconsole" });
43308
44722
  return;
43309
44723
  }
43310
- const htmlPath = path17.join(distDir, "index.html");
44724
+ const htmlPath = path18.join(distDir, "index.html");
43311
44725
  try {
43312
- const html = fs13.readFileSync(htmlPath, "utf-8");
44726
+ const html = fs14.readFileSync(htmlPath, "utf-8");
43313
44727
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
43314
44728
  res.end(html);
43315
44729
  } catch (e) {
@@ -43332,15 +44746,15 @@ data: ${JSON.stringify(msg.data)}
43332
44746
  this.json(res, 404, { error: "Not found" });
43333
44747
  return;
43334
44748
  }
43335
- const safePath = path17.normalize(pathname).replace(/^\.\.\//, "");
43336
- const filePath = path17.join(distDir, safePath);
44749
+ const safePath = path18.normalize(pathname).replace(/^\.\.\//, "");
44750
+ const filePath = path18.join(distDir, safePath);
43337
44751
  if (!filePath.startsWith(distDir)) {
43338
44752
  this.json(res, 403, { error: "Forbidden" });
43339
44753
  return;
43340
44754
  }
43341
44755
  try {
43342
- const content = fs13.readFileSync(filePath);
43343
- const ext = path17.extname(filePath);
44756
+ const content = fs14.readFileSync(filePath);
44757
+ const ext = path18.extname(filePath);
43344
44758
  const contentType = _DevServer.MIME_MAP[ext] || "application/octet-stream";
43345
44759
  res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "public, max-age=31536000, immutable" });
43346
44760
  res.end(content);
@@ -43448,14 +44862,14 @@ data: ${JSON.stringify(msg.data)}
43448
44862
  const files = [];
43449
44863
  const scan = (d, prefix) => {
43450
44864
  try {
43451
- for (const entry of fs13.readdirSync(d, { withFileTypes: true })) {
44865
+ for (const entry of fs14.readdirSync(d, { withFileTypes: true })) {
43452
44866
  if (entry.name.startsWith(".") || entry.name.endsWith(".bak")) continue;
43453
44867
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
43454
44868
  if (entry.isDirectory()) {
43455
44869
  files.push({ path: rel, size: 0, type: "dir" });
43456
- scan(path17.join(d, entry.name), rel);
44870
+ scan(path18.join(d, entry.name), rel);
43457
44871
  } else {
43458
- const stat4 = fs13.statSync(path17.join(d, entry.name));
44872
+ const stat4 = fs14.statSync(path18.join(d, entry.name));
43459
44873
  files.push({ path: rel, size: stat4.size, type: "file" });
43460
44874
  }
43461
44875
  }
@@ -43478,16 +44892,16 @@ data: ${JSON.stringify(msg.data)}
43478
44892
  this.json(res, 404, { error: `Provider directory not found: ${type}` });
43479
44893
  return;
43480
44894
  }
43481
- const fullPath = path17.resolve(dir, path17.normalize(filePath));
44895
+ const fullPath = path18.resolve(dir, path18.normalize(filePath));
43482
44896
  if (!fullPath.startsWith(dir)) {
43483
44897
  this.json(res, 403, { error: "Forbidden" });
43484
44898
  return;
43485
44899
  }
43486
- if (!fs13.existsSync(fullPath) || fs13.statSync(fullPath).isDirectory()) {
44900
+ if (!fs14.existsSync(fullPath) || fs14.statSync(fullPath).isDirectory()) {
43487
44901
  this.json(res, 404, { error: `File not found: ${filePath}` });
43488
44902
  return;
43489
44903
  }
43490
- const content = fs13.readFileSync(fullPath, "utf-8");
44904
+ const content = fs14.readFileSync(fullPath, "utf-8");
43491
44905
  this.json(res, 200, { type, path: filePath, content, lines: content.split("\n").length });
43492
44906
  }
43493
44907
  /** POST /api/providers/:type/file — write a file { path, content } */
@@ -43503,15 +44917,15 @@ data: ${JSON.stringify(msg.data)}
43503
44917
  this.json(res, 404, { error: `Provider directory not found: ${type}` });
43504
44918
  return;
43505
44919
  }
43506
- const fullPath = path17.resolve(dir, path17.normalize(filePath));
44920
+ const fullPath = path18.resolve(dir, path18.normalize(filePath));
43507
44921
  if (!fullPath.startsWith(dir)) {
43508
44922
  this.json(res, 403, { error: "Forbidden" });
43509
44923
  return;
43510
44924
  }
43511
44925
  try {
43512
- if (fs13.existsSync(fullPath)) fs13.copyFileSync(fullPath, fullPath + ".bak");
43513
- fs13.mkdirSync(path17.dirname(fullPath), { recursive: true });
43514
- fs13.writeFileSync(fullPath, content, "utf-8");
44926
+ if (fs14.existsSync(fullPath)) fs14.copyFileSync(fullPath, fullPath + ".bak");
44927
+ fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
44928
+ fs14.writeFileSync(fullPath, content, "utf-8");
43515
44929
  this.log(`File saved: ${fullPath} (${content.length} chars)`);
43516
44930
  this.providerLoader.reload();
43517
44931
  this.json(res, 200, { saved: true, path: filePath, chars: content.length });
@@ -43527,9 +44941,9 @@ data: ${JSON.stringify(msg.data)}
43527
44941
  return;
43528
44942
  }
43529
44943
  for (const name of ["scripts.js", "provider.json"]) {
43530
- const p = path17.join(dir, name);
43531
- if (fs13.existsSync(p)) {
43532
- const source = fs13.readFileSync(p, "utf-8");
44944
+ const p = path18.join(dir, name);
44945
+ if (fs14.existsSync(p)) {
44946
+ const source = fs14.readFileSync(p, "utf-8");
43533
44947
  this.json(res, 200, { type, path: p, source, lines: source.split("\n").length });
43534
44948
  return;
43535
44949
  }
@@ -43548,11 +44962,11 @@ data: ${JSON.stringify(msg.data)}
43548
44962
  this.json(res, 404, { error: `Provider not found: ${type}` });
43549
44963
  return;
43550
44964
  }
43551
- const target = fs13.existsSync(path17.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
43552
- const targetPath = path17.join(dir, target);
44965
+ const target = fs14.existsSync(path18.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
44966
+ const targetPath = path18.join(dir, target);
43553
44967
  try {
43554
- if (fs13.existsSync(targetPath)) fs13.copyFileSync(targetPath, targetPath + ".bak");
43555
- fs13.writeFileSync(targetPath, source, "utf-8");
44968
+ if (fs14.existsSync(targetPath)) fs14.copyFileSync(targetPath, targetPath + ".bak");
44969
+ fs14.writeFileSync(targetPath, source, "utf-8");
43556
44970
  this.log(`Saved provider: ${targetPath} (${source.length} chars)`);
43557
44971
  this.providerLoader.reload();
43558
44972
  this.json(res, 200, { saved: true, path: targetPath, chars: source.length });
@@ -43709,21 +45123,21 @@ data: ${JSON.stringify(msg.data)}
43709
45123
  }
43710
45124
  let targetDir;
43711
45125
  targetDir = this.providerLoader.getUserProviderDir(category, type);
43712
- const jsonPath = path17.join(targetDir, "provider.json");
43713
- if (fs13.existsSync(jsonPath)) {
45126
+ const jsonPath = path18.join(targetDir, "provider.json");
45127
+ if (fs14.existsSync(jsonPath)) {
43714
45128
  this.json(res, 409, { error: `Provider already exists at ${targetDir}`, path: targetDir });
43715
45129
  return;
43716
45130
  }
43717
45131
  try {
43718
45132
  const result = generateFiles(type, name, category, { cdpPorts, cli, processName, installPath, binary, extensionId, version: version2, osPaths, processNames });
43719
- fs13.mkdirSync(targetDir, { recursive: true });
43720
- fs13.writeFileSync(jsonPath, result["provider.json"], "utf-8");
45133
+ fs14.mkdirSync(targetDir, { recursive: true });
45134
+ fs14.writeFileSync(jsonPath, result["provider.json"], "utf-8");
43721
45135
  const createdFiles = ["provider.json"];
43722
45136
  if (result.files) {
43723
45137
  for (const [relPath, content] of Object.entries(result.files)) {
43724
- const fullPath = path17.join(targetDir, relPath);
43725
- fs13.mkdirSync(path17.dirname(fullPath), { recursive: true });
43726
- fs13.writeFileSync(fullPath, content, "utf-8");
45138
+ const fullPath = path18.join(targetDir, relPath);
45139
+ fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
45140
+ fs14.writeFileSync(fullPath, content, "utf-8");
43727
45141
  createdFiles.push(relPath);
43728
45142
  }
43729
45143
  }
@@ -43772,45 +45186,45 @@ data: ${JSON.stringify(msg.data)}
43772
45186
  }
43773
45187
  // ─── Phase 2: Auto-Implement Backend ───
43774
45188
  getLatestScriptVersionDir(scriptsDir) {
43775
- if (!fs13.existsSync(scriptsDir)) return null;
43776
- const versions = fs13.readdirSync(scriptsDir).filter((d) => {
45189
+ if (!fs14.existsSync(scriptsDir)) return null;
45190
+ const versions = fs14.readdirSync(scriptsDir).filter((d) => {
43777
45191
  try {
43778
- return fs13.statSync(path17.join(scriptsDir, d)).isDirectory();
45192
+ return fs14.statSync(path18.join(scriptsDir, d)).isDirectory();
43779
45193
  } catch {
43780
45194
  return false;
43781
45195
  }
43782
45196
  }).sort((a, b2) => b2.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
43783
45197
  if (versions.length === 0) return null;
43784
- return path17.join(scriptsDir, versions[0]);
45198
+ return path18.join(scriptsDir, versions[0]);
43785
45199
  }
43786
45200
  resolveAutoImplWritableProviderDir(category, type, requestedDir) {
43787
- const canonicalUserDir = path17.resolve(this.providerLoader.getUserProviderDir(category, type));
43788
- const desiredDir = requestedDir ? path17.resolve(requestedDir) : canonicalUserDir;
43789
- const upstreamRoot = path17.resolve(this.providerLoader.getUpstreamDir());
43790
- if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path17.sep}`)) {
45201
+ const canonicalUserDir = path18.resolve(this.providerLoader.getUserProviderDir(category, type));
45202
+ const desiredDir = requestedDir ? path18.resolve(requestedDir) : canonicalUserDir;
45203
+ const upstreamRoot = path18.resolve(this.providerLoader.getUpstreamDir());
45204
+ if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path18.sep}`)) {
43791
45205
  return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
43792
45206
  }
43793
- if (path17.basename(desiredDir) !== type) {
45207
+ if (path18.basename(desiredDir) !== type) {
43794
45208
  return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
43795
45209
  }
43796
45210
  const sourceDir = this.findProviderDir(type);
43797
45211
  if (!sourceDir) {
43798
45212
  return { dir: null, reason: `Provider source directory not found for '${type}'` };
43799
45213
  }
43800
- if (!fs13.existsSync(desiredDir)) {
43801
- fs13.mkdirSync(path17.dirname(desiredDir), { recursive: true });
43802
- fs13.cpSync(sourceDir, desiredDir, { recursive: true });
45214
+ if (!fs14.existsSync(desiredDir)) {
45215
+ fs14.mkdirSync(path18.dirname(desiredDir), { recursive: true });
45216
+ fs14.cpSync(sourceDir, desiredDir, { recursive: true });
43803
45217
  this.log(`Auto-implement writable copy created: ${desiredDir}`);
43804
45218
  }
43805
- const providerJson = path17.join(desiredDir, "provider.json");
43806
- if (!fs13.existsSync(providerJson)) {
45219
+ const providerJson = path18.join(desiredDir, "provider.json");
45220
+ if (!fs14.existsSync(providerJson)) {
43807
45221
  return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
43808
45222
  }
43809
45223
  try {
43810
- const providerData = JSON.parse(fs13.readFileSync(providerJson, "utf-8"));
45224
+ const providerData = JSON.parse(fs14.readFileSync(providerJson, "utf-8"));
43811
45225
  if (providerData.disableUpstream !== true) {
43812
45226
  providerData.disableUpstream = true;
43813
- fs13.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
45227
+ fs14.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
43814
45228
  }
43815
45229
  } catch (error48) {
43816
45230
  return {
@@ -43850,7 +45264,7 @@ data: ${JSON.stringify(msg.data)}
43850
45264
  setMode: "set_mode.js"
43851
45265
  };
43852
45266
  const targetFileNames = new Set(functions.map((fn2) => funcToFile[fn2]).filter(Boolean));
43853
- const scriptsDir = path17.join(providerDir, "scripts");
45267
+ const scriptsDir = path18.join(providerDir, "scripts");
43854
45268
  const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
43855
45269
  if (latestScriptsDir) {
43856
45270
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -43858,10 +45272,10 @@ data: ${JSON.stringify(msg.data)}
43858
45272
  lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
43859
45273
  lines.push("These are the ONLY files you are allowed to modify. Replace the TODO stubs with working implementations.");
43860
45274
  lines.push("");
43861
- for (const file2 of fs13.readdirSync(latestScriptsDir)) {
45275
+ for (const file2 of fs14.readdirSync(latestScriptsDir)) {
43862
45276
  if (file2.endsWith(".js") && targetFileNames.has(file2)) {
43863
45277
  try {
43864
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
45278
+ const content = fs14.readFileSync(path18.join(latestScriptsDir, file2), "utf-8");
43865
45279
  lines.push(`### \`${file2}\` \u270F\uFE0F EDIT`);
43866
45280
  lines.push("```javascript");
43867
45281
  lines.push(content);
@@ -43871,14 +45285,14 @@ data: ${JSON.stringify(msg.data)}
43871
45285
  }
43872
45286
  }
43873
45287
  }
43874
- const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
45288
+ const refFiles = fs14.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
43875
45289
  if (refFiles.length > 0) {
43876
45290
  lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
43877
45291
  lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
43878
45292
  lines.push("");
43879
45293
  for (const file2 of refFiles) {
43880
45294
  try {
43881
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
45295
+ const content = fs14.readFileSync(path18.join(latestScriptsDir, file2), "utf-8");
43882
45296
  lines.push(`### \`${file2}\` \u{1F512}`);
43883
45297
  lines.push("```javascript");
43884
45298
  lines.push(content);
@@ -43919,11 +45333,11 @@ data: ${JSON.stringify(msg.data)}
43919
45333
  lines.push("");
43920
45334
  }
43921
45335
  }
43922
- const docsDir = path17.join(providerDir, "../../docs");
45336
+ const docsDir = path18.join(providerDir, "../../docs");
43923
45337
  const loadGuide = (name) => {
43924
45338
  try {
43925
- const p = path17.join(docsDir, name);
43926
- if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
45339
+ const p = path18.join(docsDir, name);
45340
+ if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
43927
45341
  } catch {
43928
45342
  }
43929
45343
  return null;
@@ -44096,7 +45510,7 @@ data: ${JSON.stringify(msg.data)}
44096
45510
  parseApproval: "parse_approval.js"
44097
45511
  };
44098
45512
  const targetFileNames = new Set(functions.map((fn2) => funcToFile[fn2]).filter(Boolean));
44099
- const scriptsDir = path17.join(providerDir, "scripts");
45513
+ const scriptsDir = path18.join(providerDir, "scripts");
44100
45514
  const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
44101
45515
  if (latestScriptsDir) {
44102
45516
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -44104,11 +45518,11 @@ data: ${JSON.stringify(msg.data)}
44104
45518
  lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
44105
45519
  lines.push("These are the ONLY files you are allowed to modify. Replace TODO or heuristic-only logic with working PTY-aware implementations.");
44106
45520
  lines.push("");
44107
- for (const file2 of fs13.readdirSync(latestScriptsDir)) {
45521
+ for (const file2 of fs14.readdirSync(latestScriptsDir)) {
44108
45522
  if (!file2.endsWith(".js")) continue;
44109
45523
  if (!targetFileNames.has(file2)) continue;
44110
45524
  try {
44111
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
45525
+ const content = fs14.readFileSync(path18.join(latestScriptsDir, file2), "utf-8");
44112
45526
  lines.push(`### \`${file2}\` \u270F\uFE0F EDIT`);
44113
45527
  lines.push("```javascript");
44114
45528
  lines.push(content);
@@ -44117,14 +45531,14 @@ data: ${JSON.stringify(msg.data)}
44117
45531
  } catch {
44118
45532
  }
44119
45533
  }
44120
- const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
45534
+ const refFiles = fs14.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
44121
45535
  if (refFiles.length > 0) {
44122
45536
  lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
44123
45537
  lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
44124
45538
  lines.push("");
44125
45539
  for (const file2 of refFiles) {
44126
45540
  try {
44127
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
45541
+ const content = fs14.readFileSync(path18.join(latestScriptsDir, file2), "utf-8");
44128
45542
  lines.push(`### \`${file2}\` \u{1F512}`);
44129
45543
  lines.push("```javascript");
44130
45544
  lines.push(content);
@@ -44157,11 +45571,11 @@ data: ${JSON.stringify(msg.data)}
44157
45571
  lines.push("");
44158
45572
  }
44159
45573
  }
44160
- const docsDir = path17.join(providerDir, "../../docs");
45574
+ const docsDir = path18.join(providerDir, "../../docs");
44161
45575
  const loadGuide = (name) => {
44162
45576
  try {
44163
- const p = path17.join(docsDir, name);
44164
- if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
45577
+ const p = path18.join(docsDir, name);
45578
+ if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
44165
45579
  } catch {
44166
45580
  }
44167
45581
  return null;
@@ -44226,6 +45640,7 @@ data: ${JSON.stringify(msg.data)}
44226
45640
  lines.push("### 2. Inspect parsed + raw adapter state");
44227
45641
  lines.push("```bash");
44228
45642
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
45643
+ lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
44229
45644
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
44230
45645
  lines.push("```");
44231
45646
  lines.push("");
@@ -44360,6 +45775,16 @@ data: ${JSON.stringify(msg.data)}
44360
45775
  async handleCliSend(req, res) {
44361
45776
  return handleCliSend(this, req, res);
44362
45777
  }
45778
+ /** POST /api/cli/exercise — launch/send/approve/wait helper for provider-fix loops */
45779
+ async handleCliExercise(req, res) {
45780
+ return handleCliExercise(this, req, res);
45781
+ }
45782
+ async handleCliFixtureCapture(req, res) {
45783
+ return handleCliFixtureCapture(this, req, res);
45784
+ }
45785
+ async handleCliFixtureReplay(req, res) {
45786
+ return handleCliFixtureReplay(this, req, res);
45787
+ }
44363
45788
  /** POST /api/cli/stop — stop a running CLI { type } */
44364
45789
  async handleCliStop(req, res) {
44365
45790
  return handleCliStop(this, req, res);
@@ -44383,6 +45808,13 @@ data: ${JSON.stringify(msg.data)}
44383
45808
  async handleCliDebug(type, _req, res) {
44384
45809
  return handleCliDebug(this, type, _req, res);
44385
45810
  }
45811
+ /** GET /api/cli/trace/:type — recent CLI trace timeline plus current debug snapshot */
45812
+ async handleCliTrace(type, _req, res) {
45813
+ return handleCliTrace(this, type, _req, res);
45814
+ }
45815
+ async handleCliFixtureList(type, _req, res) {
45816
+ return handleCliFixtureList(this, type, _req, res);
45817
+ }
44386
45818
  /** POST /api/cli/resolve — resolve an approval modal { type, buttonIndex } */
44387
45819
  async handleCliResolve(req, res) {
44388
45820
  return handleCliResolve(this, req, res);
@@ -44549,7 +45981,18 @@ data: ${JSON.stringify(msg.data)}
44549
45981
  });
44550
45982
  }
44551
45983
  async boot() {
44552
- await this.client.connect();
45984
+ if (typeof this.options.ensureReady === "function") {
45985
+ await this.options.ensureReady();
45986
+ }
45987
+ try {
45988
+ await this.client.connect();
45989
+ } catch (error48) {
45990
+ if (typeof this.options.ensureReady !== "function") {
45991
+ throw error48;
45992
+ }
45993
+ await this.options.ensureReady();
45994
+ await this.client.connect();
45995
+ }
44553
45996
  this.unsubscribe = this.client.onEvent((event) => this.handleEvent(event));
44554
45997
  let record2 = null;
44555
45998
  if (this.options.attachExisting) {
@@ -44924,8 +46367,8 @@ data: ${JSON.stringify(msg.data)}
44924
46367
  const res = await fetch(extension.vsixUrl);
44925
46368
  if (res.ok) {
44926
46369
  const buffer = Buffer.from(await res.arrayBuffer());
44927
- const fs14 = await import("fs");
44928
- fs14.writeFileSync(vsixPath, buffer);
46370
+ const fs15 = await import("fs");
46371
+ fs15.writeFileSync(vsixPath, buffer);
44929
46372
  return new Promise((resolve10) => {
44930
46373
  const cmd = `"${ide.cliCommand}" --install-extension "${vsixPath}" --force`;
44931
46374
  (0, import_child_process10.exec)(cmd, { timeout: 6e4 }, (error48, _stdout, stderr) => {
@@ -45238,15 +46681,18 @@ data: ${JSON.stringify(msg.data)}
45238
46681
  var import_http = require("http");
45239
46682
  var import_ws = require("ws");
45240
46683
  var path4 = __toESM(require("path"));
45241
- var fs2 = __toESM(require("fs"));
45242
- var os4 = __toESM(require("os"));
46684
+ var fs3 = __toESM(require("fs"));
46685
+ var os5 = __toESM(require("os"));
45243
46686
  var import_daemon_core2 = __toESM(require_dist2());
45244
46687
 
45245
46688
  // src/session-host.ts
45246
46689
  var import_child_process = require("child_process");
46690
+ var fs2 = __toESM(require("fs"));
46691
+ var os2 = __toESM(require("os"));
45247
46692
  var path = __toESM(require("path"));
45248
46693
  var import_daemon_core = __toESM(require_dist2());
45249
46694
  var SESSION_HOST_APP_NAME = process.env.ADHDEV_SESSION_HOST_NAME || "adhdev";
46695
+ var SESSION_HOST_START_TIMEOUT_MS = 15e3;
45250
46696
  function buildSessionHostEnv(baseEnv) {
45251
46697
  const env = {};
45252
46698
  for (const [key, value] of Object.entries(baseEnv)) {
@@ -45267,12 +46713,58 @@ function resolveSessionHostEntry() {
45267
46713
  path.resolve(__dirname, "../../vendor/session-host-daemon/index.js")
45268
46714
  ];
45269
46715
  for (const candidate of localCandidates) {
45270
- if (require("fs").existsSync(candidate)) {
46716
+ if (fs2.existsSync(candidate)) {
45271
46717
  return candidate;
45272
46718
  }
45273
46719
  }
45274
46720
  return require.resolve("@adhdev/session-host-daemon");
45275
46721
  }
46722
+ function getSessionHostPidFile() {
46723
+ return path.join(os2.homedir(), ".adhdev", `${SESSION_HOST_APP_NAME}-session-host.pid`);
46724
+ }
46725
+ function killPid(pid) {
46726
+ try {
46727
+ if (process.platform === "win32") {
46728
+ (0, import_child_process.execFileSync)("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore" });
46729
+ } else {
46730
+ process.kill(pid, "SIGTERM");
46731
+ }
46732
+ return true;
46733
+ } catch {
46734
+ return false;
46735
+ }
46736
+ }
46737
+ function stopSessionHost() {
46738
+ let stopped = false;
46739
+ const pidFile = getSessionHostPidFile();
46740
+ try {
46741
+ if (fs2.existsSync(pidFile)) {
46742
+ const pid = Number.parseInt(fs2.readFileSync(pidFile, "utf8").trim(), 10);
46743
+ if (Number.isFinite(pid)) {
46744
+ stopped = killPid(pid) || stopped;
46745
+ }
46746
+ }
46747
+ } catch {
46748
+ } finally {
46749
+ try {
46750
+ fs2.unlinkSync(pidFile);
46751
+ } catch {
46752
+ }
46753
+ }
46754
+ if (process.platform !== "win32") {
46755
+ try {
46756
+ const raw = (0, import_child_process.execFileSync)("pgrep", ["-f", "session-host-daemon"], { encoding: "utf8" }).trim();
46757
+ for (const line of raw.split("\n")) {
46758
+ const pid = Number.parseInt(line.trim(), 10);
46759
+ if (Number.isFinite(pid)) {
46760
+ stopped = killPid(pid) || stopped;
46761
+ }
46762
+ }
46763
+ } catch {
46764
+ }
46765
+ }
46766
+ return stopped;
46767
+ }
45276
46768
  async function runSessionHostCli(args) {
45277
46769
  const entry = resolveSessionHostEntry();
45278
46770
  const child = (0, import_child_process.spawn)(process.execPath, [entry, ...args], {
@@ -45285,19 +46777,34 @@ async function runSessionHostCli(args) {
45285
46777
  });
45286
46778
  }
45287
46779
  async function ensureSessionHostReady() {
45288
- return (0, import_daemon_core.ensureSessionHostReady)({
45289
- appName: SESSION_HOST_APP_NAME,
45290
- spawnHost: () => {
45291
- const entry = resolveSessionHostEntry();
45292
- const child = (0, import_child_process.spawn)(process.execPath, [entry], {
45293
- detached: true,
45294
- stdio: "ignore",
45295
- windowsHide: true,
45296
- env: buildSessionHostEnv(process.env)
45297
- });
45298
- child.unref();
45299
- }
45300
- });
46780
+ const spawnHost = () => {
46781
+ const entry = resolveSessionHostEntry();
46782
+ const child = (0, import_child_process.spawn)(process.execPath, [entry], {
46783
+ detached: true,
46784
+ stdio: "ignore",
46785
+ windowsHide: true,
46786
+ env: buildSessionHostEnv(process.env)
46787
+ });
46788
+ child.unref();
46789
+ };
46790
+ try {
46791
+ return await (0, import_daemon_core.ensureSessionHostReady)({
46792
+ appName: SESSION_HOST_APP_NAME,
46793
+ spawnHost,
46794
+ timeoutMs: SESSION_HOST_START_TIMEOUT_MS
46795
+ });
46796
+ } catch (error48) {
46797
+ stopSessionHost();
46798
+ return (0, import_daemon_core.ensureSessionHostReady)({
46799
+ appName: SESSION_HOST_APP_NAME,
46800
+ spawnHost,
46801
+ timeoutMs: SESSION_HOST_START_TIMEOUT_MS
46802
+ }).catch((retryError) => {
46803
+ const initialMessage = error48 instanceof Error ? error48.message : String(error48);
46804
+ const retryMessage = retryError instanceof Error ? retryError.message : String(retryError);
46805
+ throw new Error(`Session host failed to start after retry (${initialMessage}; retry: ${retryMessage})`);
46806
+ });
46807
+ }
45301
46808
  }
45302
46809
  async function listHostedCliRuntimes(endpoint) {
45303
46810
  return (0, import_daemon_core.listHostedCliRuntimes)(endpoint);
@@ -45315,7 +46822,7 @@ async function proxySessionHostAttach(target, options = {}) {
45315
46822
  }
45316
46823
 
45317
46824
  // ../session-host-core/dist/index.mjs
45318
- var os2 = __toESM(require("os"), 1);
46825
+ var os3 = __toESM(require("os"), 1);
45319
46826
  var path2 = __toESM(require("path"), 1);
45320
46827
  var net = __toESM(require("net"), 1);
45321
46828
  var import_crypto = require("crypto");
@@ -45328,7 +46835,7 @@ function getDefaultSessionHostEndpoint(appName = "adhdev") {
45328
46835
  }
45329
46836
  return {
45330
46837
  kind: "unix",
45331
- path: path2.join(os2.tmpdir(), `${appName}-session-host.sock`)
46838
+ path: path2.join(os3.tmpdir(), `${appName}-session-host.sock`)
45332
46839
  };
45333
46840
  }
45334
46841
  function serializeEnvelope(envelope) {
@@ -45612,7 +47119,7 @@ if (pkgVersion === "unknown") {
45612
47119
  ];
45613
47120
  for (const candidate of possiblePaths) {
45614
47121
  try {
45615
- const data = JSON.parse(fs2.readFileSync(candidate, "utf-8"));
47122
+ const data = JSON.parse(fs3.readFileSync(candidate, "utf-8"));
45616
47123
  if (data.version) {
45617
47124
  pkgVersion = data.version;
45618
47125
  break;
@@ -45714,6 +47221,10 @@ var StandaloneServer = class {
45714
47221
  },
45715
47222
  createPtyTransportFactory: ({ runtimeId, providerType, workspace, cliArgs, providerSessionId, attachExisting }) => new import_daemon_core2.SessionHostPtyTransportFactory({
45716
47223
  endpoint: sessionHostEndpoint,
47224
+ ensureReady: async () => {
47225
+ const activeEndpoint = await this.ensureActiveSessionHostEndpoint();
47226
+ this.sessionHostEndpoint = activeEndpoint;
47227
+ },
45717
47228
  clientId: `daemon-${process.pid}`,
45718
47229
  runtimeId,
45719
47230
  providerType,
@@ -45972,7 +47483,7 @@ var StandaloneServer = class {
45972
47483
  if (publicDir) {
45973
47484
  const filePath = url2 === "/" ? "/index.html" : url2;
45974
47485
  const fullPath = path4.join(publicDir, filePath);
45975
- if (fs2.existsSync(fullPath) && fs2.statSync(fullPath).isFile()) {
47486
+ if (fs3.existsSync(fullPath) && fs3.statSync(fullPath).isFile()) {
45976
47487
  const ext = path4.extname(fullPath);
45977
47488
  const mimeTypes = {
45978
47489
  ".html": "text/html",
@@ -45985,13 +47496,13 @@ var StandaloneServer = class {
45985
47496
  ".woff2": "font/woff2"
45986
47497
  };
45987
47498
  res.writeHead(200, { "Content-Type": mimeTypes[ext] || "application/octet-stream" });
45988
- fs2.createReadStream(fullPath).pipe(res);
47499
+ fs3.createReadStream(fullPath).pipe(res);
45989
47500
  return;
45990
47501
  }
45991
47502
  const indexPath = path4.join(publicDir, "index.html");
45992
- if (fs2.existsSync(indexPath) && !url2.startsWith("/api/")) {
47503
+ if (fs3.existsSync(indexPath) && !url2.startsWith("/api/")) {
45993
47504
  res.writeHead(200, { "Content-Type": "text/html" });
45994
- fs2.createReadStream(indexPath).pipe(res);
47505
+ fs3.createReadStream(indexPath).pipe(res);
45995
47506
  return;
45996
47507
  }
45997
47508
  }
@@ -46251,7 +47762,7 @@ var StandaloneServer = class {
46251
47762
  }
46252
47763
  // ─── Network ───
46253
47764
  getLanIPs() {
46254
- const interfaces = os4.networkInterfaces();
47765
+ const interfaces = os5.networkInterfaces();
46255
47766
  const ips = [];
46256
47767
  for (const iface of Object.values(interfaces)) {
46257
47768
  if (!iface) continue;
@@ -46370,7 +47881,7 @@ Runtime commands:
46370
47881
  path4.join(process.cwd(), "public")
46371
47882
  ];
46372
47883
  for (const candidate of candidates) {
46373
- if (fs2.existsSync(path4.join(candidate, "index.html"))) {
47884
+ if (fs3.existsSync(path4.join(candidate, "index.html"))) {
46374
47885
  options.publicDir = candidate;
46375
47886
  break;
46376
47887
  }