@adhdev/daemon-standalone 0.9.77-rc.44 → 0.9.77-rc.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -22210,7 +22210,33 @@ var require_dist2 = __commonJS({
22210
22210
  });
22211
22211
  } catch (error48) {
22212
22212
  const stderr = typeof error48.stderr === "string" ? error48.stderr : "";
22213
- throw new Error(`git worktree remove failed: ${stderr.trim() || error48.message}`);
22213
+ const stdout = typeof error48.stdout === "string" ? error48.stdout : "";
22214
+ const detail = `${stderr}
22215
+ ${stdout}
22216
+ ${error48.message || ""}`;
22217
+ if (opts.allowSubmoduleForceFallback && SUBMODULE_WORKTREE_REMOVE_RE.test(detail)) {
22218
+ try {
22219
+ await execFileAsync2("git", ["worktree", "remove", "--force", worktreePath], {
22220
+ cwd: repoRoot,
22221
+ encoding: "utf8",
22222
+ timeout: GIT_TIMEOUT_MS,
22223
+ maxBuffer: GIT_MAX_BUFFER,
22224
+ windowsHide: true
22225
+ });
22226
+ } catch (forceError) {
22227
+ const forceStderr = typeof forceError.stderr === "string" ? forceError.stderr : "";
22228
+ const forceStdout = typeof forceError.stdout === "string" ? forceError.stdout : "";
22229
+ throw new Error(`git worktree remove --force fallback failed: ${forceStderr.trim() || forceStdout.trim() || forceError.message}`);
22230
+ }
22231
+ return {
22232
+ success: true,
22233
+ removedPath: worktreePath,
22234
+ fallback: "git_worktree_remove_force_submodule",
22235
+ forced: true,
22236
+ reason: "working_trees_containing_submodules"
22237
+ };
22238
+ }
22239
+ throw new Error(`git worktree remove failed: ${stderr.trim() || stdout.trim() || error48.message}`);
22214
22240
  }
22215
22241
  return { success: true, removedPath: worktreePath };
22216
22242
  }
@@ -22269,6 +22295,7 @@ var require_dist2 = __commonJS({
22269
22295
  var WORKTREE_DIR_NAME;
22270
22296
  var GIT_TIMEOUT_MS;
22271
22297
  var GIT_MAX_BUFFER;
22298
+ var SUBMODULE_WORKTREE_REMOVE_RE;
22272
22299
  var init_git_worktree = __esm2({
22273
22300
  "src/git/git-worktree.ts"() {
22274
22301
  "use strict";
@@ -22281,6 +22308,7 @@ var require_dist2 = __commonJS({
22281
22308
  WORKTREE_DIR_NAME = ".adhdev-worktrees";
22282
22309
  GIT_TIMEOUT_MS = 3e4;
22283
22310
  GIT_MAX_BUFFER = 4 * 1024 * 1024;
22311
+ SUBMODULE_WORKTREE_REMOVE_RE = /working trees containing submodules cannot be moved or removed/i;
22284
22312
  }
22285
22313
  });
22286
22314
  var config_exports = {};
@@ -22388,7 +22416,8 @@ var require_dist2 = __commonJS({
22388
22416
  };
22389
22417
  }
22390
22418
  function getConfigDir() {
22391
- const dir = (0, import_path2.join)((0, import_os2.homedir)(), ".adhdev");
22419
+ const override = process.env.ADHDEV_CONFIG_DIR;
22420
+ const dir = override && override.trim() ? override.trim() : (0, import_path2.join)((0, import_os2.homedir)(), ".adhdev");
22392
22421
  if (!(0, import_fs.existsSync)(dir)) {
22393
22422
  (0, import_fs.mkdirSync)(dir, { recursive: true });
22394
22423
  }
@@ -23112,6 +23141,7 @@ Follow these recovery rules:
23112
23141
  enqueueTask: () => enqueueTask,
23113
23142
  getMeshQueueStats: () => getMeshQueueStats,
23114
23143
  getQueue: () => getQueue,
23144
+ recordTaskAutoLaunch: () => recordTaskAutoLaunch,
23115
23145
  requeueTask: () => requeueTask,
23116
23146
  updateSessionTaskStatus: () => updateSessionTaskStatus,
23117
23147
  updateTaskStatus: () => updateTaskStatus
@@ -23187,6 +23217,19 @@ Follow these recovery rules:
23187
23217
  writeQueue(meshId, queue);
23188
23218
  return queue[idx];
23189
23219
  }
23220
+ function recordTaskAutoLaunch(meshId, taskId, autoLaunch) {
23221
+ const queue = readQueue(meshId);
23222
+ const idx = queue.findIndex((q) => q.id === taskId);
23223
+ if (idx === -1) return null;
23224
+ const now = (/* @__PURE__ */ new Date()).toISOString();
23225
+ queue[idx].autoLaunch = {
23226
+ ...autoLaunch,
23227
+ updatedAt: now
23228
+ };
23229
+ queue[idx].updatedAt = now;
23230
+ writeQueue(meshId, queue);
23231
+ return queue[idx];
23232
+ }
23190
23233
  function cancelTask(meshId, taskId, opts) {
23191
23234
  const queue = readQueue(meshId);
23192
23235
  const idx = queue.findIndex((q) => q.id === taskId);
@@ -23235,12 +23278,20 @@ Follow these recovery rules:
23235
23278
  }
23236
23279
  function getMeshQueueStats(meshId) {
23237
23280
  const queue = readQueue(meshId);
23281
+ const pending = queue.filter((q) => q.status === "pending").length;
23282
+ const assigned = queue.filter((q) => q.status === "assigned").length;
23283
+ const completed = queue.filter((q) => q.status === "completed").length;
23284
+ const failed = queue.filter((q) => q.status === "failed").length;
23285
+ const cancelled = queue.filter((q) => q.status === "cancelled").length;
23238
23286
  return {
23239
- pending: queue.filter((q) => q.status === "pending").length,
23240
- assigned: queue.filter((q) => q.status === "assigned").length,
23241
- completed: queue.filter((q) => q.status === "completed").length,
23242
- failed: queue.filter((q) => q.status === "failed").length,
23243
- cancelled: queue.filter((q) => q.status === "cancelled").length,
23287
+ total: queue.length,
23288
+ active: pending + assigned,
23289
+ historical: completed + failed + cancelled,
23290
+ pending,
23291
+ assigned,
23292
+ completed,
23293
+ failed,
23294
+ cancelled,
23244
23295
  activeAssignments: queue.filter((q) => q.status === "assigned").map((q) => ({
23245
23296
  id: q.id,
23246
23297
  nodeId: q.assignedNodeId,
@@ -23261,6 +23312,142 @@ Follow these recovery rules:
23261
23312
  init_mesh_ledger();
23262
23313
  }
23263
23314
  });
23315
+ function parseVersion(raw) {
23316
+ const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
23317
+ return match ? match[1] : raw.split("\n")[0].slice(0, 100);
23318
+ }
23319
+ function shellQuote(value) {
23320
+ if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
23321
+ return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
23322
+ }
23323
+ function expandHome(value) {
23324
+ const trimmed = value.trim();
23325
+ if (!trimmed.startsWith("~")) return trimmed;
23326
+ return path8.join(os22.homedir(), trimmed.slice(1));
23327
+ }
23328
+ function isExplicitCommandPath(command) {
23329
+ const trimmed = command.trim();
23330
+ return path8.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
23331
+ }
23332
+ function resolveCommandPath(command) {
23333
+ const trimmed = command.trim();
23334
+ if (!trimmed) return null;
23335
+ if (isExplicitCommandPath(trimmed)) {
23336
+ const expanded = expandHome(trimmed);
23337
+ const candidate = path8.isAbsolute(expanded) ? expanded : path8.resolve(expanded);
23338
+ return (0, import_fs5.existsSync)(candidate) ? candidate : null;
23339
+ }
23340
+ return null;
23341
+ }
23342
+ function execAsync(cmd, timeoutMs = 5e3) {
23343
+ return new Promise((resolve16) => {
23344
+ const child = (0, import_child_process.exec)(cmd, {
23345
+ encoding: "utf-8",
23346
+ timeout: timeoutMs,
23347
+ ...process.platform === "win32" ? { windowsHide: true } : {}
23348
+ }, (err, stdout) => {
23349
+ if (err || !stdout?.trim()) {
23350
+ resolve16(null);
23351
+ } else {
23352
+ resolve16(stdout.trim());
23353
+ }
23354
+ });
23355
+ child.on("error", () => resolve16(null));
23356
+ });
23357
+ }
23358
+ async function detectCLIs(providerLoader, options) {
23359
+ const platform10 = os22.platform();
23360
+ const whichCmd = platform10 === "win32" ? "where" : "which";
23361
+ const includeVersion = options?.includeVersion !== false;
23362
+ const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
23363
+ const results = await Promise.all(
23364
+ cliList.map(async (cli) => {
23365
+ try {
23366
+ const explicitPath = resolveCommandPath(cli.command);
23367
+ const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
23368
+ if (!pathResult) return { ...cli, installed: false };
23369
+ const firstPath = explicitPath || pathResult.split("\n")[0];
23370
+ let version2;
23371
+ if (includeVersion) {
23372
+ const versionCommands = [
23373
+ `"${firstPath}" --version`,
23374
+ `"${firstPath}" -V`,
23375
+ `"${firstPath}" -v`,
23376
+ cli.versionCommand
23377
+ ].filter((v) => !!v);
23378
+ try {
23379
+ for (const versionCommand of versionCommands) {
23380
+ const versionResult = await execAsync(versionCommand, 3e3);
23381
+ if (versionResult) {
23382
+ version2 = parseVersion(versionResult);
23383
+ break;
23384
+ }
23385
+ }
23386
+ } catch {
23387
+ }
23388
+ }
23389
+ return { ...cli, installed: true, version: version2, path: firstPath };
23390
+ } catch {
23391
+ return { ...cli, installed: false };
23392
+ }
23393
+ })
23394
+ );
23395
+ return results;
23396
+ }
23397
+ async function detectCLI(cliId, providerLoader, options) {
23398
+ const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
23399
+ if (providerLoader) {
23400
+ const cliList = providerLoader.getCliDetectionList();
23401
+ const target = cliList.find((c) => c.id === resolvedId);
23402
+ if (target) {
23403
+ const platform10 = os22.platform();
23404
+ const whichCmd = platform10 === "win32" ? "where" : "which";
23405
+ try {
23406
+ const explicitPath = resolveCommandPath(target.command);
23407
+ const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
23408
+ if (!pathResult) return null;
23409
+ const firstPath = explicitPath || pathResult.split("\n")[0];
23410
+ let version2;
23411
+ if (options?.includeVersion !== false) {
23412
+ const versionCommands = [
23413
+ `"${firstPath}" --version`,
23414
+ `"${firstPath}" -V`,
23415
+ `"${firstPath}" -v`,
23416
+ target.versionCommand
23417
+ ].filter((v) => !!v);
23418
+ try {
23419
+ for (const versionCommand of versionCommands) {
23420
+ const versionResult = await execAsync(versionCommand, 3e3);
23421
+ if (versionResult) {
23422
+ version2 = parseVersion(versionResult);
23423
+ break;
23424
+ }
23425
+ }
23426
+ } catch {
23427
+ }
23428
+ }
23429
+ return { ...target, installed: true, version: version2, path: firstPath };
23430
+ } catch {
23431
+ return null;
23432
+ }
23433
+ }
23434
+ }
23435
+ const all = await detectCLIs(providerLoader, options);
23436
+ return all.find((c) => c.id === resolvedId && c.installed) || null;
23437
+ }
23438
+ var import_child_process;
23439
+ var os22;
23440
+ var path8;
23441
+ var import_fs5;
23442
+ var init_cli_detector = __esm2({
23443
+ "src/detection/cli-detector.ts"() {
23444
+ "use strict";
23445
+ import_child_process = require("child_process");
23446
+ os22 = __toESM2(require("os"));
23447
+ path8 = __toESM2(require("path"));
23448
+ import_fs5 = require("fs");
23449
+ }
23450
+ });
23264
23451
  function setLogLevel(level) {
23265
23452
  currentLevel = level;
23266
23453
  daemonLog("Logger", `Log level set to: ${level}`, "info");
@@ -23275,13 +23462,13 @@ Follow these recovery rules:
23275
23462
  return LOG_DIR;
23276
23463
  }
23277
23464
  function getCurrentDaemonLogPath(date5 = /* @__PURE__ */ new Date()) {
23278
- return path8.join(LOG_DIR, `daemon-${date5.toISOString().slice(0, 10)}.log`);
23465
+ return path9.join(LOG_DIR, `daemon-${date5.toISOString().slice(0, 10)}.log`);
23279
23466
  }
23280
23467
  function checkDateRotation() {
23281
23468
  const today = getDateStr();
23282
23469
  if (today !== currentDate) {
23283
23470
  currentDate = today;
23284
- currentLogFile = path8.join(LOG_DIR, `daemon-${currentDate}.log`);
23471
+ currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
23285
23472
  cleanOldLogs();
23286
23473
  }
23287
23474
  }
@@ -23295,7 +23482,7 @@ Follow these recovery rules:
23295
23482
  const dateMatch = file2.match(/daemon-(\d{4}-\d{2}-\d{2})/);
23296
23483
  if (dateMatch && dateMatch[1] < cutoffStr) {
23297
23484
  try {
23298
- fs22.unlinkSync(path8.join(LOG_DIR, file2));
23485
+ fs22.unlinkSync(path9.join(LOG_DIR, file2));
23299
23486
  } catch {
23300
23487
  }
23301
23488
  }
@@ -23412,8 +23599,8 @@ Follow these recovery rules:
23412
23599
  writeToFile(`Log level: ${currentLevel}`);
23413
23600
  }
23414
23601
  var fs22;
23415
- var path8;
23416
- var os22;
23602
+ var path9;
23603
+ var os32;
23417
23604
  var LEVEL_NUM;
23418
23605
  var LEVEL_LABEL;
23419
23606
  var currentLevel;
@@ -23435,12 +23622,12 @@ Follow these recovery rules:
23435
23622
  "src/logging/logger.ts"() {
23436
23623
  "use strict";
23437
23624
  fs22 = __toESM2(require("fs"));
23438
- path8 = __toESM2(require("path"));
23439
- os22 = __toESM2(require("os"));
23625
+ path9 = __toESM2(require("path"));
23626
+ os32 = __toESM2(require("os"));
23440
23627
  LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
23441
23628
  LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
23442
23629
  currentLevel = "info";
23443
- LOG_DIR = process.platform === "win32" ? path8.join(process.env.LOCALAPPDATA || process.env.APPDATA || path8.join(os22.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path8.join(os22.homedir(), "Library", "Logs", "adhdev") : path8.join(os22.homedir(), ".local", "share", "adhdev", "logs");
23630
+ LOG_DIR = process.platform === "win32" ? path9.join(process.env.LOCALAPPDATA || process.env.APPDATA || path9.join(os32.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path9.join(os32.homedir(), "Library", "Logs", "adhdev") : path9.join(os32.homedir(), ".local", "share", "adhdev", "logs");
23444
23631
  MAX_LOG_SIZE = 5 * 1024 * 1024;
23445
23632
  MAX_LOG_DAYS = 7;
23446
23633
  try {
@@ -23448,16 +23635,16 @@ Follow these recovery rules:
23448
23635
  } catch {
23449
23636
  }
23450
23637
  currentDate = getDateStr();
23451
- currentLogFile = path8.join(LOG_DIR, `daemon-${currentDate}.log`);
23638
+ currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
23452
23639
  cleanOldLogs();
23453
23640
  try {
23454
- const oldLog = path8.join(LOG_DIR, "daemon.log");
23641
+ const oldLog = path9.join(LOG_DIR, "daemon.log");
23455
23642
  if (fs22.existsSync(oldLog)) {
23456
23643
  const stat2 = fs22.statSync(oldLog);
23457
23644
  const oldDate = stat2.mtime.toISOString().slice(0, 10);
23458
- fs22.renameSync(oldLog, path8.join(LOG_DIR, `daemon-${oldDate}.log`));
23645
+ fs22.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
23459
23646
  }
23460
- const oldLogBackup = path8.join(LOG_DIR, "daemon.log.old");
23647
+ const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
23461
23648
  if (fs22.existsSync(oldLogBackup)) {
23462
23649
  fs22.unlinkSync(oldLogBackup);
23463
23650
  }
@@ -23489,7 +23676,7 @@ Follow these recovery rules:
23489
23676
  }
23490
23677
  };
23491
23678
  interceptorInstalled = false;
23492
- LOG_PATH = path8.join(LOG_DIR, `daemon-${getDateStr()}.log`);
23679
+ LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
23493
23680
  }
23494
23681
  });
23495
23682
  var mesh_events_exports = {};
@@ -23559,7 +23746,235 @@ Follow these recovery rules:
23559
23746
  });
23560
23747
  return true;
23561
23748
  }
23562
- function triggerMeshQueue(components, meshId) {
23749
+ function normalizeProviderPriority(policy) {
23750
+ const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
23751
+ if (!Array.isArray(raw)) return [];
23752
+ const seen = /* @__PURE__ */ new Set();
23753
+ return raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean).filter((type) => {
23754
+ if (seen.has(type)) return false;
23755
+ seen.add(type);
23756
+ return true;
23757
+ });
23758
+ }
23759
+ function isTerminalSessionStatus(status) {
23760
+ return ["stopped", "failed", "terminated", "exited", "closed"].includes(status);
23761
+ }
23762
+ function isIdleSessionState(state) {
23763
+ const status = readNonEmptyString(state?.status).toLowerCase();
23764
+ if (isTerminalSessionStatus(status)) return false;
23765
+ return status === "idle" || state?.activeChat?.status === "waiting_input";
23766
+ }
23767
+ function isDirtyNode(node) {
23768
+ return node?.health === "dirty" || node?.git?.dirty === true;
23769
+ }
23770
+ function isLaunchableNode(node) {
23771
+ if (!node || node.status === "disabled" || node.status === "removed") return false;
23772
+ const health = readNonEmptyString(node.health).toLowerCase();
23773
+ if (!health) return true;
23774
+ return health === "online" || health === "unknown";
23775
+ }
23776
+ function localAutoLaunchSkipReason(node) {
23777
+ const daemonId = readNonEmptyString(node?.daemonId);
23778
+ const machineId = readNonEmptyString(node?.machineId);
23779
+ const appConfig = loadConfig2();
23780
+ const localMachineId = readNonEmptyString(appConfig.machineId) || readNonEmptyString(appConfig.registeredMachineId);
23781
+ const cloudDaemonId = localMachineId ? `daemon_${localMachineId}` : "";
23782
+ const standaloneDaemonId = localMachineId ? `standalone_${localMachineId}` : "";
23783
+ const daemonMatchesLocal = !daemonId || daemonId === cloudDaemonId || daemonId === standaloneDaemonId;
23784
+ const machineMatchesLocal = !machineId || localMachineId && machineId === localMachineId;
23785
+ if (node?.isLocalWorktree === true) {
23786
+ return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
23787
+ }
23788
+ if (daemonId || machineId) {
23789
+ return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
23790
+ }
23791
+ return null;
23792
+ }
23793
+ function activeAssignedCount(meshId) {
23794
+ return getQueue(meshId, { status: ["assigned"] }).length;
23795
+ }
23796
+ function nodeHasActiveAssignment(meshId, nodeId) {
23797
+ return getQueue(meshId, { status: ["assigned"] }).some((task) => task.assignedNodeId === nodeId);
23798
+ }
23799
+ function liveSessionCountForNode(components, meshId, nodeId) {
23800
+ return components.instanceManager.getByCategory("cli").filter((inst) => {
23801
+ const state = inst.getState();
23802
+ const settings = state.settings || {};
23803
+ if (readNonEmptyString(settings.meshNodeFor) !== meshId) return false;
23804
+ const instNodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
23805
+ if (instNodeId !== nodeId) return false;
23806
+ const status = readNonEmptyString(state.status).toLowerCase();
23807
+ return !isTerminalSessionStatus(status);
23808
+ }).length;
23809
+ }
23810
+ function recordAutoLaunchEvent(meshId, args) {
23811
+ try {
23812
+ appendLedgerEntry(meshId, {
23813
+ kind: "session_auto_launch",
23814
+ nodeId: args.nodeId,
23815
+ sessionId: args.sessionId,
23816
+ providerType: args.providerType,
23817
+ payload: {
23818
+ phase: args.phase,
23819
+ taskId: args.taskId,
23820
+ reason: args.reason,
23821
+ error: args.error
23822
+ }
23823
+ });
23824
+ } catch (e) {
23825
+ LOG2.warn("MeshQueue", `Failed to record auto-launch ledger event: ${e?.message || e}`);
23826
+ }
23827
+ }
23828
+ function markAutoLaunch(meshId, taskId, args) {
23829
+ recordTaskAutoLaunch(meshId, taskId, {
23830
+ status: args.status,
23831
+ reason: args.reason || args.error,
23832
+ nodeId: args.nodeId,
23833
+ providerType: args.providerType,
23834
+ sessionId: args.sessionId
23835
+ });
23836
+ recordAutoLaunchEvent(meshId, {
23837
+ phase: args.status,
23838
+ taskId,
23839
+ nodeId: args.nodeId,
23840
+ providerType: args.providerType,
23841
+ sessionId: args.sessionId,
23842
+ reason: args.reason,
23843
+ error: args.error
23844
+ });
23845
+ }
23846
+ async function resolveUsableProvider(components, nodeId, node) {
23847
+ const providerPriority = normalizeProviderPriority(node?.policy);
23848
+ if (!providerPriority.length) return { reason: "missing_provider_priority" };
23849
+ const providerLoader = components.providerLoader;
23850
+ if (!providerLoader) return { reason: "provider_loader_unavailable" };
23851
+ const failed = [];
23852
+ for (const requestedType of providerPriority) {
23853
+ const normalizedType = typeof providerLoader.resolveAlias === "function" ? providerLoader.resolveAlias(requestedType) : requestedType;
23854
+ if (typeof providerLoader.isMachineProviderEnabled === "function" && !providerLoader.isMachineProviderEnabled(normalizedType)) {
23855
+ failed.push(`${requestedType}: disabled`);
23856
+ continue;
23857
+ }
23858
+ let detected;
23859
+ try {
23860
+ detected = await detectCLI(normalizedType, providerLoader, { includeVersion: false });
23861
+ } catch (e) {
23862
+ failed.push(`${requestedType}: detect failed: ${e?.message || e}`);
23863
+ continue;
23864
+ }
23865
+ if (typeof providerLoader.setCliDetectionResults === "function") {
23866
+ providerLoader.setCliDetectionResults([{
23867
+ id: normalizedType,
23868
+ installed: !!detected,
23869
+ path: detected?.path
23870
+ }], false);
23871
+ }
23872
+ components.onStatusChange?.();
23873
+ if (detected) return { providerType: normalizedType };
23874
+ failed.push(`${requestedType}: not detected`);
23875
+ }
23876
+ return { reason: `provider_priority_unusable: ${failed.join("; ") || nodeId}` };
23877
+ }
23878
+ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
23879
+ const queue = getQueue(meshId);
23880
+ const pending = queue.filter((task) => task.status === "pending");
23881
+ if (!pending.length) return false;
23882
+ const maxParallelTasks = Math.max(1, Math.floor(Number(mesh?.policy?.maxParallelTasks) || 2));
23883
+ for (const task of pending) {
23884
+ if (activeAssignedCount(meshId) >= maxParallelTasks) {
23885
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_parallel_tasks_reached" });
23886
+ return false;
23887
+ }
23888
+ if (task.targetSessionId) {
23889
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_constraint" });
23890
+ continue;
23891
+ }
23892
+ const candidateNodes = Array.isArray(mesh?.nodes) ? mesh.nodes.filter((node) => task.targetNodeId ? node?.id === task.targetNodeId : true) : [];
23893
+ if (!candidateNodes.length) {
23894
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "no_matching_node", nodeId: task.targetNodeId });
23895
+ continue;
23896
+ }
23897
+ for (const node of candidateNodes) {
23898
+ const nodeId = readNonEmptyString(node?.id);
23899
+ if (!nodeId) continue;
23900
+ const launchKey = `${meshId}:${nodeId}`;
23901
+ const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
23902
+ if (autoLaunchInProgress.has(launchKey)) {
23903
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_in_progress", nodeId });
23904
+ continue;
23905
+ }
23906
+ if (Date.now() < cooldownUntil) {
23907
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_cooldown", nodeId });
23908
+ continue;
23909
+ }
23910
+ if (isDirtyNode(node)) {
23911
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "dirty_workspace", nodeId });
23912
+ continue;
23913
+ }
23914
+ if (!isLaunchableNode(node)) {
23915
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_not_launch_ready", nodeId });
23916
+ continue;
23917
+ }
23918
+ const localSkipReason = localAutoLaunchSkipReason(node);
23919
+ if (localSkipReason) {
23920
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: localSkipReason, nodeId });
23921
+ continue;
23922
+ }
23923
+ if (nodeHasActiveAssignment(meshId, nodeId)) {
23924
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_has_active_assignment", nodeId });
23925
+ continue;
23926
+ }
23927
+ const maxConcurrentSessions = Number(node?.policy?.maxConcurrentSessions);
23928
+ if (Number.isFinite(maxConcurrentSessions) && maxConcurrentSessions >= 0 && liveSessionCountForNode(components, meshId, nodeId) >= maxConcurrentSessions) {
23929
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_concurrent_sessions_reached", nodeId });
23930
+ continue;
23931
+ }
23932
+ autoLaunchInProgress.add(launchKey);
23933
+ try {
23934
+ const resolved = await resolveUsableProvider(components, nodeId, node);
23935
+ if (!resolved.providerType) {
23936
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
23937
+ continue;
23938
+ }
23939
+ markAutoLaunch(meshId, task.id, { status: "started", nodeId, providerType: resolved.providerType });
23940
+ const launchResult = await components.cliManager.handleCliCommand("launch_cli", {
23941
+ cliType: resolved.providerType,
23942
+ dir: node.workspace,
23943
+ settings: {
23944
+ meshNodeFor: meshId,
23945
+ meshNodeId: nodeId,
23946
+ spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || "hidden",
23947
+ launchedByCoordinator: true,
23948
+ autoLaunchedForQueueTaskId: task.id
23949
+ }
23950
+ });
23951
+ if (!launchResult?.success) {
23952
+ const reason = launchResult?.error || "launch_cli_failed";
23953
+ markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
23954
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
23955
+ return false;
23956
+ }
23957
+ const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
23958
+ if (!sessionId) {
23959
+ markAutoLaunch(meshId, task.id, { status: "failed", reason: "launch_missing_session_id", nodeId, providerType: resolved.providerType });
23960
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
23961
+ return false;
23962
+ }
23963
+ markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId });
23964
+ tryAssignQueueTask(components, meshId, nodeId, sessionId, resolved.providerType);
23965
+ return true;
23966
+ } catch (e) {
23967
+ markAutoLaunch(meshId, task.id, { status: "failed", error: e?.message || String(e), nodeId });
23968
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
23969
+ return false;
23970
+ } finally {
23971
+ autoLaunchInProgress.delete(launchKey);
23972
+ }
23973
+ }
23974
+ }
23975
+ return false;
23976
+ }
23977
+ async function triggerMeshQueue(components, meshId) {
23563
23978
  const mesh = getMeshWithCache(components, meshId);
23564
23979
  if (!mesh) return;
23565
23980
  const cliInstances = components.instanceManager.getByCategory("cli");
@@ -23570,9 +23985,7 @@ Follow these recovery rules:
23570
23985
  if (instMeshId !== meshId) continue;
23571
23986
  const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
23572
23987
  if (!nodeId) continue;
23573
- const status = readNonEmptyString(state.status).toLowerCase();
23574
- if (["stopped", "failed", "terminated", "exited", "closed"].includes(status)) continue;
23575
- if (status !== "idle" && state.activeChat?.status !== "waiting_input") continue;
23988
+ if (!isIdleSessionState(state)) continue;
23576
23989
  const sessionId = state.instanceId;
23577
23990
  const providerType = state.type || readNonEmptyString(settings.providerType);
23578
23991
  if (providerType) {
@@ -23588,6 +24001,7 @@ Follow these recovery rules:
23588
24001
  }
23589
24002
  }
23590
24003
  }
24004
+ await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
23591
24005
  }
23592
24006
  function buildMeshSystemMessage(args) {
23593
24007
  const metadata = formatCompletionMetadata(args.metadataEvent);
@@ -23869,10 +24283,15 @@ Do NOT retry on this node. Consider reassigning to a different node or asking th
23869
24283
  var pendingMeshCoordinatorEvents;
23870
24284
  var MESH_COORDINATOR_EVENTS;
23871
24285
  var EVENT_TO_LEDGER_KIND;
24286
+ var autoLaunchInProgress;
24287
+ var autoLaunchCooldownUntil;
24288
+ var AUTO_LAUNCH_COOLDOWN_MS;
23872
24289
  var init_mesh_events = __esm2({
23873
24290
  "src/mesh/mesh-events.ts"() {
23874
24291
  "use strict";
24292
+ init_config();
23875
24293
  init_mesh_config();
24294
+ init_cli_detector();
23876
24295
  init_logger();
23877
24296
  init_mesh_ledger();
23878
24297
  init_mesh_work_queue();
@@ -23893,6 +24312,9 @@ Do NOT retry on this node. Consider reassigning to a different node or asking th
23893
24312
  "agent:stopped": "task_failed",
23894
24313
  "monitor:long_generating": "task_stalled"
23895
24314
  };
24315
+ autoLaunchInProgress = /* @__PURE__ */ new Set();
24316
+ autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
24317
+ AUTO_LAUNCH_COOLDOWN_MS = 5e3;
23896
24318
  }
23897
24319
  });
23898
24320
  function normalizeCategories(categories) {
@@ -27011,6 +27433,7 @@ ${lastSnapshot}`;
27011
27433
  MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS: () => MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS2,
27012
27434
  MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS: () => MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS2,
27013
27435
  NodePtyTransportFactory: () => NodePtyTransportFactory,
27436
+ P2pRelayFailureError: () => P2pRelayFailureError,
27014
27437
  ProviderCliAdapter: () => ProviderCliAdapter,
27015
27438
  ProviderInstanceManager: () => ProviderInstanceManager,
27016
27439
  ProviderLoader: () => ProviderLoader,
@@ -27027,6 +27450,7 @@ ${lastSnapshot}`;
27027
27450
  buildChatTailDeliverySignature: () => buildChatTailDeliverySignature,
27028
27451
  buildCoordinatorSystemPrompt: () => buildCoordinatorSystemPrompt,
27029
27452
  buildMachineInfo: () => buildMachineInfo2,
27453
+ buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
27030
27454
  buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
27031
27455
  buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
27032
27456
  buildSessionEntries: () => buildSessionEntries,
@@ -27041,6 +27465,7 @@ ${lastSnapshot}`;
27041
27465
  claimNextTask: () => claimNextTask,
27042
27466
  classifyChatMessageVisibility: () => classifyChatMessageVisibility,
27043
27467
  classifyHotChatSessionsForSubscriptionFlush: () => classifyHotChatSessionsForSubscriptionFlush2,
27468
+ classifyP2pRelayFailure: () => classifyP2pRelayFailure,
27044
27469
  clearDebugTrace: () => clearDebugTrace,
27045
27470
  compareGitSnapshots: () => compareGitSnapshots,
27046
27471
  configureDebugTraceStore: () => configureDebugTraceStore,
@@ -27108,6 +27533,7 @@ ${lastSnapshot}`;
27108
27533
  isInternalChatMessage: () => isInternalChatMessage,
27109
27534
  isManagedStatusWaiting: () => isManagedStatusWaiting,
27110
27535
  isManagedStatusWorking: () => isManagedStatusWorking,
27536
+ isP2pRelayTransportFailure: () => isP2pRelayTransportFailure,
27111
27537
  isPathInside: () => isPathInside,
27112
27538
  isSessionHostLiveRuntime: () => isSessionHostLiveRuntime,
27113
27539
  isSessionHostRecoverySnapshot: () => isSessionHostRecoverySnapshot,
@@ -28642,7 +29068,7 @@ ${lastSnapshot}`;
28642
29068
  }
28643
29069
  }
28644
29070
  const v = validateWorkspacePath(abs);
28645
- if (!v.ok) return { error: v.error };
29071
+ if (v.ok !== true) return { error: v.error };
28646
29072
  const list = [...config2.workspaces || []];
28647
29073
  if (list.some((w) => path52.resolve(w.path) === abs)) {
28648
29074
  return { error: "Workspace already in list" };
@@ -29036,7 +29462,115 @@ ${lastSnapshot}`;
29036
29462
  init_mesh_ledger();
29037
29463
  init_mesh_work_queue();
29038
29464
  init_mesh_events();
29039
- var import_fs5 = require("fs");
29465
+ var NO_FALLBACK_REASON = "Repo Mesh command/data-plane is P2P-only; WS/REST command fallback is intentionally disabled to preserve the transport boundary.";
29466
+ var P2P_NEXT_ACTION = "Check daemon/P2P health, wait briefly for connection establishment, then do one bounded retry or requeue the mesh task after clearing stale target session metadata.";
29467
+ var NON_P2P_NEXT_ACTION = "Inspect the provider/command error and fix the underlying logic or configuration before retrying.";
29468
+ function messageFromError(error48) {
29469
+ if (error48 instanceof Error) return error48.message;
29470
+ if (typeof error48 === "string") return error48;
29471
+ if (error48 && typeof error48 === "object") {
29472
+ const candidate = error48.error ?? error48.message ?? error48.reason;
29473
+ if (typeof candidate === "string") return candidate;
29474
+ }
29475
+ return String(error48 || "mesh relay command failed");
29476
+ }
29477
+ function classifyP2pRelayFailure(error48, _context = {}) {
29478
+ const message = messageFromError(error48);
29479
+ const lower = message.toLowerCase();
29480
+ const hasP2pSignal = /p2p|datachannel|node-datachannel|webrtc|ice|mesh_relay_command|daemon_mesh_p2p_transport/i.test(message);
29481
+ const hasFailureSignal = /unavailable|missing|failed|failure|timeout|timed out|not connected|closed|disconnected|offline|no route|route unavailable|cannot send|cannot establish/i.test(message);
29482
+ if (/requires targetdaemonid and command|providerpriority|no inference provider|permission denied|read-only|not a member/i.test(message)) {
29483
+ return {
29484
+ code: "mesh_logic_or_provider_failure",
29485
+ reason: "mesh_logic_or_provider_failure",
29486
+ transport: "unknown",
29487
+ recoverable: false,
29488
+ retryRecommended: false,
29489
+ nextAction: NON_P2P_NEXT_ACTION,
29490
+ noFallbackReason: NO_FALLBACK_REASON
29491
+ };
29492
+ }
29493
+ let code = null;
29494
+ let reason = "";
29495
+ if (/timeout|timed out/i.test(message) && (hasP2pSignal || /mesh transport/i.test(message))) {
29496
+ code = "p2p_timeout";
29497
+ reason = "daemon_mesh_p2p_timeout";
29498
+ } else if (/no route|route unavailable/i.test(message)) {
29499
+ code = "p2p_no_route";
29500
+ reason = "daemon_mesh_p2p_no_route";
29501
+ } else if (/offline|not owned|not found|not connected to server/i.test(message) && /daemon|peer|target/i.test(message)) {
29502
+ code = "p2p_daemon_offline";
29503
+ reason = "daemon_mesh_target_offline";
29504
+ } else if (/closed|disconnected/i.test(message) && (hasP2pSignal || /state changed/i.test(message))) {
29505
+ code = "p2p_datachannel_closed";
29506
+ reason = "daemon_mesh_p2p_datachannel_closed";
29507
+ } else if (/not connected|cannot send|cannot establish/i.test(message) && hasP2pSignal) {
29508
+ code = "p2p_not_connected";
29509
+ reason = "daemon_mesh_p2p_not_connected";
29510
+ } else if (hasP2pSignal && hasFailureSignal) {
29511
+ code = "p2p_unavailable";
29512
+ reason = "daemon_mesh_p2p_transport_unavailable";
29513
+ }
29514
+ if (!code) {
29515
+ return {
29516
+ code: "mesh_logic_or_provider_failure",
29517
+ reason: "mesh_logic_or_provider_failure",
29518
+ transport: "unknown",
29519
+ recoverable: false,
29520
+ retryRecommended: false,
29521
+ nextAction: NON_P2P_NEXT_ACTION,
29522
+ noFallbackReason: NO_FALLBACK_REASON
29523
+ };
29524
+ }
29525
+ return {
29526
+ code,
29527
+ reason,
29528
+ transport: "p2p",
29529
+ recoverable: true,
29530
+ retryRecommended: true,
29531
+ nextAction: P2P_NEXT_ACTION,
29532
+ noFallbackReason: NO_FALLBACK_REASON
29533
+ };
29534
+ }
29535
+ function isP2pRelayTransportFailure(error48) {
29536
+ return classifyP2pRelayFailure(error48).recoverable === true;
29537
+ }
29538
+ function buildP2pRelayFailurePayload(error48, context = {}) {
29539
+ const classification = classifyP2pRelayFailure(error48, context);
29540
+ return {
29541
+ success: false,
29542
+ ...classification,
29543
+ error: messageFromError(error48),
29544
+ ...context.command ? { command: context.command } : {},
29545
+ ...context.targetDaemonId ? { targetDaemonId: context.targetDaemonId } : {}
29546
+ };
29547
+ }
29548
+ var P2pRelayFailureError = class extends Error {
29549
+ code;
29550
+ reason;
29551
+ transport;
29552
+ recoverable;
29553
+ retryRecommended;
29554
+ nextAction;
29555
+ noFallbackReason;
29556
+ command;
29557
+ targetDaemonId;
29558
+ constructor(message, context = {}) {
29559
+ super(message);
29560
+ this.name = "P2pRelayFailureError";
29561
+ const payload = buildP2pRelayFailurePayload(message, context);
29562
+ this.code = payload.code;
29563
+ this.reason = payload.reason;
29564
+ this.transport = payload.transport;
29565
+ this.recoverable = payload.recoverable;
29566
+ this.retryRecommended = payload.retryRecommended;
29567
+ this.nextAction = payload.nextAction;
29568
+ this.noFallbackReason = payload.noFallbackReason;
29569
+ this.command = context.command;
29570
+ this.targetDaemonId = context.targetDaemonId;
29571
+ }
29572
+ };
29573
+ var import_fs6 = require("fs");
29040
29574
  var import_path5 = require("path");
29041
29575
  init_config();
29042
29576
  var DEFAULT_STATE = {
@@ -29087,11 +29621,11 @@ ${lastSnapshot}`;
29087
29621
  }
29088
29622
  function loadState() {
29089
29623
  const statePath = getStatePath();
29090
- if (!(0, import_fs5.existsSync)(statePath)) {
29624
+ if (!(0, import_fs6.existsSync)(statePath)) {
29091
29625
  return { ...DEFAULT_STATE };
29092
29626
  }
29093
29627
  try {
29094
- const raw = (0, import_fs5.readFileSync)(statePath, "utf-8");
29628
+ const raw = (0, import_fs6.readFileSync)(statePath, "utf-8");
29095
29629
  return normalizeState(JSON.parse(raw));
29096
29630
  } catch {
29097
29631
  return { ...DEFAULT_STATE };
@@ -29100,15 +29634,15 @@ ${lastSnapshot}`;
29100
29634
  function saveState(state) {
29101
29635
  const statePath = getStatePath();
29102
29636
  const normalized = normalizeState(state);
29103
- (0, import_fs5.writeFileSync)(statePath, JSON.stringify(normalized, null, 2), { encoding: "utf-8", mode: 384 });
29637
+ (0, import_fs6.writeFileSync)(statePath, JSON.stringify(normalized, null, 2), { encoding: "utf-8", mode: 384 });
29104
29638
  }
29105
29639
  function resetState() {
29106
29640
  saveState({ ...DEFAULT_STATE });
29107
29641
  }
29108
- var import_child_process = require("child_process");
29109
- var import_fs6 = require("fs");
29642
+ var import_child_process2 = require("child_process");
29643
+ var import_fs7 = require("fs");
29110
29644
  var import_os22 = require("os");
29111
- var path9 = __toESM2(require("path"));
29645
+ var path10 = __toESM2(require("path"));
29112
29646
  var BUILTIN_IDE_DEFINITIONS = [];
29113
29647
  var registeredIDEs = /* @__PURE__ */ new Map();
29114
29648
  function registerIDEDefinition(def) {
@@ -29127,13 +29661,13 @@ ${lastSnapshot}`;
29127
29661
  function findCliCommand(command) {
29128
29662
  const trimmed = String(command || "").trim();
29129
29663
  if (!trimmed) return null;
29130
- if (path9.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
29131
- const candidate = trimmed.startsWith("~") ? path9.join((0, import_os22.homedir)(), trimmed.slice(1)) : trimmed;
29132
- const resolved = path9.isAbsolute(candidate) ? candidate : path9.resolve(candidate);
29133
- return (0, import_fs6.existsSync)(resolved) ? resolved : null;
29664
+ if (path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
29665
+ const candidate = trimmed.startsWith("~") ? path10.join((0, import_os22.homedir)(), trimmed.slice(1)) : trimmed;
29666
+ const resolved = path10.isAbsolute(candidate) ? candidate : path10.resolve(candidate);
29667
+ return (0, import_fs7.existsSync)(resolved) ? resolved : null;
29134
29668
  }
29135
29669
  try {
29136
- const result = (0, import_child_process.execSync)(
29670
+ const result = (0, import_child_process2.execSync)(
29137
29671
  (0, import_os22.platform)() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
29138
29672
  { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
29139
29673
  ).trim();
@@ -29144,7 +29678,7 @@ ${lastSnapshot}`;
29144
29678
  }
29145
29679
  function getIdeVersion(cliCommand) {
29146
29680
  try {
29147
- const result = (0, import_child_process.execSync)(`"${cliCommand}" --version`, {
29681
+ const result = (0, import_child_process2.execSync)(`"${cliCommand}" --version`, {
29148
29682
  encoding: "utf-8",
29149
29683
  timeout: 1e4,
29150
29684
  stdio: ["pipe", "pipe", "pipe"]
@@ -29157,13 +29691,13 @@ ${lastSnapshot}`;
29157
29691
  function checkPathExists(paths) {
29158
29692
  const home = (0, import_os22.homedir)();
29159
29693
  for (const p of paths) {
29160
- const normalized = p.startsWith("~") ? path9.join(home, p.slice(1)) : p;
29694
+ const normalized = p.startsWith("~") ? path10.join(home, p.slice(1)) : p;
29161
29695
  if (normalized.includes("*")) {
29162
29696
  const username = home.split(/[\\/]/).pop() || "";
29163
29697
  const resolved = normalized.replace("*", username);
29164
- if ((0, import_fs6.existsSync)(resolved)) return resolved;
29698
+ if ((0, import_fs7.existsSync)(resolved)) return resolved;
29165
29699
  } else {
29166
- if ((0, import_fs6.existsSync)(normalized)) return normalized;
29700
+ if ((0, import_fs7.existsSync)(normalized)) return normalized;
29167
29701
  }
29168
29702
  }
29169
29703
  return null;
@@ -29177,7 +29711,7 @@ ${lastSnapshot}`;
29177
29711
  let resolvedCli = cliPath;
29178
29712
  if (!resolvedCli && appPath && os222 === "darwin") {
29179
29713
  const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
29180
- if ((0, import_fs6.existsSync)(bundledCli)) resolvedCli = bundledCli;
29714
+ if ((0, import_fs7.existsSync)(bundledCli)) resolvedCli = bundledCli;
29181
29715
  }
29182
29716
  if (!resolvedCli && appPath && os222 === "win32") {
29183
29717
  const { dirname: dirname9 } = await import("path");
@@ -29190,7 +29724,7 @@ ${lastSnapshot}`;
29190
29724
  `${appDir}\\\\resources\\\\app\\\\bin\\\\${def.cli}.cmd`
29191
29725
  ];
29192
29726
  for (const c of candidates) {
29193
- if ((0, import_fs6.existsSync)(c)) {
29727
+ if ((0, import_fs7.existsSync)(c)) {
29194
29728
  resolvedCli = c;
29195
29729
  break;
29196
29730
  }
@@ -29211,133 +29745,7 @@ ${lastSnapshot}`;
29211
29745
  }
29212
29746
  return results;
29213
29747
  }
29214
- var import_child_process2 = require("child_process");
29215
- var os32 = __toESM2(require("os"));
29216
- var path10 = __toESM2(require("path"));
29217
- var import_fs7 = require("fs");
29218
- function parseVersion(raw) {
29219
- const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
29220
- return match ? match[1] : raw.split("\n")[0].slice(0, 100);
29221
- }
29222
- function shellQuote(value) {
29223
- if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
29224
- return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
29225
- }
29226
- function expandHome(value) {
29227
- const trimmed = value.trim();
29228
- if (!trimmed.startsWith("~")) return trimmed;
29229
- return path10.join(os32.homedir(), trimmed.slice(1));
29230
- }
29231
- function isExplicitCommandPath(command) {
29232
- const trimmed = command.trim();
29233
- return path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
29234
- }
29235
- function resolveCommandPath(command) {
29236
- const trimmed = command.trim();
29237
- if (!trimmed) return null;
29238
- if (isExplicitCommandPath(trimmed)) {
29239
- const expanded = expandHome(trimmed);
29240
- const candidate = path10.isAbsolute(expanded) ? expanded : path10.resolve(expanded);
29241
- return (0, import_fs7.existsSync)(candidate) ? candidate : null;
29242
- }
29243
- return null;
29244
- }
29245
- function execAsync(cmd, timeoutMs = 5e3) {
29246
- return new Promise((resolve16) => {
29247
- const child = (0, import_child_process2.exec)(cmd, {
29248
- encoding: "utf-8",
29249
- timeout: timeoutMs,
29250
- ...process.platform === "win32" ? { windowsHide: true } : {}
29251
- }, (err, stdout) => {
29252
- if (err || !stdout?.trim()) {
29253
- resolve16(null);
29254
- } else {
29255
- resolve16(stdout.trim());
29256
- }
29257
- });
29258
- child.on("error", () => resolve16(null));
29259
- });
29260
- }
29261
- async function detectCLIs(providerLoader, options) {
29262
- const platform10 = os32.platform();
29263
- const whichCmd = platform10 === "win32" ? "where" : "which";
29264
- const includeVersion = options?.includeVersion !== false;
29265
- const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
29266
- const results = await Promise.all(
29267
- cliList.map(async (cli) => {
29268
- try {
29269
- const explicitPath = resolveCommandPath(cli.command);
29270
- const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
29271
- if (!pathResult) return { ...cli, installed: false };
29272
- const firstPath = explicitPath || pathResult.split("\n")[0];
29273
- let version2;
29274
- if (includeVersion) {
29275
- const versionCommands = [
29276
- `"${firstPath}" --version`,
29277
- `"${firstPath}" -V`,
29278
- `"${firstPath}" -v`,
29279
- cli.versionCommand
29280
- ].filter((v) => !!v);
29281
- try {
29282
- for (const versionCommand of versionCommands) {
29283
- const versionResult = await execAsync(versionCommand, 3e3);
29284
- if (versionResult) {
29285
- version2 = parseVersion(versionResult);
29286
- break;
29287
- }
29288
- }
29289
- } catch {
29290
- }
29291
- }
29292
- return { ...cli, installed: true, version: version2, path: firstPath };
29293
- } catch {
29294
- return { ...cli, installed: false };
29295
- }
29296
- })
29297
- );
29298
- return results;
29299
- }
29300
- async function detectCLI(cliId, providerLoader, options) {
29301
- const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
29302
- if (providerLoader) {
29303
- const cliList = providerLoader.getCliDetectionList();
29304
- const target = cliList.find((c) => c.id === resolvedId);
29305
- if (target) {
29306
- const platform10 = os32.platform();
29307
- const whichCmd = platform10 === "win32" ? "where" : "which";
29308
- try {
29309
- const explicitPath = resolveCommandPath(target.command);
29310
- const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
29311
- if (!pathResult) return null;
29312
- const firstPath = explicitPath || pathResult.split("\n")[0];
29313
- let version2;
29314
- if (options?.includeVersion !== false) {
29315
- const versionCommands = [
29316
- `"${firstPath}" --version`,
29317
- `"${firstPath}" -V`,
29318
- `"${firstPath}" -v`,
29319
- target.versionCommand
29320
- ].filter((v) => !!v);
29321
- try {
29322
- for (const versionCommand of versionCommands) {
29323
- const versionResult = await execAsync(versionCommand, 3e3);
29324
- if (versionResult) {
29325
- version2 = parseVersion(versionResult);
29326
- break;
29327
- }
29328
- }
29329
- } catch {
29330
- }
29331
- }
29332
- return { ...target, installed: true, version: version2, path: firstPath };
29333
- } catch {
29334
- return null;
29335
- }
29336
- }
29337
- }
29338
- const all = await detectCLIs(providerLoader, options);
29339
- return all.find((c) => c.id === resolvedId && c.installed) || null;
29340
- }
29748
+ init_cli_detector();
29341
29749
  var os42 = __toESM2(require("os"));
29342
29750
  var import_child_process3 = require("child_process");
29343
29751
  function parseDarwinAvailableBytes(totalMem) {
@@ -38361,6 +38769,7 @@ ${effect.notification.body || ""}`.trim();
38361
38769
  var import_child_process6 = require("child_process");
38362
38770
  var import_chalk = __toESM2((init_source(), __toCommonJS(source_exports)));
38363
38771
  init_provider_cli_adapter();
38772
+ init_cli_detector();
38364
38773
  init_config();
38365
38774
  var os12 = __toESM2(require("os"));
38366
38775
  var path16 = __toESM2(require("path"));
@@ -43914,6 +44323,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
43914
44323
  return getProviderLoader().getAvailableIdeTypes();
43915
44324
  }
43916
44325
  init_config();
44326
+ init_cli_detector();
43917
44327
  init_logger();
43918
44328
  var fs8 = __toESM2(require("fs"));
43919
44329
  var path21 = __toESM2(require("path"));
@@ -45046,6 +45456,209 @@ ${(0, import_node_path.resolve)(workspace || os17.tmpdir())}`;
45046
45456
  }
45047
45457
  return { error: `No usable provider detected for node '${args.nodeId}' from providerPriority: ${failed.join("; ")}` };
45048
45458
  }
45459
+ var REFINE_VALIDATION_CATEGORIES = ["typecheck", "test", "lint", "build"];
45460
+ var REFINE_VALIDATION_TIMEOUT_MS = 12e4;
45461
+ var REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
45462
+ var REFINE_VALIDATION_SUMMARY_CHARS = 2e3;
45463
+ var REFINE_VALIDATION_MAX_COMMANDS = 4;
45464
+ function truncateValidationOutput(value) {
45465
+ const text = typeof value === "string" ? value : value == null ? "" : String(value);
45466
+ if (text.length <= REFINE_VALIDATION_SUMMARY_CHARS) return text;
45467
+ return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}
45468
+ [truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
45469
+ }
45470
+ function readPackageScripts(workspace) {
45471
+ try {
45472
+ const packageJsonPath = (0, import_path6.join)(workspace, "package.json");
45473
+ const parsed = JSON.parse(fs10.readFileSync(packageJsonPath, "utf-8"));
45474
+ return parsed?.scripts && typeof parsed.scripts === "object" && !Array.isArray(parsed.scripts) ? parsed.scripts : {};
45475
+ } catch {
45476
+ return {};
45477
+ }
45478
+ }
45479
+ function tokenizeValidationCommand(command) {
45480
+ const trimmed = command.trim();
45481
+ if (!trimmed) return null;
45482
+ if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
45483
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
45484
+ if (!tokens.length) return null;
45485
+ if (tokens.some((token) => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
45486
+ return tokens;
45487
+ }
45488
+ function scriptMatchesValidationCategory(scriptName, category) {
45489
+ return scriptName === category || scriptName.startsWith(`${category}:`);
45490
+ }
45491
+ function parsePackageManagerValidationCommand(rawCommand, category, scripts, source) {
45492
+ const tokens = tokenizeValidationCommand(rawCommand);
45493
+ if (!tokens) {
45494
+ return { rejected: { command: rawCommand, category, source, reason: "unsafe command string is not allowlisted" } };
45495
+ }
45496
+ const [binary, second, third, ...rest] = tokens;
45497
+ let scriptName = "";
45498
+ let command = binary;
45499
+ let args = [];
45500
+ if ((binary === "npm" || binary === "pnpm" || binary === "bun") && second === "run" && third) {
45501
+ scriptName = third;
45502
+ args = ["run", scriptName, ...rest];
45503
+ } else if (binary === "npm" && second === "test" && !third) {
45504
+ scriptName = "test";
45505
+ args = ["test"];
45506
+ } else if (binary === "yarn" && second === "run" && third) {
45507
+ scriptName = third;
45508
+ args = ["run", scriptName, ...rest];
45509
+ } else if (binary === "yarn" && second && !third) {
45510
+ scriptName = second;
45511
+ args = [scriptName];
45512
+ } else {
45513
+ return { rejected: { command: rawCommand, category, source, reason: "command is not a supported package-manager script invocation" } };
45514
+ }
45515
+ if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
45516
+ return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script is not declared in package.json" } };
45517
+ }
45518
+ if (!scriptMatchesValidationCategory(scriptName, category)) {
45519
+ return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script name is outside the validation category allowlist" } };
45520
+ }
45521
+ return {
45522
+ command: {
45523
+ command,
45524
+ args,
45525
+ displayCommand: [command, ...args].join(" "),
45526
+ category,
45527
+ source
45528
+ }
45529
+ };
45530
+ }
45531
+ function collectProjectContextValidationCandidates(mesh) {
45532
+ const commands = mesh?.projectContext?.commands;
45533
+ if (!commands || typeof commands !== "object" || Array.isArray(commands)) return [];
45534
+ const candidates = [];
45535
+ for (const category of REFINE_VALIDATION_CATEGORIES) {
45536
+ const entries = Array.isArray(commands[category]) ? commands[category] : [];
45537
+ for (const entry of entries) {
45538
+ if (typeof entry?.command !== "string") continue;
45539
+ candidates.push({
45540
+ command: entry.command,
45541
+ category,
45542
+ source: typeof entry.sourcePath === "string" ? entry.sourcePath : "projectContext.commands",
45543
+ confidence: typeof entry.confidence === "string" ? entry.confidence : void 0
45544
+ });
45545
+ }
45546
+ }
45547
+ return candidates.sort((a, b) => {
45548
+ const rank = (value) => value === "high" ? 0 : value === "medium" ? 1 : 2;
45549
+ return rank(a.confidence) - rank(b.confidence);
45550
+ });
45551
+ }
45552
+ function collectPolicyValidationCandidates(mesh) {
45553
+ const policy = mesh?.policy && typeof mesh.policy === "object" && !Array.isArray(mesh.policy) ? mesh.policy : {};
45554
+ const configured = Array.isArray(policy.validationCommands) ? policy.validationCommands : Array.isArray(policy.validationGate?.commands) ? policy.validationGate.commands : [];
45555
+ return configured.map((entry) => typeof entry === "string" ? { command: entry, category: "", source: "mesh.policy.validationCommands" } : entry).filter((entry) => entry && typeof entry.command === "string").map((entry) => {
45556
+ const commandText = entry.command.trim();
45557
+ const category = REFINE_VALIDATION_CATEGORIES.find((cat) => commandText.includes(` ${cat}`)) ?? "";
45558
+ return { command: commandText, category, source: "mesh.policy.validationCommands" };
45559
+ }).filter((entry) => !!entry.category);
45560
+ }
45561
+ function selectMeshRefineValidationCommands(mesh, workspace) {
45562
+ const scripts = readPackageScripts(workspace);
45563
+ const rejectedCommands = [];
45564
+ const selected = [];
45565
+ const seen = /* @__PURE__ */ new Set();
45566
+ const candidates = [
45567
+ ...collectPolicyValidationCandidates(mesh),
45568
+ ...collectProjectContextValidationCandidates(mesh)
45569
+ ];
45570
+ for (const candidate of candidates) {
45571
+ const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
45572
+ if (parsed.rejected) {
45573
+ rejectedCommands.push(parsed.rejected);
45574
+ continue;
45575
+ }
45576
+ if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
45577
+ selected.push(parsed.command);
45578
+ seen.add(parsed.command.displayCommand);
45579
+ if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
45580
+ }
45581
+ if (!selected.length && candidates.length === 0) {
45582
+ for (const category of REFINE_VALIDATION_CATEGORIES) {
45583
+ if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
45584
+ const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, "package.json:scripts");
45585
+ if (fallback.command && !seen.has(fallback.command.displayCommand)) {
45586
+ selected.push(fallback.command);
45587
+ seen.add(fallback.command.displayCommand);
45588
+ } else if (fallback.rejected) {
45589
+ rejectedCommands.push(fallback.rejected);
45590
+ }
45591
+ if (selected.length >= 2) break;
45592
+ }
45593
+ }
45594
+ return {
45595
+ commands: selected,
45596
+ rejectedCommands,
45597
+ source: selected.some((command) => command.source === "mesh.policy.validationCommands") ? "mesh_policy" : selected.some((command) => command.source !== "package.json:scripts") ? "project_context" : selected.length ? "package_json_scripts" : "unavailable"
45598
+ };
45599
+ }
45600
+ async function runMeshRefineValidationGate(mesh, workspace) {
45601
+ const { execFile: execFile3 } = await import("child_process");
45602
+ const { promisify: promisify3 } = await import("util");
45603
+ const execFileAsync3 = promisify3(execFile3);
45604
+ const selection = selectMeshRefineValidationCommands(mesh, workspace);
45605
+ const summary = {
45606
+ status: "skipped",
45607
+ required: true,
45608
+ commandsRun: [],
45609
+ rejectedCommands: selection.rejectedCommands,
45610
+ skippedReason: void 0,
45611
+ timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
45612
+ outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES
45613
+ };
45614
+ if (!selection.commands.length) {
45615
+ summary.skippedReason = "validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available";
45616
+ return summary;
45617
+ }
45618
+ for (const candidate of selection.commands) {
45619
+ const startedAt = Date.now();
45620
+ try {
45621
+ const result = await execFileAsync3(candidate.command, candidate.args, {
45622
+ cwd: workspace,
45623
+ encoding: "utf8",
45624
+ timeout: REFINE_VALIDATION_TIMEOUT_MS,
45625
+ maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
45626
+ env: { ...process.env, CI: process.env.CI || "1" }
45627
+ });
45628
+ summary.commandsRun.push({
45629
+ command: candidate.command,
45630
+ args: candidate.args,
45631
+ displayCommand: candidate.displayCommand,
45632
+ category: candidate.category,
45633
+ source: candidate.source,
45634
+ passed: true,
45635
+ exitCode: 0,
45636
+ durationMs: Date.now() - startedAt,
45637
+ stdout: truncateValidationOutput(result.stdout),
45638
+ stderr: truncateValidationOutput(result.stderr)
45639
+ });
45640
+ } catch (error48) {
45641
+ summary.commandsRun.push({
45642
+ command: candidate.command,
45643
+ args: candidate.args,
45644
+ displayCommand: candidate.displayCommand,
45645
+ category: candidate.category,
45646
+ source: candidate.source,
45647
+ passed: false,
45648
+ exitCode: typeof error48?.code === "number" ? error48.code : null,
45649
+ signal: typeof error48?.signal === "string" ? error48.signal : null,
45650
+ timedOut: error48?.killed === true || /timed out/i.test(String(error48?.message || "")),
45651
+ durationMs: Date.now() - startedAt,
45652
+ stdout: truncateValidationOutput(error48?.stdout),
45653
+ stderr: truncateValidationOutput(error48?.stderr || error48?.message)
45654
+ });
45655
+ summary.status = "failed";
45656
+ return summary;
45657
+ }
45658
+ }
45659
+ summary.status = "passed";
45660
+ return summary;
45661
+ }
45049
45662
  function loadYamlModule() {
45050
45663
  return yaml;
45051
45664
  }
@@ -45329,20 +45942,98 @@ ${(0, import_node_path.resolve)(workspace || os17.tmpdir())}`;
45329
45942
  recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
45330
45943
  };
45331
45944
  }
45945
+ const forceFallbackConvergence = await this.getWorktreeForceCleanupConvergence({
45946
+ repoRoot,
45947
+ workspace,
45948
+ node: args.node
45949
+ });
45332
45950
  try {
45333
- const result = await removeWorktree2(repoRoot, workspace, { requireClean: true });
45334
- return { success: true, removedPath: result.removedPath, repoRoot };
45951
+ const result = await removeWorktree2(repoRoot, workspace, {
45952
+ requireClean: true,
45953
+ allowSubmoduleForceFallback: forceFallbackConvergence.allow
45954
+ });
45955
+ return {
45956
+ success: true,
45957
+ removedPath: result.removedPath,
45958
+ repoRoot,
45959
+ ...result.fallback ? {
45960
+ fallback: result.fallback,
45961
+ forced: result.forced,
45962
+ reason: result.reason,
45963
+ convergence: forceFallbackConvergence
45964
+ } : {}
45965
+ };
45335
45966
  } catch (e) {
45336
45967
  const message = String(e?.message || e || "worktree cleanup failed");
45337
45968
  const dirty = message.includes("dirty worktree") || message.includes("local changes");
45969
+ const submoduleForceBlocked = /working trees containing submodules cannot be moved or removed/i.test(message) && !forceFallbackConvergence.allow;
45338
45970
  return {
45339
45971
  success: false,
45340
- code: dirty ? "mesh_worktree_cleanup_dirty" : "mesh_worktree_cleanup_failed",
45341
- error: message,
45342
- recoveryHint: dirty ? "Commit, stash, or intentionally discard the worktree changes before retrying mesh_remove_node. The mesh registry entry is preserved until cleanup is safe." : "Inspect git worktree status/list from the source repo and retry after resolving the reported cleanup failure."
45972
+ code: dirty ? "mesh_worktree_cleanup_dirty" : submoduleForceBlocked ? "mesh_worktree_cleanup_force_fallback_blocked" : "mesh_worktree_cleanup_failed",
45973
+ error: submoduleForceBlocked ? `${message}; refusing --force fallback because convergence could not be verified: ${forceFallbackConvergence.error || "unknown convergence state"}` : message,
45974
+ recoveryHint: dirty ? "Commit, stash, or intentionally discard the worktree changes before retrying mesh_remove_node. The mesh registry entry is preserved until cleanup is safe." : submoduleForceBlocked ? "Verify the worktree branch is merged/contained in the source default branch (for example origin/main) or mark the node with a safe branchConvergence final state before retrying. The mesh registry entry is preserved." : "Inspect git worktree status/list from the source repo and retry after resolving the reported cleanup failure.",
45975
+ ...submoduleForceBlocked ? { convergence: forceFallbackConvergence } : {}
45343
45976
  };
45344
45977
  }
45345
45978
  }
45979
+ async getWorktreeForceCleanupConvergence(args) {
45980
+ const metadataStatus = typeof args.node?.branchConvergence?.status === "string" ? args.node.branchConvergence.status : "";
45981
+ if (metadataStatus === "merged_to_main" || metadataStatus === "cleanup_candidate") {
45982
+ return { allow: true, status: metadataStatus, source: "node_branch_convergence" };
45983
+ }
45984
+ const { execFile: execFile3 } = await import("child_process");
45985
+ const { promisify: promisify3 } = await import("util");
45986
+ const execFileAsync3 = promisify3(execFile3);
45987
+ const runGit2 = async (gitArgs, cwd) => {
45988
+ const { stdout } = await execFileAsync3("git", gitArgs, {
45989
+ cwd,
45990
+ encoding: "utf8",
45991
+ timeout: 3e4,
45992
+ maxBuffer: 4 * 1024 * 1024,
45993
+ windowsHide: true
45994
+ });
45995
+ return String(stdout || "").trim();
45996
+ };
45997
+ let head = "";
45998
+ try {
45999
+ head = await runGit2(["rev-parse", "HEAD"], args.workspace);
46000
+ } catch (e) {
46001
+ return { allow: false, error: `could not resolve worktree HEAD: ${e?.message || e}` };
46002
+ }
46003
+ if (!head) return { allow: false, error: "worktree HEAD is empty" };
46004
+ const candidateRefs = [];
46005
+ try {
46006
+ const defaultBranch = await runGit2(["branch", "--show-current"], args.repoRoot);
46007
+ if (defaultBranch) {
46008
+ candidateRefs.push(defaultBranch, `origin/${defaultBranch}`);
46009
+ }
46010
+ } catch {
46011
+ }
46012
+ candidateRefs.push("origin/main", "origin/master", "main", "master");
46013
+ const seen = /* @__PURE__ */ new Set();
46014
+ const checkedRefs = [];
46015
+ for (const ref of candidateRefs) {
46016
+ if (!ref || seen.has(ref)) continue;
46017
+ seen.add(ref);
46018
+ let commit = "";
46019
+ try {
46020
+ commit = await runGit2(["rev-parse", "--verify", `${ref}^{commit}`], args.repoRoot);
46021
+ } catch {
46022
+ continue;
46023
+ }
46024
+ checkedRefs.push(ref);
46025
+ try {
46026
+ await runGit2(["merge-base", "--is-ancestor", head, commit], args.repoRoot);
46027
+ return { allow: true, status: "merged_to_default_ref", source: "git_merge_base", ref };
46028
+ } catch {
46029
+ }
46030
+ }
46031
+ return {
46032
+ allow: false,
46033
+ status: metadataStatus || void 0,
46034
+ error: checkedRefs.length ? `worktree HEAD is not contained in checked refs: ${checkedRefs.join(", ")}` : "no default/main refs were available for convergence verification"
46035
+ };
46036
+ }
45346
46037
  isCompletedHostedSession(record2) {
45347
46038
  return record2?.lifecycle === "stopped" || record2?.lifecycle === "failed" || record2?.lifecycle === "interrupted";
45348
46039
  }
@@ -46302,10 +46993,61 @@ ${(0, import_node_path.resolve)(workspace || os17.tmpdir())}`;
46302
46993
  if (!branch) return { success: false, error: "Could not determine branch of the worktree node" };
46303
46994
  const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
46304
46995
  const baseBranch = baseBranchStdout.trim();
46996
+ const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
46997
+ if (validationSummary.status === "failed") {
46998
+ return {
46999
+ success: false,
47000
+ code: "validation_failed",
47001
+ convergenceStatus: "blocked_review",
47002
+ error: "Refinery validation gate failed; merge/refine was not attempted.",
47003
+ branch,
47004
+ into: baseBranch,
47005
+ validationSummary,
47006
+ finalBranchConvergenceState: {
47007
+ branch,
47008
+ baseBranch,
47009
+ merged: false,
47010
+ removed: false,
47011
+ validation: "failed",
47012
+ status: "blocked_review"
47013
+ }
47014
+ };
47015
+ }
47016
+ if (validationSummary.status === "skipped") {
47017
+ return {
47018
+ success: false,
47019
+ code: "validation_unavailable",
47020
+ convergenceStatus: "blocked_review",
47021
+ error: "Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.",
47022
+ branch,
47023
+ into: baseBranch,
47024
+ validationSummary,
47025
+ finalBranchConvergenceState: {
47026
+ branch,
47027
+ baseBranch,
47028
+ merged: false,
47029
+ removed: false,
47030
+ validation: "unavailable",
47031
+ status: "blocked_review"
47032
+ }
47033
+ };
47034
+ }
46305
47035
  try {
46306
47036
  await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
46307
47037
  } catch (e) {
46308
- return { success: false, error: `Merge failed (conflicts?): ${e.message}` };
47038
+ return {
47039
+ success: false,
47040
+ error: `Merge failed (conflicts?): ${e.message}`,
47041
+ validationSummary,
47042
+ finalBranchConvergenceState: {
47043
+ branch,
47044
+ baseBranch,
47045
+ merged: false,
47046
+ removed: false,
47047
+ validation: "passed",
47048
+ status: "not_mergeable"
47049
+ }
47050
+ };
46309
47051
  }
46310
47052
  const removeResult = await this.execute("remove_mesh_node", {
46311
47053
  meshId,
@@ -46318,11 +47060,27 @@ ${(0, import_node_path.resolve)(workspace || os17.tmpdir())}`;
46318
47060
  appendLedgerEntry2(meshId, {
46319
47061
  kind: "node_removed",
46320
47062
  nodeId,
46321
- payload: { refined: true, mergedBranch: branch, into: baseBranch }
47063
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary }
46322
47064
  });
46323
47065
  } catch {
46324
47066
  }
46325
- return { success: true, merged: true, branch, into: baseBranch, removeResult };
47067
+ return {
47068
+ success: true,
47069
+ merged: true,
47070
+ branch,
47071
+ into: baseBranch,
47072
+ removeResult,
47073
+ validationSummary,
47074
+ finalBranchConvergenceState: {
47075
+ branch: baseBranch,
47076
+ mergedBranch: branch,
47077
+ baseBranch,
47078
+ merged: true,
47079
+ removed: removeResult?.success !== false,
47080
+ validation: "passed",
47081
+ status: removeResult?.success === false ? "merged_cleanup_failed" : "merged"
47082
+ }
47083
+ };
46326
47084
  } catch (e) {
46327
47085
  return { success: false, error: e.message };
46328
47086
  }
@@ -46377,7 +47135,10 @@ ${(0, import_node_path.resolve)(workspace || os17.tmpdir())}`;
46377
47135
  sessionCleanupMode,
46378
47136
  workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
46379
47137
  daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
46380
- worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0
47138
+ worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
47139
+ worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
47140
+ forced: worktreeCleanup?.forced === true ? true : void 0,
47141
+ forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
46381
47142
  }
46382
47143
  });
46383
47144
  } catch {
@@ -54460,6 +55221,7 @@ data: ${JSON.stringify(msg.data)}
54460
55221
  return false;
54461
55222
  }
54462
55223
  }
55224
+ init_cli_detector();
54463
55225
  var SessionRegistry = class {
54464
55226
  bySessionId = /* @__PURE__ */ new Map();
54465
55227
  byManagerKey = /* @__PURE__ */ new Map();