@adhdev/daemon-core 0.9.77-rc.44 → 0.9.77-rc.45

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
@@ -123,7 +123,33 @@ async function removeWorktree(repoRoot, worktreePath, opts = {}) {
123
123
  });
124
124
  } catch (error) {
125
125
  const stderr = typeof error.stderr === "string" ? error.stderr : "";
126
- throw new Error(`git worktree remove failed: ${stderr.trim() || error.message}`);
126
+ const stdout = typeof error.stdout === "string" ? error.stdout : "";
127
+ const detail = `${stderr}
128
+ ${stdout}
129
+ ${error.message || ""}`;
130
+ if (opts.allowSubmoduleForceFallback && SUBMODULE_WORKTREE_REMOVE_RE.test(detail)) {
131
+ try {
132
+ await execFileAsync2("git", ["worktree", "remove", "--force", worktreePath], {
133
+ cwd: repoRoot,
134
+ encoding: "utf8",
135
+ timeout: GIT_TIMEOUT_MS,
136
+ maxBuffer: GIT_MAX_BUFFER,
137
+ windowsHide: true
138
+ });
139
+ } catch (forceError) {
140
+ const forceStderr = typeof forceError.stderr === "string" ? forceError.stderr : "";
141
+ const forceStdout = typeof forceError.stdout === "string" ? forceError.stdout : "";
142
+ throw new Error(`git worktree remove --force fallback failed: ${forceStderr.trim() || forceStdout.trim() || forceError.message}`);
143
+ }
144
+ return {
145
+ success: true,
146
+ removedPath: worktreePath,
147
+ fallback: "git_worktree_remove_force_submodule",
148
+ forced: true,
149
+ reason: "working_trees_containing_submodules"
150
+ };
151
+ }
152
+ throw new Error(`git worktree remove failed: ${stderr.trim() || stdout.trim() || error.message}`);
127
153
  }
128
154
  return { success: true, removedPath: worktreePath };
129
155
  }
@@ -173,7 +199,7 @@ async function pruneWorktrees(repoRoot) {
173
199
  } catch {
174
200
  }
175
201
  }
176
- var path4, import_promises3, import_node_fs2, import_node_child_process2, import_node_util2, execFileAsync2, WORKTREE_DIR_NAME, GIT_TIMEOUT_MS, GIT_MAX_BUFFER;
202
+ var path4, import_promises3, import_node_fs2, import_node_child_process2, import_node_util2, execFileAsync2, WORKTREE_DIR_NAME, GIT_TIMEOUT_MS, GIT_MAX_BUFFER, SUBMODULE_WORKTREE_REMOVE_RE;
177
203
  var init_git_worktree = __esm({
178
204
  "src/git/git-worktree.ts"() {
179
205
  "use strict";
@@ -186,6 +212,7 @@ var init_git_worktree = __esm({
186
212
  WORKTREE_DIR_NAME = ".adhdev-worktrees";
187
213
  GIT_TIMEOUT_MS = 3e4;
188
214
  GIT_MAX_BUFFER = 4 * 1024 * 1024;
215
+ SUBMODULE_WORKTREE_REMOVE_RE = /working trees containing submodules cannot be moved or removed/i;
189
216
  }
190
217
  });
191
218
 
@@ -1009,6 +1036,7 @@ __export(mesh_work_queue_exports, {
1009
1036
  enqueueTask: () => enqueueTask,
1010
1037
  getMeshQueueStats: () => getMeshQueueStats,
1011
1038
  getQueue: () => getQueue,
1039
+ recordTaskAutoLaunch: () => recordTaskAutoLaunch,
1012
1040
  requeueTask: () => requeueTask,
1013
1041
  updateSessionTaskStatus: () => updateSessionTaskStatus,
1014
1042
  updateTaskStatus: () => updateTaskStatus
@@ -1084,6 +1112,19 @@ function updateTaskStatus(meshId, taskId, status) {
1084
1112
  writeQueue(meshId, queue);
1085
1113
  return queue[idx];
1086
1114
  }
1115
+ function recordTaskAutoLaunch(meshId, taskId, autoLaunch) {
1116
+ const queue = readQueue(meshId);
1117
+ const idx = queue.findIndex((q) => q.id === taskId);
1118
+ if (idx === -1) return null;
1119
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1120
+ queue[idx].autoLaunch = {
1121
+ ...autoLaunch,
1122
+ updatedAt: now
1123
+ };
1124
+ queue[idx].updatedAt = now;
1125
+ writeQueue(meshId, queue);
1126
+ return queue[idx];
1127
+ }
1087
1128
  function cancelTask(meshId, taskId, opts) {
1088
1129
  const queue = readQueue(meshId);
1089
1130
  const idx = queue.findIndex((q) => q.id === taskId);
@@ -1157,6 +1198,141 @@ var init_mesh_work_queue = __esm({
1157
1198
  }
1158
1199
  });
1159
1200
 
1201
+ // src/detection/cli-detector.ts
1202
+ function parseVersion(raw) {
1203
+ const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
1204
+ return match ? match[1] : raw.split("\n")[0].slice(0, 100);
1205
+ }
1206
+ function shellQuote(value) {
1207
+ if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
1208
+ return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
1209
+ }
1210
+ function expandHome(value) {
1211
+ const trimmed = value.trim();
1212
+ if (!trimmed.startsWith("~")) return trimmed;
1213
+ return path8.join(os2.homedir(), trimmed.slice(1));
1214
+ }
1215
+ function isExplicitCommandPath(command) {
1216
+ const trimmed = command.trim();
1217
+ return path8.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
1218
+ }
1219
+ function resolveCommandPath(command) {
1220
+ const trimmed = command.trim();
1221
+ if (!trimmed) return null;
1222
+ if (isExplicitCommandPath(trimmed)) {
1223
+ const expanded = expandHome(trimmed);
1224
+ const candidate = path8.isAbsolute(expanded) ? expanded : path8.resolve(expanded);
1225
+ return (0, import_fs5.existsSync)(candidate) ? candidate : null;
1226
+ }
1227
+ return null;
1228
+ }
1229
+ function execAsync(cmd, timeoutMs = 5e3) {
1230
+ return new Promise((resolve16) => {
1231
+ const child = (0, import_child_process.exec)(cmd, {
1232
+ encoding: "utf-8",
1233
+ timeout: timeoutMs,
1234
+ ...process.platform === "win32" ? { windowsHide: true } : {}
1235
+ }, (err, stdout) => {
1236
+ if (err || !stdout?.trim()) {
1237
+ resolve16(null);
1238
+ } else {
1239
+ resolve16(stdout.trim());
1240
+ }
1241
+ });
1242
+ child.on("error", () => resolve16(null));
1243
+ });
1244
+ }
1245
+ async function detectCLIs(providerLoader, options) {
1246
+ const platform10 = os2.platform();
1247
+ const whichCmd = platform10 === "win32" ? "where" : "which";
1248
+ const includeVersion = options?.includeVersion !== false;
1249
+ const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
1250
+ const results = await Promise.all(
1251
+ cliList.map(async (cli) => {
1252
+ try {
1253
+ const explicitPath = resolveCommandPath(cli.command);
1254
+ const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
1255
+ if (!pathResult) return { ...cli, installed: false };
1256
+ const firstPath = explicitPath || pathResult.split("\n")[0];
1257
+ let version;
1258
+ if (includeVersion) {
1259
+ const versionCommands = [
1260
+ `"${firstPath}" --version`,
1261
+ `"${firstPath}" -V`,
1262
+ `"${firstPath}" -v`,
1263
+ cli.versionCommand
1264
+ ].filter((v) => !!v);
1265
+ try {
1266
+ for (const versionCommand of versionCommands) {
1267
+ const versionResult = await execAsync(versionCommand, 3e3);
1268
+ if (versionResult) {
1269
+ version = parseVersion(versionResult);
1270
+ break;
1271
+ }
1272
+ }
1273
+ } catch {
1274
+ }
1275
+ }
1276
+ return { ...cli, installed: true, version, path: firstPath };
1277
+ } catch {
1278
+ return { ...cli, installed: false };
1279
+ }
1280
+ })
1281
+ );
1282
+ return results;
1283
+ }
1284
+ async function detectCLI(cliId, providerLoader, options) {
1285
+ const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
1286
+ if (providerLoader) {
1287
+ const cliList = providerLoader.getCliDetectionList();
1288
+ const target = cliList.find((c) => c.id === resolvedId);
1289
+ if (target) {
1290
+ const platform10 = os2.platform();
1291
+ const whichCmd = platform10 === "win32" ? "where" : "which";
1292
+ try {
1293
+ const explicitPath = resolveCommandPath(target.command);
1294
+ const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
1295
+ if (!pathResult) return null;
1296
+ const firstPath = explicitPath || pathResult.split("\n")[0];
1297
+ let version;
1298
+ if (options?.includeVersion !== false) {
1299
+ const versionCommands = [
1300
+ `"${firstPath}" --version`,
1301
+ `"${firstPath}" -V`,
1302
+ `"${firstPath}" -v`,
1303
+ target.versionCommand
1304
+ ].filter((v) => !!v);
1305
+ try {
1306
+ for (const versionCommand of versionCommands) {
1307
+ const versionResult = await execAsync(versionCommand, 3e3);
1308
+ if (versionResult) {
1309
+ version = parseVersion(versionResult);
1310
+ break;
1311
+ }
1312
+ }
1313
+ } catch {
1314
+ }
1315
+ }
1316
+ return { ...target, installed: true, version, path: firstPath };
1317
+ } catch {
1318
+ return null;
1319
+ }
1320
+ }
1321
+ }
1322
+ const all = await detectCLIs(providerLoader, options);
1323
+ return all.find((c) => c.id === resolvedId && c.installed) || null;
1324
+ }
1325
+ var import_child_process, os2, path8, import_fs5;
1326
+ var init_cli_detector = __esm({
1327
+ "src/detection/cli-detector.ts"() {
1328
+ "use strict";
1329
+ import_child_process = require("child_process");
1330
+ os2 = __toESM(require("os"));
1331
+ path8 = __toESM(require("path"));
1332
+ import_fs5 = require("fs");
1333
+ }
1334
+ });
1335
+
1160
1336
  // src/logging/logger.ts
1161
1337
  function setLogLevel(level) {
1162
1338
  currentLevel = level;
@@ -1172,13 +1348,13 @@ function getDaemonLogDir() {
1172
1348
  return LOG_DIR;
1173
1349
  }
1174
1350
  function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
1175
- return path8.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
1351
+ return path9.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
1176
1352
  }
1177
1353
  function checkDateRotation() {
1178
1354
  const today = getDateStr();
1179
1355
  if (today !== currentDate) {
1180
1356
  currentDate = today;
1181
- currentLogFile = path8.join(LOG_DIR, `daemon-${currentDate}.log`);
1357
+ currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
1182
1358
  cleanOldLogs();
1183
1359
  }
1184
1360
  }
@@ -1192,7 +1368,7 @@ function cleanOldLogs() {
1192
1368
  const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
1193
1369
  if (dateMatch && dateMatch[1] < cutoffStr) {
1194
1370
  try {
1195
- fs2.unlinkSync(path8.join(LOG_DIR, file));
1371
+ fs2.unlinkSync(path9.join(LOG_DIR, file));
1196
1372
  } catch {
1197
1373
  }
1198
1374
  }
@@ -1308,17 +1484,17 @@ function installGlobalInterceptor() {
1308
1484
  writeToFile(`Log file: ${currentLogFile}`);
1309
1485
  writeToFile(`Log level: ${currentLevel}`);
1310
1486
  }
1311
- var fs2, path8, os2, 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;
1487
+ var fs2, path9, os3, 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;
1312
1488
  var init_logger = __esm({
1313
1489
  "src/logging/logger.ts"() {
1314
1490
  "use strict";
1315
1491
  fs2 = __toESM(require("fs"));
1316
- path8 = __toESM(require("path"));
1317
- os2 = __toESM(require("os"));
1492
+ path9 = __toESM(require("path"));
1493
+ os3 = __toESM(require("os"));
1318
1494
  LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
1319
1495
  LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
1320
1496
  currentLevel = "info";
1321
- LOG_DIR = process.platform === "win32" ? path8.join(process.env.LOCALAPPDATA || process.env.APPDATA || path8.join(os2.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path8.join(os2.homedir(), "Library", "Logs", "adhdev") : path8.join(os2.homedir(), ".local", "share", "adhdev", "logs");
1497
+ LOG_DIR = process.platform === "win32" ? path9.join(process.env.LOCALAPPDATA || process.env.APPDATA || path9.join(os3.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path9.join(os3.homedir(), "Library", "Logs", "adhdev") : path9.join(os3.homedir(), ".local", "share", "adhdev", "logs");
1322
1498
  MAX_LOG_SIZE = 5 * 1024 * 1024;
1323
1499
  MAX_LOG_DAYS = 7;
1324
1500
  try {
@@ -1326,16 +1502,16 @@ var init_logger = __esm({
1326
1502
  } catch {
1327
1503
  }
1328
1504
  currentDate = getDateStr();
1329
- currentLogFile = path8.join(LOG_DIR, `daemon-${currentDate}.log`);
1505
+ currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
1330
1506
  cleanOldLogs();
1331
1507
  try {
1332
- const oldLog = path8.join(LOG_DIR, "daemon.log");
1508
+ const oldLog = path9.join(LOG_DIR, "daemon.log");
1333
1509
  if (fs2.existsSync(oldLog)) {
1334
1510
  const stat2 = fs2.statSync(oldLog);
1335
1511
  const oldDate = stat2.mtime.toISOString().slice(0, 10);
1336
- fs2.renameSync(oldLog, path8.join(LOG_DIR, `daemon-${oldDate}.log`));
1512
+ fs2.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
1337
1513
  }
1338
- const oldLogBackup = path8.join(LOG_DIR, "daemon.log.old");
1514
+ const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
1339
1515
  if (fs2.existsSync(oldLogBackup)) {
1340
1516
  fs2.unlinkSync(oldLogBackup);
1341
1517
  }
@@ -1367,7 +1543,7 @@ var init_logger = __esm({
1367
1543
  }
1368
1544
  };
1369
1545
  interceptorInstalled = false;
1370
- LOG_PATH = path8.join(LOG_DIR, `daemon-${getDateStr()}.log`);
1546
+ LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
1371
1547
  }
1372
1548
  });
1373
1549
 
@@ -1439,7 +1615,235 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
1439
1615
  });
1440
1616
  return true;
1441
1617
  }
1442
- function triggerMeshQueue(components, meshId) {
1618
+ function normalizeProviderPriority(policy) {
1619
+ const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
1620
+ if (!Array.isArray(raw)) return [];
1621
+ const seen = /* @__PURE__ */ new Set();
1622
+ return raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean).filter((type) => {
1623
+ if (seen.has(type)) return false;
1624
+ seen.add(type);
1625
+ return true;
1626
+ });
1627
+ }
1628
+ function isTerminalSessionStatus(status) {
1629
+ return ["stopped", "failed", "terminated", "exited", "closed"].includes(status);
1630
+ }
1631
+ function isIdleSessionState(state) {
1632
+ const status = readNonEmptyString(state?.status).toLowerCase();
1633
+ if (isTerminalSessionStatus(status)) return false;
1634
+ return status === "idle" || state?.activeChat?.status === "waiting_input";
1635
+ }
1636
+ function isDirtyNode(node) {
1637
+ return node?.health === "dirty" || node?.git?.dirty === true;
1638
+ }
1639
+ function isLaunchableNode(node) {
1640
+ if (!node || node.status === "disabled" || node.status === "removed") return false;
1641
+ const health = readNonEmptyString(node.health).toLowerCase();
1642
+ if (!health) return true;
1643
+ return health === "online" || health === "unknown";
1644
+ }
1645
+ function localAutoLaunchSkipReason(node) {
1646
+ const daemonId = readNonEmptyString(node?.daemonId);
1647
+ const machineId = readNonEmptyString(node?.machineId);
1648
+ const appConfig = loadConfig();
1649
+ const localMachineId = readNonEmptyString(appConfig.machineId) || readNonEmptyString(appConfig.registeredMachineId);
1650
+ const cloudDaemonId = localMachineId ? `daemon_${localMachineId}` : "";
1651
+ const standaloneDaemonId = localMachineId ? `standalone_${localMachineId}` : "";
1652
+ const daemonMatchesLocal = !daemonId || daemonId === cloudDaemonId || daemonId === standaloneDaemonId;
1653
+ const machineMatchesLocal = !machineId || localMachineId && machineId === localMachineId;
1654
+ if (node?.isLocalWorktree === true) {
1655
+ return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
1656
+ }
1657
+ if (daemonId || machineId) {
1658
+ return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
1659
+ }
1660
+ return null;
1661
+ }
1662
+ function activeAssignedCount(meshId) {
1663
+ return getQueue(meshId, { status: ["assigned"] }).length;
1664
+ }
1665
+ function nodeHasActiveAssignment(meshId, nodeId) {
1666
+ return getQueue(meshId, { status: ["assigned"] }).some((task) => task.assignedNodeId === nodeId);
1667
+ }
1668
+ function liveSessionCountForNode(components, meshId, nodeId) {
1669
+ return components.instanceManager.getByCategory("cli").filter((inst) => {
1670
+ const state = inst.getState();
1671
+ const settings = state.settings || {};
1672
+ if (readNonEmptyString(settings.meshNodeFor) !== meshId) return false;
1673
+ const instNodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
1674
+ if (instNodeId !== nodeId) return false;
1675
+ const status = readNonEmptyString(state.status).toLowerCase();
1676
+ return !isTerminalSessionStatus(status);
1677
+ }).length;
1678
+ }
1679
+ function recordAutoLaunchEvent(meshId, args) {
1680
+ try {
1681
+ appendLedgerEntry(meshId, {
1682
+ kind: "session_auto_launch",
1683
+ nodeId: args.nodeId,
1684
+ sessionId: args.sessionId,
1685
+ providerType: args.providerType,
1686
+ payload: {
1687
+ phase: args.phase,
1688
+ taskId: args.taskId,
1689
+ reason: args.reason,
1690
+ error: args.error
1691
+ }
1692
+ });
1693
+ } catch (e) {
1694
+ LOG.warn("MeshQueue", `Failed to record auto-launch ledger event: ${e?.message || e}`);
1695
+ }
1696
+ }
1697
+ function markAutoLaunch(meshId, taskId, args) {
1698
+ recordTaskAutoLaunch(meshId, taskId, {
1699
+ status: args.status,
1700
+ reason: args.reason || args.error,
1701
+ nodeId: args.nodeId,
1702
+ providerType: args.providerType,
1703
+ sessionId: args.sessionId
1704
+ });
1705
+ recordAutoLaunchEvent(meshId, {
1706
+ phase: args.status,
1707
+ taskId,
1708
+ nodeId: args.nodeId,
1709
+ providerType: args.providerType,
1710
+ sessionId: args.sessionId,
1711
+ reason: args.reason,
1712
+ error: args.error
1713
+ });
1714
+ }
1715
+ async function resolveUsableProvider(components, nodeId, node) {
1716
+ const providerPriority = normalizeProviderPriority(node?.policy);
1717
+ if (!providerPriority.length) return { reason: "missing_provider_priority" };
1718
+ const providerLoader = components.providerLoader;
1719
+ if (!providerLoader) return { reason: "provider_loader_unavailable" };
1720
+ const failed = [];
1721
+ for (const requestedType of providerPriority) {
1722
+ const normalizedType = typeof providerLoader.resolveAlias === "function" ? providerLoader.resolveAlias(requestedType) : requestedType;
1723
+ if (typeof providerLoader.isMachineProviderEnabled === "function" && !providerLoader.isMachineProviderEnabled(normalizedType)) {
1724
+ failed.push(`${requestedType}: disabled`);
1725
+ continue;
1726
+ }
1727
+ let detected;
1728
+ try {
1729
+ detected = await detectCLI(normalizedType, providerLoader, { includeVersion: false });
1730
+ } catch (e) {
1731
+ failed.push(`${requestedType}: detect failed: ${e?.message || e}`);
1732
+ continue;
1733
+ }
1734
+ if (typeof providerLoader.setCliDetectionResults === "function") {
1735
+ providerLoader.setCliDetectionResults([{
1736
+ id: normalizedType,
1737
+ installed: !!detected,
1738
+ path: detected?.path
1739
+ }], false);
1740
+ }
1741
+ components.onStatusChange?.();
1742
+ if (detected) return { providerType: normalizedType };
1743
+ failed.push(`${requestedType}: not detected`);
1744
+ }
1745
+ return { reason: `provider_priority_unusable: ${failed.join("; ") || nodeId}` };
1746
+ }
1747
+ async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
1748
+ const queue = getQueue(meshId);
1749
+ const pending = queue.filter((task) => task.status === "pending");
1750
+ if (!pending.length) return false;
1751
+ const maxParallelTasks = Math.max(1, Math.floor(Number(mesh?.policy?.maxParallelTasks) || 2));
1752
+ for (const task of pending) {
1753
+ if (activeAssignedCount(meshId) >= maxParallelTasks) {
1754
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_parallel_tasks_reached" });
1755
+ return false;
1756
+ }
1757
+ if (task.targetSessionId) {
1758
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_constraint" });
1759
+ continue;
1760
+ }
1761
+ const candidateNodes = Array.isArray(mesh?.nodes) ? mesh.nodes.filter((node) => task.targetNodeId ? node?.id === task.targetNodeId : true) : [];
1762
+ if (!candidateNodes.length) {
1763
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "no_matching_node", nodeId: task.targetNodeId });
1764
+ continue;
1765
+ }
1766
+ for (const node of candidateNodes) {
1767
+ const nodeId = readNonEmptyString(node?.id);
1768
+ if (!nodeId) continue;
1769
+ const launchKey = `${meshId}:${nodeId}`;
1770
+ const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
1771
+ if (autoLaunchInProgress.has(launchKey)) {
1772
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_in_progress", nodeId });
1773
+ continue;
1774
+ }
1775
+ if (Date.now() < cooldownUntil) {
1776
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_cooldown", nodeId });
1777
+ continue;
1778
+ }
1779
+ if (isDirtyNode(node)) {
1780
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "dirty_workspace", nodeId });
1781
+ continue;
1782
+ }
1783
+ if (!isLaunchableNode(node)) {
1784
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_not_launch_ready", nodeId });
1785
+ continue;
1786
+ }
1787
+ const localSkipReason = localAutoLaunchSkipReason(node);
1788
+ if (localSkipReason) {
1789
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: localSkipReason, nodeId });
1790
+ continue;
1791
+ }
1792
+ if (nodeHasActiveAssignment(meshId, nodeId)) {
1793
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_has_active_assignment", nodeId });
1794
+ continue;
1795
+ }
1796
+ const maxConcurrentSessions = Number(node?.policy?.maxConcurrentSessions);
1797
+ if (Number.isFinite(maxConcurrentSessions) && maxConcurrentSessions >= 0 && liveSessionCountForNode(components, meshId, nodeId) >= maxConcurrentSessions) {
1798
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_concurrent_sessions_reached", nodeId });
1799
+ continue;
1800
+ }
1801
+ autoLaunchInProgress.add(launchKey);
1802
+ try {
1803
+ const resolved = await resolveUsableProvider(components, nodeId, node);
1804
+ if (!resolved.providerType) {
1805
+ markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
1806
+ continue;
1807
+ }
1808
+ markAutoLaunch(meshId, task.id, { status: "started", nodeId, providerType: resolved.providerType });
1809
+ const launchResult = await components.cliManager.handleCliCommand("launch_cli", {
1810
+ cliType: resolved.providerType,
1811
+ dir: node.workspace,
1812
+ settings: {
1813
+ meshNodeFor: meshId,
1814
+ meshNodeId: nodeId,
1815
+ spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || "hidden",
1816
+ launchedByCoordinator: true,
1817
+ autoLaunchedForQueueTaskId: task.id
1818
+ }
1819
+ });
1820
+ if (!launchResult?.success) {
1821
+ const reason = launchResult?.error || "launch_cli_failed";
1822
+ markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
1823
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
1824
+ return false;
1825
+ }
1826
+ const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
1827
+ if (!sessionId) {
1828
+ markAutoLaunch(meshId, task.id, { status: "failed", reason: "launch_missing_session_id", nodeId, providerType: resolved.providerType });
1829
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
1830
+ return false;
1831
+ }
1832
+ markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId });
1833
+ tryAssignQueueTask(components, meshId, nodeId, sessionId, resolved.providerType);
1834
+ return true;
1835
+ } catch (e) {
1836
+ markAutoLaunch(meshId, task.id, { status: "failed", error: e?.message || String(e), nodeId });
1837
+ autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
1838
+ return false;
1839
+ } finally {
1840
+ autoLaunchInProgress.delete(launchKey);
1841
+ }
1842
+ }
1843
+ }
1844
+ return false;
1845
+ }
1846
+ async function triggerMeshQueue(components, meshId) {
1443
1847
  const mesh = getMeshWithCache(components, meshId);
1444
1848
  if (!mesh) return;
1445
1849
  const cliInstances = components.instanceManager.getByCategory("cli");
@@ -1450,9 +1854,7 @@ function triggerMeshQueue(components, meshId) {
1450
1854
  if (instMeshId !== meshId) continue;
1451
1855
  const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
1452
1856
  if (!nodeId) continue;
1453
- const status = readNonEmptyString(state.status).toLowerCase();
1454
- if (["stopped", "failed", "terminated", "exited", "closed"].includes(status)) continue;
1455
- if (status !== "idle" && state.activeChat?.status !== "waiting_input") continue;
1857
+ if (!isIdleSessionState(state)) continue;
1456
1858
  const sessionId = state.instanceId;
1457
1859
  const providerType = state.type || readNonEmptyString(settings.providerType);
1458
1860
  if (providerType) {
@@ -1468,6 +1870,7 @@ function triggerMeshQueue(components, meshId) {
1468
1870
  }
1469
1871
  }
1470
1872
  }
1873
+ await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
1471
1874
  }
1472
1875
  function buildMeshSystemMessage(args) {
1473
1876
  const metadata = formatCompletionMetadata(args.metadataEvent);
@@ -1744,11 +2147,13 @@ function setupMeshEventForwarding(components) {
1744
2147
  });
1745
2148
  });
1746
2149
  }
1747
- var remoteIdleSessions, MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND;
2150
+ var remoteIdleSessions, MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS;
1748
2151
  var init_mesh_events = __esm({
1749
2152
  "src/mesh/mesh-events.ts"() {
1750
2153
  "use strict";
2154
+ init_config();
1751
2155
  init_mesh_config();
2156
+ init_cli_detector();
1752
2157
  init_logger();
1753
2158
  init_mesh_ledger();
1754
2159
  init_mesh_work_queue();
@@ -1769,6 +2174,9 @@ var init_mesh_events = __esm({
1769
2174
  "agent:stopped": "task_failed",
1770
2175
  "monitor:long_generating": "task_stalled"
1771
2176
  };
2177
+ autoLaunchInProgress = /* @__PURE__ */ new Set();
2178
+ autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
2179
+ AUTO_LAUNCH_COOLDOWN_MS = 5e3;
1772
2180
  }
1773
2181
  });
1774
2182
 
@@ -4892,6 +5300,7 @@ __export(index_exports, {
4892
5300
  MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS: () => MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
4893
5301
  MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS: () => MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
4894
5302
  NodePtyTransportFactory: () => NodePtyTransportFactory,
5303
+ P2pRelayFailureError: () => P2pRelayFailureError,
4895
5304
  ProviderCliAdapter: () => ProviderCliAdapter,
4896
5305
  ProviderInstanceManager: () => ProviderInstanceManager,
4897
5306
  ProviderLoader: () => ProviderLoader,
@@ -4908,6 +5317,7 @@ __export(index_exports, {
4908
5317
  buildChatTailDeliverySignature: () => buildChatTailDeliverySignature,
4909
5318
  buildCoordinatorSystemPrompt: () => buildCoordinatorSystemPrompt,
4910
5319
  buildMachineInfo: () => buildMachineInfo,
5320
+ buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
4911
5321
  buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
4912
5322
  buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
4913
5323
  buildSessionEntries: () => buildSessionEntries,
@@ -4922,6 +5332,7 @@ __export(index_exports, {
4922
5332
  claimNextTask: () => claimNextTask,
4923
5333
  classifyChatMessageVisibility: () => classifyChatMessageVisibility,
4924
5334
  classifyHotChatSessionsForSubscriptionFlush: () => classifyHotChatSessionsForSubscriptionFlush,
5335
+ classifyP2pRelayFailure: () => classifyP2pRelayFailure,
4925
5336
  clearDebugTrace: () => clearDebugTrace,
4926
5337
  compareGitSnapshots: () => compareGitSnapshots,
4927
5338
  configureDebugTraceStore: () => configureDebugTraceStore,
@@ -4989,6 +5400,7 @@ __export(index_exports, {
4989
5400
  isInternalChatMessage: () => isInternalChatMessage,
4990
5401
  isManagedStatusWaiting: () => isManagedStatusWaiting,
4991
5402
  isManagedStatusWorking: () => isManagedStatusWorking,
5403
+ isP2pRelayTransportFailure: () => isP2pRelayTransportFailure,
4992
5404
  isPathInside: () => isPathInside,
4993
5405
  isSessionHostLiveRuntime: () => isSessionHostLiveRuntime,
4994
5406
  isSessionHostRecoverySnapshot: () => isSessionHostRecoverySnapshot,
@@ -6954,8 +7366,118 @@ init_mesh_ledger();
6954
7366
  init_mesh_work_queue();
6955
7367
  init_mesh_events();
6956
7368
 
7369
+ // src/mesh/p2p-relay-failure.ts
7370
+ var NO_FALLBACK_REASON = "Repo Mesh command/data-plane is P2P-only; WS/REST command fallback is intentionally disabled to preserve the transport boundary.";
7371
+ 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.";
7372
+ var NON_P2P_NEXT_ACTION = "Inspect the provider/command error and fix the underlying logic or configuration before retrying.";
7373
+ function messageFromError(error) {
7374
+ if (error instanceof Error) return error.message;
7375
+ if (typeof error === "string") return error;
7376
+ if (error && typeof error === "object") {
7377
+ const candidate = error.error ?? error.message ?? error.reason;
7378
+ if (typeof candidate === "string") return candidate;
7379
+ }
7380
+ return String(error || "mesh relay command failed");
7381
+ }
7382
+ function classifyP2pRelayFailure(error, _context = {}) {
7383
+ const message = messageFromError(error);
7384
+ const lower = message.toLowerCase();
7385
+ const hasP2pSignal = /p2p|datachannel|node-datachannel|webrtc|ice|mesh_relay_command|daemon_mesh_p2p_transport/i.test(message);
7386
+ 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);
7387
+ if (/requires targetdaemonid and command|providerpriority|no inference provider|permission denied|read-only|not a member/i.test(message)) {
7388
+ return {
7389
+ code: "mesh_logic_or_provider_failure",
7390
+ reason: "mesh_logic_or_provider_failure",
7391
+ transport: "unknown",
7392
+ recoverable: false,
7393
+ retryRecommended: false,
7394
+ nextAction: NON_P2P_NEXT_ACTION,
7395
+ noFallbackReason: NO_FALLBACK_REASON
7396
+ };
7397
+ }
7398
+ let code = null;
7399
+ let reason = "";
7400
+ if (/timeout|timed out/i.test(message) && (hasP2pSignal || /mesh transport/i.test(message))) {
7401
+ code = "p2p_timeout";
7402
+ reason = "daemon_mesh_p2p_timeout";
7403
+ } else if (/no route|route unavailable/i.test(message)) {
7404
+ code = "p2p_no_route";
7405
+ reason = "daemon_mesh_p2p_no_route";
7406
+ } else if (/offline|not owned|not found|not connected to server/i.test(message) && /daemon|peer|target/i.test(message)) {
7407
+ code = "p2p_daemon_offline";
7408
+ reason = "daemon_mesh_target_offline";
7409
+ } else if (/closed|disconnected/i.test(message) && (hasP2pSignal || /state changed/i.test(message))) {
7410
+ code = "p2p_datachannel_closed";
7411
+ reason = "daemon_mesh_p2p_datachannel_closed";
7412
+ } else if (/not connected|cannot send|cannot establish/i.test(message) && hasP2pSignal) {
7413
+ code = "p2p_not_connected";
7414
+ reason = "daemon_mesh_p2p_not_connected";
7415
+ } else if (hasP2pSignal && hasFailureSignal) {
7416
+ code = "p2p_unavailable";
7417
+ reason = "daemon_mesh_p2p_transport_unavailable";
7418
+ }
7419
+ if (!code) {
7420
+ return {
7421
+ code: "mesh_logic_or_provider_failure",
7422
+ reason: "mesh_logic_or_provider_failure",
7423
+ transport: "unknown",
7424
+ recoverable: false,
7425
+ retryRecommended: false,
7426
+ nextAction: NON_P2P_NEXT_ACTION,
7427
+ noFallbackReason: NO_FALLBACK_REASON
7428
+ };
7429
+ }
7430
+ return {
7431
+ code,
7432
+ reason,
7433
+ transport: "p2p",
7434
+ recoverable: true,
7435
+ retryRecommended: true,
7436
+ nextAction: P2P_NEXT_ACTION,
7437
+ noFallbackReason: NO_FALLBACK_REASON
7438
+ };
7439
+ }
7440
+ function isP2pRelayTransportFailure(error) {
7441
+ return classifyP2pRelayFailure(error).recoverable === true;
7442
+ }
7443
+ function buildP2pRelayFailurePayload(error, context = {}) {
7444
+ const classification = classifyP2pRelayFailure(error, context);
7445
+ return {
7446
+ success: false,
7447
+ ...classification,
7448
+ error: messageFromError(error),
7449
+ ...context.command ? { command: context.command } : {},
7450
+ ...context.targetDaemonId ? { targetDaemonId: context.targetDaemonId } : {}
7451
+ };
7452
+ }
7453
+ var P2pRelayFailureError = class extends Error {
7454
+ code;
7455
+ reason;
7456
+ transport;
7457
+ recoverable;
7458
+ retryRecommended;
7459
+ nextAction;
7460
+ noFallbackReason;
7461
+ command;
7462
+ targetDaemonId;
7463
+ constructor(message, context = {}) {
7464
+ super(message);
7465
+ this.name = "P2pRelayFailureError";
7466
+ const payload = buildP2pRelayFailurePayload(message, context);
7467
+ this.code = payload.code;
7468
+ this.reason = payload.reason;
7469
+ this.transport = payload.transport;
7470
+ this.recoverable = payload.recoverable;
7471
+ this.retryRecommended = payload.retryRecommended;
7472
+ this.nextAction = payload.nextAction;
7473
+ this.noFallbackReason = payload.noFallbackReason;
7474
+ this.command = context.command;
7475
+ this.targetDaemonId = context.targetDaemonId;
7476
+ }
7477
+ };
7478
+
6957
7479
  // src/config/state-store.ts
6958
- var import_fs5 = require("fs");
7480
+ var import_fs6 = require("fs");
6959
7481
  var import_path5 = require("path");
6960
7482
  init_config();
6961
7483
  var DEFAULT_STATE = {
@@ -7006,11 +7528,11 @@ function normalizeState(raw) {
7006
7528
  }
7007
7529
  function loadState() {
7008
7530
  const statePath = getStatePath();
7009
- if (!(0, import_fs5.existsSync)(statePath)) {
7531
+ if (!(0, import_fs6.existsSync)(statePath)) {
7010
7532
  return { ...DEFAULT_STATE };
7011
7533
  }
7012
7534
  try {
7013
- const raw = (0, import_fs5.readFileSync)(statePath, "utf-8");
7535
+ const raw = (0, import_fs6.readFileSync)(statePath, "utf-8");
7014
7536
  return normalizeState(JSON.parse(raw));
7015
7537
  } catch {
7016
7538
  return { ...DEFAULT_STATE };
@@ -7019,17 +7541,17 @@ function loadState() {
7019
7541
  function saveState(state) {
7020
7542
  const statePath = getStatePath();
7021
7543
  const normalized = normalizeState(state);
7022
- (0, import_fs5.writeFileSync)(statePath, JSON.stringify(normalized, null, 2), { encoding: "utf-8", mode: 384 });
7544
+ (0, import_fs6.writeFileSync)(statePath, JSON.stringify(normalized, null, 2), { encoding: "utf-8", mode: 384 });
7023
7545
  }
7024
7546
  function resetState() {
7025
7547
  saveState({ ...DEFAULT_STATE });
7026
7548
  }
7027
7549
 
7028
7550
  // src/detection/ide-detector.ts
7029
- var import_child_process = require("child_process");
7030
- var import_fs6 = require("fs");
7551
+ var import_child_process2 = require("child_process");
7552
+ var import_fs7 = require("fs");
7031
7553
  var import_os2 = require("os");
7032
- var path9 = __toESM(require("path"));
7554
+ var path10 = __toESM(require("path"));
7033
7555
  var BUILTIN_IDE_DEFINITIONS = [];
7034
7556
  var registeredIDEs = /* @__PURE__ */ new Map();
7035
7557
  function registerIDEDefinition(def) {
@@ -7048,13 +7570,13 @@ function getMergedDefinitions() {
7048
7570
  function findCliCommand(command) {
7049
7571
  const trimmed = String(command || "").trim();
7050
7572
  if (!trimmed) return null;
7051
- if (path9.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
7052
- const candidate = trimmed.startsWith("~") ? path9.join((0, import_os2.homedir)(), trimmed.slice(1)) : trimmed;
7053
- const resolved = path9.isAbsolute(candidate) ? candidate : path9.resolve(candidate);
7054
- return (0, import_fs6.existsSync)(resolved) ? resolved : null;
7573
+ if (path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
7574
+ const candidate = trimmed.startsWith("~") ? path10.join((0, import_os2.homedir)(), trimmed.slice(1)) : trimmed;
7575
+ const resolved = path10.isAbsolute(candidate) ? candidate : path10.resolve(candidate);
7576
+ return (0, import_fs7.existsSync)(resolved) ? resolved : null;
7055
7577
  }
7056
7578
  try {
7057
- const result = (0, import_child_process.execSync)(
7579
+ const result = (0, import_child_process2.execSync)(
7058
7580
  (0, import_os2.platform)() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
7059
7581
  { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
7060
7582
  ).trim();
@@ -7065,7 +7587,7 @@ function findCliCommand(command) {
7065
7587
  }
7066
7588
  function getIdeVersion(cliCommand) {
7067
7589
  try {
7068
- const result = (0, import_child_process.execSync)(`"${cliCommand}" --version`, {
7590
+ const result = (0, import_child_process2.execSync)(`"${cliCommand}" --version`, {
7069
7591
  encoding: "utf-8",
7070
7592
  timeout: 1e4,
7071
7593
  stdio: ["pipe", "pipe", "pipe"]
@@ -7078,13 +7600,13 @@ function getIdeVersion(cliCommand) {
7078
7600
  function checkPathExists(paths) {
7079
7601
  const home = (0, import_os2.homedir)();
7080
7602
  for (const p of paths) {
7081
- const normalized = p.startsWith("~") ? path9.join(home, p.slice(1)) : p;
7603
+ const normalized = p.startsWith("~") ? path10.join(home, p.slice(1)) : p;
7082
7604
  if (normalized.includes("*")) {
7083
7605
  const username = home.split(/[\\/]/).pop() || "";
7084
7606
  const resolved = normalized.replace("*", username);
7085
- if ((0, import_fs6.existsSync)(resolved)) return resolved;
7607
+ if ((0, import_fs7.existsSync)(resolved)) return resolved;
7086
7608
  } else {
7087
- if ((0, import_fs6.existsSync)(normalized)) return normalized;
7609
+ if ((0, import_fs7.existsSync)(normalized)) return normalized;
7088
7610
  }
7089
7611
  }
7090
7612
  return null;
@@ -7098,7 +7620,7 @@ async function detectIDEs(providerLoader) {
7098
7620
  let resolvedCli = cliPath;
7099
7621
  if (!resolvedCli && appPath && os22 === "darwin") {
7100
7622
  const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
7101
- if ((0, import_fs6.existsSync)(bundledCli)) resolvedCli = bundledCli;
7623
+ if ((0, import_fs7.existsSync)(bundledCli)) resolvedCli = bundledCli;
7102
7624
  }
7103
7625
  if (!resolvedCli && appPath && os22 === "win32") {
7104
7626
  const { dirname: dirname9 } = await import("path");
@@ -7111,7 +7633,7 @@ async function detectIDEs(providerLoader) {
7111
7633
  `${appDir}\\\\resources\\\\app\\\\bin\\\\${def.cli}.cmd`
7112
7634
  ];
7113
7635
  for (const c of candidates) {
7114
- if ((0, import_fs6.existsSync)(c)) {
7636
+ if ((0, import_fs7.existsSync)(c)) {
7115
7637
  resolvedCli = c;
7116
7638
  break;
7117
7639
  }
@@ -7133,134 +7655,8 @@ async function detectIDEs(providerLoader) {
7133
7655
  return results;
7134
7656
  }
7135
7657
 
7136
- // src/detection/cli-detector.ts
7137
- var import_child_process2 = require("child_process");
7138
- var os3 = __toESM(require("os"));
7139
- var path10 = __toESM(require("path"));
7140
- var import_fs7 = require("fs");
7141
- function parseVersion(raw) {
7142
- const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
7143
- return match ? match[1] : raw.split("\n")[0].slice(0, 100);
7144
- }
7145
- function shellQuote(value) {
7146
- if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
7147
- return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
7148
- }
7149
- function expandHome(value) {
7150
- const trimmed = value.trim();
7151
- if (!trimmed.startsWith("~")) return trimmed;
7152
- return path10.join(os3.homedir(), trimmed.slice(1));
7153
- }
7154
- function isExplicitCommandPath(command) {
7155
- const trimmed = command.trim();
7156
- return path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
7157
- }
7158
- function resolveCommandPath(command) {
7159
- const trimmed = command.trim();
7160
- if (!trimmed) return null;
7161
- if (isExplicitCommandPath(trimmed)) {
7162
- const expanded = expandHome(trimmed);
7163
- const candidate = path10.isAbsolute(expanded) ? expanded : path10.resolve(expanded);
7164
- return (0, import_fs7.existsSync)(candidate) ? candidate : null;
7165
- }
7166
- return null;
7167
- }
7168
- function execAsync(cmd, timeoutMs = 5e3) {
7169
- return new Promise((resolve16) => {
7170
- const child = (0, import_child_process2.exec)(cmd, {
7171
- encoding: "utf-8",
7172
- timeout: timeoutMs,
7173
- ...process.platform === "win32" ? { windowsHide: true } : {}
7174
- }, (err, stdout) => {
7175
- if (err || !stdout?.trim()) {
7176
- resolve16(null);
7177
- } else {
7178
- resolve16(stdout.trim());
7179
- }
7180
- });
7181
- child.on("error", () => resolve16(null));
7182
- });
7183
- }
7184
- async function detectCLIs(providerLoader, options) {
7185
- const platform10 = os3.platform();
7186
- const whichCmd = platform10 === "win32" ? "where" : "which";
7187
- const includeVersion = options?.includeVersion !== false;
7188
- const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
7189
- const results = await Promise.all(
7190
- cliList.map(async (cli) => {
7191
- try {
7192
- const explicitPath = resolveCommandPath(cli.command);
7193
- const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
7194
- if (!pathResult) return { ...cli, installed: false };
7195
- const firstPath = explicitPath || pathResult.split("\n")[0];
7196
- let version;
7197
- if (includeVersion) {
7198
- const versionCommands = [
7199
- `"${firstPath}" --version`,
7200
- `"${firstPath}" -V`,
7201
- `"${firstPath}" -v`,
7202
- cli.versionCommand
7203
- ].filter((v) => !!v);
7204
- try {
7205
- for (const versionCommand of versionCommands) {
7206
- const versionResult = await execAsync(versionCommand, 3e3);
7207
- if (versionResult) {
7208
- version = parseVersion(versionResult);
7209
- break;
7210
- }
7211
- }
7212
- } catch {
7213
- }
7214
- }
7215
- return { ...cli, installed: true, version, path: firstPath };
7216
- } catch {
7217
- return { ...cli, installed: false };
7218
- }
7219
- })
7220
- );
7221
- return results;
7222
- }
7223
- async function detectCLI(cliId, providerLoader, options) {
7224
- const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
7225
- if (providerLoader) {
7226
- const cliList = providerLoader.getCliDetectionList();
7227
- const target = cliList.find((c) => c.id === resolvedId);
7228
- if (target) {
7229
- const platform10 = os3.platform();
7230
- const whichCmd = platform10 === "win32" ? "where" : "which";
7231
- try {
7232
- const explicitPath = resolveCommandPath(target.command);
7233
- const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
7234
- if (!pathResult) return null;
7235
- const firstPath = explicitPath || pathResult.split("\n")[0];
7236
- let version;
7237
- if (options?.includeVersion !== false) {
7238
- const versionCommands = [
7239
- `"${firstPath}" --version`,
7240
- `"${firstPath}" -V`,
7241
- `"${firstPath}" -v`,
7242
- target.versionCommand
7243
- ].filter((v) => !!v);
7244
- try {
7245
- for (const versionCommand of versionCommands) {
7246
- const versionResult = await execAsync(versionCommand, 3e3);
7247
- if (versionResult) {
7248
- version = parseVersion(versionResult);
7249
- break;
7250
- }
7251
- }
7252
- } catch {
7253
- }
7254
- }
7255
- return { ...target, installed: true, version, path: firstPath };
7256
- } catch {
7257
- return null;
7258
- }
7259
- }
7260
- }
7261
- const all = await detectCLIs(providerLoader, options);
7262
- return all.find((c) => c.id === resolvedId && c.installed) || null;
7263
- }
7658
+ // src/index.ts
7659
+ init_cli_detector();
7264
7660
 
7265
7661
  // src/system/host-memory.ts
7266
7662
  var os4 = __toESM(require("os"));
@@ -16370,6 +16766,7 @@ var import_fs8 = require("fs");
16370
16766
  var import_child_process6 = require("child_process");
16371
16767
  var import_chalk = __toESM(require("chalk"));
16372
16768
  init_provider_cli_adapter();
16769
+ init_cli_detector();
16373
16770
  init_config();
16374
16771
 
16375
16772
  // src/providers/cli-provider-instance.ts
@@ -21951,6 +22348,7 @@ function getAvailableIdeIds() {
21951
22348
 
21952
22349
  // src/commands/router.ts
21953
22350
  init_config();
22351
+ init_cli_detector();
21954
22352
  init_logger();
21955
22353
 
21956
22354
  // src/logging/command-log.ts
@@ -23097,6 +23495,209 @@ async function resolveProviderTypeFromPriority(args) {
23097
23495
  }
23098
23496
  return { error: `No usable provider detected for node '${args.nodeId}' from providerPriority: ${failed.join("; ")}` };
23099
23497
  }
23498
+ var REFINE_VALIDATION_CATEGORIES = ["typecheck", "test", "lint", "build"];
23499
+ var REFINE_VALIDATION_TIMEOUT_MS = 12e4;
23500
+ var REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
23501
+ var REFINE_VALIDATION_SUMMARY_CHARS = 2e3;
23502
+ var REFINE_VALIDATION_MAX_COMMANDS = 4;
23503
+ function truncateValidationOutput(value) {
23504
+ const text = typeof value === "string" ? value : value == null ? "" : String(value);
23505
+ if (text.length <= REFINE_VALIDATION_SUMMARY_CHARS) return text;
23506
+ return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}
23507
+ [truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
23508
+ }
23509
+ function readPackageScripts(workspace) {
23510
+ try {
23511
+ const packageJsonPath = (0, import_path6.join)(workspace, "package.json");
23512
+ const parsed = JSON.parse(fs10.readFileSync(packageJsonPath, "utf-8"));
23513
+ return parsed?.scripts && typeof parsed.scripts === "object" && !Array.isArray(parsed.scripts) ? parsed.scripts : {};
23514
+ } catch {
23515
+ return {};
23516
+ }
23517
+ }
23518
+ function tokenizeValidationCommand(command) {
23519
+ const trimmed = command.trim();
23520
+ if (!trimmed) return null;
23521
+ if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
23522
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
23523
+ if (!tokens.length) return null;
23524
+ if (tokens.some((token) => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
23525
+ return tokens;
23526
+ }
23527
+ function scriptMatchesValidationCategory(scriptName, category) {
23528
+ return scriptName === category || scriptName.startsWith(`${category}:`);
23529
+ }
23530
+ function parsePackageManagerValidationCommand(rawCommand, category, scripts, source) {
23531
+ const tokens = tokenizeValidationCommand(rawCommand);
23532
+ if (!tokens) {
23533
+ return { rejected: { command: rawCommand, category, source, reason: "unsafe command string is not allowlisted" } };
23534
+ }
23535
+ const [binary, second, third, ...rest] = tokens;
23536
+ let scriptName = "";
23537
+ let command = binary;
23538
+ let args = [];
23539
+ if ((binary === "npm" || binary === "pnpm" || binary === "bun") && second === "run" && third) {
23540
+ scriptName = third;
23541
+ args = ["run", scriptName, ...rest];
23542
+ } else if (binary === "npm" && second === "test" && !third) {
23543
+ scriptName = "test";
23544
+ args = ["test"];
23545
+ } else if (binary === "yarn" && second === "run" && third) {
23546
+ scriptName = third;
23547
+ args = ["run", scriptName, ...rest];
23548
+ } else if (binary === "yarn" && second && !third) {
23549
+ scriptName = second;
23550
+ args = [scriptName];
23551
+ } else {
23552
+ return { rejected: { command: rawCommand, category, source, reason: "command is not a supported package-manager script invocation" } };
23553
+ }
23554
+ if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
23555
+ return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script is not declared in package.json" } };
23556
+ }
23557
+ if (!scriptMatchesValidationCategory(scriptName, category)) {
23558
+ return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script name is outside the validation category allowlist" } };
23559
+ }
23560
+ return {
23561
+ command: {
23562
+ command,
23563
+ args,
23564
+ displayCommand: [command, ...args].join(" "),
23565
+ category,
23566
+ source
23567
+ }
23568
+ };
23569
+ }
23570
+ function collectProjectContextValidationCandidates(mesh) {
23571
+ const commands = mesh?.projectContext?.commands;
23572
+ if (!commands || typeof commands !== "object" || Array.isArray(commands)) return [];
23573
+ const candidates = [];
23574
+ for (const category of REFINE_VALIDATION_CATEGORIES) {
23575
+ const entries = Array.isArray(commands[category]) ? commands[category] : [];
23576
+ for (const entry of entries) {
23577
+ if (typeof entry?.command !== "string") continue;
23578
+ candidates.push({
23579
+ command: entry.command,
23580
+ category,
23581
+ source: typeof entry.sourcePath === "string" ? entry.sourcePath : "projectContext.commands",
23582
+ confidence: typeof entry.confidence === "string" ? entry.confidence : void 0
23583
+ });
23584
+ }
23585
+ }
23586
+ return candidates.sort((a, b) => {
23587
+ const rank = (value) => value === "high" ? 0 : value === "medium" ? 1 : 2;
23588
+ return rank(a.confidence) - rank(b.confidence);
23589
+ });
23590
+ }
23591
+ function collectPolicyValidationCandidates(mesh) {
23592
+ const policy = mesh?.policy && typeof mesh.policy === "object" && !Array.isArray(mesh.policy) ? mesh.policy : {};
23593
+ const configured = Array.isArray(policy.validationCommands) ? policy.validationCommands : Array.isArray(policy.validationGate?.commands) ? policy.validationGate.commands : [];
23594
+ return configured.map((entry) => typeof entry === "string" ? { command: entry, category: "", source: "mesh.policy.validationCommands" } : entry).filter((entry) => entry && typeof entry.command === "string").map((entry) => {
23595
+ const commandText = entry.command.trim();
23596
+ const category = REFINE_VALIDATION_CATEGORIES.find((cat) => commandText.includes(` ${cat}`)) ?? "";
23597
+ return { command: commandText, category, source: "mesh.policy.validationCommands" };
23598
+ }).filter((entry) => !!entry.category);
23599
+ }
23600
+ function selectMeshRefineValidationCommands(mesh, workspace) {
23601
+ const scripts = readPackageScripts(workspace);
23602
+ const rejectedCommands = [];
23603
+ const selected = [];
23604
+ const seen = /* @__PURE__ */ new Set();
23605
+ const candidates = [
23606
+ ...collectPolicyValidationCandidates(mesh),
23607
+ ...collectProjectContextValidationCandidates(mesh)
23608
+ ];
23609
+ for (const candidate of candidates) {
23610
+ const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
23611
+ if (parsed.rejected) {
23612
+ rejectedCommands.push(parsed.rejected);
23613
+ continue;
23614
+ }
23615
+ if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
23616
+ selected.push(parsed.command);
23617
+ seen.add(parsed.command.displayCommand);
23618
+ if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
23619
+ }
23620
+ if (!selected.length && candidates.length === 0) {
23621
+ for (const category of REFINE_VALIDATION_CATEGORIES) {
23622
+ if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
23623
+ const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, "package.json:scripts");
23624
+ if (fallback.command && !seen.has(fallback.command.displayCommand)) {
23625
+ selected.push(fallback.command);
23626
+ seen.add(fallback.command.displayCommand);
23627
+ } else if (fallback.rejected) {
23628
+ rejectedCommands.push(fallback.rejected);
23629
+ }
23630
+ if (selected.length >= 2) break;
23631
+ }
23632
+ }
23633
+ return {
23634
+ commands: selected,
23635
+ rejectedCommands,
23636
+ 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"
23637
+ };
23638
+ }
23639
+ async function runMeshRefineValidationGate(mesh, workspace) {
23640
+ const { execFile: execFile3 } = await import("child_process");
23641
+ const { promisify: promisify3 } = await import("util");
23642
+ const execFileAsync3 = promisify3(execFile3);
23643
+ const selection = selectMeshRefineValidationCommands(mesh, workspace);
23644
+ const summary = {
23645
+ status: "skipped",
23646
+ required: true,
23647
+ commandsRun: [],
23648
+ rejectedCommands: selection.rejectedCommands,
23649
+ skippedReason: void 0,
23650
+ timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
23651
+ outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES
23652
+ };
23653
+ if (!selection.commands.length) {
23654
+ summary.skippedReason = "validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available";
23655
+ return summary;
23656
+ }
23657
+ for (const candidate of selection.commands) {
23658
+ const startedAt = Date.now();
23659
+ try {
23660
+ const result = await execFileAsync3(candidate.command, candidate.args, {
23661
+ cwd: workspace,
23662
+ encoding: "utf8",
23663
+ timeout: REFINE_VALIDATION_TIMEOUT_MS,
23664
+ maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
23665
+ env: { ...process.env, CI: process.env.CI || "1" }
23666
+ });
23667
+ summary.commandsRun.push({
23668
+ command: candidate.command,
23669
+ args: candidate.args,
23670
+ displayCommand: candidate.displayCommand,
23671
+ category: candidate.category,
23672
+ source: candidate.source,
23673
+ passed: true,
23674
+ exitCode: 0,
23675
+ durationMs: Date.now() - startedAt,
23676
+ stdout: truncateValidationOutput(result.stdout),
23677
+ stderr: truncateValidationOutput(result.stderr)
23678
+ });
23679
+ } catch (error) {
23680
+ summary.commandsRun.push({
23681
+ command: candidate.command,
23682
+ args: candidate.args,
23683
+ displayCommand: candidate.displayCommand,
23684
+ category: candidate.category,
23685
+ source: candidate.source,
23686
+ passed: false,
23687
+ exitCode: typeof error?.code === "number" ? error.code : null,
23688
+ signal: typeof error?.signal === "string" ? error.signal : null,
23689
+ timedOut: error?.killed === true || /timed out/i.test(String(error?.message || "")),
23690
+ durationMs: Date.now() - startedAt,
23691
+ stdout: truncateValidationOutput(error?.stdout),
23692
+ stderr: truncateValidationOutput(error?.stderr || error?.message)
23693
+ });
23694
+ summary.status = "failed";
23695
+ return summary;
23696
+ }
23697
+ }
23698
+ summary.status = "passed";
23699
+ return summary;
23700
+ }
23100
23701
  function loadYamlModule() {
23101
23702
  return yaml;
23102
23703
  }
@@ -23380,20 +23981,98 @@ var DaemonCommandRouter = class {
23380
23981
  recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
23381
23982
  };
23382
23983
  }
23984
+ const forceFallbackConvergence = await this.getWorktreeForceCleanupConvergence({
23985
+ repoRoot,
23986
+ workspace,
23987
+ node: args.node
23988
+ });
23383
23989
  try {
23384
- const result = await removeWorktree2(repoRoot, workspace, { requireClean: true });
23385
- return { success: true, removedPath: result.removedPath, repoRoot };
23990
+ const result = await removeWorktree2(repoRoot, workspace, {
23991
+ requireClean: true,
23992
+ allowSubmoduleForceFallback: forceFallbackConvergence.allow
23993
+ });
23994
+ return {
23995
+ success: true,
23996
+ removedPath: result.removedPath,
23997
+ repoRoot,
23998
+ ...result.fallback ? {
23999
+ fallback: result.fallback,
24000
+ forced: result.forced,
24001
+ reason: result.reason,
24002
+ convergence: forceFallbackConvergence
24003
+ } : {}
24004
+ };
23386
24005
  } catch (e) {
23387
24006
  const message = String(e?.message || e || "worktree cleanup failed");
23388
24007
  const dirty = message.includes("dirty worktree") || message.includes("local changes");
24008
+ const submoduleForceBlocked = /working trees containing submodules cannot be moved or removed/i.test(message) && !forceFallbackConvergence.allow;
23389
24009
  return {
23390
24010
  success: false,
23391
- code: dirty ? "mesh_worktree_cleanup_dirty" : "mesh_worktree_cleanup_failed",
23392
- error: message,
23393
- 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."
24011
+ code: dirty ? "mesh_worktree_cleanup_dirty" : submoduleForceBlocked ? "mesh_worktree_cleanup_force_fallback_blocked" : "mesh_worktree_cleanup_failed",
24012
+ error: submoduleForceBlocked ? `${message}; refusing --force fallback because convergence could not be verified: ${forceFallbackConvergence.error || "unknown convergence state"}` : message,
24013
+ 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.",
24014
+ ...submoduleForceBlocked ? { convergence: forceFallbackConvergence } : {}
23394
24015
  };
23395
24016
  }
23396
24017
  }
24018
+ async getWorktreeForceCleanupConvergence(args) {
24019
+ const metadataStatus = typeof args.node?.branchConvergence?.status === "string" ? args.node.branchConvergence.status : "";
24020
+ if (metadataStatus === "merged_to_main" || metadataStatus === "cleanup_candidate") {
24021
+ return { allow: true, status: metadataStatus, source: "node_branch_convergence" };
24022
+ }
24023
+ const { execFile: execFile3 } = await import("child_process");
24024
+ const { promisify: promisify3 } = await import("util");
24025
+ const execFileAsync3 = promisify3(execFile3);
24026
+ const runGit2 = async (gitArgs, cwd) => {
24027
+ const { stdout } = await execFileAsync3("git", gitArgs, {
24028
+ cwd,
24029
+ encoding: "utf8",
24030
+ timeout: 3e4,
24031
+ maxBuffer: 4 * 1024 * 1024,
24032
+ windowsHide: true
24033
+ });
24034
+ return String(stdout || "").trim();
24035
+ };
24036
+ let head = "";
24037
+ try {
24038
+ head = await runGit2(["rev-parse", "HEAD"], args.workspace);
24039
+ } catch (e) {
24040
+ return { allow: false, error: `could not resolve worktree HEAD: ${e?.message || e}` };
24041
+ }
24042
+ if (!head) return { allow: false, error: "worktree HEAD is empty" };
24043
+ const candidateRefs = [];
24044
+ try {
24045
+ const defaultBranch = await runGit2(["branch", "--show-current"], args.repoRoot);
24046
+ if (defaultBranch) {
24047
+ candidateRefs.push(defaultBranch, `origin/${defaultBranch}`);
24048
+ }
24049
+ } catch {
24050
+ }
24051
+ candidateRefs.push("origin/main", "origin/master", "main", "master");
24052
+ const seen = /* @__PURE__ */ new Set();
24053
+ const checkedRefs = [];
24054
+ for (const ref of candidateRefs) {
24055
+ if (!ref || seen.has(ref)) continue;
24056
+ seen.add(ref);
24057
+ let commit = "";
24058
+ try {
24059
+ commit = await runGit2(["rev-parse", "--verify", `${ref}^{commit}`], args.repoRoot);
24060
+ } catch {
24061
+ continue;
24062
+ }
24063
+ checkedRefs.push(ref);
24064
+ try {
24065
+ await runGit2(["merge-base", "--is-ancestor", head, commit], args.repoRoot);
24066
+ return { allow: true, status: "merged_to_default_ref", source: "git_merge_base", ref };
24067
+ } catch {
24068
+ }
24069
+ }
24070
+ return {
24071
+ allow: false,
24072
+ status: metadataStatus || void 0,
24073
+ error: checkedRefs.length ? `worktree HEAD is not contained in checked refs: ${checkedRefs.join(", ")}` : "no default/main refs were available for convergence verification"
24074
+ };
24075
+ }
23397
24076
  isCompletedHostedSession(record) {
23398
24077
  return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
23399
24078
  }
@@ -24353,10 +25032,61 @@ var DaemonCommandRouter = class {
24353
25032
  if (!branch) return { success: false, error: "Could not determine branch of the worktree node" };
24354
25033
  const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
24355
25034
  const baseBranch = baseBranchStdout.trim();
25035
+ const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
25036
+ if (validationSummary.status === "failed") {
25037
+ return {
25038
+ success: false,
25039
+ code: "validation_failed",
25040
+ convergenceStatus: "blocked_review",
25041
+ error: "Refinery validation gate failed; merge/refine was not attempted.",
25042
+ branch,
25043
+ into: baseBranch,
25044
+ validationSummary,
25045
+ finalBranchConvergenceState: {
25046
+ branch,
25047
+ baseBranch,
25048
+ merged: false,
25049
+ removed: false,
25050
+ validation: "failed",
25051
+ status: "blocked_review"
25052
+ }
25053
+ };
25054
+ }
25055
+ if (validationSummary.status === "skipped") {
25056
+ return {
25057
+ success: false,
25058
+ code: "validation_unavailable",
25059
+ convergenceStatus: "blocked_review",
25060
+ error: "Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.",
25061
+ branch,
25062
+ into: baseBranch,
25063
+ validationSummary,
25064
+ finalBranchConvergenceState: {
25065
+ branch,
25066
+ baseBranch,
25067
+ merged: false,
25068
+ removed: false,
25069
+ validation: "unavailable",
25070
+ status: "blocked_review"
25071
+ }
25072
+ };
25073
+ }
24356
25074
  try {
24357
25075
  await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
24358
25076
  } catch (e) {
24359
- return { success: false, error: `Merge failed (conflicts?): ${e.message}` };
25077
+ return {
25078
+ success: false,
25079
+ error: `Merge failed (conflicts?): ${e.message}`,
25080
+ validationSummary,
25081
+ finalBranchConvergenceState: {
25082
+ branch,
25083
+ baseBranch,
25084
+ merged: false,
25085
+ removed: false,
25086
+ validation: "passed",
25087
+ status: "not_mergeable"
25088
+ }
25089
+ };
24360
25090
  }
24361
25091
  const removeResult = await this.execute("remove_mesh_node", {
24362
25092
  meshId,
@@ -24369,11 +25099,27 @@ var DaemonCommandRouter = class {
24369
25099
  appendLedgerEntry2(meshId, {
24370
25100
  kind: "node_removed",
24371
25101
  nodeId,
24372
- payload: { refined: true, mergedBranch: branch, into: baseBranch }
25102
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary }
24373
25103
  });
24374
25104
  } catch {
24375
25105
  }
24376
- return { success: true, merged: true, branch, into: baseBranch, removeResult };
25106
+ return {
25107
+ success: true,
25108
+ merged: true,
25109
+ branch,
25110
+ into: baseBranch,
25111
+ removeResult,
25112
+ validationSummary,
25113
+ finalBranchConvergenceState: {
25114
+ branch: baseBranch,
25115
+ mergedBranch: branch,
25116
+ baseBranch,
25117
+ merged: true,
25118
+ removed: removeResult?.success !== false,
25119
+ validation: "passed",
25120
+ status: removeResult?.success === false ? "merged_cleanup_failed" : "merged"
25121
+ }
25122
+ };
24377
25123
  } catch (e) {
24378
25124
  return { success: false, error: e.message };
24379
25125
  }
@@ -24428,7 +25174,10 @@ var DaemonCommandRouter = class {
24428
25174
  sessionCleanupMode,
24429
25175
  workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
24430
25176
  daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
24431
- worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0
25177
+ worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
25178
+ worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
25179
+ forced: worktreeCleanup?.forced === true ? true : void 0,
25180
+ forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
24432
25181
  }
24433
25182
  });
24434
25183
  } catch {
@@ -32560,6 +33309,9 @@ function launchIDE(ide, workspacePath) {
32560
33309
  }
32561
33310
  }
32562
33311
 
33312
+ // src/boot/daemon-lifecycle.ts
33313
+ init_cli_detector();
33314
+
32563
33315
  // src/sessions/registry.ts
32564
33316
  var SessionRegistry = class {
32565
33317
  bySessionId = /* @__PURE__ */ new Map();
@@ -32897,6 +33649,7 @@ async function shutdownDaemonComponents(components) {
32897
33649
  MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
32898
33650
  MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
32899
33651
  NodePtyTransportFactory,
33652
+ P2pRelayFailureError,
32900
33653
  ProviderCliAdapter,
32901
33654
  ProviderInstanceManager,
32902
33655
  ProviderLoader,
@@ -32913,6 +33666,7 @@ async function shutdownDaemonComponents(components) {
32913
33666
  buildChatTailDeliverySignature,
32914
33667
  buildCoordinatorSystemPrompt,
32915
33668
  buildMachineInfo,
33669
+ buildP2pRelayFailurePayload,
32916
33670
  buildPinnedGlobalInstallCommand,
32917
33671
  buildRuntimeSystemChatMessage,
32918
33672
  buildSessionEntries,
@@ -32927,6 +33681,7 @@ async function shutdownDaemonComponents(components) {
32927
33681
  claimNextTask,
32928
33682
  classifyChatMessageVisibility,
32929
33683
  classifyHotChatSessionsForSubscriptionFlush,
33684
+ classifyP2pRelayFailure,
32930
33685
  clearDebugTrace,
32931
33686
  compareGitSnapshots,
32932
33687
  configureDebugTraceStore,
@@ -32994,6 +33749,7 @@ async function shutdownDaemonComponents(components) {
32994
33749
  isInternalChatMessage,
32995
33750
  isManagedStatusWaiting,
32996
33751
  isManagedStatusWorking,
33752
+ isP2pRelayTransportFailure,
32997
33753
  isPathInside,
32998
33754
  isSessionHostLiveRuntime,
32999
33755
  isSessionHostRecoverySnapshot,