@adhdev/daemon-core 0.8.27 → 0.8.29

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.
Files changed (43) hide show
  1. package/dist/agent-stream/manager.d.ts +1 -1
  2. package/dist/agent-stream/provider-adapter.d.ts +5 -0
  3. package/dist/commands/handler.d.ts +1 -0
  4. package/dist/commands/router.d.ts +5 -0
  5. package/dist/commands/stream-commands.d.ts +1 -1
  6. package/dist/config/chat-history.d.ts +12 -0
  7. package/dist/detection/cli-detector.d.ts +6 -2
  8. package/dist/detection/ide-detector.d.ts +2 -1
  9. package/dist/index.js +987 -377
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +985 -375
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/providers/acp-provider-instance.d.ts +1 -1
  14. package/dist/providers/cli-provider-instance.d.ts +1 -0
  15. package/dist/providers/provider-loader.d.ts +26 -0
  16. package/dist/shared-types.d.ts +2 -0
  17. package/dist/status/snapshot.d.ts +8 -0
  18. package/node_modules/@adhdev/session-host-core/dist/index.d.mts +24 -1
  19. package/node_modules/@adhdev/session-host-core/dist/index.d.ts +24 -1
  20. package/node_modules/@adhdev/session-host-core/dist/index.js +6 -1
  21. package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
  22. package/node_modules/@adhdev/session-host-core/dist/index.mjs +6 -1
  23. package/node_modules/@adhdev/session-host-core/dist/index.mjs.map +1 -1
  24. package/node_modules/@adhdev/session-host-core/package.json +1 -1
  25. package/package.json +1 -1
  26. package/src/agent-stream/manager.ts +2 -2
  27. package/src/agent-stream/provider-adapter.ts +111 -4
  28. package/src/boot/daemon-lifecycle.ts +28 -1
  29. package/src/cli-adapters/provider-cli-adapter.ts +17 -3
  30. package/src/commands/chat-commands.ts +89 -10
  31. package/src/commands/cli-manager.ts +16 -2
  32. package/src/commands/handler.ts +1 -0
  33. package/src/commands/router.ts +23 -1
  34. package/src/commands/stream-commands.ts +6 -3
  35. package/src/config/chat-history.ts +269 -18
  36. package/src/detection/cli-detector.ts +72 -29
  37. package/src/detection/ide-detector.ts +24 -8
  38. package/src/launch.ts +1 -1
  39. package/src/providers/acp-provider-instance.ts +19 -10
  40. package/src/providers/cli-provider-instance.ts +17 -2
  41. package/src/providers/provider-loader.ts +144 -11
  42. package/src/shared-types.ts +2 -0
  43. package/src/status/snapshot.ts +19 -1
package/dist/index.js CHANGED
@@ -251,13 +251,13 @@ function getDaemonLogDir() {
251
251
  return LOG_DIR;
252
252
  }
253
253
  function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
254
- return path4.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
254
+ return path6.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
255
255
  }
256
256
  function checkDateRotation() {
257
257
  const today = getDateStr();
258
258
  if (today !== currentDate) {
259
259
  currentDate = today;
260
- currentLogFile = path4.join(LOG_DIR, `daemon-${currentDate}.log`);
260
+ currentLogFile = path6.join(LOG_DIR, `daemon-${currentDate}.log`);
261
261
  cleanOldLogs();
262
262
  }
263
263
  }
@@ -271,7 +271,7 @@ function cleanOldLogs() {
271
271
  const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
272
272
  if (dateMatch && dateMatch[1] < cutoffStr) {
273
273
  try {
274
- fs2.unlinkSync(path4.join(LOG_DIR, file));
274
+ fs2.unlinkSync(path6.join(LOG_DIR, file));
275
275
  } catch {
276
276
  }
277
277
  }
@@ -388,17 +388,17 @@ function installGlobalInterceptor() {
388
388
  writeToFile(`Log file: ${currentLogFile}`);
389
389
  writeToFile(`Log level: ${currentLevel}`);
390
390
  }
391
- var fs2, path4, os4, LEVEL_NUM, LEVEL_LABEL, currentLevel, LOG_DIR, MAX_LOG_SIZE, MAX_LOG_DAYS, currentDate, currentLogFile, writeCount, RING_BUFFER_SIZE, ringBuffer, origConsoleLog, origConsoleError, origConsoleWarn, LOG, interceptorInstalled, LOG_PATH;
391
+ var fs2, path6, os4, LEVEL_NUM, LEVEL_LABEL, currentLevel, LOG_DIR, MAX_LOG_SIZE, MAX_LOG_DAYS, currentDate, currentLogFile, writeCount, RING_BUFFER_SIZE, ringBuffer, origConsoleLog, origConsoleError, origConsoleWarn, LOG, interceptorInstalled, LOG_PATH;
392
392
  var init_logger = __esm({
393
393
  "src/logging/logger.ts"() {
394
394
  "use strict";
395
395
  fs2 = __toESM(require("fs"));
396
- path4 = __toESM(require("path"));
396
+ path6 = __toESM(require("path"));
397
397
  os4 = __toESM(require("os"));
398
398
  LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
399
399
  LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
400
400
  currentLevel = "info";
401
- LOG_DIR = process.platform === "win32" ? path4.join(process.env.LOCALAPPDATA || process.env.APPDATA || path4.join(os4.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path4.join(os4.homedir(), "Library", "Logs", "adhdev") : path4.join(os4.homedir(), ".local", "share", "adhdev", "logs");
401
+ LOG_DIR = process.platform === "win32" ? path6.join(process.env.LOCALAPPDATA || process.env.APPDATA || path6.join(os4.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path6.join(os4.homedir(), "Library", "Logs", "adhdev") : path6.join(os4.homedir(), ".local", "share", "adhdev", "logs");
402
402
  MAX_LOG_SIZE = 5 * 1024 * 1024;
403
403
  MAX_LOG_DAYS = 7;
404
404
  try {
@@ -406,16 +406,16 @@ var init_logger = __esm({
406
406
  } catch {
407
407
  }
408
408
  currentDate = getDateStr();
409
- currentLogFile = path4.join(LOG_DIR, `daemon-${currentDate}.log`);
409
+ currentLogFile = path6.join(LOG_DIR, `daemon-${currentDate}.log`);
410
410
  cleanOldLogs();
411
411
  try {
412
- const oldLog = path4.join(LOG_DIR, "daemon.log");
412
+ const oldLog = path6.join(LOG_DIR, "daemon.log");
413
413
  if (fs2.existsSync(oldLog)) {
414
414
  const stat = fs2.statSync(oldLog);
415
415
  const oldDate = stat.mtime.toISOString().slice(0, 10);
416
- fs2.renameSync(oldLog, path4.join(LOG_DIR, `daemon-${oldDate}.log`));
416
+ fs2.renameSync(oldLog, path6.join(LOG_DIR, `daemon-${oldDate}.log`));
417
417
  }
418
- const oldLogBackup = path4.join(LOG_DIR, "daemon.log.old");
418
+ const oldLogBackup = path6.join(LOG_DIR, "daemon.log.old");
419
419
  if (fs2.existsSync(oldLogBackup)) {
420
420
  fs2.unlinkSync(oldLogBackup);
421
421
  }
@@ -447,7 +447,7 @@ var init_logger = __esm({
447
447
  }
448
448
  };
449
449
  interceptorInstalled = false;
450
- LOG_PATH = path4.join(LOG_DIR, `daemon-${getDateStr()}.log`);
450
+ LOG_PATH = path6.join(LOG_DIR, `daemon-${getDateStr()}.log`);
451
451
  }
452
452
  });
453
453
 
@@ -872,16 +872,22 @@ function computeTerminalQueryTail(buffer) {
872
872
  return "";
873
873
  }
874
874
  function findBinary(name) {
875
+ const trimmed = String(name || "").trim();
876
+ if (!trimmed) return trimmed;
877
+ const expanded = trimmed.startsWith("~") ? path9.join(os8.homedir(), trimmed.slice(1)) : trimmed;
878
+ if (path9.isAbsolute(expanded) || expanded.includes("/") || expanded.includes("\\")) {
879
+ return path9.isAbsolute(expanded) ? expanded : path9.resolve(expanded);
880
+ }
875
881
  const isWin = os8.platform() === "win32";
876
882
  try {
877
- const cmd = isWin ? `where ${name}` : `which ${name}`;
883
+ const cmd = isWin ? `where ${trimmed}` : `which ${trimmed}`;
878
884
  return (0, import_child_process4.execSync)(cmd, { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n")[0].trim();
879
885
  } catch {
880
- return isWin ? `${name}.cmd` : name;
886
+ return isWin ? `${trimmed}.cmd` : trimmed;
881
887
  }
882
888
  }
883
889
  function isScriptBinary(binaryPath) {
884
- if (!path7.isAbsolute(binaryPath)) return false;
890
+ if (!path9.isAbsolute(binaryPath)) return false;
885
891
  try {
886
892
  const fs15 = require("fs");
887
893
  const resolved = fs15.realpathSync(binaryPath);
@@ -897,7 +903,7 @@ function isScriptBinary(binaryPath) {
897
903
  }
898
904
  }
899
905
  function looksLikeMachOOrElf(filePath) {
900
- if (!path7.isAbsolute(filePath)) return false;
906
+ if (!path9.isAbsolute(filePath)) return false;
901
907
  try {
902
908
  const fs15 = require("fs");
903
909
  const resolved = fs15.realpathSync(filePath);
@@ -1021,12 +1027,12 @@ function normalizeCliProviderForRuntime(raw) {
1021
1027
  }
1022
1028
  };
1023
1029
  }
1024
- var os8, path7, import_child_process4, buildCliSpawnEnv, ProviderCliAdapter;
1030
+ var os8, path9, import_child_process4, buildCliSpawnEnv, ProviderCliAdapter;
1025
1031
  var init_provider_cli_adapter = __esm({
1026
1032
  "src/cli-adapters/provider-cli-adapter.ts"() {
1027
1033
  "use strict";
1028
1034
  os8 = __toESM(require("os"));
1029
- path7 = __toESM(require("path"));
1035
+ path9 = __toESM(require("path"));
1030
1036
  import_child_process4 = require("child_process");
1031
1037
  init_logger();
1032
1038
  init_terminal_screen();
@@ -1381,16 +1387,17 @@ var init_provider_cli_adapter = __esm({
1381
1387
  async spawn() {
1382
1388
  if (this.ptyProcess) return;
1383
1389
  const { spawn: spawnConfig } = this.provider;
1384
- const binaryPath = findBinary(spawnConfig.command);
1390
+ const configuredCommand = typeof this.runtimeSettings.executablePath === "string" && this.runtimeSettings.executablePath.trim() ? this.runtimeSettings.executablePath.trim() : spawnConfig.command;
1391
+ const binaryPath = findBinary(configuredCommand);
1385
1392
  const isWin = os8.platform() === "win32";
1386
1393
  const allArgs = [...spawnConfig.args, ...this.extraArgs];
1387
1394
  LOG.info("CLI", `[${this.cliType}] Spawning in ${this.workingDir}`);
1388
1395
  this.resetTraceSession();
1389
1396
  let shellCmd;
1390
1397
  let shellArgs;
1391
- const useShellUnix = !isWin && (!!spawnConfig.shell || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath) || !looksLikeMachOOrElf(binaryPath));
1398
+ const useShellUnix = !isWin && (!!spawnConfig.shell || !path9.isAbsolute(binaryPath) || isScriptBinary(binaryPath) || !looksLikeMachOOrElf(binaryPath));
1392
1399
  const isCmdShim = isWin && /\.(cmd|bat)$/i.test(binaryPath);
1393
- const useShellWin = !!spawnConfig.shell || isCmdShim || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath);
1400
+ const useShellWin = !!spawnConfig.shell || isCmdShim || !path9.isAbsolute(binaryPath) || isScriptBinary(binaryPath);
1394
1401
  const useShell = isWin ? useShellWin : useShellUnix;
1395
1402
  if (useShell) {
1396
1403
  if (!spawnConfig.shell && !isWin) {
@@ -1635,6 +1642,9 @@ var init_provider_cli_adapter = __esm({
1635
1642
  looksLikeVisibleIdlePrompt(screenText) {
1636
1643
  const text = String(screenText || "");
1637
1644
  if (!text.trim()) return false;
1645
+ if (this.cliType === "codex-cli" && /(^|\n)\s*[❯›>]\s+(?:Find and fix a bug in @filename|Improve documentation in @filename|Use \/skills|Write tests for @filename|Explain this codebase|Summarize recent commits|Implement \{feature\}|Run \/review on my current changes)(?:\n|$)/im.test(text)) {
1646
+ return true;
1647
+ }
1638
1648
  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);
1639
1649
  }
1640
1650
  findLastMatchingLineIndex(lines, predicate) {
@@ -1764,7 +1774,7 @@ var init_provider_cli_adapter = __esm({
1764
1774
  `[${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)}`
1765
1775
  );
1766
1776
  }
1767
- await new Promise((resolve9) => setTimeout(resolve9, 50));
1777
+ await new Promise((resolve12) => setTimeout(resolve12, 50));
1768
1778
  }
1769
1779
  const finalScreenText = this.terminalScreen.getText() || "";
1770
1780
  LOG.warn(
@@ -2275,7 +2285,7 @@ ${data.message || ""}`.trim();
2275
2285
  const deadline = Date.now() + 1e4;
2276
2286
  while (this.startupParseGate && Date.now() < deadline) {
2277
2287
  this.resolveStartupState("send_wait");
2278
- await new Promise((resolve9) => setTimeout(resolve9, 50));
2288
+ await new Promise((resolve12) => setTimeout(resolve12, 50));
2279
2289
  }
2280
2290
  }
2281
2291
  await this.waitForInteractivePrompt();
@@ -2500,17 +2510,17 @@ ${data.message || ""}`.trim();
2500
2510
  }
2501
2511
  }
2502
2512
  waitForStopped(timeoutMs) {
2503
- return new Promise((resolve9) => {
2513
+ return new Promise((resolve12) => {
2504
2514
  const startedAt = Date.now();
2505
2515
  const timer = setInterval(() => {
2506
2516
  if (!this.ptyProcess || this.currentStatus === "stopped") {
2507
2517
  clearInterval(timer);
2508
- resolve9(true);
2518
+ resolve12(true);
2509
2519
  return;
2510
2520
  }
2511
2521
  if (Date.now() - startedAt >= timeoutMs) {
2512
2522
  clearInterval(timer);
2513
- resolve9(false);
2523
+ resolve12(false);
2514
2524
  }
2515
2525
  }, 100);
2516
2526
  });
@@ -3209,6 +3219,7 @@ function resetState() {
3209
3219
  var import_child_process = require("child_process");
3210
3220
  var import_fs3 = require("fs");
3211
3221
  var import_os2 = require("os");
3222
+ var path4 = __toESM(require("path"));
3212
3223
  var BUILTIN_IDE_DEFINITIONS = [];
3213
3224
  var registeredIDEs = /* @__PURE__ */ new Map();
3214
3225
  function registerIDEDefinition(def) {
@@ -3225,9 +3236,16 @@ function getMergedDefinitions() {
3225
3236
  return [...merged.values()];
3226
3237
  }
3227
3238
  function findCliCommand(command) {
3239
+ const trimmed = String(command || "").trim();
3240
+ if (!trimmed) return null;
3241
+ if (path4.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
3242
+ const candidate = trimmed.startsWith("~") ? path4.join((0, import_os2.homedir)(), trimmed.slice(1)) : trimmed;
3243
+ const resolved = path4.isAbsolute(candidate) ? candidate : path4.resolve(candidate);
3244
+ return (0, import_fs3.existsSync)(resolved) ? resolved : null;
3245
+ }
3228
3246
  try {
3229
3247
  const result = (0, import_child_process.execSync)(
3230
- (0, import_os2.platform)() === "win32" ? `where ${command}` : `which ${command}`,
3248
+ (0, import_os2.platform)() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
3231
3249
  { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
3232
3250
  ).trim();
3233
3251
  return result.split("\n")[0] || null;
@@ -3250,23 +3268,23 @@ function getIdeVersion(cliCommand) {
3250
3268
  function checkPathExists(paths) {
3251
3269
  const home = (0, import_os2.homedir)();
3252
3270
  for (const p of paths) {
3253
- if (p.includes("*")) {
3271
+ const normalized = p.startsWith("~") ? path4.join(home, p.slice(1)) : p;
3272
+ if (normalized.includes("*")) {
3254
3273
  const username = home.split(/[\\/]/).pop() || "";
3255
- const resolved = p.replace("*", username);
3274
+ const resolved = normalized.replace("*", username);
3256
3275
  if ((0, import_fs3.existsSync)(resolved)) return resolved;
3257
3276
  } else {
3258
- if ((0, import_fs3.existsSync)(p)) return p;
3277
+ if ((0, import_fs3.existsSync)(normalized)) return normalized;
3259
3278
  }
3260
3279
  }
3261
3280
  return null;
3262
3281
  }
3263
- async function detectIDEs() {
3282
+ async function detectIDEs(providerLoader) {
3264
3283
  const os18 = (0, import_os2.platform)();
3265
3284
  const results = [];
3266
3285
  for (const def of getMergedDefinitions()) {
3267
- const cliPath = findCliCommand(def.cli);
3268
- const appPath = checkPathExists(def.paths[os18] || []);
3269
- const installed = !!(cliPath || appPath);
3286
+ const cliPath = findCliCommand(providerLoader?.getIdeCliCommand(def.id, def.cli) || def.cli);
3287
+ const appPath = checkPathExists(providerLoader?.getIdePathCandidates(def.id, def.paths[os18] || []) || []);
3270
3288
  let resolvedCli = cliPath;
3271
3289
  if (!resolvedCli && appPath && os18 === "darwin") {
3272
3290
  const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
@@ -3289,6 +3307,7 @@ async function detectIDEs() {
3289
3307
  }
3290
3308
  }
3291
3309
  }
3310
+ const installed = os18 === "darwin" ? !!(resolvedCli || appPath) : !!resolvedCli;
3292
3311
  const version = resolvedCli ? getIdeVersion(resolvedCli) : null;
3293
3312
  results.push({
3294
3313
  id: def.id,
@@ -3307,48 +3326,77 @@ async function detectIDEs() {
3307
3326
  // src/detection/cli-detector.ts
3308
3327
  var import_child_process2 = require("child_process");
3309
3328
  var os2 = __toESM(require("os"));
3329
+ var path5 = __toESM(require("path"));
3330
+ var import_fs4 = require("fs");
3310
3331
  function parseVersion(raw) {
3311
3332
  const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
3312
3333
  return match ? match[1] : raw.split("\n")[0].slice(0, 100);
3313
3334
  }
3335
+ function shellQuote(value) {
3336
+ if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
3337
+ return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
3338
+ }
3339
+ function expandHome(value) {
3340
+ const trimmed = value.trim();
3341
+ if (!trimmed.startsWith("~")) return trimmed;
3342
+ return path5.join(os2.homedir(), trimmed.slice(1));
3343
+ }
3344
+ function isExplicitCommandPath(command) {
3345
+ const trimmed = command.trim();
3346
+ return path5.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
3347
+ }
3348
+ function resolveCommandPath(command) {
3349
+ const trimmed = command.trim();
3350
+ if (!trimmed) return null;
3351
+ if (isExplicitCommandPath(trimmed)) {
3352
+ const expanded = expandHome(trimmed);
3353
+ const candidate = path5.isAbsolute(expanded) ? expanded : path5.resolve(expanded);
3354
+ return (0, import_fs4.existsSync)(candidate) ? candidate : null;
3355
+ }
3356
+ return null;
3357
+ }
3314
3358
  function execAsync(cmd, timeoutMs = 5e3) {
3315
- return new Promise((resolve9) => {
3359
+ return new Promise((resolve12) => {
3316
3360
  const child = (0, import_child_process2.exec)(cmd, { encoding: "utf-8", timeout: timeoutMs }, (err, stdout) => {
3317
3361
  if (err || !stdout?.trim()) {
3318
- resolve9(null);
3362
+ resolve12(null);
3319
3363
  } else {
3320
- resolve9(stdout.trim());
3364
+ resolve12(stdout.trim());
3321
3365
  }
3322
3366
  });
3323
- child.on("error", () => resolve9(null));
3367
+ child.on("error", () => resolve12(null));
3324
3368
  });
3325
3369
  }
3326
- async function detectCLIs(providerLoader) {
3370
+ async function detectCLIs(providerLoader, options) {
3327
3371
  const platform9 = os2.platform();
3328
3372
  const whichCmd = platform9 === "win32" ? "where" : "which";
3373
+ const includeVersion = options?.includeVersion !== false;
3329
3374
  const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
3330
3375
  const results = await Promise.all(
3331
3376
  cliList.map(async (cli) => {
3332
3377
  try {
3333
- const pathResult = await execAsync(`${whichCmd} ${cli.command}`);
3378
+ const explicitPath = resolveCommandPath(cli.command);
3379
+ const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
3334
3380
  if (!pathResult) return { ...cli, installed: false };
3335
- const firstPath = pathResult.split("\n")[0];
3381
+ const firstPath = explicitPath || pathResult.split("\n")[0];
3336
3382
  let version;
3337
- try {
3383
+ if (includeVersion) {
3338
3384
  const versionCommands = [
3339
- cli.versionCommand,
3340
- `${cli.command} --version`,
3341
- `${cli.command} -V`,
3342
- `${cli.command} -v`
3385
+ `"${firstPath}" --version`,
3386
+ `"${firstPath}" -V`,
3387
+ `"${firstPath}" -v`,
3388
+ cli.versionCommand
3343
3389
  ].filter((v) => !!v);
3344
- for (const versionCommand of versionCommands) {
3345
- const versionResult = await execAsync(versionCommand, 3e3);
3346
- if (versionResult) {
3347
- version = parseVersion(versionResult);
3348
- break;
3390
+ try {
3391
+ for (const versionCommand of versionCommands) {
3392
+ const versionResult = await execAsync(versionCommand, 3e3);
3393
+ if (versionResult) {
3394
+ version = parseVersion(versionResult);
3395
+ break;
3396
+ }
3349
3397
  }
3398
+ } catch {
3350
3399
  }
3351
- } catch {
3352
3400
  }
3353
3401
  return { ...cli, installed: true, version, path: firstPath };
3354
3402
  } catch {
@@ -3358,7 +3406,7 @@ async function detectCLIs(providerLoader) {
3358
3406
  );
3359
3407
  return results;
3360
3408
  }
3361
- async function detectCLI(cliId, providerLoader) {
3409
+ async function detectCLI(cliId, providerLoader, options) {
3362
3410
  const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
3363
3411
  if (providerLoader) {
3364
3412
  const cliList = providerLoader.getCliDetectionList();
@@ -3367,25 +3415,28 @@ async function detectCLI(cliId, providerLoader) {
3367
3415
  const platform9 = os2.platform();
3368
3416
  const whichCmd = platform9 === "win32" ? "where" : "which";
3369
3417
  try {
3370
- const pathResult = await execAsync(`${whichCmd} ${target.command}`);
3418
+ const explicitPath = resolveCommandPath(target.command);
3419
+ const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
3371
3420
  if (!pathResult) return null;
3372
- const firstPath = pathResult.split("\n")[0];
3421
+ const firstPath = explicitPath || pathResult.split("\n")[0];
3373
3422
  let version;
3374
- try {
3423
+ if (options?.includeVersion !== false) {
3375
3424
  const versionCommands = [
3376
- target.versionCommand,
3377
- `${target.command} --version`,
3378
- `${target.command} -V`,
3379
- `${target.command} -v`
3425
+ `"${firstPath}" --version`,
3426
+ `"${firstPath}" -V`,
3427
+ `"${firstPath}" -v`,
3428
+ target.versionCommand
3380
3429
  ].filter((v) => !!v);
3381
- for (const versionCommand of versionCommands) {
3382
- const versionResult = await execAsync(versionCommand, 3e3);
3383
- if (versionResult) {
3384
- version = parseVersion(versionResult);
3385
- break;
3430
+ try {
3431
+ for (const versionCommand of versionCommands) {
3432
+ const versionResult = await execAsync(versionCommand, 3e3);
3433
+ if (versionResult) {
3434
+ version = parseVersion(versionResult);
3435
+ break;
3436
+ }
3386
3437
  }
3438
+ } catch {
3387
3439
  }
3388
- } catch {
3389
3440
  }
3390
3441
  return { ...target, installed: true, version, path: firstPath };
3391
3442
  } catch {
@@ -3393,7 +3444,7 @@ async function detectCLI(cliId, providerLoader) {
3393
3444
  }
3394
3445
  }
3395
3446
  }
3396
- const all = await detectCLIs(providerLoader);
3447
+ const all = await detectCLIs(providerLoader, options);
3397
3448
  return all.find((c) => c.id === resolvedId && c.installed) || null;
3398
3449
  }
3399
3450
 
@@ -3520,7 +3571,7 @@ var DaemonCdpManager = class {
3520
3571
  * Returns multiple entries if multiple IDE windows are open on same port
3521
3572
  */
3522
3573
  static listAllTargets(port) {
3523
- return new Promise((resolve9) => {
3574
+ return new Promise((resolve12) => {
3524
3575
  const req = http.get(`http://127.0.0.1:${port}/json`, (res) => {
3525
3576
  let data = "";
3526
3577
  res.on("data", (chunk) => data += chunk.toString());
@@ -3536,16 +3587,16 @@ var DaemonCdpManager = class {
3536
3587
  (t) => !isNonMain(t.title || "") && t.url?.includes("workbench.html") && !t.url?.includes("agent")
3537
3588
  );
3538
3589
  const fallbackPages = pages.filter((t) => !isNonMain(t.title || ""));
3539
- resolve9(mainPages.length > 0 ? mainPages : fallbackPages);
3590
+ resolve12(mainPages.length > 0 ? mainPages : fallbackPages);
3540
3591
  } catch {
3541
- resolve9([]);
3592
+ resolve12([]);
3542
3593
  }
3543
3594
  });
3544
3595
  });
3545
- req.on("error", () => resolve9([]));
3596
+ req.on("error", () => resolve12([]));
3546
3597
  req.setTimeout(2e3, () => {
3547
3598
  req.destroy();
3548
- resolve9([]);
3599
+ resolve12([]);
3549
3600
  });
3550
3601
  });
3551
3602
  }
@@ -3585,7 +3636,7 @@ var DaemonCdpManager = class {
3585
3636
  }
3586
3637
  }
3587
3638
  findTargetOnPort(port) {
3588
- return new Promise((resolve9) => {
3639
+ return new Promise((resolve12) => {
3589
3640
  const req = http.get(`http://127.0.0.1:${port}/json`, (res) => {
3590
3641
  let data = "";
3591
3642
  res.on("data", (chunk) => data += chunk.toString());
@@ -3596,7 +3647,7 @@ var DaemonCdpManager = class {
3596
3647
  (t) => (t.type === "page" || t.type === "browser" || t.type === "Page") && t.webSocketDebuggerUrl
3597
3648
  );
3598
3649
  if (pages.length === 0) {
3599
- resolve9(targets.find((t) => t.webSocketDebuggerUrl) || null);
3650
+ resolve12(targets.find((t) => t.webSocketDebuggerUrl) || null);
3600
3651
  return;
3601
3652
  }
3602
3653
  const mainPages = pages.filter((t) => !this.isNonMainTitle(t.title || ""));
@@ -3606,24 +3657,24 @@ var DaemonCdpManager = class {
3606
3657
  const specific = list.find((t) => t.id === this._targetId);
3607
3658
  if (specific) {
3608
3659
  this._pageTitle = specific.title || "";
3609
- resolve9(specific);
3660
+ resolve12(specific);
3610
3661
  } else {
3611
3662
  this.log(`[CDP] Target ${this._targetId} not found in page list`);
3612
- resolve9(null);
3663
+ resolve12(null);
3613
3664
  }
3614
3665
  return;
3615
3666
  }
3616
3667
  this._pageTitle = list[0]?.title || "";
3617
- resolve9(list[0]);
3668
+ resolve12(list[0]);
3618
3669
  } catch {
3619
- resolve9(null);
3670
+ resolve12(null);
3620
3671
  }
3621
3672
  });
3622
3673
  });
3623
- req.on("error", () => resolve9(null));
3674
+ req.on("error", () => resolve12(null));
3624
3675
  req.setTimeout(2e3, () => {
3625
3676
  req.destroy();
3626
- resolve9(null);
3677
+ resolve12(null);
3627
3678
  });
3628
3679
  });
3629
3680
  }
@@ -3634,7 +3685,7 @@ var DaemonCdpManager = class {
3634
3685
  this.extensionProviders = providers;
3635
3686
  }
3636
3687
  connectToTarget(wsUrl) {
3637
- return new Promise((resolve9) => {
3688
+ return new Promise((resolve12) => {
3638
3689
  this.ws = new import_ws.default(wsUrl);
3639
3690
  this.ws.on("open", async () => {
3640
3691
  this._connected = true;
@@ -3644,17 +3695,17 @@ var DaemonCdpManager = class {
3644
3695
  }
3645
3696
  this.connectBrowserWs().catch(() => {
3646
3697
  });
3647
- resolve9(true);
3698
+ resolve12(true);
3648
3699
  });
3649
3700
  this.ws.on("message", (data) => {
3650
3701
  try {
3651
3702
  const msg = JSON.parse(data.toString());
3652
3703
  if (msg.id && this.pending.has(msg.id)) {
3653
- const { resolve: resolve10, reject } = this.pending.get(msg.id);
3704
+ const { resolve: resolve13, reject } = this.pending.get(msg.id);
3654
3705
  this.pending.delete(msg.id);
3655
3706
  this.failureCount = 0;
3656
3707
  if (msg.error) reject(new Error(msg.error.message));
3657
- else resolve10(msg.result);
3708
+ else resolve13(msg.result);
3658
3709
  } else if (msg.method === "Runtime.executionContextCreated") {
3659
3710
  this.contexts.add(msg.params.context.id);
3660
3711
  } else if (msg.method === "Runtime.executionContextDestroyed") {
@@ -3677,7 +3728,7 @@ var DaemonCdpManager = class {
3677
3728
  this.ws.on("error", (err) => {
3678
3729
  this.log(`[CDP] WebSocket error: ${err.message}`);
3679
3730
  this._connected = false;
3680
- resolve9(false);
3731
+ resolve12(false);
3681
3732
  });
3682
3733
  });
3683
3734
  }
@@ -3691,7 +3742,7 @@ var DaemonCdpManager = class {
3691
3742
  return;
3692
3743
  }
3693
3744
  this.log(`[CDP] Connecting browser WS for target discovery...`);
3694
- await new Promise((resolve9, reject) => {
3745
+ await new Promise((resolve12, reject) => {
3695
3746
  this.browserWs = new import_ws.default(browserWsUrl);
3696
3747
  this.browserWs.on("open", async () => {
3697
3748
  this._browserConnected = true;
@@ -3701,16 +3752,16 @@ var DaemonCdpManager = class {
3701
3752
  } catch (e) {
3702
3753
  this.log(`[CDP] setDiscoverTargets failed: ${e.message}`);
3703
3754
  }
3704
- resolve9();
3755
+ resolve12();
3705
3756
  });
3706
3757
  this.browserWs.on("message", (data) => {
3707
3758
  try {
3708
3759
  const msg = JSON.parse(data.toString());
3709
3760
  if (msg.id && this.browserPending.has(msg.id)) {
3710
- const { resolve: resolve10, reject: reject2 } = this.browserPending.get(msg.id);
3761
+ const { resolve: resolve13, reject: reject2 } = this.browserPending.get(msg.id);
3711
3762
  this.browserPending.delete(msg.id);
3712
3763
  if (msg.error) reject2(new Error(msg.error.message));
3713
- else resolve10(msg.result);
3764
+ else resolve13(msg.result);
3714
3765
  }
3715
3766
  } catch {
3716
3767
  }
@@ -3730,31 +3781,31 @@ var DaemonCdpManager = class {
3730
3781
  }
3731
3782
  }
3732
3783
  getBrowserWsUrl() {
3733
- return new Promise((resolve9) => {
3784
+ return new Promise((resolve12) => {
3734
3785
  const req = http.get(`http://127.0.0.1:${this.port}/json/version`, (res) => {
3735
3786
  let data = "";
3736
3787
  res.on("data", (chunk) => data += chunk.toString());
3737
3788
  res.on("end", () => {
3738
3789
  try {
3739
3790
  const info = JSON.parse(data);
3740
- resolve9(info.webSocketDebuggerUrl || null);
3791
+ resolve12(info.webSocketDebuggerUrl || null);
3741
3792
  } catch {
3742
- resolve9(null);
3793
+ resolve12(null);
3743
3794
  }
3744
3795
  });
3745
3796
  });
3746
- req.on("error", () => resolve9(null));
3797
+ req.on("error", () => resolve12(null));
3747
3798
  req.setTimeout(3e3, () => {
3748
3799
  req.destroy();
3749
- resolve9(null);
3800
+ resolve12(null);
3750
3801
  });
3751
3802
  });
3752
3803
  }
3753
3804
  sendBrowser(method, params = {}, timeoutMs = 15e3) {
3754
- return new Promise((resolve9, reject) => {
3805
+ return new Promise((resolve12, reject) => {
3755
3806
  if (!this.browserWs || !this._browserConnected) return reject(new Error("Browser WS not connected"));
3756
3807
  const id = this.browserMsgId++;
3757
- this.browserPending.set(id, { resolve: resolve9, reject });
3808
+ this.browserPending.set(id, { resolve: resolve12, reject });
3758
3809
  this.browserWs.send(JSON.stringify({ id, method, params }));
3759
3810
  setTimeout(() => {
3760
3811
  if (this.browserPending.has(id)) {
@@ -3794,11 +3845,11 @@ var DaemonCdpManager = class {
3794
3845
  }
3795
3846
  // ─── CDP Protocol ────────────────────────────────────────
3796
3847
  sendInternal(method, params = {}, timeoutMs = 15e3) {
3797
- return new Promise((resolve9, reject) => {
3848
+ return new Promise((resolve12, reject) => {
3798
3849
  if (!this.ws || !this._connected) return reject(new Error("CDP not connected"));
3799
3850
  if (this.ws.readyState !== import_ws.default.OPEN) return reject(new Error("WebSocket not open"));
3800
3851
  const id = this.msgId++;
3801
- this.pending.set(id, { resolve: resolve9, reject });
3852
+ this.pending.set(id, { resolve: resolve12, reject });
3802
3853
  this.ws.send(JSON.stringify({ id, method, params }));
3803
3854
  setTimeout(() => {
3804
3855
  if (this.pending.has(id)) {
@@ -4047,7 +4098,7 @@ var DaemonCdpManager = class {
4047
4098
  const browserWs = this.browserWs;
4048
4099
  let msgId = this.browserMsgId;
4049
4100
  const sendWs = (method, params = {}, sessionId) => {
4050
- return new Promise((resolve9, reject) => {
4101
+ return new Promise((resolve12, reject) => {
4051
4102
  const mid = msgId++;
4052
4103
  this.browserMsgId = msgId;
4053
4104
  const handler = (raw) => {
@@ -4056,7 +4107,7 @@ var DaemonCdpManager = class {
4056
4107
  if (msg.id === mid) {
4057
4108
  browserWs.removeListener("message", handler);
4058
4109
  if (msg.error) reject(new Error(msg.error.message || JSON.stringify(msg.error)));
4059
- else resolve9(msg.result);
4110
+ else resolve12(msg.result);
4060
4111
  }
4061
4112
  } catch {
4062
4113
  }
@@ -4247,14 +4298,14 @@ var DaemonCdpManager = class {
4247
4298
  if (!ws || ws.readyState !== import_ws.default.OPEN) {
4248
4299
  throw new Error("CDP not connected");
4249
4300
  }
4250
- return new Promise((resolve9, reject) => {
4301
+ return new Promise((resolve12, reject) => {
4251
4302
  const id = getNextId();
4252
4303
  pendingMap.set(id, {
4253
4304
  resolve: (result) => {
4254
4305
  if (result?.result?.subtype === "error") {
4255
4306
  reject(new Error(result.result.description));
4256
4307
  } else {
4257
- resolve9(result?.result?.value);
4308
+ resolve12(result?.result?.value);
4258
4309
  }
4259
4310
  },
4260
4311
  reject
@@ -4286,10 +4337,10 @@ var DaemonCdpManager = class {
4286
4337
  throw new Error("CDP not connected");
4287
4338
  }
4288
4339
  const sendViaSession = (method, params = {}) => {
4289
- return new Promise((resolve9, reject) => {
4340
+ return new Promise((resolve12, reject) => {
4290
4341
  const pendingMap = this._browserConnected ? this.browserPending : this.pending;
4291
4342
  const id = this._browserConnected ? this.browserMsgId++ : this.msgId++;
4292
- pendingMap.set(id, { resolve: resolve9, reject });
4343
+ pendingMap.set(id, { resolve: resolve12, reject });
4293
4344
  ws.send(JSON.stringify({ id, sessionId, method, params }));
4294
4345
  setTimeout(() => {
4295
4346
  if (pendingMap.has(id)) {
@@ -4917,15 +4968,76 @@ function normalizeControlValue(value) {
4917
4968
 
4918
4969
  // src/config/chat-history.ts
4919
4970
  var fs3 = __toESM(require("fs"));
4920
- var path5 = __toESM(require("path"));
4971
+ var path7 = __toESM(require("path"));
4921
4972
  var os5 = __toESM(require("os"));
4922
- var HISTORY_DIR = path5.join(os5.homedir(), ".adhdev", "history");
4973
+ var HISTORY_DIR = path7.join(os5.homedir(), ".adhdev", "history");
4923
4974
  var RETAIN_DAYS = 30;
4975
+ var CODEX_STARTER_PROMPT_RE = /^(?:[›❯]\s*)?(?:Find and fix a bug in @filename|Improve documentation in @filename|Write tests for @filename|Explain this codebase|Summarize recent commits|Implement \{feature\}|Use \/skills(?: to list available skills)?|Run \/review on my current changes)$/i;
4976
+ function normalizeHistoryComparable(text) {
4977
+ return String(text || "").replace(/\s+/g, " ").trim();
4978
+ }
4979
+ function cleanupHistoryContent(agentType, role, content) {
4980
+ let value = String(content || "").replace(/\r\n/g, "\n").trim();
4981
+ if (!value) return "";
4982
+ if (agentType === "codex-cli" && role === "assistant") {
4983
+ const filtered = value.split("\n").filter((line) => !CODEX_STARTER_PROMPT_RE.test(line.trim())).join("\n").replace(/\n{3,}/g, "\n\n").trim();
4984
+ value = filtered;
4985
+ }
4986
+ return value;
4987
+ }
4988
+ function buildHistoryMessageHash(agentType, message) {
4989
+ if (message.historyDedupKey) return message.historyDedupKey;
4990
+ const cleaned = cleanupHistoryContent(agentType, message.role, message.content);
4991
+ return `${message.kind || "standard"}:${message.role}:${message.receivedAt || 0}:${normalizeHistoryComparable(cleaned)}`;
4992
+ }
4993
+ function buildHistoryMessageSignature(agentType, message) {
4994
+ const cleaned = cleanupHistoryContent(agentType, message.role, message.content);
4995
+ return `${message.kind || "standard"}:${message.role}:${normalizeHistoryComparable(cleaned)}`;
4996
+ }
4997
+ function isAdjacentHistoryDuplicate(agentType, previous, next) {
4998
+ if (!previous || !next) return false;
4999
+ return buildHistoryMessageSignature(agentType, previous) === buildHistoryMessageSignature(agentType, next);
5000
+ }
5001
+ function collapseReplayAssistantTurns(agentType, messages) {
5002
+ if (agentType !== "codex-cli") return messages;
5003
+ const collapsed = [];
5004
+ let sawAssistantSinceLastUser = false;
5005
+ for (const message of messages) {
5006
+ if (message.role === "user") {
5007
+ sawAssistantSinceLastUser = false;
5008
+ collapsed.push(message);
5009
+ continue;
5010
+ }
5011
+ if (message.role === "assistant") {
5012
+ if (sawAssistantSinceLastUser) continue;
5013
+ sawAssistantSinceLastUser = true;
5014
+ collapsed.push(message);
5015
+ continue;
5016
+ }
5017
+ collapsed.push(message);
5018
+ }
5019
+ return collapsed;
5020
+ }
5021
+ function sanitizeHistoryMessage(agentType, message) {
5022
+ if (!message || message.role !== "user" && message.role !== "assistant" && message.role !== "system") {
5023
+ return null;
5024
+ }
5025
+ const content = cleanupHistoryContent(agentType, message.role, message.content);
5026
+ if (!content) return null;
5027
+ return {
5028
+ ...message,
5029
+ content
5030
+ };
5031
+ }
4924
5032
  var ChatHistoryWriter = class {
4925
5033
  /** Last seen message count per agent (deduplication) */
4926
5034
  lastSeenCounts = /* @__PURE__ */ new Map();
4927
5035
  /** Last seen message hash per agent (deduplication) */
4928
5036
  lastSeenHashes = /* @__PURE__ */ new Map();
5037
+ /** Last appended normalized message signature per agent/session */
5038
+ lastSeenSignatures = /* @__PURE__ */ new Map();
5039
+ /** Last appended normalized non-system turn signature per agent/session */
5040
+ lastSeenTurnSignatures = /* @__PURE__ */ new Map();
4929
5041
  rotated = false;
4930
5042
  /**
4931
5043
  * Append new messages to history
@@ -4947,14 +5059,36 @@ var ChatHistoryWriter = class {
4947
5059
  }
4948
5060
  const newMessages = [];
4949
5061
  for (const msg of messages) {
4950
- const hash = msg.historyDedupKey || `${msg.kind || "standard"}:${msg.role}:${(msg.content || "").slice(0, 50)}`;
5062
+ const role = msg.role;
5063
+ if (role !== "user" && role !== "assistant" && role !== "system") continue;
5064
+ const content = cleanupHistoryContent(agentType, role, msg.content || "");
5065
+ if (!content) continue;
5066
+ const receivedAt = msg.receivedAt || Date.now();
5067
+ const hash = buildHistoryMessageHash(agentType, {
5068
+ role,
5069
+ content,
5070
+ receivedAt,
5071
+ kind: typeof msg.kind === "string" ? msg.kind : void 0,
5072
+ historyDedupKey: msg.historyDedupKey
5073
+ });
5074
+ const signature = buildHistoryMessageSignature(agentType, {
5075
+ role,
5076
+ content,
5077
+ kind: typeof msg.kind === "string" ? msg.kind : void 0
5078
+ });
4951
5079
  if (seenHashes.has(hash)) continue;
5080
+ if (this.lastSeenSignatures.get(dedupKey) === signature) continue;
5081
+ if (role !== "system" && this.lastSeenTurnSignatures.get(dedupKey) === signature) continue;
4952
5082
  seenHashes.add(hash);
5083
+ this.lastSeenSignatures.set(dedupKey, signature);
5084
+ if (role !== "system") {
5085
+ this.lastSeenTurnSignatures.set(dedupKey, signature);
5086
+ }
4953
5087
  newMessages.push({
4954
- ts: new Date(msg.receivedAt || Date.now()).toISOString(),
4955
- receivedAt: msg.receivedAt || Date.now(),
4956
- role: msg.role,
4957
- content: msg.content || "",
5088
+ ts: new Date(receivedAt).toISOString(),
5089
+ receivedAt,
5090
+ role,
5091
+ content,
4958
5092
  kind: typeof msg.kind === "string" ? msg.kind : void 0,
4959
5093
  senderName: typeof msg.senderName === "string" ? msg.senderName : void 0,
4960
5094
  agent: agentType,
@@ -4964,16 +5098,18 @@ var ChatHistoryWriter = class {
4964
5098
  });
4965
5099
  }
4966
5100
  if (newMessages.length === 0) return;
4967
- const dir = path5.join(HISTORY_DIR, this.sanitize(agentType));
5101
+ const dir = path7.join(HISTORY_DIR, this.sanitize(agentType));
4968
5102
  fs3.mkdirSync(dir, { recursive: true });
4969
5103
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4970
5104
  const filePrefix = effectiveHistoryKey ? `${this.sanitize(effectiveHistoryKey)}_` : "";
4971
- const filePath = path5.join(dir, `${filePrefix}${date}.jsonl`);
5105
+ const filePath = path7.join(dir, `${filePrefix}${date}.jsonl`);
4972
5106
  const lines = newMessages.map((m) => JSON.stringify(m)).join("\n") + "\n";
4973
5107
  fs3.appendFileSync(filePath, lines, "utf-8");
4974
5108
  const prevCount = this.lastSeenCounts.get(dedupKey) || 0;
4975
5109
  if (messages.length < prevCount * 0.5 && prevCount > 3) {
4976
5110
  seenHashes.clear();
5111
+ this.lastSeenSignatures.delete(dedupKey);
5112
+ this.lastSeenTurnSignatures.delete(dedupKey);
4977
5113
  for (const msg of messages) {
4978
5114
  seenHashes.add(msg.historyDedupKey || `${msg.kind || "standard"}:${msg.role}:${(msg.content || "").slice(0, 50)}`);
4979
5115
  }
@@ -4987,6 +5123,54 @@ var ChatHistoryWriter = class {
4987
5123
  } catch {
4988
5124
  }
4989
5125
  }
5126
+ seedSessionHistory(agentType, messages = [], historySessionId, instanceId) {
5127
+ const effectiveHistoryKey = historySessionId || instanceId;
5128
+ const dedupKey = effectiveHistoryKey ? `${agentType}:${effectiveHistoryKey}` : agentType;
5129
+ const seenHashes = /* @__PURE__ */ new Set();
5130
+ for (const raw of messages) {
5131
+ const role = raw?.role;
5132
+ if (role !== "user" && role !== "assistant" && role !== "system") continue;
5133
+ const content = cleanupHistoryContent(agentType, role, raw?.content || "");
5134
+ if (!content) continue;
5135
+ seenHashes.add(buildHistoryMessageHash(agentType, {
5136
+ role,
5137
+ content,
5138
+ receivedAt: raw?.receivedAt || 0,
5139
+ kind: typeof raw?.kind === "string" ? raw.kind : void 0,
5140
+ historyDedupKey: raw?.historyDedupKey
5141
+ }));
5142
+ }
5143
+ this.lastSeenHashes.set(dedupKey, seenHashes);
5144
+ this.lastSeenCounts.set(dedupKey, messages.length);
5145
+ const lastMessage = [...messages].reverse().find((raw) => {
5146
+ const role = raw?.role;
5147
+ if (role !== "user" && role !== "assistant" && role !== "system") return false;
5148
+ return !!cleanupHistoryContent(agentType, role, raw?.content || "");
5149
+ });
5150
+ const lastTurnMessage = [...messages].reverse().find((raw) => {
5151
+ const role = raw?.role;
5152
+ if (role !== "user" && role !== "assistant") return false;
5153
+ return !!cleanupHistoryContent(agentType, role, raw?.content || "");
5154
+ });
5155
+ if (lastMessage) {
5156
+ this.lastSeenSignatures.set(dedupKey, buildHistoryMessageSignature(agentType, {
5157
+ role: lastMessage.role,
5158
+ content: lastMessage.content,
5159
+ kind: typeof lastMessage.kind === "string" ? lastMessage.kind : void 0
5160
+ }));
5161
+ } else {
5162
+ this.lastSeenSignatures.delete(dedupKey);
5163
+ }
5164
+ if (lastTurnMessage) {
5165
+ this.lastSeenTurnSignatures.set(dedupKey, buildHistoryMessageSignature(agentType, {
5166
+ role: lastTurnMessage.role,
5167
+ content: lastTurnMessage.content,
5168
+ kind: typeof lastTurnMessage.kind === "string" ? lastTurnMessage.kind : void 0
5169
+ }));
5170
+ } else {
5171
+ this.lastSeenTurnSignatures.delete(dedupKey);
5172
+ }
5173
+ }
4990
5174
  appendSystemMarker(agentType, content, options = {}) {
4991
5175
  this.appendNewMessages(
4992
5176
  agentType,
@@ -5017,19 +5201,29 @@ var ChatHistoryWriter = class {
5017
5201
  this.lastSeenHashes.set(toDedupKey, nextHashes);
5018
5202
  this.lastSeenHashes.delete(fromDedupKey);
5019
5203
  }
5204
+ const fromSignature = this.lastSeenSignatures.get(fromDedupKey);
5205
+ if (fromSignature) {
5206
+ this.lastSeenSignatures.set(toDedupKey, fromSignature);
5207
+ this.lastSeenSignatures.delete(fromDedupKey);
5208
+ }
5209
+ const fromTurnSignature = this.lastSeenTurnSignatures.get(fromDedupKey);
5210
+ if (fromTurnSignature) {
5211
+ this.lastSeenTurnSignatures.set(toDedupKey, fromTurnSignature);
5212
+ this.lastSeenTurnSignatures.delete(fromDedupKey);
5213
+ }
5020
5214
  const fromCount = this.lastSeenCounts.get(fromDedupKey);
5021
5215
  if (typeof fromCount === "number") {
5022
5216
  this.lastSeenCounts.set(toDedupKey, Math.max(fromCount, this.lastSeenCounts.get(toDedupKey) || 0));
5023
5217
  this.lastSeenCounts.delete(fromDedupKey);
5024
5218
  }
5025
- const dir = path5.join(HISTORY_DIR, this.sanitize(agentType));
5219
+ const dir = path7.join(HISTORY_DIR, this.sanitize(agentType));
5026
5220
  if (!fs3.existsSync(dir)) return;
5027
5221
  const fromPrefix = `${this.sanitize(fromId)}_`;
5028
5222
  const toPrefix = `${this.sanitize(toId)}_`;
5029
5223
  const files = fs3.readdirSync(dir).filter((file) => file.startsWith(fromPrefix) && file.endsWith(".jsonl"));
5030
5224
  for (const file of files) {
5031
- const sourcePath = path5.join(dir, file);
5032
- const targetPath = path5.join(dir, `${toPrefix}${file.slice(fromPrefix.length)}`);
5225
+ const sourcePath = path7.join(dir, file);
5226
+ const targetPath = path7.join(dir, `${toPrefix}${file.slice(fromPrefix.length)}`);
5033
5227
  const sourceLines = fs3.readFileSync(sourcePath, "utf-8").split("\n").filter(Boolean);
5034
5228
  const rewritten = sourceLines.map((line) => {
5035
5229
  try {
@@ -5058,10 +5252,61 @@ var ChatHistoryWriter = class {
5058
5252
  } catch {
5059
5253
  }
5060
5254
  }
5255
+ compactHistorySession(agentType, historySessionId) {
5256
+ const sessionId = String(historySessionId || "").trim();
5257
+ if (!sessionId) return;
5258
+ try {
5259
+ const dir = path7.join(HISTORY_DIR, this.sanitize(agentType));
5260
+ if (!fs3.existsSync(dir)) return;
5261
+ const prefix = `${this.sanitize(sessionId)}_`;
5262
+ const files = fs3.readdirSync(dir).filter((file) => file.startsWith(prefix) && file.endsWith(".jsonl")).sort();
5263
+ const seen = /* @__PURE__ */ new Set();
5264
+ for (const file of files) {
5265
+ const filePath = path7.join(dir, file);
5266
+ const lines = fs3.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
5267
+ const next = [];
5268
+ for (const line of lines) {
5269
+ let parsed = null;
5270
+ try {
5271
+ parsed = JSON.parse(line);
5272
+ } catch {
5273
+ parsed = null;
5274
+ }
5275
+ if (!parsed || parsed.historySessionId !== sessionId) continue;
5276
+ const sanitized = sanitizeHistoryMessage(agentType, parsed);
5277
+ if (!sanitized) continue;
5278
+ const hash = buildHistoryMessageHash(agentType, sanitized);
5279
+ if (seen.has(hash)) continue;
5280
+ seen.add(hash);
5281
+ next.push(sanitized);
5282
+ }
5283
+ next.sort((a, b) => a.receivedAt - b.receivedAt);
5284
+ const dedupedAdjacent = [];
5285
+ let lastTurn = null;
5286
+ for (const entry of next) {
5287
+ const previous = dedupedAdjacent[dedupedAdjacent.length - 1];
5288
+ if (isAdjacentHistoryDuplicate(agentType, previous, entry)) continue;
5289
+ if (entry.role !== "system" && isAdjacentHistoryDuplicate(agentType, lastTurn, entry)) continue;
5290
+ dedupedAdjacent.push(entry);
5291
+ if (entry.role !== "system") lastTurn = entry;
5292
+ }
5293
+ const collapsed = collapseReplayAssistantTurns(agentType, dedupedAdjacent);
5294
+ if (collapsed.length === 0) {
5295
+ fs3.unlinkSync(filePath);
5296
+ continue;
5297
+ }
5298
+ fs3.writeFileSync(filePath, `${collapsed.map((entry) => JSON.stringify(entry)).join("\n")}
5299
+ `, "utf-8");
5300
+ }
5301
+ } catch {
5302
+ }
5303
+ }
5061
5304
  /** Called when agent session is explicitly changed */
5062
5305
  onSessionChange(agentType) {
5063
5306
  this.lastSeenHashes.delete(agentType);
5064
5307
  this.lastSeenCounts.delete(agentType);
5308
+ this.lastSeenSignatures.delete(agentType);
5309
+ this.lastSeenTurnSignatures.delete(agentType);
5065
5310
  }
5066
5311
  /** Delete history files older than 30 days */
5067
5312
  async rotateOldFiles() {
@@ -5070,10 +5315,10 @@ var ChatHistoryWriter = class {
5070
5315
  const cutoff = Date.now() - RETAIN_DAYS * 24 * 60 * 60 * 1e3;
5071
5316
  const agentDirs = fs3.readdirSync(HISTORY_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
5072
5317
  for (const dir of agentDirs) {
5073
- const dirPath = path5.join(HISTORY_DIR, dir.name);
5318
+ const dirPath = path7.join(HISTORY_DIR, dir.name);
5074
5319
  const files = fs3.readdirSync(dirPath).filter((f) => f.endsWith(".jsonl") || f.endsWith(".terminal.log"));
5075
5320
  for (const file of files) {
5076
- const filePath = path5.join(dirPath, file);
5321
+ const filePath = path7.join(dirPath, file);
5077
5322
  const stat = fs3.statSync(filePath);
5078
5323
  if (stat.mtimeMs < cutoff) {
5079
5324
  fs3.unlinkSync(filePath);
@@ -5091,7 +5336,7 @@ var ChatHistoryWriter = class {
5091
5336
  function readChatHistory(agentType, offset = 0, limit = 30, historySessionId) {
5092
5337
  try {
5093
5338
  const sanitized = agentType.replace(/[^a-zA-Z0-9_-]/g, "_");
5094
- const dir = path5.join(HISTORY_DIR, sanitized);
5339
+ const dir = path7.join(HISTORY_DIR, sanitized);
5095
5340
  if (!fs3.existsSync(dir)) return { messages: [], hasMore: false };
5096
5341
  const sanitizedInstance = historySessionId?.replace(/[^a-zA-Z0-9_-]/g, "_");
5097
5342
  const files = fs3.readdirSync(dir).filter((f) => {
@@ -5102,23 +5347,37 @@ function readChatHistory(agentType, offset = 0, limit = 30, historySessionId) {
5102
5347
  return true;
5103
5348
  }).sort().reverse();
5104
5349
  const allMessages = [];
5105
- const needed = offset + limit + 1;
5350
+ const seen = /* @__PURE__ */ new Set();
5106
5351
  for (const file of files) {
5107
- if (allMessages.length >= needed) break;
5108
- const filePath = path5.join(dir, file);
5352
+ const filePath = path7.join(dir, file);
5109
5353
  const content = fs3.readFileSync(filePath, "utf-8");
5110
5354
  const lines = content.trim().split("\n").filter(Boolean);
5111
- for (let i = lines.length - 1; i >= 0; i--) {
5112
- if (allMessages.length >= needed) break;
5355
+ for (let i = 0; i < lines.length; i++) {
5113
5356
  try {
5114
- allMessages.push(JSON.parse(lines[i]));
5357
+ const parsed = JSON.parse(lines[i]);
5358
+ const sanitizedMessage = sanitizeHistoryMessage(agentType, parsed);
5359
+ if (!sanitizedMessage) continue;
5360
+ const hash = buildHistoryMessageHash(agentType, sanitizedMessage);
5361
+ if (seen.has(hash)) continue;
5362
+ seen.add(hash);
5363
+ allMessages.push(sanitizedMessage);
5115
5364
  } catch {
5116
5365
  }
5117
5366
  }
5118
5367
  }
5119
- const sliced = allMessages.slice(offset, offset + limit);
5120
- const hasMore = allMessages.length > offset + limit;
5121
- sliced.reverse();
5368
+ allMessages.sort((a, b) => a.receivedAt - b.receivedAt);
5369
+ const chronological = [];
5370
+ let lastTurn = null;
5371
+ for (const message of allMessages) {
5372
+ const previous = chronological[chronological.length - 1];
5373
+ if (isAdjacentHistoryDuplicate(agentType, previous, message)) continue;
5374
+ if (message.role !== "system" && isAdjacentHistoryDuplicate(agentType, lastTurn, message)) continue;
5375
+ chronological.push(message);
5376
+ if (message.role !== "system") lastTurn = message;
5377
+ }
5378
+ const collapsed = collapseReplayAssistantTurns(agentType, chronological);
5379
+ const sliced = collapsed.slice(offset, offset + limit);
5380
+ const hasMore = collapsed.length > offset + limit;
5122
5381
  return { messages: sliced, hasMore };
5123
5382
  } catch {
5124
5383
  return { messages: [], hasMore: false };
@@ -5127,7 +5386,7 @@ function readChatHistory(agentType, offset = 0, limit = 30, historySessionId) {
5127
5386
  function listSavedHistorySessions(agentType, options = {}) {
5128
5387
  try {
5129
5388
  const sanitized = agentType.replace(/[^a-zA-Z0-9_-]/g, "_");
5130
- const dir = path5.join(HISTORY_DIR, sanitized);
5389
+ const dir = path7.join(HISTORY_DIR, sanitized);
5131
5390
  if (!fs3.existsSync(dir)) return { sessions: [], hasMore: false };
5132
5391
  const groupedFiles = /* @__PURE__ */ new Map();
5133
5392
  const filePattern = /^([A-Za-z0-9_-]+)_\d{4}-\d{2}-\d{2}\.jsonl$/;
@@ -5148,7 +5407,7 @@ function listSavedHistorySessions(agentType, options = {}) {
5148
5407
  let sessionTitle = "";
5149
5408
  let preview = "";
5150
5409
  for (const file of files.sort()) {
5151
- const filePath = path5.join(dir, file);
5410
+ const filePath = path7.join(dir, file);
5152
5411
  const content = fs3.readFileSync(filePath, "utf-8");
5153
5412
  const lines = content.split("\n").filter(Boolean);
5154
5413
  for (const line of lines) {
@@ -6972,6 +7231,46 @@ function didProviderConfirmSend(result) {
6972
7231
  if (!parsed || typeof parsed !== "object") return false;
6973
7232
  return parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true;
6974
7233
  }
7234
+ async function readExtensionChatState(h) {
7235
+ try {
7236
+ const evalResult = await h.evaluateProviderScript("readChat", void 0, 5e4);
7237
+ if (!evalResult?.result) return null;
7238
+ const parsed = parseMaybeJson(evalResult.result);
7239
+ return parsed && typeof parsed === "object" ? parsed : null;
7240
+ } catch {
7241
+ return null;
7242
+ }
7243
+ }
7244
+ function getStateMessageCount(state) {
7245
+ return Array.isArray(state?.messages) ? state.messages.length : 0;
7246
+ }
7247
+ function getStateLastSignature(state) {
7248
+ const messages = Array.isArray(state?.messages) ? state.messages : [];
7249
+ const last = messages[messages.length - 1];
7250
+ if (!last) return "";
7251
+ return `${last.role || ""}:${String(last.content || "").replace(/\s+/g, " ").trim()}`;
7252
+ }
7253
+ async function getStableExtensionBaseline(h) {
7254
+ const first = await readExtensionChatState(h);
7255
+ if (getStateMessageCount(first) > 0 || getStateLastSignature(first)) return first;
7256
+ await new Promise((resolve12) => setTimeout(resolve12, 150));
7257
+ const second = await readExtensionChatState(h);
7258
+ return getStateMessageCount(second) >= getStateMessageCount(first) ? second : first;
7259
+ }
7260
+ async function verifyExtensionSendObserved(h, before) {
7261
+ const beforeCount = getStateMessageCount(before);
7262
+ const beforeSignature = getStateLastSignature(before);
7263
+ for (let attempt = 0; attempt < 12; attempt += 1) {
7264
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
7265
+ const state = await readExtensionChatState(h);
7266
+ if (state?.status === "waiting_approval") return true;
7267
+ const afterCount = getStateMessageCount(state);
7268
+ const afterSignature = getStateLastSignature(state);
7269
+ if (afterCount > beforeCount) return true;
7270
+ if (afterSignature && afterSignature !== beforeSignature) return true;
7271
+ }
7272
+ return false;
7273
+ }
6975
7274
  async function handleChatHistory(h, args) {
6976
7275
  const { agentType, offset, limit } = args;
6977
7276
  const historySessionId = getHistorySessionId(h, args);
@@ -7152,12 +7451,17 @@ async function handleSendChat(h, args) {
7152
7451
  if (isExtensionTransport(transport)) {
7153
7452
  _log(`Extension: ${provider?.type || "unknown_extension"}`);
7154
7453
  try {
7454
+ const beforeState = await getStableExtensionBaseline(h);
7155
7455
  const evalResult = await h.evaluateProviderScript("sendMessage", { message: text }, 3e4);
7156
7456
  if (evalResult?.result) {
7157
7457
  const parsed = parseMaybeJson(evalResult.result);
7158
7458
  if (didProviderConfirmSend(parsed)) {
7159
- _log(`Extension script sent OK`);
7160
- return _logSendSuccess("extension-script");
7459
+ const observed = await verifyExtensionSendObserved(h, beforeState);
7460
+ if (observed) {
7461
+ _log(`Extension script sent OK`);
7462
+ return _logSendSuccess("extension-script");
7463
+ }
7464
+ _log(`Extension script reported send but no chat-state change was observed`);
7161
7465
  }
7162
7466
  if (parsed?.needsTypeAndSend) {
7163
7467
  _log(`Extension needsTypeAndSend \u2192 AgentStreamManager`);
@@ -7323,6 +7627,14 @@ async function handleListChats(h, args) {
7323
7627
  } catch {
7324
7628
  }
7325
7629
  }
7630
+ if (parsed?.sessions && Array.isArray(parsed.sessions)) {
7631
+ LOG.info("Command", `[list_chats] OK: ${parsed.sessions.length} chats`);
7632
+ return { success: true, chats: parsed.sessions };
7633
+ }
7634
+ if (parsed?.chats && Array.isArray(parsed.chats)) {
7635
+ LOG.info("Command", `[list_chats] OK: ${parsed.chats.length} chats`);
7636
+ return { success: true, chats: parsed.chats };
7637
+ }
7326
7638
  if (Array.isArray(parsed)) {
7327
7639
  LOG.info("Command", `[list_chats] OK: ${parsed.length} chats`);
7328
7640
  return { success: true, chats: parsed };
@@ -7392,7 +7704,13 @@ async function handleSwitchChat(h, args) {
7392
7704
  } catch (e) {
7393
7705
  return { success: false, error: `webviewSwitchSession failed: ${e.message}` };
7394
7706
  }
7395
- const script = h.getProviderScript("switchSession", { SESSION_ID: JSON.stringify(sessionId) }) || h.getProviderScript("switch_session", { SESSION_ID: JSON.stringify(sessionId) });
7707
+ const switchParams = {
7708
+ sessionId,
7709
+ title: sessionId,
7710
+ id: sessionId,
7711
+ SESSION_ID: JSON.stringify(sessionId)
7712
+ };
7713
+ const script = h.getProviderScript("switchSession", switchParams) || h.getProviderScript("switch_session", switchParams);
7396
7714
  if (!script) return { success: false, error: "switch_session script not available" };
7397
7715
  try {
7398
7716
  const raw = await cdp.evaluate(script, 15e3);
@@ -7471,8 +7789,8 @@ async function handleSetMode(h, args) {
7471
7789
  const adapter = getTargetedCliAdapter(h, args, provider?.type);
7472
7790
  if (adapter) {
7473
7791
  const acpInstance = adapter._acpInstance;
7474
- if (acpInstance && typeof acpInstance.onEvent === "function") {
7475
- acpInstance.onEvent("set_mode", { mode });
7792
+ if (acpInstance && typeof acpInstance.setMode === "function") {
7793
+ await acpInstance.setMode(mode);
7476
7794
  return { success: true, mode };
7477
7795
  }
7478
7796
  }
@@ -7529,9 +7847,9 @@ async function handleChangeModel(h, args) {
7529
7847
  LOG.info("Command", `[change_model] ACP adapter found: ${!!adapter}, type=${adapter?.cliType}, hasAcpInstance=${!!adapter?._acpInstance}`);
7530
7848
  if (adapter) {
7531
7849
  const acpInstance = adapter._acpInstance;
7532
- if (acpInstance && typeof acpInstance.onEvent === "function") {
7533
- acpInstance.onEvent("change_model", { model });
7534
- LOG.info("Command", `[change_model] Dispatched change_model event to ACP instance`);
7850
+ if (acpInstance && typeof acpInstance.setConfigOption === "function") {
7851
+ await acpInstance.setConfigOption("model", model);
7852
+ LOG.info("Command", `[change_model] Updated ACP model to ${model}`);
7535
7853
  return { success: true, model };
7536
7854
  }
7537
7855
  }
@@ -7648,9 +7966,21 @@ async function handleResolveAction(h, args) {
7648
7966
  return { success: true, buttonIndex, button: buttons[buttonIndex] ?? button };
7649
7967
  }
7650
7968
  if (isExtensionTransport(transport) && h.agentStream && h.getCdp() && h.currentSession?.sessionId) {
7651
- const ok = await h.agentStream.resolveSessionAction(h.getCdp(), h.currentSession.sessionId, action);
7969
+ const ok = await h.agentStream.resolveSessionAction(h.getCdp(), h.currentSession.sessionId, action, button);
7652
7970
  return { success: ok };
7653
7971
  }
7972
+ if (transport === "acp") {
7973
+ const adapter = getTargetedCliAdapter(h, args, provider?.type);
7974
+ const acpInstance = adapter?._acpInstance;
7975
+ if (!acpInstance) return { success: false, error: "ACP instance not found" };
7976
+ try {
7977
+ await acpInstance.resolvePermission(action === "approve" || action === "accept" || action === "always");
7978
+ LOG.info("Command", `[resolveAction] ACP \u2192 ${action}`);
7979
+ return { success: true, action };
7980
+ } catch (e) {
7981
+ return { success: false, error: e?.message || "ACP resolve action failed" };
7982
+ }
7983
+ }
7654
7984
  if (provider?.scripts?.webviewResolveAction || provider?.scripts?.webview_resolve_action) {
7655
7985
  const script = h.getProviderScript("webviewResolveAction", { action, button, buttonText: button }) || h.getProviderScript("webview_resolve_action", { action, button, buttonText: button });
7656
7986
  if (script) {
@@ -7729,7 +8059,7 @@ async function handleResolveAction(h, args) {
7729
8059
 
7730
8060
  // src/commands/cdp-commands.ts
7731
8061
  var fs4 = __toESM(require("fs"));
7732
- var path6 = __toESM(require("path"));
8062
+ var path8 = __toESM(require("path"));
7733
8063
  var os6 = __toESM(require("os"));
7734
8064
  var KEY_TO_VK = {
7735
8065
  Backspace: 8,
@@ -7981,25 +8311,25 @@ function resolveSafePath(requestedPath) {
7981
8311
  const inputPath = rawPath || ".";
7982
8312
  const home = os6.homedir();
7983
8313
  if (inputPath.startsWith("~")) {
7984
- return path6.resolve(path6.join(home, inputPath.slice(1)));
8314
+ return path8.resolve(path8.join(home, inputPath.slice(1)));
7985
8315
  }
7986
8316
  if (process.platform === "win32") {
7987
8317
  const normalized = normalizeWindowsRequestedPath(inputPath);
7988
- if (path6.win32.isAbsolute(normalized)) {
7989
- return path6.win32.normalize(normalized);
8318
+ if (path8.win32.isAbsolute(normalized)) {
8319
+ return path8.win32.normalize(normalized);
7990
8320
  }
7991
- return path6.win32.resolve(normalized);
8321
+ return path8.win32.resolve(normalized);
7992
8322
  }
7993
- if (path6.isAbsolute(inputPath)) {
7994
- return path6.normalize(inputPath);
8323
+ if (path8.isAbsolute(inputPath)) {
8324
+ return path8.normalize(inputPath);
7995
8325
  }
7996
- return path6.resolve(inputPath);
8326
+ return path8.resolve(inputPath);
7997
8327
  }
7998
8328
  function listDirectoryEntriesSafe(dirPath) {
7999
8329
  const entries = fs4.readdirSync(dirPath, { withFileTypes: true });
8000
8330
  const files = [];
8001
8331
  for (const entry of entries) {
8002
- const entryPath = path6.join(dirPath, entry.name);
8332
+ const entryPath = path8.join(dirPath, entry.name);
8003
8333
  try {
8004
8334
  if (entry.isDirectory()) {
8005
8335
  files.push({ name: entry.name, type: "directory" });
@@ -8038,7 +8368,7 @@ async function handleFileRead(h, args) {
8038
8368
  async function handleFileWrite(h, args) {
8039
8369
  try {
8040
8370
  const filePath = resolveSafePath(args?.path);
8041
- fs4.mkdirSync(path6.dirname(filePath), { recursive: true });
8371
+ fs4.mkdirSync(path8.dirname(filePath), { recursive: true });
8042
8372
  fs4.writeFileSync(filePath, args?.content || "", "utf-8");
8043
8373
  return { success: true, path: filePath };
8044
8374
  } catch (e) {
@@ -8126,7 +8456,7 @@ function handleGetProviderSettings(h, args) {
8126
8456
  }
8127
8457
  return { success: true, settings: allSettings, values: allValues };
8128
8458
  }
8129
- function handleSetProviderSetting(h, args) {
8459
+ async function handleSetProviderSetting(h, args) {
8130
8460
  const loader = h.ctx.providerLoader;
8131
8461
  const { providerType, key, value } = args || {};
8132
8462
  if (!providerType || !key || value === void 0) {
@@ -8139,6 +8469,7 @@ function handleSetProviderSetting(h, args) {
8139
8469
  const updated = h.ctx.instanceManager.updateInstanceSettings(providerType, allSettings);
8140
8470
  LOG.info("Command", `[set_provider_setting] ${providerType}.${key}=${JSON.stringify(value)} \u2192 ${updated} instance(s) updated`);
8141
8471
  }
8472
+ await h.ctx.onProviderSettingChanged?.(providerType, key, value);
8142
8473
  return { success: true, providerType, key, value };
8143
8474
  }
8144
8475
  return { success: false, error: `Failed to set ${providerType}.${key} \u2014 invalid key, value, or not a public setting` };
@@ -8176,10 +8507,10 @@ function getCliScriptCommand(payload) {
8176
8507
  }
8177
8508
  const command = payload.command;
8178
8509
  if (!command || typeof command !== "object") return null;
8179
- if (command.type !== "send_message") return null;
8510
+ if (command.type !== "send_message" && command.type !== "pty_write") return null;
8180
8511
  const text = typeof command.text === "string" ? command.text.trim() : typeof command.message === "string" ? command.message.trim() : "";
8181
8512
  if (!text) return null;
8182
- return { type: "send_message", text };
8513
+ return { type: command.type, text };
8183
8514
  }
8184
8515
  function applyProviderPatch(h, args, payload) {
8185
8516
  if (!payload || typeof payload !== "object") return;
@@ -8220,6 +8551,8 @@ async function executeProviderScript(h, args, scriptName) {
8220
8551
  const cliCommand = getCliScriptCommand(parsed.payload);
8221
8552
  if (cliCommand?.type === "send_message" && cliCommand.text) {
8222
8553
  await adapter.sendMessage(cliCommand.text);
8554
+ } else if (cliCommand?.type === "pty_write" && cliCommand.text && adapter.writeRaw) {
8555
+ adapter.writeRaw(cliCommand.text + "\r");
8223
8556
  }
8224
8557
  applyProviderPatch(h, args, parsed.payload);
8225
8558
  return { success: true, ...parsed.payload && typeof parsed.payload === "object" ? parsed.payload : { result: parsed.payload } };
@@ -8898,7 +9231,7 @@ var DaemonCommandHandler = class {
8898
9231
  try {
8899
9232
  const http3 = await import("http");
8900
9233
  const postData = JSON.stringify(body);
8901
- const result = await new Promise((resolve9, reject) => {
9234
+ const result = await new Promise((resolve12, reject) => {
8902
9235
  const req = http3.request({
8903
9236
  hostname: "127.0.0.1",
8904
9237
  port: 19280,
@@ -8910,9 +9243,9 @@ var DaemonCommandHandler = class {
8910
9243
  res.on("data", (chunk) => data += chunk);
8911
9244
  res.on("end", () => {
8912
9245
  try {
8913
- resolve9(JSON.parse(data));
9246
+ resolve12(JSON.parse(data));
8914
9247
  } catch {
8915
- resolve9({ raw: data });
9248
+ resolve12({ raw: data });
8916
9249
  }
8917
9250
  });
8918
9251
  });
@@ -8930,15 +9263,15 @@ var DaemonCommandHandler = class {
8930
9263
  if (!providerType) return { success: false, error: "providerType required" };
8931
9264
  try {
8932
9265
  const http3 = await import("http");
8933
- const result = await new Promise((resolve9, reject) => {
9266
+ const result = await new Promise((resolve12, reject) => {
8934
9267
  http3.get(`http://127.0.0.1:19280/api/providers/${providerType}/${endpoint}`, (res) => {
8935
9268
  let data = "";
8936
9269
  res.on("data", (chunk) => data += chunk);
8937
9270
  res.on("end", () => {
8938
9271
  try {
8939
- resolve9(JSON.parse(data));
9272
+ resolve12(JSON.parse(data));
8940
9273
  } catch {
8941
- resolve9({ raw: data });
9274
+ resolve12({ raw: data });
8942
9275
  }
8943
9276
  });
8944
9277
  }).on("error", reject);
@@ -8952,7 +9285,7 @@ var DaemonCommandHandler = class {
8952
9285
  try {
8953
9286
  const http3 = await import("http");
8954
9287
  const postData = JSON.stringify(args || {});
8955
- const result = await new Promise((resolve9, reject) => {
9288
+ const result = await new Promise((resolve12, reject) => {
8956
9289
  const req = http3.request({
8957
9290
  hostname: "127.0.0.1",
8958
9291
  port: 19280,
@@ -8964,9 +9297,9 @@ var DaemonCommandHandler = class {
8964
9297
  res.on("data", (chunk) => data += chunk);
8965
9298
  res.on("end", () => {
8966
9299
  try {
8967
- resolve9(JSON.parse(data));
9300
+ resolve12(JSON.parse(data));
8968
9301
  } catch {
8969
- resolve9({ raw: data });
9302
+ resolve12({ raw: data });
8970
9303
  }
8971
9304
  });
8972
9305
  });
@@ -8983,7 +9316,7 @@ var DaemonCommandHandler = class {
8983
9316
 
8984
9317
  // src/commands/cli-manager.ts
8985
9318
  var os10 = __toESM(require("os"));
8986
- var path9 = __toESM(require("path"));
9319
+ var path11 = __toESM(require("path"));
8987
9320
  var crypto4 = __toESM(require("crypto"));
8988
9321
  var import_chalk = __toESM(require("chalk"));
8989
9322
  init_provider_cli_adapter();
@@ -8991,7 +9324,7 @@ init_config();
8991
9324
 
8992
9325
  // src/providers/cli-provider-instance.ts
8993
9326
  var os9 = __toESM(require("os"));
8994
- var path8 = __toESM(require("path"));
9327
+ var path10 = __toESM(require("path"));
8995
9328
  var crypto3 = __toESM(require("crypto"));
8996
9329
  var fs5 = __toESM(require("fs"));
8997
9330
  var import_node_module = require("module");
@@ -9000,7 +9333,7 @@ init_logger();
9000
9333
  var CachedDatabaseSync = null;
9001
9334
  function getDatabaseSync() {
9002
9335
  if (CachedDatabaseSync) return CachedDatabaseSync;
9003
- const requireFn = typeof require === "function" ? require : (0, import_node_module.createRequire)(path8.join(process.cwd(), "__adhdev_sqlite_loader__.js"));
9336
+ const requireFn = typeof require === "function" ? require : (0, import_node_module.createRequire)(path10.join(process.cwd(), "__adhdev_sqlite_loader__.js"));
9004
9337
  const sqliteModule = requireFn(`node:${"sqlite"}`);
9005
9338
  CachedDatabaseSync = sqliteModule.DatabaseSync;
9006
9339
  if (!CachedDatabaseSync) {
@@ -9040,6 +9373,7 @@ var CliProviderInstance = class {
9040
9373
  historyWriter;
9041
9374
  runtimeMessages = [];
9042
9375
  instanceId;
9376
+ suppressIdleHistoryReplay = false;
9043
9377
  presentationMode;
9044
9378
  providerSessionId;
9045
9379
  launchMode;
@@ -9067,7 +9401,15 @@ var CliProviderInstance = class {
9067
9401
  await this.adapter.spawn();
9068
9402
  this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
9069
9403
  if (this.providerSessionId) {
9404
+ this.historyWriter.compactHistorySession(this.type, this.providerSessionId);
9070
9405
  const restoredHistory = readChatHistory(this.type, 0, 200, this.providerSessionId);
9406
+ this.historyWriter.seedSessionHistory(
9407
+ this.type,
9408
+ restoredHistory.messages,
9409
+ this.providerSessionId,
9410
+ this.instanceId
9411
+ );
9412
+ this.suppressIdleHistoryReplay = restoredHistory.messages.length > 0;
9071
9413
  if (restoredHistory.messages.length > 0) {
9072
9414
  this.adapter.seedCommittedMessages(
9073
9415
  restoredHistory.messages.map((message) => ({
@@ -9111,7 +9453,7 @@ var CliProviderInstance = class {
9111
9453
  } else if (this.type === "codex-cli") {
9112
9454
  probedSessionId = this.probeSessionIdFromConfig({
9113
9455
  dbPath: "~/.codex/state_5.sqlite",
9114
- query: "select id from threads where cwd in ({dirs}) and created_at >= ? and archived = 0 order by created_at desc limit 1",
9456
+ query: "select id from threads where cwd in ({dirs}) and updated_at >= ? and archived = 0 order by updated_at desc limit 1",
9115
9457
  timestampFormat: "unix_s"
9116
9458
  });
9117
9459
  } else if (this.type === "goose-cli") {
@@ -9171,6 +9513,7 @@ var CliProviderInstance = class {
9171
9513
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
9172
9514
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
9173
9515
  if (parsedMessages.length > 0) {
9516
+ const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
9174
9517
  let messagesToSave = parsedMessages;
9175
9518
  if (parsedStatus?.status === "generating" || parsedStatus?.status === "long_generating") {
9176
9519
  const lastIdx = messagesToSave.length - 1;
@@ -9178,7 +9521,7 @@ var CliProviderInstance = class {
9178
9521
  messagesToSave = messagesToSave.slice(0, lastIdx);
9179
9522
  }
9180
9523
  }
9181
- if (messagesToSave.length > 0) {
9524
+ if (!shouldSkipReplayPersist && messagesToSave.length > 0) {
9182
9525
  this.historyWriter.appendNewMessages(
9183
9526
  this.type,
9184
9527
  messagesToSave,
@@ -9273,6 +9616,7 @@ var CliProviderInstance = class {
9273
9616
  if (newStatus !== this.lastStatus) {
9274
9617
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
9275
9618
  if (this.lastStatus === "idle" && newStatus === "generating") {
9619
+ this.suppressIdleHistoryReplay = false;
9276
9620
  if (this.completedDebouncePending) {
9277
9621
  LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed generating)`);
9278
9622
  if (this.completedDebounceTimer) {
@@ -9292,6 +9636,7 @@ var CliProviderInstance = class {
9292
9636
  this.generatingDebounceTimer = null;
9293
9637
  }, 1e3);
9294
9638
  } else if (newStatus === "waiting_approval") {
9639
+ this.suppressIdleHistoryReplay = false;
9295
9640
  if (this.generatingDebouncePending) {
9296
9641
  if (this.generatingDebounceTimer) {
9297
9642
  clearTimeout(this.generatingDebounceTimer);
@@ -9829,8 +10174,9 @@ var AcpProviderInstance = class {
9829
10174
  async setConfigOption(category, value) {
9830
10175
  const opt = this.configOptions.find((c) => c.category === category);
9831
10176
  if (!opt) {
9832
- this.log.warn(`[${this.type}] No config option for category: ${category}`);
9833
- return;
10177
+ const message = `[${this.type}] No config option for category: ${category}`;
10178
+ this.log.warn(message);
10179
+ throw new Error(message);
9834
10180
  }
9835
10181
  if (this.useStaticConfig) {
9836
10182
  opt.currentValue = value;
@@ -9842,8 +10188,9 @@ var AcpProviderInstance = class {
9842
10188
  return;
9843
10189
  }
9844
10190
  if (!this.connection || !this.sessionId) {
9845
- this.log.warn(`[${this.type}] Cannot set config: no active connection/session`);
9846
- return;
10191
+ const message = `[${this.type}] Cannot set config: no active connection/session`;
10192
+ this.log.warn(message);
10193
+ throw new Error(message);
9847
10194
  }
9848
10195
  try {
9849
10196
  this.log.info(`[${this.type}] Sending session/set_config_option: configId=${opt.configId} value=${value} sessionId=${this.sessionId}`);
@@ -9857,7 +10204,9 @@ var AcpProviderInstance = class {
9857
10204
  if (result?.configOptions) this.parseConfigOptions(result.configOptions);
9858
10205
  this.log.info(`[${this.type}] Config ${category} set to: ${value} | response: ${JSON.stringify(result)?.slice(0, 300)}`);
9859
10206
  } catch (e) {
9860
- this.log.warn(`[${this.type}] set_config_option failed: ${e?.message}`);
10207
+ const message = e?.message || "Unknown ACP config error";
10208
+ this.log.warn(`[${this.type}] set_config_option failed: ${message}`);
10209
+ throw new Error(message);
9861
10210
  }
9862
10211
  }
9863
10212
  async setMode(modeId) {
@@ -9873,8 +10222,9 @@ var AcpProviderInstance = class {
9873
10222
  return;
9874
10223
  }
9875
10224
  if (!this.connection || !this.sessionId) {
9876
- this.log.warn(`[${this.type}] Cannot set mode: no active connection/session`);
9877
- return;
10225
+ const message = `[${this.type}] Cannot set mode: no active connection/session`;
10226
+ this.log.warn(message);
10227
+ throw new Error(message);
9878
10228
  }
9879
10229
  try {
9880
10230
  await this.connection.setSessionMode({
@@ -9884,7 +10234,9 @@ var AcpProviderInstance = class {
9884
10234
  this.currentMode = modeId;
9885
10235
  this.log.info(`[${this.type}] Mode set to: ${modeId}`);
9886
10236
  } catch (e) {
9887
- this.log.warn(`[${this.type}] set_mode failed: ${e?.message}`);
10237
+ const message = e?.message || "Unknown ACP mode error";
10238
+ this.log.warn(`[${this.type}] set_mode failed: ${message}`);
10239
+ throw new Error(message);
9888
10240
  }
9889
10241
  }
9890
10242
  /** Static config: kill process and restart with new args */
@@ -9932,7 +10284,7 @@ var AcpProviderInstance = class {
9932
10284
  if (!spawnConfig) {
9933
10285
  throw new Error(`[ACP:${this.type}] No spawn config defined`);
9934
10286
  }
9935
- const command = spawnConfig.command;
10287
+ const command = typeof this.settings.executablePath === "string" && this.settings.executablePath.trim() ? this.settings.executablePath.trim() : spawnConfig.command;
9936
10288
  let baseArgs = spawnConfig.args || [];
9937
10289
  if (this.provider.spawnArgBuilder && Object.keys(this.selectedConfig).length > 0) {
9938
10290
  baseArgs = this.provider.spawnArgBuilder(this.selectedConfig);
@@ -10048,13 +10400,13 @@ var AcpProviderInstance = class {
10048
10400
  }
10049
10401
  this.currentStatus = "waiting_approval";
10050
10402
  this.detectStatusTransition();
10051
- const approved = await new Promise((resolve9) => {
10052
- this.permissionResolvers.push(resolve9);
10403
+ const approved = await new Promise((resolve12) => {
10404
+ this.permissionResolvers.push(resolve12);
10053
10405
  setTimeout(() => {
10054
- const idx = this.permissionResolvers.indexOf(resolve9);
10406
+ const idx = this.permissionResolvers.indexOf(resolve12);
10055
10407
  if (idx >= 0) {
10056
10408
  this.permissionResolvers.splice(idx, 1);
10057
- resolve9(false);
10409
+ resolve12(false);
10058
10410
  }
10059
10411
  }, 3e5);
10060
10412
  });
@@ -10761,7 +11113,7 @@ var DaemonCliManager = class {
10761
11113
  async startSession(cliType, workingDir, cliArgs, initialModel, options) {
10762
11114
  const trimmed = (workingDir || "").trim();
10763
11115
  if (!trimmed) throw new Error("working directory required");
10764
- const resolvedDir = trimmed.startsWith("~") ? trimmed.replace(/^~/, os10.homedir()) : path9.resolve(trimmed);
11116
+ const resolvedDir = trimmed.startsWith("~") ? trimmed.replace(/^~/, os10.homedir()) : path11.resolve(trimmed);
10765
11117
  const normalizedType = this.providerLoader.resolveAlias(cliType);
10766
11118
  const provider = this.providerLoader.getByAlias(cliType);
10767
11119
  const key = crypto4.randomUUID();
@@ -10848,10 +11200,10 @@ ${installInfo}`
10848
11200
  if (!cliInfo) {
10849
11201
  const installHint = provider?.install || "";
10850
11202
  const displayName = provider?.displayName || provider?.name || cliType;
10851
- const spawnCmd = provider?.spawn?.command || cliType;
11203
+ const spawnCmd = this.providerLoader.getSpawnCommand(normalizedType, provider?.spawn?.command || cliType);
10852
11204
  throw new Error(
10853
11205
  `${displayName} is not installed.
10854
- Command '${spawnCmd}' not found on PATH.
11206
+ Command '${spawnCmd}' is not available.
10855
11207
  ` + (installHint ? `
10856
11208
  ${installHint}
10857
11209
  ` : "") + `
@@ -10997,6 +11349,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
10997
11349
  if (!instanceManager) return 0;
10998
11350
  const sessions = records || await this.deps.listHostedCliRuntimes?.() || [];
10999
11351
  let restored = 0;
11352
+ const restoredBindings = /* @__PURE__ */ new Set();
11000
11353
  for (const record of sessions) {
11001
11354
  if (!record?.runtimeId || !record?.cliType || !record?.workspace) continue;
11002
11355
  if (this.adapters.has(record.runtimeId) || instanceManager.getInstance(record.runtimeId)) continue;
@@ -11010,6 +11363,18 @@ Run 'adhdev doctor' for detailed diagnostics.`
11010
11363
  record.cliArgs,
11011
11364
  record.providerSessionId
11012
11365
  );
11366
+ const bindingKey = [
11367
+ normalizedType,
11368
+ record.workspace,
11369
+ sessionBinding.providerSessionId || record.runtimeId
11370
+ ].join("::");
11371
+ if (restoredBindings.has(bindingKey)) {
11372
+ LOG.info(
11373
+ "CLI",
11374
+ `\u21B7 Skipping duplicate hosted runtime restore: ${record.runtimeKey || record.runtimeId} (${normalizedType} @ ${record.workspace}) binding=${sessionBinding.providerSessionId || "runtime"}`
11375
+ );
11376
+ continue;
11377
+ }
11013
11378
  try {
11014
11379
  await this.registerCliInstance(
11015
11380
  record.runtimeId,
@@ -11025,6 +11390,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
11025
11390
  launchMode: "manual"
11026
11391
  }
11027
11392
  );
11393
+ restoredBindings.add(bindingKey);
11028
11394
  restored += 1;
11029
11395
  LOG.info("CLI", `\u267B Restored hosted runtime: ${record.runtimeKey || record.runtimeId} (${record.displayName || record.workspace})`);
11030
11396
  } catch (error) {
@@ -11211,16 +11577,17 @@ Run 'adhdev doctor' for detailed diagnostics.`
11211
11577
  var import_child_process6 = require("child_process");
11212
11578
  var net = __toESM(require("net"));
11213
11579
  var os12 = __toESM(require("os"));
11214
- var path11 = __toESM(require("path"));
11580
+ var path13 = __toESM(require("path"));
11215
11581
 
11216
11582
  // src/providers/provider-loader.ts
11217
11583
  var fs6 = __toESM(require("fs"));
11218
- var path10 = __toESM(require("path"));
11584
+ var path12 = __toESM(require("path"));
11219
11585
  var os11 = __toESM(require("os"));
11220
11586
  var chokidar = __toESM(require("chokidar"));
11221
11587
  init_logger();
11222
11588
  var ProviderLoader = class _ProviderLoader {
11223
11589
  providers = /* @__PURE__ */ new Map();
11590
+ providerAvailability = /* @__PURE__ */ new Map();
11224
11591
  userDir;
11225
11592
  upstreamDir;
11226
11593
  disableUpstream;
@@ -11236,12 +11603,12 @@ var ProviderLoader = class _ProviderLoader {
11236
11603
  static META_FILE = ".meta.json";
11237
11604
  constructor(options) {
11238
11605
  this.logFn = options?.logFn || LOG.forComponent("Provider").asLogFn();
11239
- const defaultProvidersDir = path10.join(os11.homedir(), ".adhdev", "providers");
11606
+ const defaultProvidersDir = path12.join(os11.homedir(), ".adhdev", "providers");
11240
11607
  if (options?.userDir) {
11241
11608
  this.userDir = options.userDir;
11242
11609
  this.log(`Config 'providerDir' applied: ${this.userDir}`);
11243
11610
  } else {
11244
- const localRepoPath = path10.resolve(__dirname, "../../../../../adhdev-providers");
11611
+ const localRepoPath = path12.resolve(__dirname, "../../../../../adhdev-providers");
11245
11612
  if (fs6.existsSync(localRepoPath)) {
11246
11613
  this.userDir = localRepoPath;
11247
11614
  this.log(`Auto-detected local public repository: ${this.userDir} (Dev workspace speedup)`);
@@ -11250,7 +11617,7 @@ var ProviderLoader = class _ProviderLoader {
11250
11617
  this.log(`Using default user providers directory: ${this.userDir}`);
11251
11618
  }
11252
11619
  }
11253
- this.upstreamDir = path10.join(defaultProvidersDir, ".upstream");
11620
+ this.upstreamDir = path12.join(defaultProvidersDir, ".upstream");
11254
11621
  this.disableUpstream = options?.disableUpstream ?? false;
11255
11622
  }
11256
11623
  log(msg) {
@@ -11280,7 +11647,7 @@ var ProviderLoader = class _ProviderLoader {
11280
11647
  * Canonical provider directory shape for a given root.
11281
11648
  */
11282
11649
  getProviderDir(root, category, type) {
11283
- return path10.join(root, category, type);
11650
+ return path12.join(root, category, type);
11284
11651
  }
11285
11652
  /**
11286
11653
  * Canonical user override directory for a provider.
@@ -11307,7 +11674,7 @@ var ProviderLoader = class _ProviderLoader {
11307
11674
  resolveProviderFile(type, ...segments) {
11308
11675
  const dir = this.findProviderDirInternal(type);
11309
11676
  if (!dir) return null;
11310
- return path10.join(dir, ...segments);
11677
+ return path12.join(dir, ...segments);
11311
11678
  }
11312
11679
  /**
11313
11680
  * Load all providers (3-tier priority)
@@ -11318,6 +11685,7 @@ var ProviderLoader = class _ProviderLoader {
11318
11685
  */
11319
11686
  loadAll() {
11320
11687
  this.providers.clear();
11688
+ this.providerAvailability.clear();
11321
11689
  let upstreamCount = 0;
11322
11690
  if (!this.disableUpstream && fs6.existsSync(this.upstreamDir)) {
11323
11691
  upstreamCount = this.loadDir(this.upstreamDir);
@@ -11345,7 +11713,7 @@ var ProviderLoader = class _ProviderLoader {
11345
11713
  if (!fs6.existsSync(this.upstreamDir)) return false;
11346
11714
  try {
11347
11715
  return fs6.readdirSync(this.upstreamDir).some(
11348
- (d) => fs6.statSync(path10.join(this.upstreamDir, d)).isDirectory()
11716
+ (d) => fs6.statSync(path12.join(this.upstreamDir, d)).isDirectory()
11349
11717
  );
11350
11718
  } catch {
11351
11719
  return false;
@@ -11388,11 +11756,12 @@ var ProviderLoader = class _ProviderLoader {
11388
11756
  if ((p.category === "cli" || p.category === "acp") && p.spawn?.command) {
11389
11757
  const verCmdConfig = p.versionCommand;
11390
11758
  const versionCommand = typeof verCmdConfig === "object" && verCmdConfig !== null ? verCmdConfig[process.platform] : verCmdConfig;
11759
+ const command = this.getSpawnCommand(p.type, p.spawn.command);
11391
11760
  result.push({
11392
11761
  id: p.type,
11393
11762
  displayName: p.displayName || p.name,
11394
11763
  icon: p.icon || "\u{1F527}",
11395
- command: p.spawn.command,
11764
+ command,
11396
11765
  category: p.category,
11397
11766
  ...typeof versionCommand === "string" && versionCommand.trim() ? { versionCommand: versionCommand.trim() } : {}
11398
11767
  });
@@ -11521,6 +11890,71 @@ var ProviderLoader = class _ProviderLoader {
11521
11890
  getAvailableIdeTypes() {
11522
11891
  return [...this.providers.values()].filter((p) => p.category === "ide" && p.cdpPorts).map((p) => p.type);
11523
11892
  }
11893
+ getSpawnCommand(type, fallback) {
11894
+ const override = this.getOptionalStringSetting(type, "executablePath");
11895
+ if (override) return override;
11896
+ return fallback || this.providers.get(type)?.spawn?.command || type;
11897
+ }
11898
+ getIdeCliCommand(type, fallback) {
11899
+ const override = this.getOptionalStringSetting(type, "cliPathOverride");
11900
+ if (override) return override;
11901
+ return fallback || this.providers.get(type)?.cli || null;
11902
+ }
11903
+ getIdePathCandidates(type, fallback) {
11904
+ const override = this.getOptionalStringSetting(type, "appPathOverride");
11905
+ if (override) return [override];
11906
+ if (fallback && fallback.length > 0) return fallback;
11907
+ const osPaths = this.providers.get(type)?.paths?.[process.platform];
11908
+ return Array.isArray(osPaths) ? [...osPaths] : [];
11909
+ }
11910
+ setProviderAvailability(type, state) {
11911
+ this.providerAvailability.set(type, {
11912
+ installed: !!state.installed,
11913
+ detectedPath: state.detectedPath ?? null
11914
+ });
11915
+ }
11916
+ setCliDetectionResults(results, replace = true) {
11917
+ if (replace) {
11918
+ for (const provider of this.providers.values()) {
11919
+ if (provider.category === "cli" || provider.category === "acp") {
11920
+ this.providerAvailability.set(provider.type, { installed: false, detectedPath: null });
11921
+ }
11922
+ }
11923
+ }
11924
+ for (const result of results) {
11925
+ this.setProviderAvailability(result.id, {
11926
+ installed: !!result.installed,
11927
+ detectedPath: result.path || null
11928
+ });
11929
+ }
11930
+ }
11931
+ setIdeDetectionResults(results, replace = true) {
11932
+ if (replace) {
11933
+ for (const provider of this.providers.values()) {
11934
+ if (provider.category === "ide") {
11935
+ this.providerAvailability.set(provider.type, { installed: false, detectedPath: null });
11936
+ }
11937
+ }
11938
+ }
11939
+ for (const result of results) {
11940
+ this.setProviderAvailability(result.id, {
11941
+ installed: !!result.installed,
11942
+ detectedPath: result.cliCommand || result.path || null
11943
+ });
11944
+ }
11945
+ }
11946
+ getAvailableProviderInfos() {
11947
+ return this.getAll().map((provider) => {
11948
+ const availability = this.providerAvailability.get(provider.type);
11949
+ return {
11950
+ ...provider,
11951
+ ...availability ? {
11952
+ installed: availability.installed,
11953
+ detectedPath: availability.detectedPath
11954
+ } : {}
11955
+ };
11956
+ });
11957
+ }
11524
11958
  /**
11525
11959
  * Register IDE providers to core/detector registry
11526
11960
  * → Enables detectIDEs() to detect provider.js-based IDEs
@@ -11595,8 +12029,8 @@ var ProviderLoader = class _ProviderLoader {
11595
12029
  resolved._resolvedScriptDir = entry.scriptDir;
11596
12030
  resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
11597
12031
  if (providerDir) {
11598
- const fullDir = path10.join(providerDir, entry.scriptDir);
11599
- resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
12032
+ const fullDir = path12.join(providerDir, entry.scriptDir);
12033
+ resolved._resolvedScriptsPath = fs6.existsSync(path12.join(fullDir, "scripts.js")) ? path12.join(fullDir, "scripts.js") : fullDir;
11600
12034
  }
11601
12035
  matched = true;
11602
12036
  }
@@ -11611,8 +12045,8 @@ var ProviderLoader = class _ProviderLoader {
11611
12045
  resolved._resolvedScriptDir = base.defaultScriptDir;
11612
12046
  resolved._resolvedScriptsSource = "defaultScriptDir:version_miss";
11613
12047
  if (providerDir) {
11614
- const fullDir = path10.join(providerDir, base.defaultScriptDir);
11615
- resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
12048
+ const fullDir = path12.join(providerDir, base.defaultScriptDir);
12049
+ resolved._resolvedScriptsPath = fs6.existsSync(path12.join(fullDir, "scripts.js")) ? path12.join(fullDir, "scripts.js") : fullDir;
11616
12050
  }
11617
12051
  }
11618
12052
  resolved._versionWarning = `Version ${currentVersion} not in compatibility matrix. Using default scripts.`;
@@ -11629,8 +12063,8 @@ var ProviderLoader = class _ProviderLoader {
11629
12063
  resolved._resolvedScriptDir = dirOverride;
11630
12064
  resolved._resolvedScriptsSource = `versions:${range}`;
11631
12065
  if (providerDir) {
11632
- const fullDir = path10.join(providerDir, dirOverride);
11633
- resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
12066
+ const fullDir = path12.join(providerDir, dirOverride);
12067
+ resolved._resolvedScriptsPath = fs6.existsSync(path12.join(fullDir, "scripts.js")) ? path12.join(fullDir, "scripts.js") : fullDir;
11634
12068
  }
11635
12069
  }
11636
12070
  } else if (override.scripts) {
@@ -11646,8 +12080,8 @@ var ProviderLoader = class _ProviderLoader {
11646
12080
  resolved._resolvedScriptDir = base.defaultScriptDir;
11647
12081
  resolved._resolvedScriptsSource = "defaultScriptDir:no_version";
11648
12082
  if (providerDir) {
11649
- const fullDir = path10.join(providerDir, base.defaultScriptDir);
11650
- resolved._resolvedScriptsPath = fs6.existsSync(path10.join(fullDir, "scripts.js")) ? path10.join(fullDir, "scripts.js") : fullDir;
12083
+ const fullDir = path12.join(providerDir, base.defaultScriptDir);
12084
+ resolved._resolvedScriptsPath = fs6.existsSync(path12.join(fullDir, "scripts.js")) ? path12.join(fullDir, "scripts.js") : fullDir;
11651
12085
  }
11652
12086
  }
11653
12087
  }
@@ -11672,14 +12106,14 @@ var ProviderLoader = class _ProviderLoader {
11672
12106
  this.log(` [loadScriptsFromDir] ${type}: providerDir not found`);
11673
12107
  return null;
11674
12108
  }
11675
- const dir = path10.join(providerDir, scriptDir);
12109
+ const dir = path12.join(providerDir, scriptDir);
11676
12110
  if (!fs6.existsSync(dir)) {
11677
12111
  this.log(` [loadScriptsFromDir] ${type}: dir not found: ${dir}`);
11678
12112
  return null;
11679
12113
  }
11680
12114
  const cached = this.scriptsCache.get(dir);
11681
12115
  if (cached) return cached;
11682
- const scriptsJs = path10.join(dir, "scripts.js");
12116
+ const scriptsJs = path12.join(dir, "scripts.js");
11683
12117
  if (fs6.existsSync(scriptsJs)) {
11684
12118
  try {
11685
12119
  delete require.cache[require.resolve(scriptsJs)];
@@ -11721,7 +12155,7 @@ var ProviderLoader = class _ProviderLoader {
11721
12155
  return;
11722
12156
  }
11723
12157
  if (filePath.endsWith(".js") || filePath.endsWith(".json")) {
11724
- this.log(`File changed: ${path10.basename(filePath)}, reloading...`);
12158
+ this.log(`File changed: ${path12.basename(filePath)}, reloading...`);
11725
12159
  this.reload();
11726
12160
  }
11727
12161
  };
@@ -11776,7 +12210,7 @@ var ProviderLoader = class _ProviderLoader {
11776
12210
  }
11777
12211
  const https = require("https");
11778
12212
  const { execSync: execSync7 } = require("child_process");
11779
- const metaPath = path10.join(this.upstreamDir, _ProviderLoader.META_FILE);
12213
+ const metaPath = path12.join(this.upstreamDir, _ProviderLoader.META_FILE);
11780
12214
  let prevEtag = "";
11781
12215
  let prevTimestamp = 0;
11782
12216
  try {
@@ -11793,7 +12227,7 @@ var ProviderLoader = class _ProviderLoader {
11793
12227
  return { updated: false };
11794
12228
  }
11795
12229
  try {
11796
- const etag = await new Promise((resolve9, reject) => {
12230
+ const etag = await new Promise((resolve12, reject) => {
11797
12231
  const options = {
11798
12232
  method: "HEAD",
11799
12233
  hostname: "github.com",
@@ -11811,7 +12245,7 @@ var ProviderLoader = class _ProviderLoader {
11811
12245
  headers: { "User-Agent": "adhdev-launcher" },
11812
12246
  timeout: 1e4
11813
12247
  }, (res2) => {
11814
- resolve9(res2.headers.etag || res2.headers["last-modified"] || "");
12248
+ resolve12(res2.headers.etag || res2.headers["last-modified"] || "");
11815
12249
  });
11816
12250
  req2.on("error", reject);
11817
12251
  req2.on("timeout", () => {
@@ -11820,7 +12254,7 @@ var ProviderLoader = class _ProviderLoader {
11820
12254
  });
11821
12255
  req2.end();
11822
12256
  } else {
11823
- resolve9(res.headers.etag || res.headers["last-modified"] || "");
12257
+ resolve12(res.headers.etag || res.headers["last-modified"] || "");
11824
12258
  }
11825
12259
  });
11826
12260
  req.on("error", reject);
@@ -11836,17 +12270,17 @@ var ProviderLoader = class _ProviderLoader {
11836
12270
  return { updated: false };
11837
12271
  }
11838
12272
  this.log("Downloading latest providers from GitHub...");
11839
- const tmpTar = path10.join(os11.tmpdir(), `adhdev-providers-${Date.now()}.tar.gz`);
11840
- const tmpExtract = path10.join(os11.tmpdir(), `adhdev-providers-extract-${Date.now()}`);
12273
+ const tmpTar = path12.join(os11.tmpdir(), `adhdev-providers-${Date.now()}.tar.gz`);
12274
+ const tmpExtract = path12.join(os11.tmpdir(), `adhdev-providers-extract-${Date.now()}`);
11841
12275
  await this.downloadFile(_ProviderLoader.GITHUB_TARBALL_URL, tmpTar);
11842
12276
  fs6.mkdirSync(tmpExtract, { recursive: true });
11843
12277
  execSync7(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 3e4 });
11844
12278
  const extracted = fs6.readdirSync(tmpExtract);
11845
12279
  const rootDir = extracted.find(
11846
- (d) => fs6.statSync(path10.join(tmpExtract, d)).isDirectory() && d.startsWith("adhdev-providers")
12280
+ (d) => fs6.statSync(path12.join(tmpExtract, d)).isDirectory() && d.startsWith("adhdev-providers")
11847
12281
  );
11848
12282
  if (!rootDir) throw new Error("Unexpected tarball structure");
11849
- const sourceDir = path10.join(tmpExtract, rootDir);
12283
+ const sourceDir = path12.join(tmpExtract, rootDir);
11850
12284
  const backupDir = this.upstreamDir + ".bak";
11851
12285
  if (fs6.existsSync(this.upstreamDir)) {
11852
12286
  if (fs6.existsSync(backupDir)) fs6.rmSync(backupDir, { recursive: true, force: true });
@@ -11884,7 +12318,7 @@ var ProviderLoader = class _ProviderLoader {
11884
12318
  downloadFile(url, destPath) {
11885
12319
  const https = require("https");
11886
12320
  const http3 = require("http");
11887
- return new Promise((resolve9, reject) => {
12321
+ return new Promise((resolve12, reject) => {
11888
12322
  const doRequest = (reqUrl, redirectCount = 0) => {
11889
12323
  if (redirectCount > 5) {
11890
12324
  reject(new Error("Too many redirects"));
@@ -11904,7 +12338,7 @@ var ProviderLoader = class _ProviderLoader {
11904
12338
  res.pipe(ws);
11905
12339
  ws.on("finish", () => {
11906
12340
  ws.close();
11907
- resolve9();
12341
+ resolve12();
11908
12342
  });
11909
12343
  ws.on("error", reject);
11910
12344
  });
@@ -11921,8 +12355,8 @@ var ProviderLoader = class _ProviderLoader {
11921
12355
  copyDirRecursive(src, dest) {
11922
12356
  fs6.mkdirSync(dest, { recursive: true });
11923
12357
  for (const entry of fs6.readdirSync(src, { withFileTypes: true })) {
11924
- const srcPath = path10.join(src, entry.name);
11925
- const destPath = path10.join(dest, entry.name);
12358
+ const srcPath = path12.join(src, entry.name);
12359
+ const destPath = path12.join(dest, entry.name);
11926
12360
  if (entry.isDirectory()) {
11927
12361
  this.copyDirRecursive(srcPath, destPath);
11928
12362
  } else {
@@ -11933,7 +12367,7 @@ var ProviderLoader = class _ProviderLoader {
11933
12367
  /** .meta.json save */
11934
12368
  writeMeta(metaPath, etag, timestamp) {
11935
12369
  try {
11936
- fs6.mkdirSync(path10.dirname(metaPath), { recursive: true });
12370
+ fs6.mkdirSync(path12.dirname(metaPath), { recursive: true });
11937
12371
  fs6.writeFileSync(metaPath, JSON.stringify({
11938
12372
  etag,
11939
12373
  timestamp,
@@ -11950,7 +12384,7 @@ var ProviderLoader = class _ProviderLoader {
11950
12384
  const scan = (d) => {
11951
12385
  try {
11952
12386
  for (const entry of fs6.readdirSync(d, { withFileTypes: true })) {
11953
- if (entry.isDirectory()) scan(path10.join(d, entry.name));
12387
+ if (entry.isDirectory()) scan(path12.join(d, entry.name));
11954
12388
  else if (entry.name === "provider.json") count++;
11955
12389
  }
11956
12390
  } catch {
@@ -11964,9 +12398,8 @@ var ProviderLoader = class _ProviderLoader {
11964
12398
  * Get public settings schema for a provider (for dashboard UI rendering)
11965
12399
  */
11966
12400
  getPublicSettings(type) {
11967
- const provider = this.providers.get(type);
11968
- if (!provider?.settings) return [];
11969
- return Object.entries(provider.settings).filter(([, def]) => def.public === true).map(([key, def]) => ({ key, ...def }));
12401
+ const settings = this.getSettingsSchema(type);
12402
+ return Object.entries(settings).filter(([, def]) => def.public === true).map(([key, def]) => ({ key, ...def }));
11970
12403
  }
11971
12404
  /**
11972
12405
  * Get public settings schema for all providers
@@ -11983,8 +12416,7 @@ var ProviderLoader = class _ProviderLoader {
11983
12416
  * Resolved setting value for a provider (default + user override)
11984
12417
  */
11985
12418
  getSettingValue(type, key) {
11986
- const provider = this.providers.get(type);
11987
- const schemaDef = provider?.settings?.[key];
12419
+ const schemaDef = this.getSettingsSchema(type)[key];
11988
12420
  const defaultVal = schemaDef ? schemaDef.default : void 0;
11989
12421
  try {
11990
12422
  const { loadConfig: loadConfig2 } = (init_config(), __toCommonJS(config_exports));
@@ -11999,10 +12431,9 @@ var ProviderLoader = class _ProviderLoader {
11999
12431
  * All resolved settings for a provider (default + user override)
12000
12432
  */
12001
12433
  getSettings(type) {
12002
- const provider = this.providers.get(type);
12003
- if (!provider?.settings) return {};
12434
+ const settings = this.getSettingsSchema(type);
12004
12435
  const result = {};
12005
- for (const [key, def] of Object.entries(provider.settings)) {
12436
+ for (const [key] of Object.entries(settings)) {
12006
12437
  result[key] = this.getSettingValue(type, key);
12007
12438
  }
12008
12439
  return result;
@@ -12011,11 +12442,11 @@ var ProviderLoader = class _ProviderLoader {
12011
12442
  * Save provider setting value (writes to config.json)
12012
12443
  */
12013
12444
  setSetting(type, key, value) {
12014
- const provider = this.providers.get(type);
12015
- const schemaDef = provider?.settings?.[key];
12445
+ const schemaDef = this.getSettingsSchema(type)[key];
12016
12446
  if (!schemaDef) return false;
12017
12447
  if (!schemaDef.public) return false;
12018
12448
  if (schemaDef.type === "boolean" && typeof value !== "boolean") return false;
12449
+ if (schemaDef.type === "string" && typeof value !== "string") return false;
12019
12450
  if (schemaDef.type === "number") {
12020
12451
  if (typeof value !== "number") return false;
12021
12452
  if (schemaDef.min !== void 0 && value < schemaDef.min) return false;
@@ -12036,6 +12467,53 @@ var ProviderLoader = class _ProviderLoader {
12036
12467
  return false;
12037
12468
  }
12038
12469
  }
12470
+ getOptionalStringSetting(type, key) {
12471
+ const value = this.getSettingValue(type, key);
12472
+ if (typeof value !== "string") return null;
12473
+ const trimmed = value.trim();
12474
+ return trimmed ? trimmed : null;
12475
+ }
12476
+ getSettingsSchema(type) {
12477
+ const provider = this.providers.get(type);
12478
+ if (!provider) return {};
12479
+ return {
12480
+ ...this.getSyntheticSettings(type, provider),
12481
+ ...provider.settings || {}
12482
+ };
12483
+ }
12484
+ getSyntheticSettings(type, provider) {
12485
+ const result = {};
12486
+ if ((provider.category === "cli" || provider.category === "acp") && provider.spawn?.command && !provider.settings?.executablePath) {
12487
+ result.executablePath = {
12488
+ type: "string",
12489
+ default: "",
12490
+ public: true,
12491
+ label: "Executable path",
12492
+ description: "Optional absolute path for this provider binary. Leave blank to use the default PATH lookup."
12493
+ };
12494
+ }
12495
+ if (provider.category === "ide") {
12496
+ if (provider.cli && !provider.settings?.cliPathOverride) {
12497
+ result.cliPathOverride = {
12498
+ type: "string",
12499
+ default: "",
12500
+ public: true,
12501
+ label: "CLI path override",
12502
+ description: "Optional absolute path for the IDE CLI launcher. Leave blank to use the detected default."
12503
+ };
12504
+ }
12505
+ if (provider.paths && !provider.settings?.appPathOverride) {
12506
+ result.appPathOverride = {
12507
+ type: "string",
12508
+ default: "",
12509
+ public: true,
12510
+ label: "App path override",
12511
+ description: "Optional absolute path for the IDE app bundle or executable. Leave blank to use the default install locations."
12512
+ };
12513
+ }
12514
+ }
12515
+ return result;
12516
+ }
12039
12517
  // ─── Private ───────────────────────────────────
12040
12518
  /**
12041
12519
  * Find the on-disk directory for a provider by type.
@@ -12049,17 +12527,17 @@ var ProviderLoader = class _ProviderLoader {
12049
12527
  for (const root of searchRoots) {
12050
12528
  if (!fs6.existsSync(root)) continue;
12051
12529
  const candidate = this.getProviderDir(root, cat, type);
12052
- if (fs6.existsSync(path10.join(candidate, "provider.json"))) return candidate;
12053
- const catDir = path10.join(root, cat);
12530
+ if (fs6.existsSync(path12.join(candidate, "provider.json"))) return candidate;
12531
+ const catDir = path12.join(root, cat);
12054
12532
  if (fs6.existsSync(catDir)) {
12055
12533
  try {
12056
12534
  for (const entry of fs6.readdirSync(catDir, { withFileTypes: true })) {
12057
12535
  if (!entry.isDirectory()) continue;
12058
- const jsonPath = path10.join(catDir, entry.name, "provider.json");
12536
+ const jsonPath = path12.join(catDir, entry.name, "provider.json");
12059
12537
  if (fs6.existsSync(jsonPath)) {
12060
12538
  try {
12061
12539
  const data = JSON.parse(fs6.readFileSync(jsonPath, "utf-8"));
12062
- if (data.type === type) return path10.join(catDir, entry.name);
12540
+ if (data.type === type) return path12.join(catDir, entry.name);
12063
12541
  } catch {
12064
12542
  }
12065
12543
  }
@@ -12076,7 +12554,7 @@ var ProviderLoader = class _ProviderLoader {
12076
12554
  * (template substitution is NOT applied here — scripts.js handles that)
12077
12555
  */
12078
12556
  buildScriptWrappersFromDir(dir) {
12079
- const scriptsJs = path10.join(dir, "scripts.js");
12557
+ const scriptsJs = path12.join(dir, "scripts.js");
12080
12558
  if (fs6.existsSync(scriptsJs)) {
12081
12559
  try {
12082
12560
  delete require.cache[require.resolve(scriptsJs)];
@@ -12090,7 +12568,7 @@ var ProviderLoader = class _ProviderLoader {
12090
12568
  for (const file of fs6.readdirSync(dir)) {
12091
12569
  if (!file.endsWith(".js")) continue;
12092
12570
  const scriptName = toCamel(file.replace(".js", ""));
12093
- const filePath = path10.join(dir, file);
12571
+ const filePath = path12.join(dir, file);
12094
12572
  result[scriptName] = (...args) => {
12095
12573
  try {
12096
12574
  let content = fs6.readFileSync(filePath, "utf-8");
@@ -12150,7 +12628,7 @@ var ProviderLoader = class _ProviderLoader {
12150
12628
  }
12151
12629
  const hasJson = entries.some((e) => e.name === "provider.json");
12152
12630
  if (hasJson) {
12153
- const jsonPath = path10.join(d, "provider.json");
12631
+ const jsonPath = path12.join(d, "provider.json");
12154
12632
  try {
12155
12633
  const raw = fs6.readFileSync(jsonPath, "utf-8");
12156
12634
  const mod = JSON.parse(raw);
@@ -12163,7 +12641,7 @@ var ProviderLoader = class _ProviderLoader {
12163
12641
  delete mod.extensionIdPattern_flags;
12164
12642
  }
12165
12643
  const hasCompatibility = Array.isArray(mod.compatibility);
12166
- const scriptsPath = path10.join(d, "scripts.js");
12644
+ const scriptsPath = path12.join(d, "scripts.js");
12167
12645
  if (!hasCompatibility && fs6.existsSync(scriptsPath)) {
12168
12646
  try {
12169
12647
  delete require.cache[require.resolve(scriptsPath)];
@@ -12189,7 +12667,7 @@ var ProviderLoader = class _ProviderLoader {
12189
12667
  if (!entry.isDirectory()) continue;
12190
12668
  if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
12191
12669
  if (excludeDirs && d === dir && excludeDirs.includes(entry.name)) continue;
12192
- scan(path10.join(d, entry.name));
12670
+ scan(path12.join(d, entry.name));
12193
12671
  }
12194
12672
  }
12195
12673
  };
@@ -12285,17 +12763,17 @@ async function findFreePort(ports) {
12285
12763
  throw new Error("No free port found");
12286
12764
  }
12287
12765
  function checkPortFree(port) {
12288
- return new Promise((resolve9) => {
12766
+ return new Promise((resolve12) => {
12289
12767
  const server = net.createServer();
12290
12768
  server.unref();
12291
- server.on("error", () => resolve9(false));
12769
+ server.on("error", () => resolve12(false));
12292
12770
  server.listen(port, "127.0.0.1", () => {
12293
- server.close(() => resolve9(true));
12771
+ server.close(() => resolve12(true));
12294
12772
  });
12295
12773
  });
12296
12774
  }
12297
12775
  async function isCdpActive(port) {
12298
- return new Promise((resolve9) => {
12776
+ return new Promise((resolve12) => {
12299
12777
  const req = require("http").get(`http://127.0.0.1:${port}/json/version`, {
12300
12778
  timeout: 2e3
12301
12779
  }, (res) => {
@@ -12304,16 +12782,16 @@ async function isCdpActive(port) {
12304
12782
  res.on("end", () => {
12305
12783
  try {
12306
12784
  const info = JSON.parse(data);
12307
- resolve9(!!info["WebKit-Version"] || !!info["Browser"]);
12785
+ resolve12(!!info["WebKit-Version"] || !!info["Browser"]);
12308
12786
  } catch {
12309
- resolve9(false);
12787
+ resolve12(false);
12310
12788
  }
12311
12789
  });
12312
12790
  });
12313
- req.on("error", () => resolve9(false));
12791
+ req.on("error", () => resolve12(false));
12314
12792
  req.on("timeout", () => {
12315
12793
  req.destroy();
12316
- resolve9(false);
12794
+ resolve12(false);
12317
12795
  });
12318
12796
  });
12319
12797
  }
@@ -12447,8 +12925,8 @@ function detectCurrentWorkspace(ideId) {
12447
12925
  const appNameMap = getMacAppIdentifiers();
12448
12926
  const appName = appNameMap[ideId];
12449
12927
  if (appName) {
12450
- const storagePath = path11.join(
12451
- process.env.APPDATA || path11.join(os12.homedir(), "AppData", "Roaming"),
12928
+ const storagePath = path13.join(
12929
+ process.env.APPDATA || path13.join(os12.homedir(), "AppData", "Roaming"),
12452
12930
  appName,
12453
12931
  "storage.json"
12454
12932
  );
@@ -12472,7 +12950,7 @@ function detectCurrentWorkspace(ideId) {
12472
12950
  async function launchWithCdp(options = {}) {
12473
12951
  const platform9 = os12.platform();
12474
12952
  let targetIde;
12475
- const ides = await detectIDEs();
12953
+ const ides = await detectIDEs(getProviderLoader());
12476
12954
  if (options.ideId) {
12477
12955
  targetIde = ides.find((i) => i.id === options.ideId && i.installed);
12478
12956
  if (!targetIde) {
@@ -12626,9 +13104,9 @@ init_logger();
12626
13104
 
12627
13105
  // src/logging/command-log.ts
12628
13106
  var fs7 = __toESM(require("fs"));
12629
- var path12 = __toESM(require("path"));
13107
+ var path14 = __toESM(require("path"));
12630
13108
  var os13 = __toESM(require("os"));
12631
- var LOG_DIR2 = process.platform === "win32" ? path12.join(process.env.LOCALAPPDATA || process.env.APPDATA || path12.join(os13.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path12.join(os13.homedir(), "Library", "Logs", "adhdev") : path12.join(os13.homedir(), ".local", "share", "adhdev", "logs");
13109
+ var LOG_DIR2 = process.platform === "win32" ? path14.join(process.env.LOCALAPPDATA || process.env.APPDATA || path14.join(os13.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path14.join(os13.homedir(), "Library", "Logs", "adhdev") : path14.join(os13.homedir(), ".local", "share", "adhdev", "logs");
12632
13110
  var MAX_FILE_SIZE = 5 * 1024 * 1024;
12633
13111
  var MAX_DAYS = 7;
12634
13112
  try {
@@ -12666,13 +13144,13 @@ function getDateStr2() {
12666
13144
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
12667
13145
  }
12668
13146
  var currentDate2 = getDateStr2();
12669
- var currentFile = path12.join(LOG_DIR2, `commands-${currentDate2}.jsonl`);
13147
+ var currentFile = path14.join(LOG_DIR2, `commands-${currentDate2}.jsonl`);
12670
13148
  var writeCount2 = 0;
12671
13149
  function checkRotation() {
12672
13150
  const today = getDateStr2();
12673
13151
  if (today !== currentDate2) {
12674
13152
  currentDate2 = today;
12675
- currentFile = path12.join(LOG_DIR2, `commands-${currentDate2}.jsonl`);
13153
+ currentFile = path14.join(LOG_DIR2, `commands-${currentDate2}.jsonl`);
12676
13154
  cleanOldFiles();
12677
13155
  }
12678
13156
  }
@@ -12686,7 +13164,7 @@ function cleanOldFiles() {
12686
13164
  const dateMatch = file.match(/commands-(\d{4}-\d{2}-\d{2})/);
12687
13165
  if (dateMatch && dateMatch[1] < cutoffStr) {
12688
13166
  try {
12689
- fs7.unlinkSync(path12.join(LOG_DIR2, file));
13167
+ fs7.unlinkSync(path14.join(LOG_DIR2, file));
12690
13168
  } catch {
12691
13169
  }
12692
13170
  }
@@ -12778,12 +13256,15 @@ function buildDetectedIdeInfos(detectedIdes, cdpManagers) {
12778
13256
  }));
12779
13257
  }
12780
13258
  function buildAvailableProviders(providerLoader) {
12781
- return providerLoader.getAll().map((provider) => ({
13259
+ const providers = providerLoader.getAvailableProviderInfos?.() || providerLoader.getAll();
13260
+ return providers.map((provider) => ({
12782
13261
  type: provider.type,
12783
13262
  name: provider.displayName || provider.type,
12784
13263
  displayName: provider.displayName || provider.type,
12785
13264
  icon: provider.icon || "\u{1F4BB}",
12786
- category: provider.category
13265
+ category: provider.category,
13266
+ ...provider.installed !== void 0 ? { installed: provider.installed } : {},
13267
+ ...provider.detectedPath !== void 0 ? { detectedPath: provider.detectedPath } : {}
12787
13268
  }));
12788
13269
  }
12789
13270
  function parseMessageTime(value) {
@@ -12911,13 +13392,13 @@ var import_child_process7 = require("child_process");
12911
13392
  var import_child_process8 = require("child_process");
12912
13393
  var fs8 = __toESM(require("fs"));
12913
13394
  var os15 = __toESM(require("os"));
12914
- var path13 = __toESM(require("path"));
13395
+ var path15 = __toESM(require("path"));
12915
13396
  var UPGRADE_HELPER_ENV = "ADHDEV_DAEMON_UPGRADE_HELPER";
12916
13397
  function getUpgradeLogPath() {
12917
13398
  const home = os15.homedir();
12918
- const dir = path13.join(home, ".adhdev");
13399
+ const dir = path15.join(home, ".adhdev");
12919
13400
  fs8.mkdirSync(dir, { recursive: true });
12920
- return path13.join(dir, "daemon-upgrade.log");
13401
+ return path15.join(dir, "daemon-upgrade.log");
12921
13402
  }
12922
13403
  function appendUpgradeLog(message) {
12923
13404
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
@@ -12950,14 +13431,14 @@ async function waitForPidExit(pid, timeoutMs) {
12950
13431
  while (Date.now() - start < timeoutMs) {
12951
13432
  try {
12952
13433
  process.kill(pid, 0);
12953
- await new Promise((resolve9) => setTimeout(resolve9, 250));
13434
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
12954
13435
  } catch {
12955
13436
  return;
12956
13437
  }
12957
13438
  }
12958
13439
  }
12959
13440
  function stopSessionHostProcesses(appName) {
12960
- const pidFile = path13.join(os15.homedir(), ".adhdev", `${appName}-session-host.pid`);
13441
+ const pidFile = path15.join(os15.homedir(), ".adhdev", `${appName}-session-host.pid`);
12961
13442
  try {
12962
13443
  if (fs8.existsSync(pidFile)) {
12963
13444
  const pid = Number.parseInt(fs8.readFileSync(pidFile, "utf8").trim(), 10);
@@ -12986,7 +13467,7 @@ function stopSessionHostProcesses(appName) {
12986
13467
  }
12987
13468
  }
12988
13469
  function removeDaemonPidFile() {
12989
- const pidFile = path13.join(os15.homedir(), ".adhdev", "daemon.pid");
13470
+ const pidFile = path15.join(os15.homedir(), ".adhdev", "daemon.pid");
12990
13471
  try {
12991
13472
  fs8.unlinkSync(pidFile);
12992
13473
  } catch {
@@ -12997,7 +13478,7 @@ function cleanupStaleGlobalInstallDirs(pkgName) {
12997
13478
  const npmRoot = (0, import_child_process7.execFileSync)(getNpmExecutable(), ["root", "-g"], { encoding: "utf8", ...npmExecOpts }).trim();
12998
13479
  if (!npmRoot) return;
12999
13480
  const npmPrefix = (0, import_child_process7.execFileSync)(getNpmExecutable(), ["prefix", "-g"], { encoding: "utf8", ...npmExecOpts }).trim();
13000
- const binDir = process.platform === "win32" ? npmPrefix : path13.join(npmPrefix, "bin");
13481
+ const binDir = process.platform === "win32" ? npmPrefix : path15.join(npmPrefix, "bin");
13001
13482
  const packageBaseName = pkgName.startsWith("@") ? pkgName.split("/")[1] : pkgName;
13002
13483
  const binNames = /* @__PURE__ */ new Set([packageBaseName]);
13003
13484
  if (pkgName === "@adhdev/daemon-standalone") {
@@ -13005,25 +13486,25 @@ function cleanupStaleGlobalInstallDirs(pkgName) {
13005
13486
  }
13006
13487
  if (pkgName.startsWith("@")) {
13007
13488
  const [scope, name] = pkgName.split("/");
13008
- const scopeDir = path13.join(npmRoot, scope);
13489
+ const scopeDir = path15.join(npmRoot, scope);
13009
13490
  if (!fs8.existsSync(scopeDir)) return;
13010
13491
  for (const entry of fs8.readdirSync(scopeDir)) {
13011
13492
  if (!entry.startsWith(`.${name}-`)) continue;
13012
- fs8.rmSync(path13.join(scopeDir, entry), { recursive: true, force: true });
13013
- appendUpgradeLog(`Removed stale scoped staging dir: ${path13.join(scopeDir, entry)}`);
13493
+ fs8.rmSync(path15.join(scopeDir, entry), { recursive: true, force: true });
13494
+ appendUpgradeLog(`Removed stale scoped staging dir: ${path15.join(scopeDir, entry)}`);
13014
13495
  }
13015
13496
  } else {
13016
13497
  for (const entry of fs8.readdirSync(npmRoot)) {
13017
13498
  if (!entry.startsWith(`.${pkgName}-`)) continue;
13018
- fs8.rmSync(path13.join(npmRoot, entry), { recursive: true, force: true });
13019
- appendUpgradeLog(`Removed stale staging dir: ${path13.join(npmRoot, entry)}`);
13499
+ fs8.rmSync(path15.join(npmRoot, entry), { recursive: true, force: true });
13500
+ appendUpgradeLog(`Removed stale staging dir: ${path15.join(npmRoot, entry)}`);
13020
13501
  }
13021
13502
  }
13022
13503
  if (fs8.existsSync(binDir)) {
13023
13504
  for (const entry of fs8.readdirSync(binDir)) {
13024
13505
  if (![...binNames].some((name) => entry.startsWith(`.${name}-`))) continue;
13025
- fs8.rmSync(path13.join(binDir, entry), { recursive: true, force: true });
13026
- appendUpgradeLog(`Removed stale bin staging entry: ${path13.join(binDir, entry)}`);
13506
+ fs8.rmSync(path15.join(binDir, entry), { recursive: true, force: true });
13507
+ appendUpgradeLog(`Removed stale bin staging entry: ${path15.join(binDir, entry)}`);
13027
13508
  }
13028
13509
  }
13029
13510
  }
@@ -13065,7 +13546,7 @@ async function runDaemonUpgradeHelper(payload) {
13065
13546
  appendUpgradeLog(installOutput.trim());
13066
13547
  }
13067
13548
  if (process.platform === "win32") {
13068
- await new Promise((resolve9) => setTimeout(resolve9, 500));
13549
+ await new Promise((resolve12) => setTimeout(resolve12, 500));
13069
13550
  cleanupStaleGlobalInstallDirs(payload.packageName);
13070
13551
  appendUpgradeLog("Post-install staging cleanup complete");
13071
13552
  }
@@ -13261,6 +13742,15 @@ var DaemonCommandRouter = class {
13261
13742
  const record = await this.deps.sessionHostControl.forceDetachClient(sessionId, clientId);
13262
13743
  return { success: true, record };
13263
13744
  }
13745
+ case "session_host_prune_duplicate_sessions": {
13746
+ if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13747
+ const result = await this.deps.sessionHostControl.pruneDuplicateSessions({
13748
+ providerType: typeof args?.providerType === "string" ? args.providerType : void 0,
13749
+ workspace: typeof args?.workspace === "string" ? args.workspace : void 0,
13750
+ dryRun: args?.dryRun === true
13751
+ });
13752
+ return { success: true, result };
13753
+ }
13264
13754
  case "session_host_acquire_write": {
13265
13755
  if (!this.deps.sessionHostControl) return { success: false, error: "Session host control unavailable" };
13266
13756
  const sessionId = typeof args?.sessionId === "string" ? args.sessionId : "";
@@ -13343,6 +13833,12 @@ var DaemonCommandRouter = class {
13343
13833
  if (!ideType) throw new Error("ideType required");
13344
13834
  const killProcess = args?.killProcess !== false;
13345
13835
  await this.stopIde(ideType, killProcess);
13836
+ try {
13837
+ const results = await detectIDEs(this.deps.providerLoader);
13838
+ this.deps.detectedIdes.value = results;
13839
+ this.deps.providerLoader.setIdeDetectionResults(results, true);
13840
+ } catch {
13841
+ }
13346
13842
  return { success: true, ideType, stopped: true, processKilled: killProcess };
13347
13843
  }
13348
13844
  // ─── IDE restart ───
@@ -13385,6 +13881,12 @@ var DaemonCommandRouter = class {
13385
13881
  }
13386
13882
  }
13387
13883
  this.deps.onIdeConnected?.();
13884
+ try {
13885
+ const results = await detectIDEs(this.deps.providerLoader);
13886
+ this.deps.detectedIdes.value = results;
13887
+ this.deps.providerLoader.setIdeDetectionResults(results, true);
13888
+ } catch {
13889
+ }
13388
13890
  if (result.success && resolvedWorkspace) {
13389
13891
  try {
13390
13892
  const next = appendRecentActivity(loadState(), {
@@ -13412,8 +13914,9 @@ var DaemonCommandRouter = class {
13412
13914
  }
13413
13915
  // ─── Detect IDEs ───
13414
13916
  case "detect_ides": {
13415
- const results = await detectIDEs();
13917
+ const results = await detectIDEs(this.deps.providerLoader);
13416
13918
  this.deps.detectedIdes.value = results;
13919
+ this.deps.providerLoader.setIdeDetectionResults(results, true);
13417
13920
  return { success: true, detectedInfo: results };
13418
13921
  }
13419
13922
  // ─── Set User Name ───
@@ -13833,6 +14336,51 @@ var ProviderStreamAdapter = class {
13833
14336
  isTransportError(reason) {
13834
14337
  return /Session with given id not found/i.test(reason) || /CDP not connected/i.test(reason) || /Target closed/i.test(reason) || /WebSocket not open/i.test(reason) || /not connected/i.test(reason) || /execution context/i.test(reason) || /Cannot find context with specified id/i.test(reason);
13835
14338
  }
14339
+ titlesMatch(actual, expected) {
14340
+ const lhs = actual.trim().toLowerCase();
14341
+ const rhs = expected.trim().toLowerCase();
14342
+ if (!lhs || !rhs) return false;
14343
+ return lhs === rhs || lhs.includes(rhs) || rhs.includes(lhs);
14344
+ }
14345
+ messageCount(state) {
14346
+ return Array.isArray(state?.messages) ? state.messages.length : 0;
14347
+ }
14348
+ lastMessageSignature(state) {
14349
+ const messages = Array.isArray(state?.messages) ? state.messages : [];
14350
+ const last = messages[messages.length - 1];
14351
+ if (!last) return "";
14352
+ return `${last.role || ""}:${String(last.content || "").replace(/\s+/g, " ").trim()}`;
14353
+ }
14354
+ async verifySendOutcome(evaluate, before) {
14355
+ const beforeCount = this.messageCount(before);
14356
+ const beforeSignature = this.lastMessageSignature(before);
14357
+ for (let attempt = 0; attempt < 12; attempt += 1) {
14358
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
14359
+ let state;
14360
+ try {
14361
+ state = await this.readChat(evaluate);
14362
+ } catch {
14363
+ continue;
14364
+ }
14365
+ if (state.status === "waiting_approval") {
14366
+ return true;
14367
+ }
14368
+ const afterCount = this.messageCount(state);
14369
+ const afterSignature = this.lastMessageSignature(state);
14370
+ if (afterCount > beforeCount) return true;
14371
+ if (afterSignature && afterSignature !== beforeSignature) return true;
14372
+ }
14373
+ return false;
14374
+ }
14375
+ async readStableBaselineState(evaluate) {
14376
+ const first = await this.readChat(evaluate);
14377
+ if (this.messageCount(first) > 0 || this.lastMessageSignature(first)) {
14378
+ return first;
14379
+ }
14380
+ await new Promise((resolve12) => setTimeout(resolve12, 150));
14381
+ const second = await this.readChat(evaluate);
14382
+ return this.messageCount(second) >= this.messageCount(first) ? second : first;
14383
+ }
13836
14384
  async readChat(evaluate) {
13837
14385
  const script = this.callScript("readChat");
13838
14386
  if (!script) return this.errorState("readChat script not available");
@@ -13858,6 +14406,9 @@ var ProviderStreamAdapter = class {
13858
14406
  mode: data.mode,
13859
14407
  activeModal: data.activeModal
13860
14408
  };
14409
+ if (typeof data.title === "string" && data.title.trim()) {
14410
+ state.title = data.title.trim();
14411
+ }
13861
14412
  const controlValues = extractProviderControlValues(this.provider.controls, data);
13862
14413
  if (controlValues) state.controlValues = controlValues;
13863
14414
  const effects = normalizeProviderEffects(data);
@@ -13881,6 +14432,12 @@ var ProviderStreamAdapter = class {
13881
14432
  }
13882
14433
  }
13883
14434
  async sendMessage(evaluate, text) {
14435
+ let beforeState = null;
14436
+ try {
14437
+ beforeState = await this.readStableBaselineState(evaluate);
14438
+ } catch {
14439
+ beforeState = null;
14440
+ }
13884
14441
  const params = { message: text };
13885
14442
  const script = this.callScript("sendMessage", params) || this.callScript("sendMessage", text);
13886
14443
  if (!script) throw new Error(`[${this.agentName}] sendMessage script not available`);
@@ -13898,7 +14455,9 @@ var ProviderStreamAdapter = class {
13898
14455
  }
13899
14456
  if (parsed && typeof parsed === "object") {
13900
14457
  if (parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true) {
13901
- return;
14458
+ const verified = await this.verifySendOutcome(evaluate, beforeState);
14459
+ if (verified) return;
14460
+ throw new Error(`[${this.agentName}] sendMessage was not observed in chat state`);
13902
14461
  }
13903
14462
  if (typeof parsed.error === "string" && parsed.error.trim()) {
13904
14463
  throw new Error(`[${this.agentName}] sendMessage failed: ${parsed.error}`);
@@ -13909,7 +14468,15 @@ var ProviderStreamAdapter = class {
13909
14468
  async resolveAction(evaluate, action, button) {
13910
14469
  const script = this.callScript("resolveAction", { action, button });
13911
14470
  if (!script) return false;
13912
- return await evaluate(script) === true;
14471
+ const result = await evaluate(script);
14472
+ const parsed = this.parseMaybeJson(result);
14473
+ if (parsed === true) return true;
14474
+ if (typeof parsed === "string") {
14475
+ const normalized = parsed.trim().toLowerCase();
14476
+ return normalized === "ok" || normalized === "success" || normalized === "true" || normalized === "resolved" || normalized === "approved" || normalized === "rejected";
14477
+ }
14478
+ if (!parsed || typeof parsed !== "object") return false;
14479
+ return parsed.resolved === true || parsed.success === true || parsed.ok === true || parsed.found === true;
13913
14480
  }
13914
14481
  async newSession(evaluate) {
13915
14482
  const script = this.callScript("newSession");
@@ -13927,7 +14494,10 @@ var ProviderStreamAdapter = class {
13927
14494
  const raw = await evaluate(script, 1e4);
13928
14495
  const data = typeof raw === "string" ? JSON.parse(raw) : raw;
13929
14496
  if (data?.error) return [];
13930
- return Array.isArray(data) ? data : [];
14497
+ if (Array.isArray(data)) return data;
14498
+ if (Array.isArray(data?.sessions)) return data.sessions;
14499
+ if (Array.isArray(data?.chats)) return data.chats;
14500
+ return [];
13931
14501
  } catch {
13932
14502
  return [];
13933
14503
  }
@@ -13935,7 +14505,24 @@ var ProviderStreamAdapter = class {
13935
14505
  async switchSession(evaluate, sessionId) {
13936
14506
  const script = this.callScript("switchSession", sessionId);
13937
14507
  if (!script) return false;
13938
- return await evaluate(script, 1e4) === true;
14508
+ const raw = await evaluate(script, 1e4);
14509
+ const data = this.parseMaybeJson(raw);
14510
+ if (data === true) return true;
14511
+ if (typeof data === "string") {
14512
+ const normalized = data.trim().toLowerCase();
14513
+ return normalized === "true" || normalized === "ok" || normalized === "switched" || normalized === "success";
14514
+ }
14515
+ if (data && typeof data === "object") {
14516
+ if (data.switched === true || data.success === true || data.ok === true) return true;
14517
+ if (typeof data.error === "string" && data.error.trim()) return false;
14518
+ }
14519
+ for (let attempt = 0; attempt < 6; attempt += 1) {
14520
+ await new Promise((resolve12) => setTimeout(resolve12, 250));
14521
+ const state = await this.readChat(evaluate);
14522
+ const title = typeof state.title === "string" ? state.title : "";
14523
+ if (this.titlesMatch(title, sessionId)) return true;
14524
+ }
14525
+ return false;
13939
14526
  }
13940
14527
  async focusEditor(evaluate) {
13941
14528
  const script = this.callScript("focusEditor");
@@ -14141,7 +14728,7 @@ var DaemonAgentStreamManager = class {
14141
14728
  return false;
14142
14729
  }
14143
14730
  }
14144
- async resolveSessionAction(cdp, sessionId, action) {
14731
+ async resolveSessionAction(cdp, sessionId, action, button) {
14145
14732
  await this.ensureSessionPanelOpen(sessionId);
14146
14733
  const target = this.getSessionTarget(sessionId);
14147
14734
  if (!target?.parentSessionId) return false;
@@ -14151,7 +14738,7 @@ var DaemonAgentStreamManager = class {
14151
14738
  if (!agent) return false;
14152
14739
  try {
14153
14740
  const evaluate = (expr, timeout) => cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
14154
- return await agent.adapter.resolveAction(evaluate, action);
14741
+ return await agent.adapter.resolveAction(evaluate, action, button);
14155
14742
  } catch (e) {
14156
14743
  this.logFn(`[AgentStream] resolveAction(${sessionId}) error: ${e.message}`);
14157
14744
  return false;
@@ -14625,11 +15212,11 @@ var ProviderInstanceManager = class {
14625
15212
 
14626
15213
  // src/providers/version-archive.ts
14627
15214
  var fs10 = __toESM(require("fs"));
14628
- var path14 = __toESM(require("path"));
15215
+ var path16 = __toESM(require("path"));
14629
15216
  var os16 = __toESM(require("os"));
14630
15217
  var import_child_process9 = require("child_process");
14631
15218
  var import_os3 = require("os");
14632
- var ARCHIVE_PATH = path14.join(os16.homedir(), ".adhdev", "version-history.json");
15219
+ var ARCHIVE_PATH = path16.join(os16.homedir(), ".adhdev", "version-history.json");
14633
15220
  var MAX_ENTRIES_PER_PROVIDER = 20;
14634
15221
  var VersionArchive = class {
14635
15222
  history = {};
@@ -14676,7 +15263,7 @@ var VersionArchive = class {
14676
15263
  }
14677
15264
  save() {
14678
15265
  try {
14679
- fs10.mkdirSync(path14.dirname(ARCHIVE_PATH), { recursive: true });
15266
+ fs10.mkdirSync(path16.dirname(ARCHIVE_PATH), { recursive: true });
14680
15267
  fs10.writeFileSync(ARCHIVE_PATH, JSON.stringify(this.history, null, 2));
14681
15268
  } catch {
14682
15269
  }
@@ -14717,7 +15304,7 @@ function checkPathExists2(paths) {
14717
15304
  for (const p of paths) {
14718
15305
  if (p.includes("*")) {
14719
15306
  const home = os16.homedir();
14720
- const resolved = p.replace(/\*/g, home.split(path14.sep).pop() || "");
15307
+ const resolved = p.replace(/\*/g, home.split(path16.sep).pop() || "");
14721
15308
  if (fs10.existsSync(resolved)) return resolved;
14722
15309
  } else {
14723
15310
  if (fs10.existsSync(p)) return p;
@@ -14727,7 +15314,7 @@ function checkPathExists2(paths) {
14727
15314
  }
14728
15315
  function getMacAppVersion(appPath) {
14729
15316
  if ((0, import_os3.platform)() !== "darwin" || !appPath.endsWith(".app")) return null;
14730
- const plistPath = path14.join(appPath, "Contents", "Info.plist");
15317
+ const plistPath = path16.join(appPath, "Contents", "Info.plist");
14731
15318
  if (!fs10.existsSync(plistPath)) return null;
14732
15319
  const raw = runCommand(`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "${plistPath}"`);
14733
15320
  return raw || null;
@@ -14754,7 +15341,7 @@ async function detectAllVersions(loader, archive) {
14754
15341
  const cliBin = provider.cli ? findBinary2(provider.cli) : null;
14755
15342
  let resolvedBin = cliBin;
14756
15343
  if (!resolvedBin && appPath && currentOs === "darwin") {
14757
- const bundled = path14.join(appPath, "Contents", "Resources", "app", "bin", provider.cli || "");
15344
+ const bundled = path16.join(appPath, "Contents", "Resources", "app", "bin", provider.cli || "");
14758
15345
  if (provider.cli && fs10.existsSync(bundled)) resolvedBin = bundled;
14759
15346
  }
14760
15347
  info.installed = !!(appPath || resolvedBin);
@@ -14795,7 +15382,7 @@ async function detectAllVersions(loader, archive) {
14795
15382
  // src/daemon/dev-server.ts
14796
15383
  var http2 = __toESM(require("http"));
14797
15384
  var fs14 = __toESM(require("fs"));
14798
- var path18 = __toESM(require("path"));
15385
+ var path20 = __toESM(require("path"));
14799
15386
 
14800
15387
  // src/daemon/scaffold-template.ts
14801
15388
  function generateFiles(type, name, category, opts = {}) {
@@ -15131,7 +15718,7 @@ init_logger();
15131
15718
 
15132
15719
  // src/daemon/dev-cdp-handlers.ts
15133
15720
  var fs11 = __toESM(require("fs"));
15134
- var path15 = __toESM(require("path"));
15721
+ var path17 = __toESM(require("path"));
15135
15722
  init_logger();
15136
15723
  async function handleCdpEvaluate(ctx, req, res) {
15137
15724
  const body = await ctx.readBody(req);
@@ -15310,17 +15897,17 @@ async function handleScriptHints(ctx, type, _req, res) {
15310
15897
  return;
15311
15898
  }
15312
15899
  let scriptsPath = "";
15313
- const directScripts = path15.join(dir, "scripts.js");
15900
+ const directScripts = path17.join(dir, "scripts.js");
15314
15901
  if (fs11.existsSync(directScripts)) {
15315
15902
  scriptsPath = directScripts;
15316
15903
  } else {
15317
- const scriptsDir = path15.join(dir, "scripts");
15904
+ const scriptsDir = path17.join(dir, "scripts");
15318
15905
  if (fs11.existsSync(scriptsDir)) {
15319
15906
  const versions = fs11.readdirSync(scriptsDir).filter((d) => {
15320
- return fs11.statSync(path15.join(scriptsDir, d)).isDirectory();
15907
+ return fs11.statSync(path17.join(scriptsDir, d)).isDirectory();
15321
15908
  }).sort().reverse();
15322
15909
  for (const ver of versions) {
15323
- const p = path15.join(scriptsDir, ver, "scripts.js");
15910
+ const p = path17.join(scriptsDir, ver, "scripts.js");
15324
15911
  if (fs11.existsSync(p)) {
15325
15912
  scriptsPath = p;
15326
15913
  break;
@@ -16139,7 +16726,7 @@ async function handleDomContext(ctx, type, req, res) {
16139
16726
 
16140
16727
  // src/daemon/dev-cli-debug.ts
16141
16728
  var fs12 = __toESM(require("fs"));
16142
- var path16 = __toESM(require("path"));
16729
+ var path18 = __toESM(require("path"));
16143
16730
  function slugifyFixtureName(value) {
16144
16731
  const normalized = String(value || "").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
16145
16732
  return normalized || `fixture-${Date.now()}`;
@@ -16149,11 +16736,11 @@ function getCliFixtureDir(ctx, type) {
16149
16736
  if (!providerDir) {
16150
16737
  throw new Error(`Provider directory not found for '${type}'`);
16151
16738
  }
16152
- return path16.join(providerDir, "fixtures");
16739
+ return path18.join(providerDir, "fixtures");
16153
16740
  }
16154
16741
  function readCliFixture(ctx, type, name) {
16155
16742
  const fixtureDir = getCliFixtureDir(ctx, type);
16156
- const filePath = path16.join(fixtureDir, `${name}.json`);
16743
+ const filePath = path18.join(fixtureDir, `${name}.json`);
16157
16744
  if (!fs12.existsSync(filePath)) {
16158
16745
  throw new Error(`Fixture not found: ${filePath}`);
16159
16746
  }
@@ -16313,7 +16900,7 @@ function getCliTargetBundle(ctx, type, instanceId) {
16313
16900
  return { target, instance, adapter };
16314
16901
  }
16315
16902
  function sleep(ms) {
16316
- return new Promise((resolve9) => setTimeout(resolve9, ms));
16903
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
16317
16904
  }
16318
16905
  async function waitForCliReady(ctx, type, instanceId, timeoutMs) {
16319
16906
  const startedAt = Date.now();
@@ -16912,7 +17499,7 @@ async function handleCliFixtureCapture(ctx, req, res) {
16912
17499
  },
16913
17500
  notes: typeof body?.notes === "string" ? body.notes : void 0
16914
17501
  };
16915
- const filePath = path16.join(fixtureDir, `${name}.json`);
17502
+ const filePath = path18.join(fixtureDir, `${name}.json`);
16916
17503
  fs12.writeFileSync(filePath, JSON.stringify(fixture, null, 2));
16917
17504
  ctx.json(res, 200, {
16918
17505
  saved: true,
@@ -16936,7 +17523,7 @@ async function handleCliFixtureList(ctx, type, _req, res) {
16936
17523
  return;
16937
17524
  }
16938
17525
  const fixtures = fs12.readdirSync(fixtureDir).filter((file) => file.endsWith(".json")).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" })).map((file) => {
16939
- const fullPath = path16.join(fixtureDir, file);
17526
+ const fullPath = path18.join(fixtureDir, file);
16940
17527
  try {
16941
17528
  const raw = JSON.parse(fs12.readFileSync(fullPath, "utf-8"));
16942
17529
  return {
@@ -17072,7 +17659,7 @@ async function handleCliRaw(ctx, req, res) {
17072
17659
 
17073
17660
  // src/daemon/dev-auto-implement.ts
17074
17661
  var fs13 = __toESM(require("fs"));
17075
- var path17 = __toESM(require("path"));
17662
+ var path19 = __toESM(require("path"));
17076
17663
  var os17 = __toESM(require("os"));
17077
17664
  function getAutoImplPid(ctx) {
17078
17665
  const proc = ctx.autoImplProcess;
@@ -17112,22 +17699,22 @@ function getLatestScriptVersionDir(scriptsDir) {
17112
17699
  if (!fs13.existsSync(scriptsDir)) return null;
17113
17700
  const versions = fs13.readdirSync(scriptsDir).filter((d) => {
17114
17701
  try {
17115
- return fs13.statSync(path17.join(scriptsDir, d)).isDirectory();
17702
+ return fs13.statSync(path19.join(scriptsDir, d)).isDirectory();
17116
17703
  } catch {
17117
17704
  return false;
17118
17705
  }
17119
17706
  }).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
17120
17707
  if (versions.length === 0) return null;
17121
- return path17.join(scriptsDir, versions[0]);
17708
+ return path19.join(scriptsDir, versions[0]);
17122
17709
  }
17123
17710
  function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
17124
- const canonicalUserDir = path17.resolve(ctx.providerLoader.getUserProviderDir(category, type));
17125
- const desiredDir = requestedDir ? path17.resolve(requestedDir) : canonicalUserDir;
17126
- const upstreamRoot = path17.resolve(ctx.providerLoader.getUpstreamDir());
17127
- if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path17.sep}`)) {
17711
+ const canonicalUserDir = path19.resolve(ctx.providerLoader.getUserProviderDir(category, type));
17712
+ const desiredDir = requestedDir ? path19.resolve(requestedDir) : canonicalUserDir;
17713
+ const upstreamRoot = path19.resolve(ctx.providerLoader.getUpstreamDir());
17714
+ if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path19.sep}`)) {
17128
17715
  return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
17129
17716
  }
17130
- if (path17.basename(desiredDir) !== type) {
17717
+ if (path19.basename(desiredDir) !== type) {
17131
17718
  return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
17132
17719
  }
17133
17720
  const sourceDir = ctx.findProviderDir(type);
@@ -17135,11 +17722,11 @@ function resolveAutoImplWritableProviderDir(ctx, category, type, requestedDir) {
17135
17722
  return { dir: null, reason: `Provider source directory not found for '${type}'` };
17136
17723
  }
17137
17724
  if (!fs13.existsSync(desiredDir)) {
17138
- fs13.mkdirSync(path17.dirname(desiredDir), { recursive: true });
17725
+ fs13.mkdirSync(path19.dirname(desiredDir), { recursive: true });
17139
17726
  fs13.cpSync(sourceDir, desiredDir, { recursive: true });
17140
17727
  ctx.log(`Auto-implement writable copy created: ${desiredDir}`);
17141
17728
  }
17142
- const providerJson = path17.join(desiredDir, "provider.json");
17729
+ const providerJson = path19.join(desiredDir, "provider.json");
17143
17730
  if (!fs13.existsSync(providerJson)) {
17144
17731
  return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
17145
17732
  }
@@ -17162,13 +17749,13 @@ function loadAutoImplReferenceScripts(ctx, referenceType) {
17162
17749
  const refDir = ctx.findProviderDir(referenceType);
17163
17750
  if (!refDir || !fs13.existsSync(refDir)) return {};
17164
17751
  const referenceScripts = {};
17165
- const scriptsDir = path17.join(refDir, "scripts");
17752
+ const scriptsDir = path19.join(refDir, "scripts");
17166
17753
  const latestDir = getLatestScriptVersionDir(scriptsDir);
17167
17754
  if (!latestDir) return referenceScripts;
17168
17755
  for (const file of fs13.readdirSync(latestDir)) {
17169
17756
  if (!file.endsWith(".js")) continue;
17170
17757
  try {
17171
- referenceScripts[file] = fs13.readFileSync(path17.join(latestDir, file), "utf-8");
17758
+ referenceScripts[file] = fs13.readFileSync(path19.join(latestDir, file), "utf-8");
17172
17759
  } catch {
17173
17760
  }
17174
17761
  }
@@ -17276,9 +17863,9 @@ async function handleAutoImplement(ctx, type, req, res) {
17276
17863
  });
17277
17864
  const referenceScripts = loadAutoImplReferenceScripts(ctx, resolvedReference);
17278
17865
  const prompt = buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domContext, referenceScripts, comment, resolvedReference, verification);
17279
- const tmpDir = path17.join(os17.tmpdir(), "adhdev-autoimpl");
17866
+ const tmpDir = path19.join(os17.tmpdir(), "adhdev-autoimpl");
17280
17867
  if (!fs13.existsSync(tmpDir)) fs13.mkdirSync(tmpDir, { recursive: true });
17281
- const promptFile = path17.join(tmpDir, `prompt-${type}-${Date.now()}.md`);
17868
+ const promptFile = path19.join(tmpDir, `prompt-${type}-${Date.now()}.md`);
17282
17869
  fs13.writeFileSync(promptFile, prompt, "utf-8");
17283
17870
  ctx.log(`Auto-implement prompt written to ${promptFile} (${prompt.length} chars)`);
17284
17871
  const agentProvider = ctx.providerLoader.resolve(agent) || ctx.providerLoader.getMeta(agent);
@@ -17705,7 +18292,7 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
17705
18292
  setMode: "set_mode.js"
17706
18293
  };
17707
18294
  const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
17708
- const scriptsDir = path17.join(providerDir, "scripts");
18295
+ const scriptsDir = path19.join(providerDir, "scripts");
17709
18296
  const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
17710
18297
  if (latestScriptsDir) {
17711
18298
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -17716,7 +18303,7 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
17716
18303
  for (const file of fs13.readdirSync(latestScriptsDir)) {
17717
18304
  if (file.endsWith(".js") && targetFileNames.has(file)) {
17718
18305
  try {
17719
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
18306
+ const content = fs13.readFileSync(path19.join(latestScriptsDir, file), "utf-8");
17720
18307
  lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
17721
18308
  lines.push("```javascript");
17722
18309
  lines.push(content);
@@ -17733,7 +18320,7 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
17733
18320
  lines.push("");
17734
18321
  for (const file of refFiles) {
17735
18322
  try {
17736
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
18323
+ const content = fs13.readFileSync(path19.join(latestScriptsDir, file), "utf-8");
17737
18324
  lines.push(`### \`${file}\` \u{1F512}`);
17738
18325
  lines.push("```javascript");
17739
18326
  lines.push(content);
@@ -17774,10 +18361,10 @@ function buildAutoImplPrompt(ctx, type, provider, providerDir, functions, domCon
17774
18361
  lines.push("");
17775
18362
  }
17776
18363
  }
17777
- const docsDir = path17.join(providerDir, "../../docs");
18364
+ const docsDir = path19.join(providerDir, "../../docs");
17778
18365
  const loadGuide = (name) => {
17779
18366
  try {
17780
- const p = path17.join(docsDir, name);
18367
+ const p = path19.join(docsDir, name);
17781
18368
  if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
17782
18369
  } catch {
17783
18370
  }
@@ -18012,7 +18599,7 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
18012
18599
  parseApproval: "parse_approval.js"
18013
18600
  };
18014
18601
  const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
18015
- const scriptsDir = path17.join(providerDir, "scripts");
18602
+ const scriptsDir = path19.join(providerDir, "scripts");
18016
18603
  const latestScriptsDir = getLatestScriptVersionDir(scriptsDir);
18017
18604
  if (latestScriptsDir) {
18018
18605
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -18024,7 +18611,7 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
18024
18611
  if (!file.endsWith(".js")) continue;
18025
18612
  if (!targetFileNames.has(file)) continue;
18026
18613
  try {
18027
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
18614
+ const content = fs13.readFileSync(path19.join(latestScriptsDir, file), "utf-8");
18028
18615
  lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
18029
18616
  lines.push("```javascript");
18030
18617
  lines.push(content);
@@ -18040,7 +18627,7 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
18040
18627
  lines.push("");
18041
18628
  for (const file of refFiles) {
18042
18629
  try {
18043
- const content = fs13.readFileSync(path17.join(latestScriptsDir, file), "utf-8");
18630
+ const content = fs13.readFileSync(path19.join(latestScriptsDir, file), "utf-8");
18044
18631
  lines.push(`### \`${file}\` \u{1F512}`);
18045
18632
  lines.push("```javascript");
18046
18633
  lines.push(content);
@@ -18073,10 +18660,10 @@ function buildCliAutoImplPrompt(ctx, type, provider, providerDir, functions, ref
18073
18660
  lines.push("");
18074
18661
  }
18075
18662
  }
18076
- const docsDir = path17.join(providerDir, "../../docs");
18663
+ const docsDir = path19.join(providerDir, "../../docs");
18077
18664
  const loadGuide = (name) => {
18078
18665
  try {
18079
- const p = path17.join(docsDir, name);
18666
+ const p = path19.join(docsDir, name);
18080
18667
  if (fs13.existsSync(p)) return fs13.readFileSync(p, "utf-8");
18081
18668
  } catch {
18082
18669
  }
@@ -18487,8 +19074,8 @@ var DevServer = class _DevServer {
18487
19074
  }
18488
19075
  getEndpointList() {
18489
19076
  return this.routes.map((r) => {
18490
- const path19 = typeof r.pattern === "string" ? r.pattern : r.pattern.source.replace(/\\\//g, "/").replace(/\(\[.*?\]\+\)/g, ":type").replace(/[\^$]/g, "");
18491
- return `${r.method.padEnd(5)} ${path19}`;
19077
+ const path21 = typeof r.pattern === "string" ? r.pattern : r.pattern.source.replace(/\\\//g, "/").replace(/\(\[.*?\]\+\)/g, ":type").replace(/[\^$]/g, "");
19078
+ return `${r.method.padEnd(5)} ${path21}`;
18492
19079
  });
18493
19080
  }
18494
19081
  async start(port = DEV_SERVER_PORT) {
@@ -18519,15 +19106,15 @@ var DevServer = class _DevServer {
18519
19106
  this.json(res, 500, { error: e.message });
18520
19107
  }
18521
19108
  });
18522
- return new Promise((resolve9, reject) => {
19109
+ return new Promise((resolve12, reject) => {
18523
19110
  this.server.listen(port, "127.0.0.1", () => {
18524
19111
  this.log(`Dev server listening on http://127.0.0.1:${port}`);
18525
- resolve9();
19112
+ resolve12();
18526
19113
  });
18527
19114
  this.server.on("error", (e) => {
18528
19115
  if (e.code === "EADDRINUSE") {
18529
19116
  this.log(`Port ${port} in use, skipping dev server`);
18530
- resolve9();
19117
+ resolve12();
18531
19118
  } else {
18532
19119
  reject(e);
18533
19120
  }
@@ -18610,20 +19197,20 @@ var DevServer = class _DevServer {
18610
19197
  child.stderr?.on("data", (d) => {
18611
19198
  stderr += d.toString().slice(0, 2e3);
18612
19199
  });
18613
- await new Promise((resolve9) => {
19200
+ await new Promise((resolve12) => {
18614
19201
  const timer = setTimeout(() => {
18615
19202
  child.kill();
18616
- resolve9();
19203
+ resolve12();
18617
19204
  }, 3e3);
18618
19205
  child.on("exit", () => {
18619
19206
  clearTimeout(timer);
18620
- resolve9();
19207
+ resolve12();
18621
19208
  });
18622
19209
  child.stdout?.once("data", () => {
18623
19210
  setTimeout(() => {
18624
19211
  child.kill();
18625
19212
  clearTimeout(timer);
18626
- resolve9();
19213
+ resolve12();
18627
19214
  }, 500);
18628
19215
  });
18629
19216
  });
@@ -18770,12 +19357,12 @@ var DevServer = class _DevServer {
18770
19357
  // ─── DevConsole SPA ───
18771
19358
  getConsoleDistDir() {
18772
19359
  const candidates = [
18773
- path18.resolve(__dirname, "../../web-devconsole/dist"),
18774
- path18.resolve(__dirname, "../../../web-devconsole/dist"),
18775
- path18.join(process.cwd(), "packages/web-devconsole/dist")
19360
+ path20.resolve(__dirname, "../../web-devconsole/dist"),
19361
+ path20.resolve(__dirname, "../../../web-devconsole/dist"),
19362
+ path20.join(process.cwd(), "packages/web-devconsole/dist")
18776
19363
  ];
18777
19364
  for (const dir of candidates) {
18778
- if (fs14.existsSync(path18.join(dir, "index.html"))) return dir;
19365
+ if (fs14.existsSync(path20.join(dir, "index.html"))) return dir;
18779
19366
  }
18780
19367
  return null;
18781
19368
  }
@@ -18785,7 +19372,7 @@ var DevServer = class _DevServer {
18785
19372
  this.json(res, 500, { error: "DevConsole not found. Run: npm run build -w packages/web-devconsole" });
18786
19373
  return;
18787
19374
  }
18788
- const htmlPath = path18.join(distDir, "index.html");
19375
+ const htmlPath = path20.join(distDir, "index.html");
18789
19376
  try {
18790
19377
  const html = fs14.readFileSync(htmlPath, "utf-8");
18791
19378
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
@@ -18810,15 +19397,15 @@ var DevServer = class _DevServer {
18810
19397
  this.json(res, 404, { error: "Not found" });
18811
19398
  return;
18812
19399
  }
18813
- const safePath = path18.normalize(pathname).replace(/^\.\.\//, "");
18814
- const filePath = path18.join(distDir, safePath);
19400
+ const safePath = path20.normalize(pathname).replace(/^\.\.\//, "");
19401
+ const filePath = path20.join(distDir, safePath);
18815
19402
  if (!filePath.startsWith(distDir)) {
18816
19403
  this.json(res, 403, { error: "Forbidden" });
18817
19404
  return;
18818
19405
  }
18819
19406
  try {
18820
19407
  const content = fs14.readFileSync(filePath);
18821
- const ext = path18.extname(filePath);
19408
+ const ext = path20.extname(filePath);
18822
19409
  const contentType = _DevServer.MIME_MAP[ext] || "application/octet-stream";
18823
19410
  res.writeHead(200, { "Content-Type": contentType, "Cache-Control": "public, max-age=31536000, immutable" });
18824
19411
  res.end(content);
@@ -18931,9 +19518,9 @@ var DevServer = class _DevServer {
18931
19518
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
18932
19519
  if (entry.isDirectory()) {
18933
19520
  files.push({ path: rel, size: 0, type: "dir" });
18934
- scan(path18.join(d, entry.name), rel);
19521
+ scan(path20.join(d, entry.name), rel);
18935
19522
  } else {
18936
- const stat = fs14.statSync(path18.join(d, entry.name));
19523
+ const stat = fs14.statSync(path20.join(d, entry.name));
18937
19524
  files.push({ path: rel, size: stat.size, type: "file" });
18938
19525
  }
18939
19526
  }
@@ -18956,7 +19543,7 @@ var DevServer = class _DevServer {
18956
19543
  this.json(res, 404, { error: `Provider directory not found: ${type}` });
18957
19544
  return;
18958
19545
  }
18959
- const fullPath = path18.resolve(dir, path18.normalize(filePath));
19546
+ const fullPath = path20.resolve(dir, path20.normalize(filePath));
18960
19547
  if (!fullPath.startsWith(dir)) {
18961
19548
  this.json(res, 403, { error: "Forbidden" });
18962
19549
  return;
@@ -18981,14 +19568,14 @@ var DevServer = class _DevServer {
18981
19568
  this.json(res, 404, { error: `Provider directory not found: ${type}` });
18982
19569
  return;
18983
19570
  }
18984
- const fullPath = path18.resolve(dir, path18.normalize(filePath));
19571
+ const fullPath = path20.resolve(dir, path20.normalize(filePath));
18985
19572
  if (!fullPath.startsWith(dir)) {
18986
19573
  this.json(res, 403, { error: "Forbidden" });
18987
19574
  return;
18988
19575
  }
18989
19576
  try {
18990
19577
  if (fs14.existsSync(fullPath)) fs14.copyFileSync(fullPath, fullPath + ".bak");
18991
- fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
19578
+ fs14.mkdirSync(path20.dirname(fullPath), { recursive: true });
18992
19579
  fs14.writeFileSync(fullPath, content, "utf-8");
18993
19580
  this.log(`File saved: ${fullPath} (${content.length} chars)`);
18994
19581
  this.providerLoader.reload();
@@ -19005,7 +19592,7 @@ var DevServer = class _DevServer {
19005
19592
  return;
19006
19593
  }
19007
19594
  for (const name of ["scripts.js", "provider.json"]) {
19008
- const p = path18.join(dir, name);
19595
+ const p = path20.join(dir, name);
19009
19596
  if (fs14.existsSync(p)) {
19010
19597
  const source = fs14.readFileSync(p, "utf-8");
19011
19598
  this.json(res, 200, { type, path: p, source, lines: source.split("\n").length });
@@ -19026,8 +19613,8 @@ var DevServer = class _DevServer {
19026
19613
  this.json(res, 404, { error: `Provider not found: ${type}` });
19027
19614
  return;
19028
19615
  }
19029
- const target = fs14.existsSync(path18.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
19030
- const targetPath = path18.join(dir, target);
19616
+ const target = fs14.existsSync(path20.join(dir, "scripts.js")) ? "scripts.js" : "provider.json";
19617
+ const targetPath = path20.join(dir, target);
19031
19618
  try {
19032
19619
  if (fs14.existsSync(targetPath)) fs14.copyFileSync(targetPath, targetPath + ".bak");
19033
19620
  fs14.writeFileSync(targetPath, source, "utf-8");
@@ -19132,14 +19719,14 @@ var DevServer = class _DevServer {
19132
19719
  child.stderr?.on("data", (d) => {
19133
19720
  stderr += d.toString();
19134
19721
  });
19135
- await new Promise((resolve9) => {
19722
+ await new Promise((resolve12) => {
19136
19723
  const timer = setTimeout(() => {
19137
19724
  child.kill();
19138
- resolve9();
19725
+ resolve12();
19139
19726
  }, timeout);
19140
19727
  child.on("exit", () => {
19141
19728
  clearTimeout(timer);
19142
- resolve9();
19729
+ resolve12();
19143
19730
  });
19144
19731
  });
19145
19732
  const elapsed = Date.now() - start;
@@ -19187,7 +19774,7 @@ var DevServer = class _DevServer {
19187
19774
  }
19188
19775
  let targetDir;
19189
19776
  targetDir = this.providerLoader.getUserProviderDir(category, type);
19190
- const jsonPath = path18.join(targetDir, "provider.json");
19777
+ const jsonPath = path20.join(targetDir, "provider.json");
19191
19778
  if (fs14.existsSync(jsonPath)) {
19192
19779
  this.json(res, 409, { error: `Provider already exists at ${targetDir}`, path: targetDir });
19193
19780
  return;
@@ -19199,8 +19786,8 @@ var DevServer = class _DevServer {
19199
19786
  const createdFiles = ["provider.json"];
19200
19787
  if (result.files) {
19201
19788
  for (const [relPath, content] of Object.entries(result.files)) {
19202
- const fullPath = path18.join(targetDir, relPath);
19203
- fs14.mkdirSync(path18.dirname(fullPath), { recursive: true });
19789
+ const fullPath = path20.join(targetDir, relPath);
19790
+ fs14.mkdirSync(path20.dirname(fullPath), { recursive: true });
19204
19791
  fs14.writeFileSync(fullPath, content, "utf-8");
19205
19792
  createdFiles.push(relPath);
19206
19793
  }
@@ -19253,22 +19840,22 @@ var DevServer = class _DevServer {
19253
19840
  if (!fs14.existsSync(scriptsDir)) return null;
19254
19841
  const versions = fs14.readdirSync(scriptsDir).filter((d) => {
19255
19842
  try {
19256
- return fs14.statSync(path18.join(scriptsDir, d)).isDirectory();
19843
+ return fs14.statSync(path20.join(scriptsDir, d)).isDirectory();
19257
19844
  } catch {
19258
19845
  return false;
19259
19846
  }
19260
19847
  }).sort((a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" }));
19261
19848
  if (versions.length === 0) return null;
19262
- return path18.join(scriptsDir, versions[0]);
19849
+ return path20.join(scriptsDir, versions[0]);
19263
19850
  }
19264
19851
  resolveAutoImplWritableProviderDir(category, type, requestedDir) {
19265
- const canonicalUserDir = path18.resolve(this.providerLoader.getUserProviderDir(category, type));
19266
- const desiredDir = requestedDir ? path18.resolve(requestedDir) : canonicalUserDir;
19267
- const upstreamRoot = path18.resolve(this.providerLoader.getUpstreamDir());
19268
- if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path18.sep}`)) {
19852
+ const canonicalUserDir = path20.resolve(this.providerLoader.getUserProviderDir(category, type));
19853
+ const desiredDir = requestedDir ? path20.resolve(requestedDir) : canonicalUserDir;
19854
+ const upstreamRoot = path20.resolve(this.providerLoader.getUpstreamDir());
19855
+ if (desiredDir === upstreamRoot || desiredDir.startsWith(`${upstreamRoot}${path20.sep}`)) {
19269
19856
  return { dir: null, reason: `Refusing to write into upstream provider directory: ${desiredDir}` };
19270
19857
  }
19271
- if (path18.basename(desiredDir) !== type) {
19858
+ if (path20.basename(desiredDir) !== type) {
19272
19859
  return { dir: null, reason: `Requested writable provider directory must end with '${type}': ${desiredDir}` };
19273
19860
  }
19274
19861
  const sourceDir = this.findProviderDir(type);
@@ -19276,11 +19863,11 @@ var DevServer = class _DevServer {
19276
19863
  return { dir: null, reason: `Provider source directory not found for '${type}'` };
19277
19864
  }
19278
19865
  if (!fs14.existsSync(desiredDir)) {
19279
- fs14.mkdirSync(path18.dirname(desiredDir), { recursive: true });
19866
+ fs14.mkdirSync(path20.dirname(desiredDir), { recursive: true });
19280
19867
  fs14.cpSync(sourceDir, desiredDir, { recursive: true });
19281
19868
  this.log(`Auto-implement writable copy created: ${desiredDir}`);
19282
19869
  }
19283
- const providerJson = path18.join(desiredDir, "provider.json");
19870
+ const providerJson = path20.join(desiredDir, "provider.json");
19284
19871
  if (!fs14.existsSync(providerJson)) {
19285
19872
  return { dir: null, reason: `provider.json not found in writable provider directory: ${desiredDir}` };
19286
19873
  }
@@ -19328,7 +19915,7 @@ var DevServer = class _DevServer {
19328
19915
  setMode: "set_mode.js"
19329
19916
  };
19330
19917
  const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
19331
- const scriptsDir = path18.join(providerDir, "scripts");
19918
+ const scriptsDir = path20.join(providerDir, "scripts");
19332
19919
  const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
19333
19920
  if (latestScriptsDir) {
19334
19921
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -19339,7 +19926,7 @@ var DevServer = class _DevServer {
19339
19926
  for (const file of fs14.readdirSync(latestScriptsDir)) {
19340
19927
  if (file.endsWith(".js") && targetFileNames.has(file)) {
19341
19928
  try {
19342
- const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
19929
+ const content = fs14.readFileSync(path20.join(latestScriptsDir, file), "utf-8");
19343
19930
  lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
19344
19931
  lines.push("```javascript");
19345
19932
  lines.push(content);
@@ -19356,7 +19943,7 @@ var DevServer = class _DevServer {
19356
19943
  lines.push("");
19357
19944
  for (const file of refFiles) {
19358
19945
  try {
19359
- const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
19946
+ const content = fs14.readFileSync(path20.join(latestScriptsDir, file), "utf-8");
19360
19947
  lines.push(`### \`${file}\` \u{1F512}`);
19361
19948
  lines.push("```javascript");
19362
19949
  lines.push(content);
@@ -19397,10 +19984,10 @@ var DevServer = class _DevServer {
19397
19984
  lines.push("");
19398
19985
  }
19399
19986
  }
19400
- const docsDir = path18.join(providerDir, "../../docs");
19987
+ const docsDir = path20.join(providerDir, "../../docs");
19401
19988
  const loadGuide = (name) => {
19402
19989
  try {
19403
- const p = path18.join(docsDir, name);
19990
+ const p = path20.join(docsDir, name);
19404
19991
  if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
19405
19992
  } catch {
19406
19993
  }
@@ -19574,7 +20161,7 @@ var DevServer = class _DevServer {
19574
20161
  parseApproval: "parse_approval.js"
19575
20162
  };
19576
20163
  const targetFileNames = new Set(functions.map((fn) => funcToFile[fn]).filter(Boolean));
19577
- const scriptsDir = path18.join(providerDir, "scripts");
20164
+ const scriptsDir = path20.join(providerDir, "scripts");
19578
20165
  const latestScriptsDir = this.getLatestScriptVersionDir(scriptsDir);
19579
20166
  if (latestScriptsDir) {
19580
20167
  lines.push(`Scripts version directory: \`${latestScriptsDir}\``);
@@ -19586,7 +20173,7 @@ var DevServer = class _DevServer {
19586
20173
  if (!file.endsWith(".js")) continue;
19587
20174
  if (!targetFileNames.has(file)) continue;
19588
20175
  try {
19589
- const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
20176
+ const content = fs14.readFileSync(path20.join(latestScriptsDir, file), "utf-8");
19590
20177
  lines.push(`### \`${file}\` \u270F\uFE0F EDIT`);
19591
20178
  lines.push("```javascript");
19592
20179
  lines.push(content);
@@ -19602,7 +20189,7 @@ var DevServer = class _DevServer {
19602
20189
  lines.push("");
19603
20190
  for (const file of refFiles) {
19604
20191
  try {
19605
- const content = fs14.readFileSync(path18.join(latestScriptsDir, file), "utf-8");
20192
+ const content = fs14.readFileSync(path20.join(latestScriptsDir, file), "utf-8");
19606
20193
  lines.push(`### \`${file}\` \u{1F512}`);
19607
20194
  lines.push("```javascript");
19608
20195
  lines.push(content);
@@ -19635,10 +20222,10 @@ var DevServer = class _DevServer {
19635
20222
  lines.push("");
19636
20223
  }
19637
20224
  }
19638
- const docsDir = path18.join(providerDir, "../../docs");
20225
+ const docsDir = path20.join(providerDir, "../../docs");
19639
20226
  const loadGuide = (name) => {
19640
20227
  try {
19641
- const p = path18.join(docsDir, name);
20228
+ const p = path20.join(docsDir, name);
19642
20229
  if (fs14.existsSync(p)) return fs14.readFileSync(p, "utf-8");
19643
20230
  } catch {
19644
20231
  }
@@ -19814,14 +20401,14 @@ data: ${JSON.stringify(msg.data)}
19814
20401
  res.end(JSON.stringify(data, null, 2));
19815
20402
  }
19816
20403
  async readBody(req) {
19817
- return new Promise((resolve9) => {
20404
+ return new Promise((resolve12) => {
19818
20405
  let body = "";
19819
20406
  req.on("data", (chunk) => body += chunk);
19820
20407
  req.on("end", () => {
19821
20408
  try {
19822
- resolve9(JSON.parse(body));
20409
+ resolve12(JSON.parse(body));
19823
20410
  } catch {
19824
- resolve9({});
20411
+ resolve12({});
19825
20412
  }
19826
20413
  });
19827
20414
  });
@@ -20289,7 +20876,7 @@ async function waitForReady(endpoint, timeoutMs = STARTUP_TIMEOUT_MS) {
20289
20876
  const deadline = Date.now() + timeoutMs;
20290
20877
  while (Date.now() < deadline) {
20291
20878
  if (await canConnect(endpoint)) return;
20292
- await new Promise((resolve9) => setTimeout(resolve9, STARTUP_POLL_MS));
20879
+ await new Promise((resolve12) => setTimeout(resolve12, STARTUP_POLL_MS));
20293
20880
  }
20294
20881
  throw new Error(`Session host did not become ready within ${timeoutMs}ms`);
20295
20882
  }
@@ -20445,10 +21032,10 @@ async function installExtension(ide, extension) {
20445
21032
  const buffer = Buffer.from(await res.arrayBuffer());
20446
21033
  const fs15 = await import("fs");
20447
21034
  fs15.writeFileSync(vsixPath, buffer);
20448
- return new Promise((resolve9) => {
21035
+ return new Promise((resolve12) => {
20449
21036
  const cmd = `"${ide.cliCommand}" --install-extension "${vsixPath}" --force`;
20450
21037
  (0, import_child_process10.exec)(cmd, { timeout: 6e4 }, (error, _stdout, stderr) => {
20451
- resolve9({
21038
+ resolve12({
20452
21039
  extensionId: extension.id,
20453
21040
  marketplaceId: extension.marketplaceId,
20454
21041
  success: !error,
@@ -20461,11 +21048,11 @@ async function installExtension(ide, extension) {
20461
21048
  } catch (e) {
20462
21049
  }
20463
21050
  }
20464
- return new Promise((resolve9) => {
21051
+ return new Promise((resolve12) => {
20465
21052
  const cmd = `"${ide.cliCommand}" --install-extension ${extension.marketplaceId} --force`;
20466
21053
  (0, import_child_process10.exec)(cmd, { timeout: 6e4 }, (error, stdout, stderr) => {
20467
21054
  if (error) {
20468
- resolve9({
21055
+ resolve12({
20469
21056
  extensionId: extension.id,
20470
21057
  marketplaceId: extension.marketplaceId,
20471
21058
  success: false,
@@ -20473,7 +21060,7 @@ async function installExtension(ide, extension) {
20473
21060
  error: stderr || error.message
20474
21061
  });
20475
21062
  } else {
20476
- resolve9({
21063
+ resolve12({
20477
21064
  extensionId: extension.id,
20478
21065
  marketplaceId: extension.marketplaceId,
20479
21066
  success: true,
@@ -20609,13 +21196,32 @@ async function initDaemonComponents(config) {
20609
21196
  const detectedIdesRef = { value: [] };
20610
21197
  let agentStreamManager = null;
20611
21198
  let poller = null;
21199
+ const refreshProviderAvailability = async (providerType) => {
21200
+ const targetProvider = providerType ? providerLoader.getMeta(providerLoader.resolveAlias(providerType)) : null;
21201
+ const targetCategory = targetProvider?.category;
21202
+ if (!providerType || targetCategory === "cli" || targetCategory === "acp") {
21203
+ if (providerType && targetProvider) {
21204
+ const detected = await detectCLI(targetProvider.type, providerLoader, { includeVersion: false });
21205
+ providerLoader.setProviderAvailability(targetProvider.type, {
21206
+ installed: !!detected,
21207
+ detectedPath: detected?.path || null
21208
+ });
21209
+ } else {
21210
+ providerLoader.setCliDetectionResults(await detectCLIs(providerLoader, { includeVersion: false }), true);
21211
+ }
21212
+ }
21213
+ if (!providerType || targetCategory === "ide") {
21214
+ detectedIdesRef.value = await detectIDEs(providerLoader);
21215
+ providerLoader.setIdeDetectionResults(detectedIdesRef.value, true);
21216
+ }
21217
+ };
20612
21218
  const cliManager = new DaemonCliManager({
20613
21219
  ...config.cliManagerDeps,
20614
21220
  getInstanceManager: () => instanceManager,
20615
21221
  getSessionRegistry: () => sessionRegistry
20616
21222
  }, providerLoader);
20617
21223
  LOG.info("Init", "Detecting IDEs...");
20618
- detectedIdesRef.value = await detectIDEs();
21224
+ await refreshProviderAvailability();
20619
21225
  const installed = detectedIdesRef.value.filter((i) => i.installed);
20620
21226
  LOG.info("Init", `Found ${installed.length} IDE(s): ${installed.map((i) => i.id).join(", ") || "none"}`);
20621
21227
  const cdpSetupContext = {
@@ -20655,7 +21261,11 @@ async function initDaemonComponents(config) {
20655
21261
  adapters: cliManager.adapters,
20656
21262
  providerLoader,
20657
21263
  instanceManager,
20658
- sessionRegistry
21264
+ sessionRegistry,
21265
+ onProviderSettingChanged: async (providerType) => {
21266
+ await refreshProviderAvailability(providerType);
21267
+ config.onStatusChange?.();
21268
+ }
20659
21269
  });
20660
21270
  agentStreamManager = new DaemonAgentStreamManager(
20661
21271
  LOG.forComponent("AgentStream").asLogFn(),