@adhdev/daemon-standalone 0.7.46 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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 {
@@ -40354,8 +40806,8 @@ ${installInfo}`
40354
40806
  return results;
40355
40807
  }
40356
40808
  var http2 = __toESM2(require("http"));
40357
- var fs13 = __toESM2(require("fs"));
40358
- var path17 = __toESM2(require("path"));
40809
+ var fs14 = __toESM2(require("fs"));
40810
+ var path18 = __toESM2(require("path"));
40359
40811
  function generateFiles(type, name, category, opts = {}) {
40360
40812
  const { cdpPorts, cli, processName, installPath, binary, extensionId, version: version2 = "0.1" } = opts;
40361
40813
  if (category === "cli" || category === "acp") {
@@ -41690,6 +42142,162 @@ async (params) => {
41690
42142
  ctx.json(res, 500, { error: `DOM context collection failed: ${e.message}` });
41691
42143
  }
41692
42144
  }
42145
+ var fs12 = __toESM2(require("fs"));
42146
+ var path16 = __toESM2(require("path"));
42147
+ function slugifyFixtureName(value) {
42148
+ const normalized = String(value || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
42149
+ return normalized || `fixture-${Date.now()}`;
42150
+ }
42151
+ function getCliFixtureDir(ctx, type) {
42152
+ const providerDir = ctx.providerLoader.findProviderDir(type);
42153
+ if (!providerDir) {
42154
+ throw new Error(`Provider directory not found for '${type}'`);
42155
+ }
42156
+ return path16.join(providerDir, "fixtures");
42157
+ }
42158
+ function readCliFixture(ctx, type, name) {
42159
+ const fixtureDir = getCliFixtureDir(ctx, type);
42160
+ const filePath = path16.join(fixtureDir, `${name}.json`);
42161
+ if (!fs12.existsSync(filePath)) {
42162
+ throw new Error(`Fixture not found: ${filePath}`);
42163
+ }
42164
+ return JSON.parse(fs12.readFileSync(filePath, "utf-8"));
42165
+ }
42166
+ function getExerciseTranscriptText(result) {
42167
+ const parts = [];
42168
+ const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
42169
+ const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
42170
+ for (const message of [...debugMessages, ...traceMessages]) {
42171
+ if (!message || typeof message.content !== "string") continue;
42172
+ parts.push(message.content);
42173
+ }
42174
+ if (typeof result?.debug?.partialResponse === "string") parts.push(result.debug.partialResponse);
42175
+ if (typeof result?.trace?.responseBuffer === "string") parts.push(result.trace.responseBuffer);
42176
+ return parts.join("\n");
42177
+ }
42178
+ function getExerciseLastAssistant(result) {
42179
+ const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
42180
+ const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
42181
+ for (const messages of [debugMessages, traceMessages]) {
42182
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
42183
+ const message = messages[i];
42184
+ if (message?.role === "assistant" && typeof message.content === "string" && message.content.trim()) {
42185
+ return message.content;
42186
+ }
42187
+ }
42188
+ }
42189
+ return "";
42190
+ }
42191
+ function getExerciseMessageCount(result) {
42192
+ const debugMessages = Array.isArray(result?.debug?.messages) ? result.debug.messages : [];
42193
+ const traceMessages = Array.isArray(result?.trace?.messages) ? result.trace.messages : [];
42194
+ return Math.max(debugMessages.length, traceMessages.length);
42195
+ }
42196
+ function compileFixtureRegex(source) {
42197
+ const value = String(source || "").trim();
42198
+ if (!value) return null;
42199
+ const delimited = value.match(/^\/([\s\S]+)\/([dgimsuvy]*)$/);
42200
+ try {
42201
+ if (delimited) {
42202
+ return new RegExp(delimited[1], delimited[2]);
42203
+ }
42204
+ return new RegExp(value, "m");
42205
+ } catch {
42206
+ return null;
42207
+ }
42208
+ }
42209
+ function statusesContainSequence(actual, expected) {
42210
+ if (!expected.length) return true;
42211
+ let index = 0;
42212
+ for (const status of actual) {
42213
+ if (status === expected[index]) index += 1;
42214
+ if (index >= expected.length) return true;
42215
+ }
42216
+ return false;
42217
+ }
42218
+ function validateCliFixtureResult(result, assertions) {
42219
+ const failures = [];
42220
+ const transcriptText = getExerciseTranscriptText(result);
42221
+ const lastAssistant = getExerciseLastAssistant(result);
42222
+ const mustContainAny = assertions.mustContainAny || [];
42223
+ const mustNotContainAny = assertions.mustNotContainAny || [];
42224
+ const mustMatchAny = assertions.mustMatchAny || [];
42225
+ const mustNotMatchAny = assertions.mustNotMatchAny || [];
42226
+ const lastAssistantMustContainAny = assertions.lastAssistantMustContainAny || [];
42227
+ const lastAssistantMustNotContainAny = assertions.lastAssistantMustNotContainAny || [];
42228
+ const lastAssistantMustMatchAny = assertions.lastAssistantMustMatchAny || [];
42229
+ const lastAssistantMustNotMatchAny = assertions.lastAssistantMustNotMatchAny || [];
42230
+ const statusesSeen = Array.isArray(result?.statusesSeen) ? result.statusesSeen.map((value) => String(value)) : [];
42231
+ if (assertions.requireNotTimedOut !== false && result?.timedOut) {
42232
+ failures.push("Exercise timed out");
42233
+ }
42234
+ const missingRequired = mustContainAny.filter((value) => !transcriptText.includes(value));
42235
+ if (missingRequired.length > 0) {
42236
+ failures.push(`Missing required substrings: ${missingRequired.join(", ")}`);
42237
+ }
42238
+ const presentBanned = mustNotContainAny.filter((value) => transcriptText.includes(value));
42239
+ if (presentBanned.length > 0) {
42240
+ failures.push(`Found banned substrings: ${presentBanned.join(", ")}`);
42241
+ }
42242
+ const missingRegex = mustMatchAny.filter((value) => {
42243
+ const regex = compileFixtureRegex(value);
42244
+ return !regex || !regex.test(transcriptText);
42245
+ });
42246
+ if (missingRegex.length > 0) {
42247
+ failures.push(`Missing required regex matches: ${missingRegex.join(", ")}`);
42248
+ }
42249
+ const presentBannedRegex = mustNotMatchAny.filter((value) => {
42250
+ const regex = compileFixtureRegex(value);
42251
+ return !!regex && regex.test(transcriptText);
42252
+ });
42253
+ if (presentBannedRegex.length > 0) {
42254
+ failures.push(`Found banned regex matches: ${presentBannedRegex.join(", ")}`);
42255
+ }
42256
+ const missingLastAssistant = lastAssistantMustContainAny.filter((value) => !lastAssistant.includes(value));
42257
+ if (missingLastAssistant.length > 0) {
42258
+ failures.push(`Missing required lastAssistant substrings: ${missingLastAssistant.join(", ")}`);
42259
+ }
42260
+ const presentBannedLastAssistant = lastAssistantMustNotContainAny.filter((value) => lastAssistant.includes(value));
42261
+ if (presentBannedLastAssistant.length > 0) {
42262
+ failures.push(`Found banned lastAssistant substrings: ${presentBannedLastAssistant.join(", ")}`);
42263
+ }
42264
+ const missingLastAssistantRegex = lastAssistantMustMatchAny.filter((value) => {
42265
+ const regex = compileFixtureRegex(value);
42266
+ return !regex || !regex.test(lastAssistant);
42267
+ });
42268
+ if (missingLastAssistantRegex.length > 0) {
42269
+ failures.push(`Missing required lastAssistant regex matches: ${missingLastAssistantRegex.join(", ")}`);
42270
+ }
42271
+ const presentBannedLastAssistantRegex = lastAssistantMustNotMatchAny.filter((value) => {
42272
+ const regex = compileFixtureRegex(value);
42273
+ return !!regex && regex.test(lastAssistant);
42274
+ });
42275
+ if (presentBannedLastAssistantRegex.length > 0) {
42276
+ failures.push(`Found banned lastAssistant regex matches: ${presentBannedLastAssistantRegex.join(", ")}`);
42277
+ }
42278
+ if (assertions.statusesSeen?.length && !statusesContainSequence(statusesSeen, assertions.statusesSeen)) {
42279
+ failures.push(`Expected statuses sequence not observed: ${assertions.statusesSeen.join(" -> ")}`);
42280
+ }
42281
+ if (result && typeof result === "object") {
42282
+ result.lastAssistant = lastAssistant;
42283
+ }
42284
+ return failures;
42285
+ }
42286
+ function getCliProviderResolutionMeta(ctx, type, adapter) {
42287
+ const adapterMeta = typeof adapter?.getProviderResolutionMeta === "function" ? adapter.getProviderResolutionMeta() : adapter?.getDebugState?.()?.providerResolution || null;
42288
+ const resolvedProvider = ctx.providerLoader.resolve(type);
42289
+ if (!adapterMeta && !resolvedProvider) return null;
42290
+ return {
42291
+ type,
42292
+ providerDir: adapterMeta?.providerDir || resolvedProvider?._resolvedProviderDir || ctx.providerLoader.findProviderDir(type),
42293
+ scriptDir: adapterMeta?.scriptDir || resolvedProvider?._resolvedScriptDir || null,
42294
+ scriptsPath: adapterMeta?.scriptsPath || resolvedProvider?._resolvedScriptsPath || null,
42295
+ scriptsSource: adapterMeta?.scriptsSource || resolvedProvider?._resolvedScriptsSource || null,
42296
+ resolvedVersion: adapterMeta?.resolvedVersion || resolvedProvider?._resolvedVersion || null,
42297
+ resolvedOs: adapterMeta?.resolvedOs || resolvedProvider?._resolvedOs || null,
42298
+ versionWarning: adapterMeta?.versionWarning || resolvedProvider?._versionWarning || null
42299
+ };
42300
+ }
41693
42301
  function findCliTarget(ctx, type, instanceId) {
41694
42302
  if (!ctx.instanceManager) return null;
41695
42303
  const cliStates = ctx.instanceManager.collectAllStates().filter((s15) => s15.category === "cli" || s15.category === "acp");
@@ -41698,6 +42306,331 @@ async (params) => {
41698
42306
  const matches = cliStates.filter((s15) => s15.type === type);
41699
42307
  return matches[matches.length - 1] || null;
41700
42308
  }
42309
+ function getCliTargetBundle(ctx, type, instanceId) {
42310
+ if (!ctx.instanceManager) return null;
42311
+ const target = findCliTarget(ctx, type, instanceId);
42312
+ if (!target) return null;
42313
+ const instance = ctx.instanceManager.getInstance(target.instanceId);
42314
+ if (!instance) return null;
42315
+ const adapter = instance.getAdapter?.() || instance.adapter;
42316
+ if (!adapter) return null;
42317
+ return { target, instance, adapter };
42318
+ }
42319
+ function sleep(ms2) {
42320
+ return new Promise((resolve10) => setTimeout(resolve10, ms2));
42321
+ }
42322
+ async function waitForCliReady(ctx, type, instanceId, timeoutMs) {
42323
+ const startedAt = Date.now();
42324
+ while (Date.now() - startedAt < timeoutMs) {
42325
+ const bundle = getCliTargetBundle(ctx, type, instanceId);
42326
+ if (bundle) {
42327
+ const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
42328
+ const startupParseGate = !!debug?.startupParseGate;
42329
+ const adapterReady = !!debug?.ready;
42330
+ const visibleStatusReady = bundle.target.status === "generating" || bundle.target.status === "waiting_approval";
42331
+ const idleReady = bundle.target.status === "idle" && !startupParseGate;
42332
+ if (adapterReady || visibleStatusReady || idleReady) {
42333
+ return bundle;
42334
+ }
42335
+ }
42336
+ await sleep(100);
42337
+ }
42338
+ return getCliTargetBundle(ctx, type, instanceId);
42339
+ }
42340
+ async function runCliExerciseInternal(ctx, body) {
42341
+ if (!ctx.cliManager) {
42342
+ throw new Error("CliManager not available");
42343
+ }
42344
+ if (!ctx.instanceManager) {
42345
+ throw new Error("InstanceManager not available");
42346
+ }
42347
+ const {
42348
+ type,
42349
+ text,
42350
+ instanceId: requestedInstanceId,
42351
+ workingDir,
42352
+ args,
42353
+ autoLaunch = true,
42354
+ freshSession = true,
42355
+ autoResolveApprovals = true,
42356
+ approvalButtonIndex = 0,
42357
+ timeoutMs = 45e3,
42358
+ readyTimeoutMs = 15e3,
42359
+ idleSettledMs = 1200,
42360
+ traceLimit = 160,
42361
+ stopWhenDone = false
42362
+ } = body || {};
42363
+ if (!type) {
42364
+ throw new Error("type required (e.g. claude-cli, codex-cli)");
42365
+ }
42366
+ if (!text || typeof text !== "string") {
42367
+ throw new Error("text required (prompt to send to the CLI)");
42368
+ }
42369
+ let resolvedInstanceId = requestedInstanceId;
42370
+ if (freshSession) {
42371
+ const staleTargets = ctx.instanceManager.collectAllStates().filter((state) => (state.category === "cli" || state.category === "acp") && state.type === type).map((state) => state.instanceId);
42372
+ for (const staleId of staleTargets) {
42373
+ ctx.instanceManager.removeInstance(staleId);
42374
+ }
42375
+ resolvedInstanceId = void 0;
42376
+ }
42377
+ let bundle = getCliTargetBundle(ctx, type, resolvedInstanceId);
42378
+ if (!bundle && autoLaunch) {
42379
+ const launchArgs = [type, workingDir || process.cwd(), Array.isArray(args) ? args : []];
42380
+ let launched = null;
42381
+ let lastLaunchError = null;
42382
+ for (let attempt = 1; attempt <= 2; attempt += 1) {
42383
+ try {
42384
+ launched = await ctx.cliManager.startSession(...launchArgs);
42385
+ lastLaunchError = null;
42386
+ break;
42387
+ } catch (error48) {
42388
+ lastLaunchError = error48 instanceof Error ? error48 : new Error(String(error48?.message || error48));
42389
+ const message = String(lastLaunchError.message || "");
42390
+ const retryable = /ECONNREFUSED|session-host|Session host/i.test(message);
42391
+ if (!retryable || attempt === 2) break;
42392
+ await sleep(1e3);
42393
+ }
42394
+ }
42395
+ if (!launched) {
42396
+ throw lastLaunchError || new Error(`Failed to start ${type}`);
42397
+ }
42398
+ resolvedInstanceId = launched.runtimeSessionId;
42399
+ bundle = await waitForCliReady(ctx, type, resolvedInstanceId, Math.max(1e3, readyTimeoutMs));
42400
+ }
42401
+ if (!bundle) {
42402
+ throw new Error(`No running instance found for: ${resolvedInstanceId || type}`);
42403
+ }
42404
+ const initialDebug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
42405
+ const initialTrace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
42406
+ const providerResolution = getCliProviderResolutionMeta(ctx, bundle.target.type, bundle.adapter);
42407
+ const preTraceCount = Number(initialTrace?.entryCount || 0);
42408
+ const startAt = Date.now();
42409
+ const statusesSeen = [];
42410
+ const approvalsResolved = [];
42411
+ let lastStatus = "";
42412
+ let lastModalKey = "";
42413
+ let idleSince = 0;
42414
+ let sawBusy = false;
42415
+ ctx.instanceManager.sendEvent(bundle.target.instanceId, "send_message", { text });
42416
+ while (Date.now() - startAt < Math.max(1e3, timeoutMs)) {
42417
+ await sleep(150);
42418
+ bundle = getCliTargetBundle(ctx, type, bundle.target.instanceId);
42419
+ if (!bundle) {
42420
+ throw new Error("CLI instance disappeared during exercise");
42421
+ }
42422
+ const debug = typeof bundle.adapter.getDebugState === "function" ? bundle.adapter.getDebugState() : null;
42423
+ const trace = typeof bundle.adapter.getTraceState === "function" ? bundle.adapter.getTraceState(traceLimit) : null;
42424
+ const status = String(debug?.status || bundle.target.status || "unknown");
42425
+ const traceEntries = Array.isArray(trace?.entries) ? trace.entries : [];
42426
+ const sawSendMessage = traceEntries.some((entry) => entry?.type === "send_message");
42427
+ const sawSubmitWrite = traceEntries.some((entry) => entry?.type === "submit_write");
42428
+ const hasTurnStarted = sawSendMessage || sawSubmitWrite || !!debug?.currentTurnScope;
42429
+ if (status !== lastStatus) {
42430
+ statusesSeen.push(status);
42431
+ lastStatus = status;
42432
+ }
42433
+ if (status === "generating" || status === "waiting_approval") {
42434
+ sawBusy = true;
42435
+ idleSince = 0;
42436
+ }
42437
+ const modal = debug?.activeModal || trace?.activeModal || null;
42438
+ if (autoResolveApprovals && status === "waiting_approval" && modal && Array.isArray(modal.buttons) && modal.buttons.length > 0) {
42439
+ const clampedIndex = Math.max(0, Math.min(Number(approvalButtonIndex) || 0, modal.buttons.length - 1));
42440
+ const modalKey = JSON.stringify({
42441
+ message: modal.message || "",
42442
+ buttons: modal.buttons,
42443
+ index: clampedIndex
42444
+ });
42445
+ if (modalKey !== lastModalKey && typeof bundle.adapter.resolveModal === "function") {
42446
+ lastModalKey = modalKey;
42447
+ approvalsResolved.push({
42448
+ at: Date.now(),
42449
+ buttonIndex: clampedIndex,
42450
+ label: modal.buttons[clampedIndex] || null
42451
+ });
42452
+ bundle.adapter.resolveModal(clampedIndex);
42453
+ continue;
42454
+ }
42455
+ }
42456
+ const traceCount = Number(trace?.entryCount || 0);
42457
+ const hasProgress = hasTurnStarted && (traceCount > preTraceCount || statusesSeen.length > 1 || approvalsResolved.length > 0);
42458
+ if (status === "idle" && hasProgress && sawBusy) {
42459
+ if (!idleSince) idleSince = Date.now();
42460
+ if (Date.now() - idleSince >= Math.max(200, idleSettledMs)) {
42461
+ const payload2 = {
42462
+ exercised: true,
42463
+ instanceId: bundle.target.instanceId,
42464
+ providerState: {
42465
+ type: bundle.target.type,
42466
+ name: bundle.target.name,
42467
+ status: bundle.target.status,
42468
+ mode: "mode" in bundle.target ? bundle.target.mode : void 0
42469
+ },
42470
+ providerResolution,
42471
+ initialDebug,
42472
+ initialTrace,
42473
+ debug,
42474
+ trace,
42475
+ statusesSeen,
42476
+ approvalsResolved,
42477
+ elapsedMs: Date.now() - startAt,
42478
+ timedOut: false
42479
+ };
42480
+ payload2.lastAssistant = getExerciseLastAssistant(payload2);
42481
+ payload2.messageCount = getExerciseMessageCount(payload2);
42482
+ if (stopWhenDone) {
42483
+ ctx.instanceManager.removeInstance(bundle.target.instanceId);
42484
+ }
42485
+ return payload2;
42486
+ }
42487
+ } else if (status === "idle" && hasProgress) {
42488
+ if (!idleSince) idleSince = Date.now();
42489
+ if (Date.now() - idleSince >= Math.max(500, idleSettledMs) && Date.now() - startAt >= 750) {
42490
+ const payload2 = {
42491
+ exercised: true,
42492
+ instanceId: bundle.target.instanceId,
42493
+ providerState: {
42494
+ type: bundle.target.type,
42495
+ name: bundle.target.name,
42496
+ status: bundle.target.status,
42497
+ mode: "mode" in bundle.target ? bundle.target.mode : void 0
42498
+ },
42499
+ providerResolution,
42500
+ initialDebug,
42501
+ initialTrace,
42502
+ debug,
42503
+ trace,
42504
+ statusesSeen,
42505
+ approvalsResolved,
42506
+ elapsedMs: Date.now() - startAt,
42507
+ timedOut: false
42508
+ };
42509
+ payload2.lastAssistant = getExerciseLastAssistant(payload2);
42510
+ payload2.messageCount = getExerciseMessageCount(payload2);
42511
+ if (stopWhenDone) {
42512
+ ctx.instanceManager.removeInstance(bundle.target.instanceId);
42513
+ }
42514
+ return payload2;
42515
+ }
42516
+ } else {
42517
+ idleSince = 0;
42518
+ }
42519
+ }
42520
+ const finalBundle = getCliTargetBundle(ctx, type, bundle.target.instanceId) || bundle;
42521
+ const finalDebug = typeof finalBundle.adapter.getDebugState === "function" ? finalBundle.adapter.getDebugState() : null;
42522
+ const finalTrace = typeof finalBundle.adapter.getTraceState === "function" ? finalBundle.adapter.getTraceState(traceLimit) : null;
42523
+ if (stopWhenDone) {
42524
+ ctx.instanceManager.removeInstance(finalBundle.target.instanceId);
42525
+ }
42526
+ const payload = {
42527
+ exercised: true,
42528
+ instanceId: finalBundle.target.instanceId,
42529
+ providerState: {
42530
+ type: finalBundle.target.type,
42531
+ name: finalBundle.target.name,
42532
+ status: finalBundle.target.status,
42533
+ mode: "mode" in finalBundle.target ? finalBundle.target.mode : void 0
42534
+ },
42535
+ providerResolution: getCliProviderResolutionMeta(ctx, finalBundle.target.type, finalBundle.adapter),
42536
+ initialDebug,
42537
+ initialTrace,
42538
+ debug: finalDebug,
42539
+ trace: finalTrace,
42540
+ statusesSeen,
42541
+ approvalsResolved,
42542
+ elapsedMs: Date.now() - startAt,
42543
+ timedOut: true
42544
+ };
42545
+ payload.lastAssistant = getExerciseLastAssistant(payload);
42546
+ payload.messageCount = getExerciseMessageCount(payload);
42547
+ return payload;
42548
+ }
42549
+ async function runCliAutoImplVerification(ctx, type, verification) {
42550
+ const assertions = {
42551
+ mustContainAny: verification?.mustContainAny || [],
42552
+ mustNotContainAny: verification?.mustNotContainAny || [],
42553
+ mustMatchAny: verification?.mustMatchAny || [],
42554
+ mustNotMatchAny: verification?.mustNotMatchAny || [],
42555
+ lastAssistantMustContainAny: verification?.lastAssistantMustContainAny || [],
42556
+ lastAssistantMustNotContainAny: verification?.lastAssistantMustNotContainAny || [],
42557
+ lastAssistantMustMatchAny: verification?.lastAssistantMustMatchAny || [],
42558
+ lastAssistantMustNotMatchAny: verification?.lastAssistantMustNotMatchAny || [],
42559
+ requireNotTimedOut: true
42560
+ };
42561
+ const rawFixtureNames = Array.isArray(verification?.fixtureNames) ? verification.fixtureNames.map((value) => String(value || "").trim()).filter(Boolean) : [];
42562
+ if (rawFixtureNames.length > 0) {
42563
+ const results = [];
42564
+ for (const rawFixtureName2 of rawFixtureNames) {
42565
+ const name = slugifyFixtureName(rawFixtureName2);
42566
+ const fixture = readCliFixture(ctx, type, name);
42567
+ const mergedAssertions = {
42568
+ ...fixture.assertions,
42569
+ ...assertions
42570
+ };
42571
+ const result2 = await runCliExerciseInternal(ctx, {
42572
+ ...fixture.request,
42573
+ type
42574
+ });
42575
+ const failures2 = validateCliFixtureResult(result2, mergedAssertions);
42576
+ results.push({
42577
+ fixtureName: name,
42578
+ pass: failures2.length === 0,
42579
+ failures: failures2,
42580
+ result: result2,
42581
+ assertions: mergedAssertions,
42582
+ fixture
42583
+ });
42584
+ }
42585
+ const firstFailure = results.find((item) => !item.pass) || results[results.length - 1];
42586
+ return {
42587
+ mode: "fixture_replay_suite",
42588
+ pass: results.every((item) => item.pass),
42589
+ failures: results.flatMap((item) => item.failures.map((failure) => `${item.fixtureName}: ${failure}`)),
42590
+ result: firstFailure.result,
42591
+ assertions: firstFailure.assertions,
42592
+ fixture: firstFailure.fixture,
42593
+ results
42594
+ };
42595
+ }
42596
+ const rawFixtureName = String(verification?.fixtureName || "").trim();
42597
+ if (rawFixtureName) {
42598
+ const name = slugifyFixtureName(rawFixtureName);
42599
+ try {
42600
+ const fixture = readCliFixture(ctx, type, name);
42601
+ const mergedAssertions = {
42602
+ ...fixture.assertions,
42603
+ ...assertions
42604
+ };
42605
+ const result2 = await runCliExerciseInternal(ctx, {
42606
+ ...fixture.request,
42607
+ type
42608
+ });
42609
+ const failures2 = validateCliFixtureResult(result2, mergedAssertions);
42610
+ return {
42611
+ mode: "fixture_replay",
42612
+ pass: failures2.length === 0,
42613
+ failures: failures2,
42614
+ result: result2,
42615
+ assertions: mergedAssertions,
42616
+ fixture
42617
+ };
42618
+ } catch {
42619
+ }
42620
+ }
42621
+ const result = await runCliExerciseInternal(ctx, {
42622
+ ...verification?.request || {},
42623
+ type
42624
+ });
42625
+ const failures = validateCliFixtureResult(result, assertions);
42626
+ return {
42627
+ mode: "exercise",
42628
+ pass: failures.length === 0,
42629
+ failures,
42630
+ result,
42631
+ assertions
42632
+ };
42633
+ }
41701
42634
  async function handleCliStatus(ctx, _req, res) {
41702
42635
  if (!ctx.instanceManager) {
41703
42636
  ctx.json(res, 503, { error: "InstanceManager not available (daemon not fully initialized)" });
@@ -41836,12 +42769,14 @@ async (params) => {
41836
42769
  status: target.status,
41837
42770
  mode: "mode" in target ? target.mode : void 0
41838
42771
  },
42772
+ providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
41839
42773
  debug: debugState
41840
42774
  });
41841
42775
  } else {
41842
42776
  ctx.json(res, 200, {
41843
42777
  instanceId: target.instanceId,
41844
42778
  providerState: target,
42779
+ providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
41845
42780
  debug: null,
41846
42781
  message: "No debug state available (adapter.getDebugState not found)"
41847
42782
  });
@@ -41850,6 +42785,191 @@ async (params) => {
41850
42785
  ctx.json(res, 500, { error: `Debug state failed: ${e.message}` });
41851
42786
  }
41852
42787
  }
42788
+ async function handleCliTrace(ctx, type, req, res) {
42789
+ if (!ctx.instanceManager) {
42790
+ ctx.json(res, 503, { error: "InstanceManager not available" });
42791
+ return;
42792
+ }
42793
+ const target = findCliTarget(ctx, type);
42794
+ if (!target) {
42795
+ const allStates = ctx.instanceManager.collectAllStates();
42796
+ ctx.json(res, 404, {
42797
+ error: `No running instance for: ${type}`,
42798
+ available: allStates.filter((s15) => s15.category === "cli" || s15.category === "acp").map((s15) => s15.type)
42799
+ });
42800
+ return;
42801
+ }
42802
+ const instance = ctx.instanceManager.getInstance(target.instanceId);
42803
+ if (!instance) {
42804
+ ctx.json(res, 404, { error: `Instance not found: ${target.instanceId}` });
42805
+ return;
42806
+ }
42807
+ try {
42808
+ const adapter = instance.getAdapter?.() || instance.adapter;
42809
+ const url2 = new URL(req.url || "/", "http://127.0.0.1");
42810
+ const limit = parseInt(url2.searchParams.get("limit") || "120", 10);
42811
+ if (adapter && typeof adapter.getTraceState === "function") {
42812
+ const trace = adapter.getTraceState(limit);
42813
+ const debug = typeof adapter.getDebugState === "function" ? adapter.getDebugState() : null;
42814
+ ctx.json(res, 200, {
42815
+ instanceId: target.instanceId,
42816
+ providerState: {
42817
+ type: target.type,
42818
+ name: target.name,
42819
+ status: target.status,
42820
+ mode: "mode" in target ? target.mode : void 0
42821
+ },
42822
+ providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
42823
+ debug,
42824
+ trace
42825
+ });
42826
+ } else {
42827
+ ctx.json(res, 200, {
42828
+ instanceId: target.instanceId,
42829
+ providerState: target,
42830
+ providerResolution: getCliProviderResolutionMeta(ctx, target.type, adapter),
42831
+ debug: typeof adapter?.getDebugState === "function" ? adapter.getDebugState() : null,
42832
+ trace: null,
42833
+ message: "No trace state available (adapter.getTraceState not found)"
42834
+ });
42835
+ }
42836
+ } catch (e) {
42837
+ ctx.json(res, 500, { error: `Trace state failed: ${e.message}` });
42838
+ }
42839
+ }
42840
+ async function handleCliExercise(ctx, req, res) {
42841
+ try {
42842
+ const body = await ctx.readBody(req);
42843
+ const result = await runCliExerciseInternal(ctx, body || {});
42844
+ ctx.json(res, 200, result);
42845
+ } catch (e) {
42846
+ ctx.json(res, 500, { error: `Exercise failed: ${e.message}` });
42847
+ }
42848
+ }
42849
+ async function handleCliFixtureCapture(ctx, req, res) {
42850
+ try {
42851
+ const body = await ctx.readBody(req);
42852
+ const type = String(body?.type || "");
42853
+ const request = body?.request || {};
42854
+ if (!type) {
42855
+ ctx.json(res, 400, { error: "type required" });
42856
+ return;
42857
+ }
42858
+ if (!request?.text) {
42859
+ ctx.json(res, 400, { error: "request.text required" });
42860
+ return;
42861
+ }
42862
+ const fixtureDir = getCliFixtureDir(ctx, type);
42863
+ fs12.mkdirSync(fixtureDir, { recursive: true });
42864
+ const name = slugifyFixtureName(String(body?.name || `${type}-${Date.now()}`));
42865
+ const result = await runCliExerciseInternal(ctx, { ...request, type });
42866
+ const fixture = {
42867
+ version: 1,
42868
+ kind: "cli-exercise-fixture",
42869
+ name,
42870
+ type,
42871
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
42872
+ providerDir: ctx.providerLoader.findProviderDir(type),
42873
+ providerResolution: result?.providerResolution || null,
42874
+ request: { ...request, type },
42875
+ result,
42876
+ assertions: {
42877
+ mustContainAny: Array.isArray(body?.assertions?.mustContainAny) ? body.assertions.mustContainAny : [],
42878
+ mustNotContainAny: Array.isArray(body?.assertions?.mustNotContainAny) ? body.assertions.mustNotContainAny : [],
42879
+ mustMatchAny: Array.isArray(body?.assertions?.mustMatchAny) ? body.assertions.mustMatchAny : [],
42880
+ mustNotMatchAny: Array.isArray(body?.assertions?.mustNotMatchAny) ? body.assertions.mustNotMatchAny : [],
42881
+ lastAssistantMustContainAny: Array.isArray(body?.assertions?.lastAssistantMustContainAny) ? body.assertions.lastAssistantMustContainAny : [],
42882
+ lastAssistantMustNotContainAny: Array.isArray(body?.assertions?.lastAssistantMustNotContainAny) ? body.assertions.lastAssistantMustNotContainAny : [],
42883
+ lastAssistantMustMatchAny: Array.isArray(body?.assertions?.lastAssistantMustMatchAny) ? body.assertions.lastAssistantMustMatchAny : [],
42884
+ lastAssistantMustNotMatchAny: Array.isArray(body?.assertions?.lastAssistantMustNotMatchAny) ? body.assertions.lastAssistantMustNotMatchAny : [],
42885
+ statusesSeen: Array.isArray(body?.assertions?.statusesSeen) ? body.assertions.statusesSeen : void 0,
42886
+ requireNotTimedOut: body?.assertions?.requireNotTimedOut !== false
42887
+ },
42888
+ notes: typeof body?.notes === "string" ? body.notes : void 0
42889
+ };
42890
+ const filePath = path16.join(fixtureDir, `${name}.json`);
42891
+ fs12.writeFileSync(filePath, JSON.stringify(fixture, null, 2));
42892
+ ctx.json(res, 200, {
42893
+ saved: true,
42894
+ name,
42895
+ path: filePath,
42896
+ fixture,
42897
+ verification: {
42898
+ pass: validateCliFixtureResult(result, fixture.assertions).length === 0,
42899
+ failures: validateCliFixtureResult(result, fixture.assertions)
42900
+ }
42901
+ });
42902
+ } catch (e) {
42903
+ ctx.json(res, 500, { error: `Fixture capture failed: ${e.message}` });
42904
+ }
42905
+ }
42906
+ async function handleCliFixtureList(ctx, type, _req, res) {
42907
+ try {
42908
+ const fixtureDir = getCliFixtureDir(ctx, type);
42909
+ if (!fs12.existsSync(fixtureDir)) {
42910
+ ctx.json(res, 200, { fixtures: [], count: 0 });
42911
+ return;
42912
+ }
42913
+ const fixtures = fs12.readdirSync(fixtureDir).filter((file2) => file2.endsWith(".json")).sort((a, b2) => b2.localeCompare(a, void 0, { numeric: true, sensitivity: "base" })).map((file2) => {
42914
+ const fullPath = path16.join(fixtureDir, file2);
42915
+ try {
42916
+ const raw = JSON.parse(fs12.readFileSync(fullPath, "utf-8"));
42917
+ return {
42918
+ name: raw.name || file2.replace(/\.json$/i, ""),
42919
+ path: fullPath,
42920
+ createdAt: raw.createdAt || null,
42921
+ notes: raw.notes || null,
42922
+ requestText: raw.request?.text || "",
42923
+ assertions: raw.assertions || {}
42924
+ };
42925
+ } catch {
42926
+ return {
42927
+ name: file2.replace(/\.json$/i, ""),
42928
+ path: fullPath,
42929
+ createdAt: null,
42930
+ notes: "Unreadable fixture",
42931
+ requestText: "",
42932
+ assertions: {}
42933
+ };
42934
+ }
42935
+ });
42936
+ ctx.json(res, 200, { fixtures, count: fixtures.length });
42937
+ } catch (e) {
42938
+ ctx.json(res, 500, { error: `Fixture list failed: ${e.message}` });
42939
+ }
42940
+ }
42941
+ async function handleCliFixtureReplay(ctx, req, res) {
42942
+ try {
42943
+ const body = await ctx.readBody(req);
42944
+ const type = String(body?.type || "");
42945
+ const rawName = String(body?.name || "").trim();
42946
+ if (!type || !rawName) {
42947
+ ctx.json(res, 400, { error: "type and name required" });
42948
+ return;
42949
+ }
42950
+ const name = slugifyFixtureName(rawName);
42951
+ const fixture = readCliFixture(ctx, type, name);
42952
+ const result = await runCliExerciseInternal(ctx, {
42953
+ ...fixture.request,
42954
+ type
42955
+ });
42956
+ const assertions = {
42957
+ ...fixture.assertions,
42958
+ ...body?.assertions || {}
42959
+ };
42960
+ const failures = validateCliFixtureResult(result, assertions);
42961
+ ctx.json(res, 200, {
42962
+ replayed: true,
42963
+ pass: failures.length === 0,
42964
+ failures,
42965
+ fixture,
42966
+ result,
42967
+ assertions
42968
+ });
42969
+ } catch (e) {
42970
+ ctx.json(res, 500, { error: `Fixture replay failed: ${e.message}` });
42971
+ }
42972
+ }
41853
42973
  async function handleCliResolve(ctx, req, res) {
41854
42974
  const body = await ctx.readBody(req);
41855
42975
  const { type, buttonIndex, instanceId } = body;
@@ -41924,9 +43044,29 @@ async (params) => {
41924
43044
  ctx.json(res, 500, { error: `Raw send failed: ${e.message}` });
41925
43045
  }
41926
43046
  }
41927
- var fs12 = __toESM2(require("fs"));
41928
- var path16 = __toESM2(require("path"));
43047
+ var fs13 = __toESM2(require("fs"));
43048
+ var path17 = __toESM2(require("path"));
41929
43049
  var os17 = __toESM2(require("os"));
43050
+ function getAutoImplPid(ctx) {
43051
+ const proc = ctx.autoImplProcess;
43052
+ return proc && typeof proc.pid === "number" && proc.pid > 0 ? proc.pid : null;
43053
+ }
43054
+ function isPidAlive(pid) {
43055
+ try {
43056
+ process.kill(pid, 0);
43057
+ return true;
43058
+ } catch (error48) {
43059
+ return error48?.code === "EPERM";
43060
+ }
43061
+ }
43062
+ function clearStaleAutoImplState(ctx, reason) {
43063
+ if (!ctx.autoImplStatus.running && !ctx.autoImplProcess) return;
43064
+ const pid = getAutoImplPid(ctx);
43065
+ if (pid && isPidAlive(pid)) return;
43066
+ ctx.log(`Clearing stale auto-implement state: ${reason}${pid ? ` (pid ${pid})` : ""}`);
43067
+ ctx.autoImplProcess = null;
43068
+ ctx.autoImplStatus.running = false;
43069
+ }
41930
43070
  function getDefaultAutoImplReference(ctx, category, type) {
41931
43071
  if (category === "cli") {
41932
43072
  return type === "codex-cli" ? "claude-cli" : "codex-cli";
@@ -41942,45 +43082,45 @@ async (params) => {
41942
43082
  return fallback?.type || null;
41943
43083
  }
41944
43084
  function getLatestScriptVersionDir(scriptsDir) {
41945
- if (!fs12.existsSync(scriptsDir)) return null;
41946
- const versions = fs12.readdirSync(scriptsDir).filter((d) => {
43085
+ if (!fs13.existsSync(scriptsDir)) return null;
43086
+ const versions = fs13.readdirSync(scriptsDir).filter((d) => {
41947
43087
  try {
41948
- return fs12.statSync(path16.join(scriptsDir, d)).isDirectory();
43088
+ return fs13.statSync(path17.join(scriptsDir, d)).isDirectory();
41949
43089
  } catch {
41950
43090
  return false;
41951
43091
  }
41952
43092
  }).sort((a, b2) => b2.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
41953
43093
  if (versions.length === 0) return null;
41954
- return path16.join(scriptsDir, versions[0]);
43094
+ return path17.join(scriptsDir, versions[0]);
41955
43095
  }
41956
43096
  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}`)) {
43097
+ const canonicalUserDir = path17.resolve(ctx.providerLoader.getUserProviderDir(category, type));
43098
+ const desiredDir = requestedDir ? path17.resolve(requestedDir) : canonicalUserDir;
43099
+ const upstreamRoot = path17.resolve(ctx.providerLoader.getUpstreamDir());
43100
+ if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path17.sep}`)) {
41961
43101
  return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
41962
43102
  }
41963
- if (path16.basename(desiredDir) !== type) {
43103
+ if (path17.basename(desiredDir) !== type) {
41964
43104
  return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
41965
43105
  }
41966
43106
  const sourceDir = ctx.findProviderDir(type);
41967
43107
  if (!sourceDir) {
41968
43108
  return { dir: null, reason: `Provider source directory not found for '${type}'` };
41969
43109
  }
41970
- if (!fs12.existsSync(desiredDir)) {
41971
- fs12.mkdirSync(path16.dirname(desiredDir), { recursive: true });
41972
- fs12.cpSync(sourceDir, desiredDir, { recursive: true });
43110
+ if (!fs13.existsSync(desiredDir)) {
43111
+ fs13.mkdirSync(path17.dirname(desiredDir), { recursive: true });
43112
+ fs13.cpSync(sourceDir, desiredDir, { recursive: true });
41973
43113
  ctx.log(`Auto-implement writable copy created: ${desiredDir}`);
41974
43114
  }
41975
- const providerJson = path16.join(desiredDir, "provider.json");
41976
- if (!fs12.existsSync(providerJson)) {
43115
+ const providerJson = path17.join(desiredDir, "provider.json");
43116
+ if (!fs13.existsSync(providerJson)) {
41977
43117
  return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
41978
43118
  }
41979
43119
  try {
41980
- const providerData = JSON.parse(fs12.readFileSync(providerJson, "utf-8"));
43120
+ const providerData = JSON.parse(fs13.readFileSync(providerJson, "utf-8"));
41981
43121
  if (providerData.disableUpstream !== true) {
41982
43122
  providerData.disableUpstream = true;
41983
- fs12.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
43123
+ fs13.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
41984
43124
  }
41985
43125
  } catch (error48) {
41986
43126
  return {
@@ -41993,15 +43133,15 @@ async (params) => {
41993
43133
  function loadAutoImplReferenceScripts(ctx, referenceType) {
41994
43134
  if (!referenceType) return {};
41995
43135
  const refDir = ctx.findProviderDir(referenceType);
41996
- if (!refDir || !fs12.existsSync(refDir)) return {};
43136
+ if (!refDir || !fs13.existsSync(refDir)) return {};
41997
43137
  const referenceScripts = {};
41998
- const scriptsDir = path16.join(refDir, "scripts");
43138
+ const scriptsDir = path17.join(refDir, "scripts");
41999
43139
  const latestDir = getLatestScriptVersionDir(scriptsDir);
42000
43140
  if (!latestDir) return referenceScripts;
42001
- for (const file2 of fs12.readdirSync(latestDir)) {
43141
+ for (const file2 of fs13.readdirSync(latestDir)) {
42002
43142
  if (!file2.endsWith(".js")) continue;
42003
43143
  try {
42004
- referenceScripts[file2] = fs12.readFileSync(path16.join(latestDir, file2), "utf-8");
43144
+ referenceScripts[file2] = fs13.readFileSync(path17.join(latestDir, file2), "utf-8");
42005
43145
  } catch {
42006
43146
  }
42007
43147
  }
@@ -42009,11 +43149,20 @@ async (params) => {
42009
43149
  }
42010
43150
  async function handleAutoImplement(ctx, type, req, res) {
42011
43151
  const body = await ctx.readBody(req);
42012
- const { agent = "claude-cli", functions, reference, model, comment, providerDir: requestedProviderDir } = body;
43152
+ const {
43153
+ agent = "claude-cli",
43154
+ functions,
43155
+ reference,
43156
+ model,
43157
+ comment,
43158
+ providerDir: requestedProviderDir,
43159
+ verification
43160
+ } = body;
42013
43161
  if (!functions || !Array.isArray(functions) || functions.length === 0) {
42014
43162
  ctx.json(res, 400, { error: 'functions[] is required (e.g. ["readChat", "sendMessage"])' });
42015
43163
  return;
42016
43164
  }
43165
+ clearStaleAutoImplState(ctx, "new auto-implement request");
42017
43166
  if (ctx.autoImplStatus.running) {
42018
43167
  ctx.json(res, 409, { error: "Auto-implement already in progress", type: ctx.autoImplStatus.type });
42019
43168
  return;
@@ -42031,7 +43180,55 @@ async (params) => {
42031
43180
  return;
42032
43181
  }
42033
43182
  const providerDir = writableProvider.dir;
43183
+ ctx.autoImplStatus = { running: false, type, progress: [] };
43184
+ if (provider.category === "cli" && verification && (verification.fixtureName || verification.fixtureNames && verification.fixtureNames.length > 0)) {
43185
+ sendAutoImplSSE(ctx, {
43186
+ event: "progress",
43187
+ data: {
43188
+ function: "_preflight",
43189
+ status: "verifying",
43190
+ message: "Running preflight verification before spawning agent..."
43191
+ }
43192
+ });
43193
+ try {
43194
+ const preflight = await runCliAutoImplVerification(ctx, type, verification);
43195
+ sendAutoImplSSE(ctx, { event: "verification", data: preflight });
43196
+ if (preflight.pass) {
43197
+ sendAutoImplSSE(ctx, {
43198
+ event: "complete",
43199
+ data: {
43200
+ success: true,
43201
+ exitCode: 0,
43202
+ functions,
43203
+ message: `\u2705 No-op: exact ${preflight.mode} already passes`,
43204
+ verification: preflight,
43205
+ skipped: true
43206
+ }
43207
+ });
43208
+ ctx.json(res, 200, {
43209
+ started: false,
43210
+ skipped: true,
43211
+ type,
43212
+ functions,
43213
+ providerDir,
43214
+ verification: preflight,
43215
+ message: "Preflight verification already passes. No auto-implement run needed."
43216
+ });
43217
+ return;
43218
+ }
43219
+ } catch (error48) {
43220
+ sendAutoImplSSE(ctx, {
43221
+ event: "progress",
43222
+ data: {
43223
+ function: "_preflight",
43224
+ status: "verify_failed",
43225
+ message: `Preflight verification errored, continuing to agent run: ${error48?.message || error48}`
43226
+ }
43227
+ });
43228
+ }
43229
+ }
42034
43230
  try {
43231
+ ctx.autoImplStatus = { running: true, type, progress: ctx.autoImplStatus.progress };
42035
43232
  const resolvedReference = resolveAutoImplReference(ctx, provider.category, reference, type);
42036
43233
  sendAutoImplSSE(ctx, {
42037
43234
  event: "progress",
@@ -42051,17 +43248,17 @@ async (params) => {
42051
43248
  }
42052
43249
  });
42053
43250
  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");
43251
+ const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference, verification);
43252
+ const tmpDir = path17.join(os17.tmpdir(), "adhdev-autoimpl");
43253
+ if (!fs13.existsSync(tmpDir)) fs13.mkdirSync(tmpDir, { recursive: true });
43254
+ const promptFile = path17.join(tmpDir, `prompt-${type}-${Date.now()}.md`);
43255
+ fs13.writeFileSync(promptFile, prompt, "utf-8");
42059
43256
  ctx.log(`Auto-implement prompt written to ${promptFile} (${prompt.length} chars)`);
42060
43257
  const agentProvider = ctx.providerLoader.resolve(agent) || ctx.providerLoader.getMeta(agent);
42061
43258
  const spawn4 = agentProvider?.spawn;
42062
43259
  if (!spawn4?.command) {
42063
43260
  try {
42064
- fs12.unlinkSync(promptFile);
43261
+ fs13.unlinkSync(promptFile);
42065
43262
  } catch {
42066
43263
  }
42067
43264
  ctx.json(res, 400, { error: `Agent '${agent}' has no spawn config. Select a CLI provider with a spawn configuration.` });
@@ -42070,7 +43267,8 @@ async (params) => {
42070
43267
  const agentCategory = agentProvider?.category;
42071
43268
  if (agentCategory === "acp") {
42072
43269
  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: [] };
43270
+ ctx.autoImplStatus.running = true;
43271
+ ctx.autoImplStatus.type = type;
42074
43272
  const { ClientSideConnection: ClientSideConnection2, ndJsonStream: ndJsonStream2, PROTOCOL_VERSION: PROTOCOL_VERSION2 } = await Promise.resolve().then(() => (init_acp(), acp_exports));
42075
43273
  const { Readable: Readable2, Writable: Writable2 } = await import("stream");
42076
43274
  const { spawn: spawnFn2 } = await import("child_process");
@@ -42162,7 +43360,7 @@ async (params) => {
42162
43360
  } catch {
42163
43361
  }
42164
43362
  try {
42165
- fs12.unlinkSync(promptFile);
43363
+ fs13.unlinkSync(promptFile);
42166
43364
  } catch {
42167
43365
  }
42168
43366
  ctx.log(`Auto-implement (ACP) ${success2 ? "completed" : "failed"}: ${type} (exit: ${code})`);
@@ -42240,7 +43438,8 @@ async (params) => {
42240
43438
  }
42241
43439
  }
42242
43440
  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: [] };
43441
+ ctx.autoImplStatus.running = true;
43442
+ ctx.autoImplStatus.type = type;
42244
43443
  const spawnedAt = Date.now();
42245
43444
  let child;
42246
43445
  let isPty = false;
@@ -42283,6 +43482,7 @@ async (params) => {
42283
43482
  let approvalKeys = { 0: "y\r" };
42284
43483
  let approvalBuffer = "";
42285
43484
  let lastApprovalTime = 0;
43485
+ let completionSignalSeen = false;
42286
43486
  try {
42287
43487
  const { normalizeCliProviderForRuntime: normalizeCliProviderForRuntime2 } = await Promise.resolve().then(() => (init_provider_cli_adapter(), provider_cli_adapter_exports));
42288
43488
  const normalized = normalizeCliProviderForRuntime2(agentProvider);
@@ -42296,6 +43496,7 @@ async (params) => {
42296
43496
  approvalBuffer = (approvalBuffer + cleanData).slice(-1500);
42297
43497
  const elapsed = Date.now() - spawnedAt;
42298
43498
  if (elapsed > 15e3 && cleanData.includes("_PIPELINE_COMPLETE_SIGNAL_")) {
43499
+ completionSignalSeen = true;
42299
43500
  ctx.log(`Agent finished task after ${Math.round(elapsed / 1e3)}s. Terminating interactive CLI session to unblock pipeline.`);
42300
43501
  sendAutoImplSSE(ctx, { event: "output", data: { chunk: `
42301
43502
  [\u{1F916} ADHDev Pipeline] Completion token detected. Proceeding...
@@ -42319,6 +43520,55 @@ async (params) => {
42319
43520
  lastApprovalTime = Date.now();
42320
43521
  }
42321
43522
  };
43523
+ const finalizeCliAutoImpl = async (code) => {
43524
+ ctx.autoImplProcess = null;
43525
+ let success2 = completionSignalSeen || code === 0;
43526
+ let message = success2 ? completionSignalSeen && code !== 0 ? "\u2705 Auto-implement complete (completion signal)" : "\u2705 Auto-implement complete" : `\u274C Agent exited (code: ${code})`;
43527
+ let verificationSummary = null;
43528
+ try {
43529
+ ctx.providerLoader.reload();
43530
+ } catch {
43531
+ }
43532
+ if (provider.category === "cli" && verification) {
43533
+ sendAutoImplSSE(ctx, {
43534
+ event: "progress",
43535
+ data: {
43536
+ function: "_verify",
43537
+ status: "running",
43538
+ message: "Running exact post-patch verification..."
43539
+ }
43540
+ });
43541
+ try {
43542
+ verificationSummary = await runCliAutoImplVerification(ctx, type, verification);
43543
+ sendAutoImplSSE(ctx, { event: "verification", data: verificationSummary });
43544
+ success2 = verificationSummary.pass;
43545
+ message = verificationSummary.pass ? `\u2705 Auto-implement complete (${verificationSummary.mode})` : `\u274C Post-patch verification failed (${verificationSummary.mode}): ${verificationSummary.failures.join("; ") || "unknown failure"}`;
43546
+ } catch (error48) {
43547
+ success2 = false;
43548
+ message = `\u274C Post-patch verification error: ${error48?.message || error48}`;
43549
+ sendAutoImplSSE(ctx, {
43550
+ event: "verification",
43551
+ data: { pass: false, error: error48?.message || String(error48) }
43552
+ });
43553
+ }
43554
+ }
43555
+ ctx.autoImplStatus.running = false;
43556
+ sendAutoImplSSE(ctx, {
43557
+ event: "complete",
43558
+ data: {
43559
+ success: success2,
43560
+ exitCode: code,
43561
+ functions,
43562
+ message,
43563
+ verification: verificationSummary
43564
+ }
43565
+ });
43566
+ try {
43567
+ fs13.unlinkSync(promptFile);
43568
+ } catch {
43569
+ }
43570
+ ctx.log(`Auto-implement ${success2 ? "completed" : "failed"}: ${type} (exit: ${code})${verificationSummary ? ` verify=${verificationSummary.pass ? "pass" : "fail"}` : ""}`);
43571
+ };
42322
43572
  if (isPty) {
42323
43573
  child.onData((data) => {
42324
43574
  stdout += data;
@@ -42330,21 +43580,7 @@ async (params) => {
42330
43580
  sendAutoImplSSE(ctx, { event: "output", data: { chunk: data, stream: "stdout" } });
42331
43581
  });
42332
43582
  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
- }
43583
+ void finalizeCliAutoImpl(code);
42348
43584
  });
42349
43585
  } else {
42350
43586
  child.stdout?.on("data", (d) => {
@@ -42361,27 +43597,7 @@ async (params) => {
42361
43597
  sendAutoImplSSE(ctx, { event: "output", data: { chunk, stream: "stderr" } });
42362
43598
  });
42363
43599
  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})`);
43600
+ void finalizeCliAutoImpl(code);
42385
43601
  });
42386
43602
  }
42387
43603
  ctx.json(res, 202, {
@@ -42398,9 +43614,9 @@ async (params) => {
42398
43614
  ctx.json(res, 500, { error: `Auto-implement failed: ${e.message}` });
42399
43615
  }
42400
43616
  }
42401
- function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, userComment, referenceType) {
43617
+ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, userComment, referenceType, verification) {
42402
43618
  if (provider.category === "cli") {
42403
- return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType);
43619
+ return buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification);
42404
43620
  }
42405
43621
  const lines = [];
42406
43622
  lines.push("You are implementing browser automation scripts for an IDE provider.");
@@ -42425,7 +43641,7 @@ async (params) => {
42425
43641
  setMode: "set_mode.js"
42426
43642
  };
42427
43643
  const targetFileNames = new Set(functions.map((fn2) => funcToFile[fn2]).filter(Boolean));
42428
- const scriptsDir = path16.join(providerDir, "scripts");
43644
+ const scriptsDir = path17.join(providerDir, "scripts");
42429
43645
  const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
42430
43646
  if (latestScriptsDir) {
42431
43647
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -42433,10 +43649,10 @@ async (params) => {
42433
43649
  lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
42434
43650
  lines.push("These are the ONLY files you are allowed to modify. Replace the TODO stubs with working implementations.");
42435
43651
  lines.push("");
42436
- for (const file2 of fs12.readdirSync(latestScriptsDir)) {
43652
+ for (const file2 of fs13.readdirSync(latestScriptsDir)) {
42437
43653
  if (file2.endsWith(".js") && targetFileNames.has(file2)) {
42438
43654
  try {
42439
- const content = fs12.readFileSync(path16.join(latestScriptsDir, file2), "utf-8");
43655
+ const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
42440
43656
  lines.push(`### \`${file2}\` \u270F\uFE0F EDIT`);
42441
43657
  lines.push("```javascript");
42442
43658
  lines.push(content);
@@ -42446,14 +43662,14 @@ async (params) => {
42446
43662
  }
42447
43663
  }
42448
43664
  }
42449
- const refFiles = fs12.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
43665
+ const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
42450
43666
  if (refFiles.length > 0) {
42451
43667
  lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
42452
43668
  lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
42453
43669
  lines.push("");
42454
43670
  for (const file2 of refFiles) {
42455
43671
  try {
42456
- const content = fs12.readFileSync(path16.join(latestScriptsDir, file2), "utf-8");
43672
+ const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
42457
43673
  lines.push(`### \`${file2}\` \u{1F512}`);
42458
43674
  lines.push("```javascript");
42459
43675
  lines.push(content);
@@ -42494,11 +43710,11 @@ async (params) => {
42494
43710
  lines.push("");
42495
43711
  }
42496
43712
  }
42497
- const docsDir = path16.join(providerDir, "../../docs");
43713
+ const docsDir = path17.join(providerDir, "../../docs");
42498
43714
  const loadGuide = (name) => {
42499
43715
  try {
42500
- const p = path16.join(docsDir, name);
42501
- if (fs12.existsSync(p)) return fs12.readFileSync(p, "utf-8");
43716
+ const p = path17.join(docsDir, name);
43717
+ if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
42502
43718
  } catch {
42503
43719
  }
42504
43720
  return null;
@@ -42656,8 +43872,69 @@ async (params) => {
42656
43872
  lines.push("Start NOW. Do not ask for permission. Explore the DOM -> Code -> Test.");
42657
43873
  return lines.join("\n");
42658
43874
  }
42659
- function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType) {
43875
+ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, referenceScripts, userComment, referenceType, verification) {
42660
43876
  const lines = [];
43877
+ const defaultExercisePayload = {
43878
+ type,
43879
+ workingDir: providerDir,
43880
+ freshSession: true,
43881
+ autoLaunch: true,
43882
+ autoResolveApprovals: true,
43883
+ approvalButtonIndex: 0,
43884
+ timeoutMs: 45e3,
43885
+ traceLimit: 200,
43886
+ 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."
43887
+ };
43888
+ const exercisePayload = {
43889
+ ...defaultExercisePayload,
43890
+ ...verification?.request || {},
43891
+ type,
43892
+ workingDir: providerDir
43893
+ };
43894
+ const exerciseJson = JSON.stringify(exercisePayload).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
43895
+ const verificationInspectFields = verification?.inspectFields?.length ? verification.inspectFields : [
43896
+ "debug.messages",
43897
+ "trace.entries[].payload.parsedLastAssistant",
43898
+ "trace.entries[].payload.lastAssistant"
43899
+ ];
43900
+ const verificationMustContainAny = verification?.mustContainAny || [];
43901
+ const verificationMustNotContainAny = verification?.mustNotContainAny || [];
43902
+ const verificationMustMatchAny = verification?.mustMatchAny || [];
43903
+ const verificationMustNotMatchAny = verification?.mustNotMatchAny || [];
43904
+ const verificationLastAssistantMustContainAny = verification?.lastAssistantMustContainAny || [];
43905
+ const verificationLastAssistantMustNotContainAny = verification?.lastAssistantMustNotContainAny || [];
43906
+ const verificationLastAssistantMustMatchAny = verification?.lastAssistantMustMatchAny || [];
43907
+ const verificationLastAssistantMustNotMatchAny = verification?.lastAssistantMustNotMatchAny || [];
43908
+ const quotedMustContain = verificationMustContainAny.map((value) => JSON.stringify(value)).join(", ");
43909
+ const quotedMustNotContain = verificationMustNotContainAny.map((value) => JSON.stringify(value)).join(", ");
43910
+ const quotedMustMatch = verificationMustMatchAny.map((value) => JSON.stringify(value)).join(", ");
43911
+ const quotedMustNotMatch = verificationMustNotMatchAny.map((value) => JSON.stringify(value)).join(", ");
43912
+ const quotedLastAssistantMustContain = verificationLastAssistantMustContainAny.map((value) => JSON.stringify(value)).join(", ");
43913
+ const quotedLastAssistantMustNotContain = verificationLastAssistantMustNotContainAny.map((value) => JSON.stringify(value)).join(", ");
43914
+ const quotedLastAssistantMustMatch = verificationLastAssistantMustMatchAny.map((value) => JSON.stringify(value)).join(", ");
43915
+ const quotedLastAssistantMustNotMatch = verificationLastAssistantMustNotMatchAny.map((value) => JSON.stringify(value)).join(", ");
43916
+ const fixtureName = verification?.fixtureName || `${type}-provider-fix`;
43917
+ const fixtureNames = Array.isArray(verification?.fixtureNames) ? verification.fixtureNames.map((value) => String(value || "").trim()).filter(Boolean) : [];
43918
+ const fixtureCaptureJson = JSON.stringify({
43919
+ type,
43920
+ name: fixtureName,
43921
+ request: exercisePayload,
43922
+ assertions: {
43923
+ mustContainAny: verificationMustContainAny,
43924
+ mustNotContainAny: verificationMustNotContainAny,
43925
+ mustMatchAny: verificationMustMatchAny,
43926
+ mustNotMatchAny: verificationMustNotMatchAny,
43927
+ lastAssistantMustContainAny: verificationLastAssistantMustContainAny,
43928
+ lastAssistantMustNotContainAny: verificationLastAssistantMustNotContainAny,
43929
+ lastAssistantMustMatchAny: verificationLastAssistantMustMatchAny,
43930
+ lastAssistantMustNotMatchAny: verificationLastAssistantMustNotMatchAny,
43931
+ requireNotTimedOut: true
43932
+ }
43933
+ }).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
43934
+ const fixtureReplayJson = JSON.stringify({
43935
+ type,
43936
+ name: fixtureName
43937
+ }).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
42661
43938
  lines.push("You are implementing PTY parsing scripts for a CLI provider.");
42662
43939
  lines.push("Be concise. Do NOT explain your reasoning. Edit files directly and verify with the local DevServer.");
42663
43940
  lines.push("");
@@ -42671,7 +43948,7 @@ async (params) => {
42671
43948
  parseApproval: "parse_approval.js"
42672
43949
  };
42673
43950
  const targetFileNames = new Set(functions.map((fn2) => funcToFile[fn2]).filter(Boolean));
42674
- const scriptsDir = path16.join(providerDir, "scripts");
43951
+ const scriptsDir = path17.join(providerDir, "scripts");
42675
43952
  const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
42676
43953
  if (latestScriptsDir) {
42677
43954
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -42679,11 +43956,11 @@ async (params) => {
42679
43956
  lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
42680
43957
  lines.push("These are the ONLY files you are allowed to modify. Replace TODO or heuristic-only logic with working PTY-aware implementations.");
42681
43958
  lines.push("");
42682
- for (const file2 of fs12.readdirSync(latestScriptsDir)) {
43959
+ for (const file2 of fs13.readdirSync(latestScriptsDir)) {
42683
43960
  if (!file2.endsWith(".js")) continue;
42684
43961
  if (!targetFileNames.has(file2)) continue;
42685
43962
  try {
42686
- const content = fs12.readFileSync(path16.join(latestScriptsDir, file2), "utf-8");
43963
+ const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
42687
43964
  lines.push(`### \`${file2}\` \u270F\uFE0F EDIT`);
42688
43965
  lines.push("```javascript");
42689
43966
  lines.push(content);
@@ -42692,14 +43969,14 @@ async (params) => {
42692
43969
  } catch {
42693
43970
  }
42694
43971
  }
42695
- const refFiles = fs12.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
43972
+ const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
42696
43973
  if (refFiles.length > 0) {
42697
43974
  lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
42698
43975
  lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
42699
43976
  lines.push("");
42700
43977
  for (const file2 of refFiles) {
42701
43978
  try {
42702
- const content = fs12.readFileSync(path16.join(latestScriptsDir, file2), "utf-8");
43979
+ const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
42703
43980
  lines.push(`### \`${file2}\` \u{1F512}`);
42704
43981
  lines.push("```javascript");
42705
43982
  lines.push(content);
@@ -42732,17 +44009,17 @@ async (params) => {
42732
44009
  lines.push("");
42733
44010
  }
42734
44011
  }
42735
- const docsDir = path16.join(providerDir, "../../docs");
44012
+ const docsDir = path17.join(providerDir, "../../docs");
42736
44013
  const loadGuide = (name) => {
42737
44014
  try {
42738
- const p = path16.join(docsDir, name);
42739
- if (fs12.existsSync(p)) return fs12.readFileSync(p, "utf-8");
44015
+ const p = path17.join(docsDir, name);
44016
+ if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
42740
44017
  } catch {
42741
44018
  }
42742
44019
  return null;
42743
44020
  };
42744
44021
  const providerGuide = loadGuide("PROVIDER_GUIDE.md");
42745
- if (providerGuide) {
44022
+ if (providerGuide && provider.category !== "cli") {
42746
44023
  lines.push("## Documentation: PROVIDER_GUIDE.md");
42747
44024
  lines.push("```markdown");
42748
44025
  lines.push(providerGuide);
@@ -42784,6 +44061,11 @@ async (params) => {
42784
44061
  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
44062
  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
44063
  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.");
44064
+ 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.');
44065
+ 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.");
44066
+ 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.');
44067
+ 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.");
44068
+ 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
44069
  lines.push("");
42788
44070
  lines.push("## Task");
42789
44071
  lines.push(`Edit files in \`${providerDir}\` to implement: **${functions.join(", ")}**`);
@@ -42791,35 +44073,145 @@ async (params) => {
42791
44073
  lines.push("## Verification API");
42792
44074
  lines.push("Use the DevServer CLI debug endpoints, not DOM/CDP routes.");
42793
44075
  lines.push("");
42794
- lines.push("### 1. Launch the target CLI");
44076
+ lines.push("### 1. Preferred: run a full autonomous repro");
44077
+ 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
44078
  lines.push("```bash");
42796
- lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/launch \\`);
44079
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
42797
44080
  lines.push(' -H "Content-Type: application/json" \\');
42798
- lines.push(` -d '{"type":"${type}","workingDir":"${providerDir.replace(/\\/g, "\\\\")}"}'`);
44081
+ lines.push(` -d '${exerciseJson}'`);
42799
44082
  lines.push("```");
42800
44083
  lines.push("");
44084
+ if (verification?.description) {
44085
+ lines.push("Verification intent:");
44086
+ lines.push(verification.description);
44087
+ lines.push("");
44088
+ }
44089
+ lines.push("Read the JSON response carefully. It already includes:");
44090
+ lines.push("1. `instanceId`");
44091
+ lines.push("2. `statusesSeen` and `approvalsResolved`");
44092
+ lines.push("3. `debug` for the final settled state");
44093
+ lines.push("4. `trace.entries` for the repro turn");
44094
+ lines.push("");
44095
+ lines.push("Save the response to a temp file and inspect the exact parsed transcript fields before editing:");
44096
+ lines.push("```bash");
44097
+ lines.push(`EXERCISE_JSON=$(mktemp)`);
44098
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/exercise \\`);
44099
+ lines.push(' -H "Content-Type: application/json" \\');
44100
+ lines.push(` -d '${exerciseJson}' > "$EXERCISE_JSON"`);
44101
+ lines.push(`jq '{timedOut,statusesSeen,approvalsResolved,inspect:{${verificationInspectFields.map((field, index) => `f${index + 1}: .${field}`).join(", ")}}}' "$EXERCISE_JSON"`);
44102
+ lines.push("```");
44103
+ lines.push("");
44104
+ 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) {
44105
+ 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.");
44106
+ lines.push("```bash");
44107
+ if (verificationMustContainAny.length > 0) {
44108
+ 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"`);
44109
+ }
44110
+ if (verificationMustNotContainAny.length > 0) {
44111
+ 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"`);
44112
+ }
44113
+ if (verificationMustMatchAny.length > 0) {
44114
+ 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"`);
44115
+ }
44116
+ if (verificationMustNotMatchAny.length > 0) {
44117
+ 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"`);
44118
+ }
44119
+ if (verificationLastAssistantMustContainAny.length > 0) {
44120
+ 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"`);
44121
+ }
44122
+ if (verificationLastAssistantMustNotContainAny.length > 0) {
44123
+ 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"`);
44124
+ }
44125
+ if (verificationLastAssistantMustMatchAny.length > 0) {
44126
+ 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"`);
44127
+ }
44128
+ if (verificationLastAssistantMustNotMatchAny.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 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"`);
44130
+ }
44131
+ lines.push("```");
44132
+ lines.push("");
44133
+ }
44134
+ 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.");
44135
+ lines.push("");
44136
+ lines.push("### 1b. Persist or replay the exact repro as a reusable fixture");
44137
+ if (fixtureNames.length > 0) {
44138
+ 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(", ")}.`);
44139
+ for (const name of fixtureNames) {
44140
+ const replayJson = JSON.stringify({ type, name }).replace(/\\/g, "\\\\").replace(/'/g, `'\\''`);
44141
+ lines.push("```bash");
44142
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
44143
+ lines.push(' -H "Content-Type: application/json" \\');
44144
+ lines.push(` -d '${replayJson}'`);
44145
+ lines.push("```");
44146
+ lines.push("");
44147
+ }
44148
+ lines.push("Do not create new fixtures unless one of the listed fixtures is missing or stale.");
44149
+ } else if (verification?.fixtureName) {
44150
+ 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.`);
44151
+ lines.push("```bash");
44152
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
44153
+ lines.push(' -H "Content-Type: application/json" \\');
44154
+ lines.push(` -d '${fixtureReplayJson}'`);
44155
+ lines.push("```");
44156
+ lines.push("");
44157
+ 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.");
44158
+ lines.push("```bash");
44159
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
44160
+ lines.push(' -H "Content-Type: application/json" \\');
44161
+ lines.push(` -d '${fixtureCaptureJson}'`);
44162
+ lines.push("```");
44163
+ } else {
44164
+ lines.push("Capture the exact exercise once before editing. After patching, replay THIS fixture and do not declare success unless replay passes.");
44165
+ lines.push("```bash");
44166
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/capture \\`);
44167
+ lines.push(' -H "Content-Type: application/json" \\');
44168
+ lines.push(` -d '${fixtureCaptureJson}'`);
44169
+ lines.push("");
44170
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/fixture/replay \\`);
44171
+ lines.push(' -H "Content-Type: application/json" \\');
44172
+ lines.push(` -d '${fixtureReplayJson}'`);
44173
+ lines.push("```");
44174
+ }
44175
+ lines.push("");
44176
+ 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.");
44177
+ lines.push("");
42801
44178
  lines.push("### 2. Inspect parsed + raw adapter state");
42802
44179
  lines.push("```bash");
44180
+ lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/launch \\`);
44181
+ lines.push(' -H "Content-Type: application/json" \\');
44182
+ lines.push(` -d '{"type":"${type}","workingDir":"${providerDir.replace(/\\/g, "\\\\")}"}'`);
42803
44183
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
44184
+ lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
42804
44185
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
42805
44186
  lines.push("```");
42806
44187
  lines.push("");
44188
+ lines.push("The CLI trace endpoint is the primary debugging source. Read it BEFORE editing any parser code.");
44189
+ lines.push("Use the trace timeline to find the latest `settled` or `commit_transcript` frame for the repro turn and inspect these fields first:");
44190
+ lines.push("1. `payload.screenText`");
44191
+ lines.push("2. `payload.detectStatus` and `payload.parsedStatus`");
44192
+ lines.push("3. `payload.parsedLastAssistant`");
44193
+ lines.push("4. `payload.approval` / `payload.parsedActiveModal`");
44194
+ lines.push("5. `payload.rawPreview` only when control-sequence residue matters");
44195
+ lines.push("");
42807
44196
  lines.push("The debug payload should be read in this priority order:");
42808
44197
  lines.push("1. `screenText` / current visible state");
42809
44198
  lines.push("2. parsed `status`, `messages`, `activeModal`");
42810
44199
  lines.push("3. `rawBuffer` only for style/control-sequence cues");
42811
44200
  lines.push("4. `buffer` only when the current screen is insufficient");
42812
44201
  lines.push("");
42813
- lines.push("Extract the current `instanceId` from the launch or status response and keep using it below.");
44202
+ 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.");
44203
+ lines.push("Do NOT guess based only on the final chat bubble or a truncated UI preview.");
42814
44204
  lines.push("");
42815
- lines.push("### 3. Send a realistic approval-triggering prompt");
44205
+ lines.push("Extract the current `instanceId` from the exercise, launch, or status response and keep using it below.");
44206
+ lines.push("");
44207
+ lines.push("### 3. Manual fallback only: send a realistic approval-triggering prompt");
42816
44208
  lines.push("```bash");
42817
44209
  lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/send \\`);
42818
44210
  lines.push(' -H "Content-Type: application/json" \\');
42819
44211
  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
44212
  lines.push("```");
42821
44213
  lines.push("");
42822
- lines.push("### 4. If approval appears, resolve it until the CLI reaches idle");
44214
+ lines.push("### 4. Manual fallback only: if approval appears, resolve it until the CLI reaches idle");
42823
44215
  lines.push("```bash");
42824
44216
  lines.push(`curl -sS -X POST http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/resolve \\`);
42825
44217
  lines.push(' -H "Content-Type: application/json" \\');
@@ -42829,10 +44221,14 @@ async (params) => {
42829
44221
  lines.push(` -d '{"type":"${type}","instanceId":"<INSTANCE_ID>","keys":"1"}'`);
42830
44222
  lines.push("```");
42831
44223
  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.");
44224
+ 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
44225
  lines.push("");
42834
44226
  lines.push("### Patch Discipline");
42835
44227
  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.");
44228
+ 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.");
44229
+ 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.");
44230
+ 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.");
44231
+ 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
44232
  lines.push("");
42837
44233
  lines.push("### 5. Verify the side effects outside the CLI");
42838
44234
  lines.push("```bash");
@@ -42857,6 +44253,8 @@ async (params) => {
42857
44253
  lines.push("7. Re-run the debug endpoints after edits. Do NOT finish until the parsed result looks correct.");
42858
44254
  lines.push("8. Confirm the parser still works after a redraw or scroll change without duplicating transcript history.");
42859
44255
  lines.push("9. Confirm the implementation prefers current-screen signals over stale history when both are present.");
44256
+ 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.");
44257
+ 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
44258
  lines.push("");
42861
44259
  if (userComment) {
42862
44260
  lines.push("## \u26A0\uFE0F User Instructions (HIGH PRIORITY)");
@@ -42865,10 +44263,11 @@ async (params) => {
42865
44263
  lines.push(userComment);
42866
44264
  lines.push("");
42867
44265
  }
42868
- lines.push("Start NOW. Launch the CLI, inspect PTY state, edit the scripts, and verify via the CLI debug endpoints.");
44266
+ 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
44267
  return lines.join("\n");
42870
44268
  }
42871
44269
  function handleAutoImplSSE(ctx, type, req, res) {
44270
+ clearStaleAutoImplState(ctx, "SSE connection opened");
42872
44271
  res.writeHead(200, {
42873
44272
  "Content-Type": "text/event-stream",
42874
44273
  "Cache-Control": "no-cache",
@@ -42890,6 +44289,7 @@ data: ${JSON.stringify(p.data)}
42890
44289
  });
42891
44290
  }
42892
44291
  function handleAutoImplCancel(ctx, _type, _req, res) {
44292
+ clearStaleAutoImplState(ctx, "cancel request");
42893
44293
  if (ctx.autoImplProcess) {
42894
44294
  ctx.autoImplProcess.kill("SIGTERM");
42895
44295
  setTimeout(() => {
@@ -42971,11 +44371,16 @@ data: ${JSON.stringify(msg.data)}
42971
44371
  { method: "GET", pattern: "/api/cli/status", handler: (q2, s15) => this.handleCliStatus(q2, s15) },
42972
44372
  { method: "POST", pattern: "/api/cli/launch", handler: (q2, s15) => this.handleCliLaunch(q2, s15) },
42973
44373
  { method: "POST", pattern: "/api/cli/send", handler: (q2, s15) => this.handleCliSend(q2, s15) },
44374
+ { method: "POST", pattern: "/api/cli/exercise", handler: (q2, s15) => this.handleCliExercise(q2, s15) },
44375
+ { method: "POST", pattern: "/api/cli/fixture/capture", handler: (q2, s15) => this.handleCliFixtureCapture(q2, s15) },
44376
+ { method: "POST", pattern: "/api/cli/fixture/replay", handler: (q2, s15) => this.handleCliFixtureReplay(q2, s15) },
42974
44377
  { method: "POST", pattern: "/api/cli/resolve", handler: (q2, s15) => this.handleCliResolve(q2, s15) },
42975
44378
  { method: "POST", pattern: "/api/cli/raw", handler: (q2, s15) => this.handleCliRaw(q2, s15) },
42976
44379
  { method: "POST", pattern: "/api/cli/stop", handler: (q2, s15) => this.handleCliStop(q2, s15) },
42977
44380
  { method: "GET", pattern: "/api/cli/events", handler: (q2, s15) => this.handleCliSSE(q2, s15) },
42978
44381
  { method: "GET", pattern: /^\/api\/cli\/debug\/([^/]+)$/, handler: (q2, s15, p) => this.handleCliDebug(p[0], q2, s15) },
44382
+ { method: "GET", pattern: /^\/api\/cli\/trace\/([^/]+)$/, handler: (q2, s15, p) => this.handleCliTrace(p[0], q2, s15) },
44383
+ { method: "GET", pattern: /^\/api\/cli\/fixtures\/([^/]+)$/, handler: (q2, s15, p) => this.handleCliFixtureList(p[0], q2, s15) },
42979
44384
  // Dynamic routes (provider :type param)
42980
44385
  { method: "POST", pattern: /^\/api\/providers\/([^/]+)\/script$/, handler: (q2, s15, p) => this.handleRunScript(p[0], q2, s15) },
42981
44386
  { method: "GET", pattern: /^\/api\/providers\/([^/]+)\/files$/, handler: (q2, s15, p) => this.handleListFiles(p[0], q2, s15) },
@@ -43009,8 +44414,8 @@ data: ${JSON.stringify(msg.data)}
43009
44414
  }
43010
44415
  getEndpointList() {
43011
44416
  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}`;
44417
+ const path19 = typeof r.pattern === "string" ? r.pattern : r.pattern.source.replace(/\\\//g, "/").replace(/\(\[.*?\]\+\)/g, ":type").replace(/[\^$]/g, "");
44418
+ return `${r.method.padEnd(5)} ${path19}`;
43014
44419
  });
43015
44420
  }
43016
44421
  async start(port = DEV_SERVER_PORT) {
@@ -43292,12 +44697,12 @@ data: ${JSON.stringify(msg.data)}
43292
44697
  // ─── DevConsole SPA ───
43293
44698
  getConsoleDistDir() {
43294
44699
  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")
44700
+ path18.resolve(__dirname, "../../web-devconsole/dist"),
44701
+ path18.resolve(__dirname, "../../../web-devconsole/dist"),
44702
+ path18.join(process.cwd(), "packages/web-devconsole/dist")
43298
44703
  ];
43299
44704
  for (const dir of candidates) {
43300
- if (fs13.existsSync(path17.join(dir, "index.html"))) return dir;
44705
+ if (fs14.existsSync(path18.join(dir, "index.html"))) return dir;
43301
44706
  }
43302
44707
  return null;
43303
44708
  }
@@ -43307,9 +44712,9 @@ data: ${JSON.stringify(msg.data)}
43307
44712
  this.json(res, 500, { error: "DevConsole not found. Run: npm run build -w packages/web-devconsole" });
43308
44713
  return;
43309
44714
  }
43310
- const htmlPath = path17.join(distDir, "index.html");
44715
+ const htmlPath = path18.join(distDir, "index.html");
43311
44716
  try {
43312
- const html = fs13.readFileSync(htmlPath, "utf-8");
44717
+ const html = fs14.readFileSync(htmlPath, "utf-8");
43313
44718
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
43314
44719
  res.end(html);
43315
44720
  } catch (e) {
@@ -43332,15 +44737,15 @@ data: ${JSON.stringify(msg.data)}
43332
44737
  this.json(res, 404, { error: "Not found" });
43333
44738
  return;
43334
44739
  }
43335
- const safePath = path17.normalize(pathname).replace(/^\.\.\//, "");
43336
- const filePath = path17.join(distDir, safePath);
44740
+ const safePath = path18.normalize(pathname).replace(/^\.\.\//, "");
44741
+ const filePath = path18.join(distDir, safePath);
43337
44742
  if (!filePath.startsWith(distDir)) {
43338
44743
  this.json(res, 403, { error: "Forbidden" });
43339
44744
  return;
43340
44745
  }
43341
44746
  try {
43342
- const content = fs13.readFileSync(filePath);
43343
- const ext = path17.extname(filePath);
44747
+ const content = fs14.readFileSync(filePath);
44748
+ const ext = path18.extname(filePath);
43344
44749
  const contentType = _DevServer.MIME_MAP[ext] || "application/octet-stream";
43345
44750
  res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "public, max-age=31536000, immutable" });
43346
44751
  res.end(content);
@@ -43448,14 +44853,14 @@ data: ${JSON.stringify(msg.data)}
43448
44853
  const files = [];
43449
44854
  const scan = (d, prefix) => {
43450
44855
  try {
43451
- for (const entry of fs13.readdirSync(d, { withFileTypes: true })) {
44856
+ for (const entry of fs14.readdirSync(d, { withFileTypes: true })) {
43452
44857
  if (entry.name.startsWith(".") || entry.name.endsWith(".bak")) continue;
43453
44858
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
43454
44859
  if (entry.isDirectory()) {
43455
44860
  files.push({ path: rel, size: 0, type: "dir" });
43456
- scan(path17.join(d, entry.name), rel);
44861
+ scan(path18.join(d, entry.name), rel);
43457
44862
  } else {
43458
- const stat4 = fs13.statSync(path17.join(d, entry.name));
44863
+ const stat4 = fs14.statSync(path18.join(d, entry.name));
43459
44864
  files.push({ path: rel, size: stat4.size, type: "file" });
43460
44865
  }
43461
44866
  }
@@ -43478,16 +44883,16 @@ data: ${JSON.stringify(msg.data)}
43478
44883
  this.json(res, 404, { error: `Provider directory not found: ${type}` });
43479
44884
  return;
43480
44885
  }
43481
- const fullPath = path17.resolve(dir, path17.normalize(filePath));
44886
+ const fullPath = path18.resolve(dir, path18.normalize(filePath));
43482
44887
  if (!fullPath.startsWith(dir)) {
43483
44888
  this.json(res, 403, { error: "Forbidden" });
43484
44889
  return;
43485
44890
  }
43486
- if (!fs13.existsSync(fullPath) || fs13.statSync(fullPath).isDirectory()) {
44891
+ if (!fs14.existsSync(fullPath) || fs14.statSync(fullPath).isDirectory()) {
43487
44892
  this.json(res, 404, { error: `File not found: ${filePath}` });
43488
44893
  return;
43489
44894
  }
43490
- const content = fs13.readFileSync(fullPath, "utf-8");
44895
+ const content = fs14.readFileSync(fullPath, "utf-8");
43491
44896
  this.json(res, 200, { type, path: filePath, content, lines: content.split("\n").length });
43492
44897
  }
43493
44898
  /** POST /api/providers/:type/file — write a file { path, content } */
@@ -43503,15 +44908,15 @@ data: ${JSON.stringify(msg.data)}
43503
44908
  this.json(res, 404, { error: `Provider directory not found: ${type}` });
43504
44909
  return;
43505
44910
  }
43506
- const fullPath = path17.resolve(dir, path17.normalize(filePath));
44911
+ const fullPath = path18.resolve(dir, path18.normalize(filePath));
43507
44912
  if (!fullPath.startsWith(dir)) {
43508
44913
  this.json(res, 403, { error: "Forbidden" });
43509
44914
  return;
43510
44915
  }
43511
44916
  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");
44917
+ if (fs14.existsSync(fullPath)) fs14.copyFileSync(fullPath, fullPath + ".bak");
44918
+ fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
44919
+ fs14.writeFileSync(fullPath, content, "utf-8");
43515
44920
  this.log(`File saved: ${fullPath} (${content.length} chars)`);
43516
44921
  this.providerLoader.reload();
43517
44922
  this.json(res, 200, { saved: true, path: filePath, chars: content.length });
@@ -43527,9 +44932,9 @@ data: ${JSON.stringify(msg.data)}
43527
44932
  return;
43528
44933
  }
43529
44934
  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");
44935
+ const p = path18.join(dir, name);
44936
+ if (fs14.existsSync(p)) {
44937
+ const source = fs14.readFileSync(p, "utf-8");
43533
44938
  this.json(res, 200, { type, path: p, source, lines: source.split("\n").length });
43534
44939
  return;
43535
44940
  }
@@ -43548,11 +44953,11 @@ data: ${JSON.stringify(msg.data)}
43548
44953
  this.json(res, 404, { error: `Provider not found: ${type}` });
43549
44954
  return;
43550
44955
  }
43551
- const target = fs13.existsSync(path17.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
43552
- const targetPath = path17.join(dir, target);
44956
+ const target = fs14.existsSync(path18.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
44957
+ const targetPath = path18.join(dir, target);
43553
44958
  try {
43554
- if (fs13.existsSync(targetPath)) fs13.copyFileSync(targetPath, targetPath + ".bak");
43555
- fs13.writeFileSync(targetPath, source, "utf-8");
44959
+ if (fs14.existsSync(targetPath)) fs14.copyFileSync(targetPath, targetPath + ".bak");
44960
+ fs14.writeFileSync(targetPath, source, "utf-8");
43556
44961
  this.log(`Saved provider: ${targetPath} (${source.length} chars)`);
43557
44962
  this.providerLoader.reload();
43558
44963
  this.json(res, 200, { saved: true, path: targetPath, chars: source.length });
@@ -43709,21 +45114,21 @@ data: ${JSON.stringify(msg.data)}
43709
45114
  }
43710
45115
  let targetDir;
43711
45116
  targetDir = this.providerLoader.getUserProviderDir(category, type);
43712
- const jsonPath = path17.join(targetDir, "provider.json");
43713
- if (fs13.existsSync(jsonPath)) {
45117
+ const jsonPath = path18.join(targetDir, "provider.json");
45118
+ if (fs14.existsSync(jsonPath)) {
43714
45119
  this.json(res, 409, { error: `Provider already exists at ${targetDir}`, path: targetDir });
43715
45120
  return;
43716
45121
  }
43717
45122
  try {
43718
45123
  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");
45124
+ fs14.mkdirSync(targetDir, { recursive: true });
45125
+ fs14.writeFileSync(jsonPath, result["provider.json"], "utf-8");
43721
45126
  const createdFiles = ["provider.json"];
43722
45127
  if (result.files) {
43723
45128
  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");
45129
+ const fullPath = path18.join(targetDir, relPath);
45130
+ fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
45131
+ fs14.writeFileSync(fullPath, content, "utf-8");
43727
45132
  createdFiles.push(relPath);
43728
45133
  }
43729
45134
  }
@@ -43772,45 +45177,45 @@ data: ${JSON.stringify(msg.data)}
43772
45177
  }
43773
45178
  // ─── Phase 2: Auto-Implement Backend ───
43774
45179
  getLatestScriptVersionDir(scriptsDir) {
43775
- if (!fs13.existsSync(scriptsDir)) return null;
43776
- const versions = fs13.readdirSync(scriptsDir).filter((d) => {
45180
+ if (!fs14.existsSync(scriptsDir)) return null;
45181
+ const versions = fs14.readdirSync(scriptsDir).filter((d) => {
43777
45182
  try {
43778
- return fs13.statSync(path17.join(scriptsDir, d)).isDirectory();
45183
+ return fs14.statSync(path18.join(scriptsDir, d)).isDirectory();
43779
45184
  } catch {
43780
45185
  return false;
43781
45186
  }
43782
45187
  }).sort((a, b2) => b2.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
43783
45188
  if (versions.length === 0) return null;
43784
- return path17.join(scriptsDir, versions[0]);
45189
+ return path18.join(scriptsDir, versions[0]);
43785
45190
  }
43786
45191
  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}`)) {
45192
+ const canonicalUserDir = path18.resolve(this.providerLoader.getUserProviderDir(category, type));
45193
+ const desiredDir = requestedDir ? path18.resolve(requestedDir) : canonicalUserDir;
45194
+ const upstreamRoot = path18.resolve(this.providerLoader.getUpstreamDir());
45195
+ if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path18.sep}`)) {
43791
45196
  return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
43792
45197
  }
43793
- if (path17.basename(desiredDir) !== type) {
45198
+ if (path18.basename(desiredDir) !== type) {
43794
45199
  return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
43795
45200
  }
43796
45201
  const sourceDir = this.findProviderDir(type);
43797
45202
  if (!sourceDir) {
43798
45203
  return { dir: null, reason: `Provider source directory not found for '${type}'` };
43799
45204
  }
43800
- if (!fs13.existsSync(desiredDir)) {
43801
- fs13.mkdirSync(path17.dirname(desiredDir), { recursive: true });
43802
- fs13.cpSync(sourceDir, desiredDir, { recursive: true });
45205
+ if (!fs14.existsSync(desiredDir)) {
45206
+ fs14.mkdirSync(path18.dirname(desiredDir), { recursive: true });
45207
+ fs14.cpSync(sourceDir, desiredDir, { recursive: true });
43803
45208
  this.log(`Auto-implement writable copy created: ${desiredDir}`);
43804
45209
  }
43805
- const providerJson = path17.join(desiredDir, "provider.json");
43806
- if (!fs13.existsSync(providerJson)) {
45210
+ const providerJson = path18.join(desiredDir, "provider.json");
45211
+ if (!fs14.existsSync(providerJson)) {
43807
45212
  return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
43808
45213
  }
43809
45214
  try {
43810
- const providerData = JSON.parse(fs13.readFileSync(providerJson, "utf-8"));
45215
+ const providerData = JSON.parse(fs14.readFileSync(providerJson, "utf-8"));
43811
45216
  if (providerData.disableUpstream !== true) {
43812
45217
  providerData.disableUpstream = true;
43813
- fs13.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
45218
+ fs14.writeFileSync(providerJson, JSON.stringify(providerData, null, 2));
43814
45219
  }
43815
45220
  } catch (error48) {
43816
45221
  return {
@@ -43850,7 +45255,7 @@ data: ${JSON.stringify(msg.data)}
43850
45255
  setMode: "set_mode.js"
43851
45256
  };
43852
45257
  const targetFileNames = new Set(functions.map((fn2) => funcToFile[fn2]).filter(Boolean));
43853
- const scriptsDir = path17.join(providerDir, "scripts");
45258
+ const scriptsDir = path18.join(providerDir, "scripts");
43854
45259
  const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
43855
45260
  if (latestScriptsDir) {
43856
45261
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -43858,10 +45263,10 @@ data: ${JSON.stringify(msg.data)}
43858
45263
  lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
43859
45264
  lines.push("These are the ONLY files you are allowed to modify. Replace the TODO stubs with working implementations.");
43860
45265
  lines.push("");
43861
- for (const file2 of fs13.readdirSync(latestScriptsDir)) {
45266
+ for (const file2 of fs14.readdirSync(latestScriptsDir)) {
43862
45267
  if (file2.endsWith(".js") && targetFileNames.has(file2)) {
43863
45268
  try {
43864
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
45269
+ const content = fs14.readFileSync(path18.join(latestScriptsDir, file2), "utf-8");
43865
45270
  lines.push(`### \`${file2}\` \u270F\uFE0F EDIT`);
43866
45271
  lines.push("```javascript");
43867
45272
  lines.push(content);
@@ -43871,14 +45276,14 @@ data: ${JSON.stringify(msg.data)}
43871
45276
  }
43872
45277
  }
43873
45278
  }
43874
- const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
45279
+ const refFiles = fs14.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
43875
45280
  if (refFiles.length > 0) {
43876
45281
  lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
43877
45282
  lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
43878
45283
  lines.push("");
43879
45284
  for (const file2 of refFiles) {
43880
45285
  try {
43881
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
45286
+ const content = fs14.readFileSync(path18.join(latestScriptsDir, file2), "utf-8");
43882
45287
  lines.push(`### \`${file2}\` \u{1F512}`);
43883
45288
  lines.push("```javascript");
43884
45289
  lines.push(content);
@@ -43919,11 +45324,11 @@ data: ${JSON.stringify(msg.data)}
43919
45324
  lines.push("");
43920
45325
  }
43921
45326
  }
43922
- const docsDir = path17.join(providerDir, "../../docs");
45327
+ const docsDir = path18.join(providerDir, "../../docs");
43923
45328
  const loadGuide = (name) => {
43924
45329
  try {
43925
- const p = path17.join(docsDir, name);
43926
- if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
45330
+ const p = path18.join(docsDir, name);
45331
+ if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
43927
45332
  } catch {
43928
45333
  }
43929
45334
  return null;
@@ -44096,7 +45501,7 @@ data: ${JSON.stringify(msg.data)}
44096
45501
  parseApproval: "parse_approval.js"
44097
45502
  };
44098
45503
  const targetFileNames = new Set(functions.map((fn2) => funcToFile[fn2]).filter(Boolean));
44099
- const scriptsDir = path17.join(providerDir, "scripts");
45504
+ const scriptsDir = path18.join(providerDir, "scripts");
44100
45505
  const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
44101
45506
  if (latestScriptsDir) {
44102
45507
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -44104,11 +45509,11 @@ data: ${JSON.stringify(msg.data)}
44104
45509
  lines.push("## \u270F\uFE0F Target Files (EDIT THESE)");
44105
45510
  lines.push("These are the ONLY files you are allowed to modify. Replace TODO or heuristic-only logic with working PTY-aware implementations.");
44106
45511
  lines.push("");
44107
- for (const file2 of fs13.readdirSync(latestScriptsDir)) {
45512
+ for (const file2 of fs14.readdirSync(latestScriptsDir)) {
44108
45513
  if (!file2.endsWith(".js")) continue;
44109
45514
  if (!targetFileNames.has(file2)) continue;
44110
45515
  try {
44111
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
45516
+ const content = fs14.readFileSync(path18.join(latestScriptsDir, file2), "utf-8");
44112
45517
  lines.push(`### \`${file2}\` \u270F\uFE0F EDIT`);
44113
45518
  lines.push("```javascript");
44114
45519
  lines.push(content);
@@ -44117,14 +45522,14 @@ data: ${JSON.stringify(msg.data)}
44117
45522
  } catch {
44118
45523
  }
44119
45524
  }
44120
- const refFiles = fs13.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
45525
+ const refFiles = fs14.readdirSync(latestScriptsDir).filter((f) => f.endsWith(".js") && !targetFileNames.has(f));
44121
45526
  if (refFiles.length > 0) {
44122
45527
  lines.push("## \u{1F512} Other Scripts (REFERENCE ONLY \u2014 DO NOT EDIT)");
44123
45528
  lines.push("These files are shown for context only. Do NOT modify them under any circumstances.");
44124
45529
  lines.push("");
44125
45530
  for (const file2 of refFiles) {
44126
45531
  try {
44127
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file2), "utf-8");
45532
+ const content = fs14.readFileSync(path18.join(latestScriptsDir, file2), "utf-8");
44128
45533
  lines.push(`### \`${file2}\` \u{1F512}`);
44129
45534
  lines.push("```javascript");
44130
45535
  lines.push(content);
@@ -44157,11 +45562,11 @@ data: ${JSON.stringify(msg.data)}
44157
45562
  lines.push("");
44158
45563
  }
44159
45564
  }
44160
- const docsDir = path17.join(providerDir, "../../docs");
45565
+ const docsDir = path18.join(providerDir, "../../docs");
44161
45566
  const loadGuide = (name) => {
44162
45567
  try {
44163
- const p = path17.join(docsDir, name);
44164
- if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
45568
+ const p = path18.join(docsDir, name);
45569
+ if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
44165
45570
  } catch {
44166
45571
  }
44167
45572
  return null;
@@ -44226,6 +45631,7 @@ data: ${JSON.stringify(msg.data)}
44226
45631
  lines.push("### 2. Inspect parsed + raw adapter state");
44227
45632
  lines.push("```bash");
44228
45633
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/debug/${type}`);
45634
+ lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/trace/${type}`);
44229
45635
  lines.push(`curl -sS http://127.0.0.1:${DEV_SERVER_PORT}/api/cli/status`);
44230
45636
  lines.push("```");
44231
45637
  lines.push("");
@@ -44360,6 +45766,16 @@ data: ${JSON.stringify(msg.data)}
44360
45766
  async handleCliSend(req, res) {
44361
45767
  return handleCliSend(this, req, res);
44362
45768
  }
45769
+ /** POST /api/cli/exercise — launch/send/approve/wait helper for provider-fix loops */
45770
+ async handleCliExercise(req, res) {
45771
+ return handleCliExercise(this, req, res);
45772
+ }
45773
+ async handleCliFixtureCapture(req, res) {
45774
+ return handleCliFixtureCapture(this, req, res);
45775
+ }
45776
+ async handleCliFixtureReplay(req, res) {
45777
+ return handleCliFixtureReplay(this, req, res);
45778
+ }
44363
45779
  /** POST /api/cli/stop — stop a running CLI { type } */
44364
45780
  async handleCliStop(req, res) {
44365
45781
  return handleCliStop(this, req, res);
@@ -44383,6 +45799,13 @@ data: ${JSON.stringify(msg.data)}
44383
45799
  async handleCliDebug(type, _req, res) {
44384
45800
  return handleCliDebug(this, type, _req, res);
44385
45801
  }
45802
+ /** GET /api/cli/trace/:type — recent CLI trace timeline plus current debug snapshot */
45803
+ async handleCliTrace(type, _req, res) {
45804
+ return handleCliTrace(this, type, _req, res);
45805
+ }
45806
+ async handleCliFixtureList(type, _req, res) {
45807
+ return handleCliFixtureList(this, type, _req, res);
45808
+ }
44386
45809
  /** POST /api/cli/resolve — resolve an approval modal { type, buttonIndex } */
44387
45810
  async handleCliResolve(req, res) {
44388
45811
  return handleCliResolve(this, req, res);
@@ -44549,7 +45972,18 @@ data: ${JSON.stringify(msg.data)}
44549
45972
  });
44550
45973
  }
44551
45974
  async boot() {
44552
- await this.client.connect();
45975
+ if (typeof this.options.ensureReady === "function") {
45976
+ await this.options.ensureReady();
45977
+ }
45978
+ try {
45979
+ await this.client.connect();
45980
+ } catch (error48) {
45981
+ if (typeof this.options.ensureReady !== "function") {
45982
+ throw error48;
45983
+ }
45984
+ await this.options.ensureReady();
45985
+ await this.client.connect();
45986
+ }
44553
45987
  this.unsubscribe = this.client.onEvent((event) => this.handleEvent(event));
44554
45988
  let record2 = null;
44555
45989
  if (this.options.attachExisting) {
@@ -44924,8 +46358,8 @@ data: ${JSON.stringify(msg.data)}
44924
46358
  const res = await fetch(extension.vsixUrl);
44925
46359
  if (res.ok) {
44926
46360
  const buffer = Buffer.from(await res.arrayBuffer());
44927
- const fs14 = await import("fs");
44928
- fs14.writeFileSync(vsixPath, buffer);
46361
+ const fs15 = await import("fs");
46362
+ fs15.writeFileSync(vsixPath, buffer);
44929
46363
  return new Promise((resolve10) => {
44930
46364
  const cmd = `"${ide.cliCommand}" --install-extension "${vsixPath}" --force`;
44931
46365
  (0, import_child_process10.exec)(cmd, { timeout: 6e4 }, (error48, _stdout, stderr) => {
@@ -45238,15 +46672,18 @@ data: ${JSON.stringify(msg.data)}
45238
46672
  var import_http = require("http");
45239
46673
  var import_ws = require("ws");
45240
46674
  var path4 = __toESM(require("path"));
45241
- var fs2 = __toESM(require("fs"));
45242
- var os4 = __toESM(require("os"));
46675
+ var fs3 = __toESM(require("fs"));
46676
+ var os5 = __toESM(require("os"));
45243
46677
  var import_daemon_core2 = __toESM(require_dist2());
45244
46678
 
45245
46679
  // src/session-host.ts
45246
46680
  var import_child_process = require("child_process");
46681
+ var fs2 = __toESM(require("fs"));
46682
+ var os2 = __toESM(require("os"));
45247
46683
  var path = __toESM(require("path"));
45248
46684
  var import_daemon_core = __toESM(require_dist2());
45249
46685
  var SESSION_HOST_APP_NAME = process.env.ADHDEV_SESSION_HOST_NAME || "adhdev";
46686
+ var SESSION_HOST_START_TIMEOUT_MS = 15e3;
45250
46687
  function buildSessionHostEnv(baseEnv) {
45251
46688
  const env = {};
45252
46689
  for (const [key, value] of Object.entries(baseEnv)) {
@@ -45267,12 +46704,58 @@ function resolveSessionHostEntry() {
45267
46704
  path.resolve(__dirname, "../../vendor/session-host-daemon/index.js")
45268
46705
  ];
45269
46706
  for (const candidate of localCandidates) {
45270
- if (require("fs").existsSync(candidate)) {
46707
+ if (fs2.existsSync(candidate)) {
45271
46708
  return candidate;
45272
46709
  }
45273
46710
  }
45274
46711
  return require.resolve("@adhdev/session-host-daemon");
45275
46712
  }
46713
+ function getSessionHostPidFile() {
46714
+ return path.join(os2.homedir(), ".adhdev", `${SESSION_HOST_APP_NAME}-session-host.pid`);
46715
+ }
46716
+ function killPid(pid) {
46717
+ try {
46718
+ if (process.platform === "win32") {
46719
+ (0, import_child_process.execFileSync)("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore" });
46720
+ } else {
46721
+ process.kill(pid, "SIGTERM");
46722
+ }
46723
+ return true;
46724
+ } catch {
46725
+ return false;
46726
+ }
46727
+ }
46728
+ function stopSessionHost() {
46729
+ let stopped = false;
46730
+ const pidFile = getSessionHostPidFile();
46731
+ try {
46732
+ if (fs2.existsSync(pidFile)) {
46733
+ const pid = Number.parseInt(fs2.readFileSync(pidFile, "utf8").trim(), 10);
46734
+ if (Number.isFinite(pid)) {
46735
+ stopped = killPid(pid) || stopped;
46736
+ }
46737
+ }
46738
+ } catch {
46739
+ } finally {
46740
+ try {
46741
+ fs2.unlinkSync(pidFile);
46742
+ } catch {
46743
+ }
46744
+ }
46745
+ if (process.platform !== "win32") {
46746
+ try {
46747
+ const raw = (0, import_child_process.execFileSync)("pgrep", ["-f", "session-host-daemon"], { encoding: "utf8" }).trim();
46748
+ for (const line of raw.split("\n")) {
46749
+ const pid = Number.parseInt(line.trim(), 10);
46750
+ if (Number.isFinite(pid)) {
46751
+ stopped = killPid(pid) || stopped;
46752
+ }
46753
+ }
46754
+ } catch {
46755
+ }
46756
+ }
46757
+ return stopped;
46758
+ }
45276
46759
  async function runSessionHostCli(args) {
45277
46760
  const entry = resolveSessionHostEntry();
45278
46761
  const child = (0, import_child_process.spawn)(process.execPath, [entry, ...args], {
@@ -45285,19 +46768,34 @@ async function runSessionHostCli(args) {
45285
46768
  });
45286
46769
  }
45287
46770
  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
- });
46771
+ const spawnHost = () => {
46772
+ const entry = resolveSessionHostEntry();
46773
+ const child = (0, import_child_process.spawn)(process.execPath, [entry], {
46774
+ detached: true,
46775
+ stdio: "ignore",
46776
+ windowsHide: true,
46777
+ env: buildSessionHostEnv(process.env)
46778
+ });
46779
+ child.unref();
46780
+ };
46781
+ try {
46782
+ return await (0, import_daemon_core.ensureSessionHostReady)({
46783
+ appName: SESSION_HOST_APP_NAME,
46784
+ spawnHost,
46785
+ timeoutMs: SESSION_HOST_START_TIMEOUT_MS
46786
+ });
46787
+ } catch (error48) {
46788
+ stopSessionHost();
46789
+ return (0, import_daemon_core.ensureSessionHostReady)({
46790
+ appName: SESSION_HOST_APP_NAME,
46791
+ spawnHost,
46792
+ timeoutMs: SESSION_HOST_START_TIMEOUT_MS
46793
+ }).catch((retryError) => {
46794
+ const initialMessage = error48 instanceof Error ? error48.message : String(error48);
46795
+ const retryMessage = retryError instanceof Error ? retryError.message : String(retryError);
46796
+ throw new Error(`Session host failed to start after retry (${initialMessage}; retry: ${retryMessage})`);
46797
+ });
46798
+ }
45301
46799
  }
45302
46800
  async function listHostedCliRuntimes(endpoint) {
45303
46801
  return (0, import_daemon_core.listHostedCliRuntimes)(endpoint);
@@ -45315,7 +46813,7 @@ async function proxySessionHostAttach(target, options = {}) {
45315
46813
  }
45316
46814
 
45317
46815
  // ../session-host-core/dist/index.mjs
45318
- var os2 = __toESM(require("os"), 1);
46816
+ var os3 = __toESM(require("os"), 1);
45319
46817
  var path2 = __toESM(require("path"), 1);
45320
46818
  var net = __toESM(require("net"), 1);
45321
46819
  var import_crypto = require("crypto");
@@ -45328,7 +46826,7 @@ function getDefaultSessionHostEndpoint(appName = "adhdev") {
45328
46826
  }
45329
46827
  return {
45330
46828
  kind: "unix",
45331
- path: path2.join(os2.tmpdir(), `${appName}-session-host.sock`)
46829
+ path: path2.join(os3.tmpdir(), `${appName}-session-host.sock`)
45332
46830
  };
45333
46831
  }
45334
46832
  function serializeEnvelope(envelope) {
@@ -45612,7 +47110,7 @@ if (pkgVersion === "unknown") {
45612
47110
  ];
45613
47111
  for (const candidate of possiblePaths) {
45614
47112
  try {
45615
- const data = JSON.parse(fs2.readFileSync(candidate, "utf-8"));
47113
+ const data = JSON.parse(fs3.readFileSync(candidate, "utf-8"));
45616
47114
  if (data.version) {
45617
47115
  pkgVersion = data.version;
45618
47116
  break;
@@ -45714,6 +47212,10 @@ var StandaloneServer = class {
45714
47212
  },
45715
47213
  createPtyTransportFactory: ({ runtimeId, providerType, workspace, cliArgs, providerSessionId, attachExisting }) => new import_daemon_core2.SessionHostPtyTransportFactory({
45716
47214
  endpoint: sessionHostEndpoint,
47215
+ ensureReady: async () => {
47216
+ const activeEndpoint = await this.ensureActiveSessionHostEndpoint();
47217
+ this.sessionHostEndpoint = activeEndpoint;
47218
+ },
45717
47219
  clientId: `daemon-${process.pid}`,
45718
47220
  runtimeId,
45719
47221
  providerType,
@@ -45972,7 +47474,7 @@ var StandaloneServer = class {
45972
47474
  if (publicDir) {
45973
47475
  const filePath = url2 === "/" ? "/index.html" : url2;
45974
47476
  const fullPath = path4.join(publicDir, filePath);
45975
- if (fs2.existsSync(fullPath) && fs2.statSync(fullPath).isFile()) {
47477
+ if (fs3.existsSync(fullPath) && fs3.statSync(fullPath).isFile()) {
45976
47478
  const ext = path4.extname(fullPath);
45977
47479
  const mimeTypes = {
45978
47480
  ".html": "text/html",
@@ -45985,13 +47487,13 @@ var StandaloneServer = class {
45985
47487
  ".woff2": "font/woff2"
45986
47488
  };
45987
47489
  res.writeHead(200, { "Content-Type": mimeTypes[ext] || "application/octet-stream" });
45988
- fs2.createReadStream(fullPath).pipe(res);
47490
+ fs3.createReadStream(fullPath).pipe(res);
45989
47491
  return;
45990
47492
  }
45991
47493
  const indexPath = path4.join(publicDir, "index.html");
45992
- if (fs2.existsSync(indexPath) && !url2.startsWith("/api/")) {
47494
+ if (fs3.existsSync(indexPath) && !url2.startsWith("/api/")) {
45993
47495
  res.writeHead(200, { "Content-Type": "text/html" });
45994
- fs2.createReadStream(indexPath).pipe(res);
47496
+ fs3.createReadStream(indexPath).pipe(res);
45995
47497
  return;
45996
47498
  }
45997
47499
  }
@@ -46251,7 +47753,7 @@ var StandaloneServer = class {
46251
47753
  }
46252
47754
  // ─── Network ───
46253
47755
  getLanIPs() {
46254
- const interfaces = os4.networkInterfaces();
47756
+ const interfaces = os5.networkInterfaces();
46255
47757
  const ips = [];
46256
47758
  for (const iface of Object.values(interfaces)) {
46257
47759
  if (!iface) continue;
@@ -46370,7 +47872,7 @@ Runtime commands:
46370
47872
  path4.join(process.cwd(), "public")
46371
47873
  ];
46372
47874
  for (const candidate of candidates) {
46373
- if (fs2.existsSync(path4.join(candidate, "index.html"))) {
47875
+ if (fs3.existsSync(path4.join(candidate, "index.html"))) {
46374
47876
  options.publicDir = candidate;
46375
47877
  break;
46376
47878
  }