@adhdev/daemon-core 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/commands/router.d.ts +1 -0
- package/dist/git/git-worktree.d.ts +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +946 -181
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +942 -182
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +1 -1
- package/dist/mesh/mesh-ledger.d.ts +1 -1
- package/dist/mesh/mesh-work-queue.d.ts +13 -0
- package/dist/mesh/p2p-relay-failure.d.ts +35 -0
- package/dist/shared-types.d.ts +6 -0
- package/package.json +1 -1
- package/src/commands/router.ts +446 -11
- package/src/config/config.ts +2 -1
- package/src/config/workspaces.ts +1 -1
- package/src/git/git-worktree.ts +35 -1
- package/src/index.d.ts +3 -0
- package/src/index.ts +14 -0
- package/src/mesh/mesh-events.ts +292 -5
- package/src/mesh/mesh-ledger.ts +1 -0
- package/src/mesh/mesh-work-queue.ts +43 -5
- package/src/mesh/p2p-relay-failure.ts +152 -0
- package/src/shared-types.ts +6 -0
package/dist/index.mjs
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
|
-
|
|
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 execFileAsync2, WORKTREE_DIR_NAME, GIT_TIMEOUT_MS, GIT_MAX_BUFFER;
|
|
202
|
+
var 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";
|
|
@@ -181,6 +207,7 @@ var init_git_worktree = __esm({
|
|
|
181
207
|
WORKTREE_DIR_NAME = ".adhdev-worktrees";
|
|
182
208
|
GIT_TIMEOUT_MS = 3e4;
|
|
183
209
|
GIT_MAX_BUFFER = 4 * 1024 * 1024;
|
|
210
|
+
SUBMODULE_WORKTREE_REMOVE_RE = /working trees containing submodules cannot be moved or removed/i;
|
|
184
211
|
}
|
|
185
212
|
});
|
|
186
213
|
|
|
@@ -294,7 +321,8 @@ function ensureMachineId(config) {
|
|
|
294
321
|
};
|
|
295
322
|
}
|
|
296
323
|
function getConfigDir() {
|
|
297
|
-
const
|
|
324
|
+
const override = process.env.ADHDEV_CONFIG_DIR;
|
|
325
|
+
const dir = override && override.trim() ? override.trim() : join2(homedir(), ".adhdev");
|
|
298
326
|
if (!existsSync2(dir)) {
|
|
299
327
|
mkdirSync(dir, { recursive: true });
|
|
300
328
|
}
|
|
@@ -1004,6 +1032,7 @@ __export(mesh_work_queue_exports, {
|
|
|
1004
1032
|
enqueueTask: () => enqueueTask,
|
|
1005
1033
|
getMeshQueueStats: () => getMeshQueueStats,
|
|
1006
1034
|
getQueue: () => getQueue,
|
|
1035
|
+
recordTaskAutoLaunch: () => recordTaskAutoLaunch,
|
|
1007
1036
|
requeueTask: () => requeueTask,
|
|
1008
1037
|
updateSessionTaskStatus: () => updateSessionTaskStatus,
|
|
1009
1038
|
updateTaskStatus: () => updateTaskStatus
|
|
@@ -1082,6 +1111,19 @@ function updateTaskStatus(meshId, taskId, status) {
|
|
|
1082
1111
|
writeQueue(meshId, queue);
|
|
1083
1112
|
return queue[idx];
|
|
1084
1113
|
}
|
|
1114
|
+
function recordTaskAutoLaunch(meshId, taskId, autoLaunch) {
|
|
1115
|
+
const queue = readQueue(meshId);
|
|
1116
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
1117
|
+
if (idx === -1) return null;
|
|
1118
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1119
|
+
queue[idx].autoLaunch = {
|
|
1120
|
+
...autoLaunch,
|
|
1121
|
+
updatedAt: now
|
|
1122
|
+
};
|
|
1123
|
+
queue[idx].updatedAt = now;
|
|
1124
|
+
writeQueue(meshId, queue);
|
|
1125
|
+
return queue[idx];
|
|
1126
|
+
}
|
|
1085
1127
|
function cancelTask(meshId, taskId, opts) {
|
|
1086
1128
|
const queue = readQueue(meshId);
|
|
1087
1129
|
const idx = queue.findIndex((q) => q.id === taskId);
|
|
@@ -1130,12 +1172,20 @@ function updateSessionTaskStatus(meshId, sessionId, status) {
|
|
|
1130
1172
|
}
|
|
1131
1173
|
function getMeshQueueStats(meshId) {
|
|
1132
1174
|
const queue = readQueue(meshId);
|
|
1175
|
+
const pending = queue.filter((q) => q.status === "pending").length;
|
|
1176
|
+
const assigned = queue.filter((q) => q.status === "assigned").length;
|
|
1177
|
+
const completed = queue.filter((q) => q.status === "completed").length;
|
|
1178
|
+
const failed = queue.filter((q) => q.status === "failed").length;
|
|
1179
|
+
const cancelled = queue.filter((q) => q.status === "cancelled").length;
|
|
1133
1180
|
return {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1181
|
+
total: queue.length,
|
|
1182
|
+
active: pending + assigned,
|
|
1183
|
+
historical: completed + failed + cancelled,
|
|
1184
|
+
pending,
|
|
1185
|
+
assigned,
|
|
1186
|
+
completed,
|
|
1187
|
+
failed,
|
|
1188
|
+
cancelled,
|
|
1139
1189
|
activeAssignments: queue.filter((q) => q.status === "assigned").map((q) => ({
|
|
1140
1190
|
id: q.id,
|
|
1141
1191
|
nodeId: q.assignedNodeId,
|
|
@@ -1151,10 +1201,144 @@ var init_mesh_work_queue = __esm({
|
|
|
1151
1201
|
}
|
|
1152
1202
|
});
|
|
1153
1203
|
|
|
1204
|
+
// src/detection/cli-detector.ts
|
|
1205
|
+
import { exec } from "child_process";
|
|
1206
|
+
import * as os2 from "os";
|
|
1207
|
+
import * as path8 from "path";
|
|
1208
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1209
|
+
function parseVersion(raw) {
|
|
1210
|
+
const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
|
|
1211
|
+
return match ? match[1] : raw.split("\n")[0].slice(0, 100);
|
|
1212
|
+
}
|
|
1213
|
+
function shellQuote(value) {
|
|
1214
|
+
if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
1215
|
+
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
1216
|
+
}
|
|
1217
|
+
function expandHome(value) {
|
|
1218
|
+
const trimmed = value.trim();
|
|
1219
|
+
if (!trimmed.startsWith("~")) return trimmed;
|
|
1220
|
+
return path8.join(os2.homedir(), trimmed.slice(1));
|
|
1221
|
+
}
|
|
1222
|
+
function isExplicitCommandPath(command) {
|
|
1223
|
+
const trimmed = command.trim();
|
|
1224
|
+
return path8.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
|
|
1225
|
+
}
|
|
1226
|
+
function resolveCommandPath(command) {
|
|
1227
|
+
const trimmed = command.trim();
|
|
1228
|
+
if (!trimmed) return null;
|
|
1229
|
+
if (isExplicitCommandPath(trimmed)) {
|
|
1230
|
+
const expanded = expandHome(trimmed);
|
|
1231
|
+
const candidate = path8.isAbsolute(expanded) ? expanded : path8.resolve(expanded);
|
|
1232
|
+
return existsSync7(candidate) ? candidate : null;
|
|
1233
|
+
}
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
function execAsync(cmd, timeoutMs = 5e3) {
|
|
1237
|
+
return new Promise((resolve16) => {
|
|
1238
|
+
const child = exec(cmd, {
|
|
1239
|
+
encoding: "utf-8",
|
|
1240
|
+
timeout: timeoutMs,
|
|
1241
|
+
...process.platform === "win32" ? { windowsHide: true } : {}
|
|
1242
|
+
}, (err, stdout) => {
|
|
1243
|
+
if (err || !stdout?.trim()) {
|
|
1244
|
+
resolve16(null);
|
|
1245
|
+
} else {
|
|
1246
|
+
resolve16(stdout.trim());
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
child.on("error", () => resolve16(null));
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
async function detectCLIs(providerLoader, options) {
|
|
1253
|
+
const platform10 = os2.platform();
|
|
1254
|
+
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
1255
|
+
const includeVersion = options?.includeVersion !== false;
|
|
1256
|
+
const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
|
|
1257
|
+
const results = await Promise.all(
|
|
1258
|
+
cliList.map(async (cli) => {
|
|
1259
|
+
try {
|
|
1260
|
+
const explicitPath = resolveCommandPath(cli.command);
|
|
1261
|
+
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
|
|
1262
|
+
if (!pathResult) return { ...cli, installed: false };
|
|
1263
|
+
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
1264
|
+
let version;
|
|
1265
|
+
if (includeVersion) {
|
|
1266
|
+
const versionCommands = [
|
|
1267
|
+
`"${firstPath}" --version`,
|
|
1268
|
+
`"${firstPath}" -V`,
|
|
1269
|
+
`"${firstPath}" -v`,
|
|
1270
|
+
cli.versionCommand
|
|
1271
|
+
].filter((v) => !!v);
|
|
1272
|
+
try {
|
|
1273
|
+
for (const versionCommand of versionCommands) {
|
|
1274
|
+
const versionResult = await execAsync(versionCommand, 3e3);
|
|
1275
|
+
if (versionResult) {
|
|
1276
|
+
version = parseVersion(versionResult);
|
|
1277
|
+
break;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
} catch {
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
return { ...cli, installed: true, version, path: firstPath };
|
|
1284
|
+
} catch {
|
|
1285
|
+
return { ...cli, installed: false };
|
|
1286
|
+
}
|
|
1287
|
+
})
|
|
1288
|
+
);
|
|
1289
|
+
return results;
|
|
1290
|
+
}
|
|
1291
|
+
async function detectCLI(cliId, providerLoader, options) {
|
|
1292
|
+
const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
|
|
1293
|
+
if (providerLoader) {
|
|
1294
|
+
const cliList = providerLoader.getCliDetectionList();
|
|
1295
|
+
const target = cliList.find((c) => c.id === resolvedId);
|
|
1296
|
+
if (target) {
|
|
1297
|
+
const platform10 = os2.platform();
|
|
1298
|
+
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
1299
|
+
try {
|
|
1300
|
+
const explicitPath = resolveCommandPath(target.command);
|
|
1301
|
+
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
|
|
1302
|
+
if (!pathResult) return null;
|
|
1303
|
+
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
1304
|
+
let version;
|
|
1305
|
+
if (options?.includeVersion !== false) {
|
|
1306
|
+
const versionCommands = [
|
|
1307
|
+
`"${firstPath}" --version`,
|
|
1308
|
+
`"${firstPath}" -V`,
|
|
1309
|
+
`"${firstPath}" -v`,
|
|
1310
|
+
target.versionCommand
|
|
1311
|
+
].filter((v) => !!v);
|
|
1312
|
+
try {
|
|
1313
|
+
for (const versionCommand of versionCommands) {
|
|
1314
|
+
const versionResult = await execAsync(versionCommand, 3e3);
|
|
1315
|
+
if (versionResult) {
|
|
1316
|
+
version = parseVersion(versionResult);
|
|
1317
|
+
break;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
} catch {
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
return { ...target, installed: true, version, path: firstPath };
|
|
1324
|
+
} catch {
|
|
1325
|
+
return null;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
const all = await detectCLIs(providerLoader, options);
|
|
1330
|
+
return all.find((c) => c.id === resolvedId && c.installed) || null;
|
|
1331
|
+
}
|
|
1332
|
+
var init_cli_detector = __esm({
|
|
1333
|
+
"src/detection/cli-detector.ts"() {
|
|
1334
|
+
"use strict";
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1154
1338
|
// src/logging/logger.ts
|
|
1155
1339
|
import * as fs2 from "fs";
|
|
1156
|
-
import * as
|
|
1157
|
-
import * as
|
|
1340
|
+
import * as path9 from "path";
|
|
1341
|
+
import * as os3 from "os";
|
|
1158
1342
|
function setLogLevel(level) {
|
|
1159
1343
|
currentLevel = level;
|
|
1160
1344
|
daemonLog("Logger", `Log level set to: ${level}`, "info");
|
|
@@ -1169,13 +1353,13 @@ function getDaemonLogDir() {
|
|
|
1169
1353
|
return LOG_DIR;
|
|
1170
1354
|
}
|
|
1171
1355
|
function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
|
|
1172
|
-
return
|
|
1356
|
+
return path9.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
|
|
1173
1357
|
}
|
|
1174
1358
|
function checkDateRotation() {
|
|
1175
1359
|
const today = getDateStr();
|
|
1176
1360
|
if (today !== currentDate) {
|
|
1177
1361
|
currentDate = today;
|
|
1178
|
-
currentLogFile =
|
|
1362
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
1179
1363
|
cleanOldLogs();
|
|
1180
1364
|
}
|
|
1181
1365
|
}
|
|
@@ -1189,7 +1373,7 @@ function cleanOldLogs() {
|
|
|
1189
1373
|
const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
|
|
1190
1374
|
if (dateMatch && dateMatch[1] < cutoffStr) {
|
|
1191
1375
|
try {
|
|
1192
|
-
fs2.unlinkSync(
|
|
1376
|
+
fs2.unlinkSync(path9.join(LOG_DIR, file));
|
|
1193
1377
|
} catch {
|
|
1194
1378
|
}
|
|
1195
1379
|
}
|
|
@@ -1312,7 +1496,7 @@ var init_logger = __esm({
|
|
|
1312
1496
|
LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
1313
1497
|
LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
|
|
1314
1498
|
currentLevel = "info";
|
|
1315
|
-
LOG_DIR = process.platform === "win32" ?
|
|
1499
|
+
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");
|
|
1316
1500
|
MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
1317
1501
|
MAX_LOG_DAYS = 7;
|
|
1318
1502
|
try {
|
|
@@ -1320,16 +1504,16 @@ var init_logger = __esm({
|
|
|
1320
1504
|
} catch {
|
|
1321
1505
|
}
|
|
1322
1506
|
currentDate = getDateStr();
|
|
1323
|
-
currentLogFile =
|
|
1507
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
1324
1508
|
cleanOldLogs();
|
|
1325
1509
|
try {
|
|
1326
|
-
const oldLog =
|
|
1510
|
+
const oldLog = path9.join(LOG_DIR, "daemon.log");
|
|
1327
1511
|
if (fs2.existsSync(oldLog)) {
|
|
1328
1512
|
const stat2 = fs2.statSync(oldLog);
|
|
1329
1513
|
const oldDate = stat2.mtime.toISOString().slice(0, 10);
|
|
1330
|
-
fs2.renameSync(oldLog,
|
|
1514
|
+
fs2.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
|
|
1331
1515
|
}
|
|
1332
|
-
const oldLogBackup =
|
|
1516
|
+
const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
|
|
1333
1517
|
if (fs2.existsSync(oldLogBackup)) {
|
|
1334
1518
|
fs2.unlinkSync(oldLogBackup);
|
|
1335
1519
|
}
|
|
@@ -1361,7 +1545,7 @@ var init_logger = __esm({
|
|
|
1361
1545
|
}
|
|
1362
1546
|
};
|
|
1363
1547
|
interceptorInstalled = false;
|
|
1364
|
-
LOG_PATH =
|
|
1548
|
+
LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
|
|
1365
1549
|
}
|
|
1366
1550
|
});
|
|
1367
1551
|
|
|
@@ -1433,7 +1617,235 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
1433
1617
|
});
|
|
1434
1618
|
return true;
|
|
1435
1619
|
}
|
|
1436
|
-
function
|
|
1620
|
+
function normalizeProviderPriority(policy) {
|
|
1621
|
+
const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
|
|
1622
|
+
if (!Array.isArray(raw)) return [];
|
|
1623
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1624
|
+
return raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean).filter((type) => {
|
|
1625
|
+
if (seen.has(type)) return false;
|
|
1626
|
+
seen.add(type);
|
|
1627
|
+
return true;
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
function isTerminalSessionStatus(status) {
|
|
1631
|
+
return ["stopped", "failed", "terminated", "exited", "closed"].includes(status);
|
|
1632
|
+
}
|
|
1633
|
+
function isIdleSessionState(state) {
|
|
1634
|
+
const status = readNonEmptyString(state?.status).toLowerCase();
|
|
1635
|
+
if (isTerminalSessionStatus(status)) return false;
|
|
1636
|
+
return status === "idle" || state?.activeChat?.status === "waiting_input";
|
|
1637
|
+
}
|
|
1638
|
+
function isDirtyNode(node) {
|
|
1639
|
+
return node?.health === "dirty" || node?.git?.dirty === true;
|
|
1640
|
+
}
|
|
1641
|
+
function isLaunchableNode(node) {
|
|
1642
|
+
if (!node || node.status === "disabled" || node.status === "removed") return false;
|
|
1643
|
+
const health = readNonEmptyString(node.health).toLowerCase();
|
|
1644
|
+
if (!health) return true;
|
|
1645
|
+
return health === "online" || health === "unknown";
|
|
1646
|
+
}
|
|
1647
|
+
function localAutoLaunchSkipReason(node) {
|
|
1648
|
+
const daemonId = readNonEmptyString(node?.daemonId);
|
|
1649
|
+
const machineId = readNonEmptyString(node?.machineId);
|
|
1650
|
+
const appConfig = loadConfig();
|
|
1651
|
+
const localMachineId = readNonEmptyString(appConfig.machineId) || readNonEmptyString(appConfig.registeredMachineId);
|
|
1652
|
+
const cloudDaemonId = localMachineId ? `daemon_${localMachineId}` : "";
|
|
1653
|
+
const standaloneDaemonId = localMachineId ? `standalone_${localMachineId}` : "";
|
|
1654
|
+
const daemonMatchesLocal = !daemonId || daemonId === cloudDaemonId || daemonId === standaloneDaemonId;
|
|
1655
|
+
const machineMatchesLocal = !machineId || localMachineId && machineId === localMachineId;
|
|
1656
|
+
if (node?.isLocalWorktree === true) {
|
|
1657
|
+
return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
|
|
1658
|
+
}
|
|
1659
|
+
if (daemonId || machineId) {
|
|
1660
|
+
return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
|
|
1661
|
+
}
|
|
1662
|
+
return null;
|
|
1663
|
+
}
|
|
1664
|
+
function activeAssignedCount(meshId) {
|
|
1665
|
+
return getQueue(meshId, { status: ["assigned"] }).length;
|
|
1666
|
+
}
|
|
1667
|
+
function nodeHasActiveAssignment(meshId, nodeId) {
|
|
1668
|
+
return getQueue(meshId, { status: ["assigned"] }).some((task) => task.assignedNodeId === nodeId);
|
|
1669
|
+
}
|
|
1670
|
+
function liveSessionCountForNode(components, meshId, nodeId) {
|
|
1671
|
+
return components.instanceManager.getByCategory("cli").filter((inst) => {
|
|
1672
|
+
const state = inst.getState();
|
|
1673
|
+
const settings = state.settings || {};
|
|
1674
|
+
if (readNonEmptyString(settings.meshNodeFor) !== meshId) return false;
|
|
1675
|
+
const instNodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
1676
|
+
if (instNodeId !== nodeId) return false;
|
|
1677
|
+
const status = readNonEmptyString(state.status).toLowerCase();
|
|
1678
|
+
return !isTerminalSessionStatus(status);
|
|
1679
|
+
}).length;
|
|
1680
|
+
}
|
|
1681
|
+
function recordAutoLaunchEvent(meshId, args) {
|
|
1682
|
+
try {
|
|
1683
|
+
appendLedgerEntry(meshId, {
|
|
1684
|
+
kind: "session_auto_launch",
|
|
1685
|
+
nodeId: args.nodeId,
|
|
1686
|
+
sessionId: args.sessionId,
|
|
1687
|
+
providerType: args.providerType,
|
|
1688
|
+
payload: {
|
|
1689
|
+
phase: args.phase,
|
|
1690
|
+
taskId: args.taskId,
|
|
1691
|
+
reason: args.reason,
|
|
1692
|
+
error: args.error
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1695
|
+
} catch (e) {
|
|
1696
|
+
LOG.warn("MeshQueue", `Failed to record auto-launch ledger event: ${e?.message || e}`);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
function markAutoLaunch(meshId, taskId, args) {
|
|
1700
|
+
recordTaskAutoLaunch(meshId, taskId, {
|
|
1701
|
+
status: args.status,
|
|
1702
|
+
reason: args.reason || args.error,
|
|
1703
|
+
nodeId: args.nodeId,
|
|
1704
|
+
providerType: args.providerType,
|
|
1705
|
+
sessionId: args.sessionId
|
|
1706
|
+
});
|
|
1707
|
+
recordAutoLaunchEvent(meshId, {
|
|
1708
|
+
phase: args.status,
|
|
1709
|
+
taskId,
|
|
1710
|
+
nodeId: args.nodeId,
|
|
1711
|
+
providerType: args.providerType,
|
|
1712
|
+
sessionId: args.sessionId,
|
|
1713
|
+
reason: args.reason,
|
|
1714
|
+
error: args.error
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
async function resolveUsableProvider(components, nodeId, node) {
|
|
1718
|
+
const providerPriority = normalizeProviderPriority(node?.policy);
|
|
1719
|
+
if (!providerPriority.length) return { reason: "missing_provider_priority" };
|
|
1720
|
+
const providerLoader = components.providerLoader;
|
|
1721
|
+
if (!providerLoader) return { reason: "provider_loader_unavailable" };
|
|
1722
|
+
const failed = [];
|
|
1723
|
+
for (const requestedType of providerPriority) {
|
|
1724
|
+
const normalizedType = typeof providerLoader.resolveAlias === "function" ? providerLoader.resolveAlias(requestedType) : requestedType;
|
|
1725
|
+
if (typeof providerLoader.isMachineProviderEnabled === "function" && !providerLoader.isMachineProviderEnabled(normalizedType)) {
|
|
1726
|
+
failed.push(`${requestedType}: disabled`);
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
let detected;
|
|
1730
|
+
try {
|
|
1731
|
+
detected = await detectCLI(normalizedType, providerLoader, { includeVersion: false });
|
|
1732
|
+
} catch (e) {
|
|
1733
|
+
failed.push(`${requestedType}: detect failed: ${e?.message || e}`);
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
if (typeof providerLoader.setCliDetectionResults === "function") {
|
|
1737
|
+
providerLoader.setCliDetectionResults([{
|
|
1738
|
+
id: normalizedType,
|
|
1739
|
+
installed: !!detected,
|
|
1740
|
+
path: detected?.path
|
|
1741
|
+
}], false);
|
|
1742
|
+
}
|
|
1743
|
+
components.onStatusChange?.();
|
|
1744
|
+
if (detected) return { providerType: normalizedType };
|
|
1745
|
+
failed.push(`${requestedType}: not detected`);
|
|
1746
|
+
}
|
|
1747
|
+
return { reason: `provider_priority_unusable: ${failed.join("; ") || nodeId}` };
|
|
1748
|
+
}
|
|
1749
|
+
async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
|
|
1750
|
+
const queue = getQueue(meshId);
|
|
1751
|
+
const pending = queue.filter((task) => task.status === "pending");
|
|
1752
|
+
if (!pending.length) return false;
|
|
1753
|
+
const maxParallelTasks = Math.max(1, Math.floor(Number(mesh?.policy?.maxParallelTasks) || 2));
|
|
1754
|
+
for (const task of pending) {
|
|
1755
|
+
if (activeAssignedCount(meshId) >= maxParallelTasks) {
|
|
1756
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_parallel_tasks_reached" });
|
|
1757
|
+
return false;
|
|
1758
|
+
}
|
|
1759
|
+
if (task.targetSessionId) {
|
|
1760
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_constraint" });
|
|
1761
|
+
continue;
|
|
1762
|
+
}
|
|
1763
|
+
const candidateNodes = Array.isArray(mesh?.nodes) ? mesh.nodes.filter((node) => task.targetNodeId ? node?.id === task.targetNodeId : true) : [];
|
|
1764
|
+
if (!candidateNodes.length) {
|
|
1765
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "no_matching_node", nodeId: task.targetNodeId });
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
for (const node of candidateNodes) {
|
|
1769
|
+
const nodeId = readNonEmptyString(node?.id);
|
|
1770
|
+
if (!nodeId) continue;
|
|
1771
|
+
const launchKey = `${meshId}:${nodeId}`;
|
|
1772
|
+
const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
|
|
1773
|
+
if (autoLaunchInProgress.has(launchKey)) {
|
|
1774
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_in_progress", nodeId });
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
if (Date.now() < cooldownUntil) {
|
|
1778
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_cooldown", nodeId });
|
|
1779
|
+
continue;
|
|
1780
|
+
}
|
|
1781
|
+
if (isDirtyNode(node)) {
|
|
1782
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "dirty_workspace", nodeId });
|
|
1783
|
+
continue;
|
|
1784
|
+
}
|
|
1785
|
+
if (!isLaunchableNode(node)) {
|
|
1786
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_not_launch_ready", nodeId });
|
|
1787
|
+
continue;
|
|
1788
|
+
}
|
|
1789
|
+
const localSkipReason = localAutoLaunchSkipReason(node);
|
|
1790
|
+
if (localSkipReason) {
|
|
1791
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: localSkipReason, nodeId });
|
|
1792
|
+
continue;
|
|
1793
|
+
}
|
|
1794
|
+
if (nodeHasActiveAssignment(meshId, nodeId)) {
|
|
1795
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_has_active_assignment", nodeId });
|
|
1796
|
+
continue;
|
|
1797
|
+
}
|
|
1798
|
+
const maxConcurrentSessions = Number(node?.policy?.maxConcurrentSessions);
|
|
1799
|
+
if (Number.isFinite(maxConcurrentSessions) && maxConcurrentSessions >= 0 && liveSessionCountForNode(components, meshId, nodeId) >= maxConcurrentSessions) {
|
|
1800
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_concurrent_sessions_reached", nodeId });
|
|
1801
|
+
continue;
|
|
1802
|
+
}
|
|
1803
|
+
autoLaunchInProgress.add(launchKey);
|
|
1804
|
+
try {
|
|
1805
|
+
const resolved = await resolveUsableProvider(components, nodeId, node);
|
|
1806
|
+
if (!resolved.providerType) {
|
|
1807
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
markAutoLaunch(meshId, task.id, { status: "started", nodeId, providerType: resolved.providerType });
|
|
1811
|
+
const launchResult = await components.cliManager.handleCliCommand("launch_cli", {
|
|
1812
|
+
cliType: resolved.providerType,
|
|
1813
|
+
dir: node.workspace,
|
|
1814
|
+
settings: {
|
|
1815
|
+
meshNodeFor: meshId,
|
|
1816
|
+
meshNodeId: nodeId,
|
|
1817
|
+
spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || "hidden",
|
|
1818
|
+
launchedByCoordinator: true,
|
|
1819
|
+
autoLaunchedForQueueTaskId: task.id
|
|
1820
|
+
}
|
|
1821
|
+
});
|
|
1822
|
+
if (!launchResult?.success) {
|
|
1823
|
+
const reason = launchResult?.error || "launch_cli_failed";
|
|
1824
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
|
|
1825
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
1826
|
+
return false;
|
|
1827
|
+
}
|
|
1828
|
+
const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
|
|
1829
|
+
if (!sessionId) {
|
|
1830
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason: "launch_missing_session_id", nodeId, providerType: resolved.providerType });
|
|
1831
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
1832
|
+
return false;
|
|
1833
|
+
}
|
|
1834
|
+
markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId });
|
|
1835
|
+
tryAssignQueueTask(components, meshId, nodeId, sessionId, resolved.providerType);
|
|
1836
|
+
return true;
|
|
1837
|
+
} catch (e) {
|
|
1838
|
+
markAutoLaunch(meshId, task.id, { status: "failed", error: e?.message || String(e), nodeId });
|
|
1839
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
1840
|
+
return false;
|
|
1841
|
+
} finally {
|
|
1842
|
+
autoLaunchInProgress.delete(launchKey);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
return false;
|
|
1847
|
+
}
|
|
1848
|
+
async function triggerMeshQueue(components, meshId) {
|
|
1437
1849
|
const mesh = getMeshWithCache(components, meshId);
|
|
1438
1850
|
if (!mesh) return;
|
|
1439
1851
|
const cliInstances = components.instanceManager.getByCategory("cli");
|
|
@@ -1444,9 +1856,7 @@ function triggerMeshQueue(components, meshId) {
|
|
|
1444
1856
|
if (instMeshId !== meshId) continue;
|
|
1445
1857
|
const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
1446
1858
|
if (!nodeId) continue;
|
|
1447
|
-
|
|
1448
|
-
if (["stopped", "failed", "terminated", "exited", "closed"].includes(status)) continue;
|
|
1449
|
-
if (status !== "idle" && state.activeChat?.status !== "waiting_input") continue;
|
|
1859
|
+
if (!isIdleSessionState(state)) continue;
|
|
1450
1860
|
const sessionId = state.instanceId;
|
|
1451
1861
|
const providerType = state.type || readNonEmptyString(settings.providerType);
|
|
1452
1862
|
if (providerType) {
|
|
@@ -1462,6 +1872,7 @@ function triggerMeshQueue(components, meshId) {
|
|
|
1462
1872
|
}
|
|
1463
1873
|
}
|
|
1464
1874
|
}
|
|
1875
|
+
await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
|
|
1465
1876
|
}
|
|
1466
1877
|
function buildMeshSystemMessage(args) {
|
|
1467
1878
|
const metadata = formatCompletionMetadata(args.metadataEvent);
|
|
@@ -1738,11 +2149,13 @@ function setupMeshEventForwarding(components) {
|
|
|
1738
2149
|
});
|
|
1739
2150
|
});
|
|
1740
2151
|
}
|
|
1741
|
-
var remoteIdleSessions, MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND;
|
|
2152
|
+
var remoteIdleSessions, MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS;
|
|
1742
2153
|
var init_mesh_events = __esm({
|
|
1743
2154
|
"src/mesh/mesh-events.ts"() {
|
|
1744
2155
|
"use strict";
|
|
2156
|
+
init_config();
|
|
1745
2157
|
init_mesh_config();
|
|
2158
|
+
init_cli_detector();
|
|
1746
2159
|
init_logger();
|
|
1747
2160
|
init_mesh_ledger();
|
|
1748
2161
|
init_mesh_work_queue();
|
|
@@ -1763,6 +2176,9 @@ var init_mesh_events = __esm({
|
|
|
1763
2176
|
"agent:stopped": "task_failed",
|
|
1764
2177
|
"monitor:long_generating": "task_stalled"
|
|
1765
2178
|
};
|
|
2179
|
+
autoLaunchInProgress = /* @__PURE__ */ new Set();
|
|
2180
|
+
autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
|
|
2181
|
+
AUTO_LAUNCH_COOLDOWN_MS = 5e3;
|
|
1766
2182
|
}
|
|
1767
2183
|
});
|
|
1768
2184
|
|
|
@@ -6320,7 +6736,7 @@ function addWorkspaceEntry(config, rawPath, label, options) {
|
|
|
6320
6736
|
}
|
|
6321
6737
|
}
|
|
6322
6738
|
const v = validateWorkspacePath(abs);
|
|
6323
|
-
if (
|
|
6739
|
+
if (v.ok !== true) return { error: v.error };
|
|
6324
6740
|
const list = [...config.workspaces || []];
|
|
6325
6741
|
if (list.some((w) => path5.resolve(w.path) === abs)) {
|
|
6326
6742
|
return { error: "Workspace already in list" };
|
|
@@ -6729,10 +7145,120 @@ init_mesh_ledger();
|
|
|
6729
7145
|
init_mesh_work_queue();
|
|
6730
7146
|
init_mesh_events();
|
|
6731
7147
|
|
|
7148
|
+
// src/mesh/p2p-relay-failure.ts
|
|
7149
|
+
var NO_FALLBACK_REASON = "Repo Mesh command/data-plane is P2P-only; WS/REST command fallback is intentionally disabled to preserve the transport boundary.";
|
|
7150
|
+
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.";
|
|
7151
|
+
var NON_P2P_NEXT_ACTION = "Inspect the provider/command error and fix the underlying logic or configuration before retrying.";
|
|
7152
|
+
function messageFromError(error) {
|
|
7153
|
+
if (error instanceof Error) return error.message;
|
|
7154
|
+
if (typeof error === "string") return error;
|
|
7155
|
+
if (error && typeof error === "object") {
|
|
7156
|
+
const candidate = error.error ?? error.message ?? error.reason;
|
|
7157
|
+
if (typeof candidate === "string") return candidate;
|
|
7158
|
+
}
|
|
7159
|
+
return String(error || "mesh relay command failed");
|
|
7160
|
+
}
|
|
7161
|
+
function classifyP2pRelayFailure(error, _context = {}) {
|
|
7162
|
+
const message = messageFromError(error);
|
|
7163
|
+
const lower = message.toLowerCase();
|
|
7164
|
+
const hasP2pSignal = /p2p|datachannel|node-datachannel|webrtc|ice|mesh_relay_command|daemon_mesh_p2p_transport/i.test(message);
|
|
7165
|
+
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);
|
|
7166
|
+
if (/requires targetdaemonid and command|providerpriority|no inference provider|permission denied|read-only|not a member/i.test(message)) {
|
|
7167
|
+
return {
|
|
7168
|
+
code: "mesh_logic_or_provider_failure",
|
|
7169
|
+
reason: "mesh_logic_or_provider_failure",
|
|
7170
|
+
transport: "unknown",
|
|
7171
|
+
recoverable: false,
|
|
7172
|
+
retryRecommended: false,
|
|
7173
|
+
nextAction: NON_P2P_NEXT_ACTION,
|
|
7174
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
7175
|
+
};
|
|
7176
|
+
}
|
|
7177
|
+
let code = null;
|
|
7178
|
+
let reason = "";
|
|
7179
|
+
if (/timeout|timed out/i.test(message) && (hasP2pSignal || /mesh transport/i.test(message))) {
|
|
7180
|
+
code = "p2p_timeout";
|
|
7181
|
+
reason = "daemon_mesh_p2p_timeout";
|
|
7182
|
+
} else if (/no route|route unavailable/i.test(message)) {
|
|
7183
|
+
code = "p2p_no_route";
|
|
7184
|
+
reason = "daemon_mesh_p2p_no_route";
|
|
7185
|
+
} else if (/offline|not owned|not found|not connected to server/i.test(message) && /daemon|peer|target/i.test(message)) {
|
|
7186
|
+
code = "p2p_daemon_offline";
|
|
7187
|
+
reason = "daemon_mesh_target_offline";
|
|
7188
|
+
} else if (/closed|disconnected/i.test(message) && (hasP2pSignal || /state changed/i.test(message))) {
|
|
7189
|
+
code = "p2p_datachannel_closed";
|
|
7190
|
+
reason = "daemon_mesh_p2p_datachannel_closed";
|
|
7191
|
+
} else if (/not connected|cannot send|cannot establish/i.test(message) && hasP2pSignal) {
|
|
7192
|
+
code = "p2p_not_connected";
|
|
7193
|
+
reason = "daemon_mesh_p2p_not_connected";
|
|
7194
|
+
} else if (hasP2pSignal && hasFailureSignal) {
|
|
7195
|
+
code = "p2p_unavailable";
|
|
7196
|
+
reason = "daemon_mesh_p2p_transport_unavailable";
|
|
7197
|
+
}
|
|
7198
|
+
if (!code) {
|
|
7199
|
+
return {
|
|
7200
|
+
code: "mesh_logic_or_provider_failure",
|
|
7201
|
+
reason: "mesh_logic_or_provider_failure",
|
|
7202
|
+
transport: "unknown",
|
|
7203
|
+
recoverable: false,
|
|
7204
|
+
retryRecommended: false,
|
|
7205
|
+
nextAction: NON_P2P_NEXT_ACTION,
|
|
7206
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
7207
|
+
};
|
|
7208
|
+
}
|
|
7209
|
+
return {
|
|
7210
|
+
code,
|
|
7211
|
+
reason,
|
|
7212
|
+
transport: "p2p",
|
|
7213
|
+
recoverable: true,
|
|
7214
|
+
retryRecommended: true,
|
|
7215
|
+
nextAction: P2P_NEXT_ACTION,
|
|
7216
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
7217
|
+
};
|
|
7218
|
+
}
|
|
7219
|
+
function isP2pRelayTransportFailure(error) {
|
|
7220
|
+
return classifyP2pRelayFailure(error).recoverable === true;
|
|
7221
|
+
}
|
|
7222
|
+
function buildP2pRelayFailurePayload(error, context = {}) {
|
|
7223
|
+
const classification = classifyP2pRelayFailure(error, context);
|
|
7224
|
+
return {
|
|
7225
|
+
success: false,
|
|
7226
|
+
...classification,
|
|
7227
|
+
error: messageFromError(error),
|
|
7228
|
+
...context.command ? { command: context.command } : {},
|
|
7229
|
+
...context.targetDaemonId ? { targetDaemonId: context.targetDaemonId } : {}
|
|
7230
|
+
};
|
|
7231
|
+
}
|
|
7232
|
+
var P2pRelayFailureError = class extends Error {
|
|
7233
|
+
code;
|
|
7234
|
+
reason;
|
|
7235
|
+
transport;
|
|
7236
|
+
recoverable;
|
|
7237
|
+
retryRecommended;
|
|
7238
|
+
nextAction;
|
|
7239
|
+
noFallbackReason;
|
|
7240
|
+
command;
|
|
7241
|
+
targetDaemonId;
|
|
7242
|
+
constructor(message, context = {}) {
|
|
7243
|
+
super(message);
|
|
7244
|
+
this.name = "P2pRelayFailureError";
|
|
7245
|
+
const payload = buildP2pRelayFailurePayload(message, context);
|
|
7246
|
+
this.code = payload.code;
|
|
7247
|
+
this.reason = payload.reason;
|
|
7248
|
+
this.transport = payload.transport;
|
|
7249
|
+
this.recoverable = payload.recoverable;
|
|
7250
|
+
this.retryRecommended = payload.retryRecommended;
|
|
7251
|
+
this.nextAction = payload.nextAction;
|
|
7252
|
+
this.noFallbackReason = payload.noFallbackReason;
|
|
7253
|
+
this.command = context.command;
|
|
7254
|
+
this.targetDaemonId = context.targetDaemonId;
|
|
7255
|
+
}
|
|
7256
|
+
};
|
|
7257
|
+
|
|
6732
7258
|
// src/config/state-store.ts
|
|
6733
7259
|
init_config();
|
|
6734
|
-
import { existsSync as
|
|
6735
|
-
import { join as
|
|
7260
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
7261
|
+
import { join as join9 } from "path";
|
|
6736
7262
|
var DEFAULT_STATE = {
|
|
6737
7263
|
recentActivity: [],
|
|
6738
7264
|
savedProviderSessions: [],
|
|
@@ -6745,7 +7271,7 @@ function isPlainObject2(value) {
|
|
|
6745
7271
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
6746
7272
|
}
|
|
6747
7273
|
function getStatePath() {
|
|
6748
|
-
return
|
|
7274
|
+
return join9(getConfigDir(), "state.json");
|
|
6749
7275
|
}
|
|
6750
7276
|
function normalizeState(raw) {
|
|
6751
7277
|
const parsed = isPlainObject2(raw) ? raw : {};
|
|
@@ -6781,7 +7307,7 @@ function normalizeState(raw) {
|
|
|
6781
7307
|
}
|
|
6782
7308
|
function loadState() {
|
|
6783
7309
|
const statePath = getStatePath();
|
|
6784
|
-
if (!
|
|
7310
|
+
if (!existsSync9(statePath)) {
|
|
6785
7311
|
return { ...DEFAULT_STATE };
|
|
6786
7312
|
}
|
|
6787
7313
|
try {
|
|
@@ -6802,9 +7328,9 @@ function resetState() {
|
|
|
6802
7328
|
|
|
6803
7329
|
// src/detection/ide-detector.ts
|
|
6804
7330
|
import { execSync } from "child_process";
|
|
6805
|
-
import { existsSync as
|
|
6806
|
-
import { platform, homedir as
|
|
6807
|
-
import * as
|
|
7331
|
+
import { existsSync as existsSync10 } from "fs";
|
|
7332
|
+
import { platform as platform2, homedir as homedir5 } from "os";
|
|
7333
|
+
import * as path10 from "path";
|
|
6808
7334
|
var BUILTIN_IDE_DEFINITIONS = [];
|
|
6809
7335
|
var registeredIDEs = /* @__PURE__ */ new Map();
|
|
6810
7336
|
function registerIDEDefinition(def) {
|
|
@@ -6823,14 +7349,14 @@ function getMergedDefinitions() {
|
|
|
6823
7349
|
function findCliCommand(command) {
|
|
6824
7350
|
const trimmed = String(command || "").trim();
|
|
6825
7351
|
if (!trimmed) return null;
|
|
6826
|
-
if (
|
|
6827
|
-
const candidate = trimmed.startsWith("~") ?
|
|
6828
|
-
const resolved =
|
|
6829
|
-
return
|
|
7352
|
+
if (path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
|
|
7353
|
+
const candidate = trimmed.startsWith("~") ? path10.join(homedir5(), trimmed.slice(1)) : trimmed;
|
|
7354
|
+
const resolved = path10.isAbsolute(candidate) ? candidate : path10.resolve(candidate);
|
|
7355
|
+
return existsSync10(resolved) ? resolved : null;
|
|
6830
7356
|
}
|
|
6831
7357
|
try {
|
|
6832
7358
|
const result = execSync(
|
|
6833
|
-
|
|
7359
|
+
platform2() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
|
|
6834
7360
|
{ encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
6835
7361
|
).trim();
|
|
6836
7362
|
return result.split("\n")[0] || null;
|
|
@@ -6851,21 +7377,21 @@ function getIdeVersion(cliCommand) {
|
|
|
6851
7377
|
}
|
|
6852
7378
|
}
|
|
6853
7379
|
function checkPathExists(paths) {
|
|
6854
|
-
const home =
|
|
7380
|
+
const home = homedir5();
|
|
6855
7381
|
for (const p of paths) {
|
|
6856
|
-
const normalized = p.startsWith("~") ?
|
|
7382
|
+
const normalized = p.startsWith("~") ? path10.join(home, p.slice(1)) : p;
|
|
6857
7383
|
if (normalized.includes("*")) {
|
|
6858
7384
|
const username = home.split(/[\\/]/).pop() || "";
|
|
6859
7385
|
const resolved = normalized.replace("*", username);
|
|
6860
|
-
if (
|
|
7386
|
+
if (existsSync10(resolved)) return resolved;
|
|
6861
7387
|
} else {
|
|
6862
|
-
if (
|
|
7388
|
+
if (existsSync10(normalized)) return normalized;
|
|
6863
7389
|
}
|
|
6864
7390
|
}
|
|
6865
7391
|
return null;
|
|
6866
7392
|
}
|
|
6867
7393
|
async function detectIDEs(providerLoader) {
|
|
6868
|
-
const os22 =
|
|
7394
|
+
const os22 = platform2();
|
|
6869
7395
|
const results = [];
|
|
6870
7396
|
for (const def of getMergedDefinitions()) {
|
|
6871
7397
|
const cliPath = findCliCommand(providerLoader?.getIdeCliCommand(def.id, def.cli) || def.cli);
|
|
@@ -6873,7 +7399,7 @@ async function detectIDEs(providerLoader) {
|
|
|
6873
7399
|
let resolvedCli = cliPath;
|
|
6874
7400
|
if (!resolvedCli && appPath && os22 === "darwin") {
|
|
6875
7401
|
const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
|
|
6876
|
-
if (
|
|
7402
|
+
if (existsSync10(bundledCli)) resolvedCli = bundledCli;
|
|
6877
7403
|
}
|
|
6878
7404
|
if (!resolvedCli && appPath && os22 === "win32") {
|
|
6879
7405
|
const { dirname: dirname9 } = await import("path");
|
|
@@ -6886,7 +7412,7 @@ async function detectIDEs(providerLoader) {
|
|
|
6886
7412
|
`${appDir}\\\\resources\\\\app\\\\bin\\\\${def.cli}.cmd`
|
|
6887
7413
|
];
|
|
6888
7414
|
for (const c of candidates) {
|
|
6889
|
-
if (
|
|
7415
|
+
if (existsSync10(c)) {
|
|
6890
7416
|
resolvedCli = c;
|
|
6891
7417
|
break;
|
|
6892
7418
|
}
|
|
@@ -6908,134 +7434,8 @@ async function detectIDEs(providerLoader) {
|
|
|
6908
7434
|
return results;
|
|
6909
7435
|
}
|
|
6910
7436
|
|
|
6911
|
-
// src/
|
|
6912
|
-
|
|
6913
|
-
import * as os3 from "os";
|
|
6914
|
-
import * as path10 from "path";
|
|
6915
|
-
import { existsSync as existsSync10 } from "fs";
|
|
6916
|
-
function parseVersion(raw) {
|
|
6917
|
-
const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
|
|
6918
|
-
return match ? match[1] : raw.split("\n")[0].slice(0, 100);
|
|
6919
|
-
}
|
|
6920
|
-
function shellQuote(value) {
|
|
6921
|
-
if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
6922
|
-
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
6923
|
-
}
|
|
6924
|
-
function expandHome(value) {
|
|
6925
|
-
const trimmed = value.trim();
|
|
6926
|
-
if (!trimmed.startsWith("~")) return trimmed;
|
|
6927
|
-
return path10.join(os3.homedir(), trimmed.slice(1));
|
|
6928
|
-
}
|
|
6929
|
-
function isExplicitCommandPath(command) {
|
|
6930
|
-
const trimmed = command.trim();
|
|
6931
|
-
return path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
|
|
6932
|
-
}
|
|
6933
|
-
function resolveCommandPath(command) {
|
|
6934
|
-
const trimmed = command.trim();
|
|
6935
|
-
if (!trimmed) return null;
|
|
6936
|
-
if (isExplicitCommandPath(trimmed)) {
|
|
6937
|
-
const expanded = expandHome(trimmed);
|
|
6938
|
-
const candidate = path10.isAbsolute(expanded) ? expanded : path10.resolve(expanded);
|
|
6939
|
-
return existsSync10(candidate) ? candidate : null;
|
|
6940
|
-
}
|
|
6941
|
-
return null;
|
|
6942
|
-
}
|
|
6943
|
-
function execAsync(cmd, timeoutMs = 5e3) {
|
|
6944
|
-
return new Promise((resolve16) => {
|
|
6945
|
-
const child = exec(cmd, {
|
|
6946
|
-
encoding: "utf-8",
|
|
6947
|
-
timeout: timeoutMs,
|
|
6948
|
-
...process.platform === "win32" ? { windowsHide: true } : {}
|
|
6949
|
-
}, (err, stdout) => {
|
|
6950
|
-
if (err || !stdout?.trim()) {
|
|
6951
|
-
resolve16(null);
|
|
6952
|
-
} else {
|
|
6953
|
-
resolve16(stdout.trim());
|
|
6954
|
-
}
|
|
6955
|
-
});
|
|
6956
|
-
child.on("error", () => resolve16(null));
|
|
6957
|
-
});
|
|
6958
|
-
}
|
|
6959
|
-
async function detectCLIs(providerLoader, options) {
|
|
6960
|
-
const platform10 = os3.platform();
|
|
6961
|
-
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
6962
|
-
const includeVersion = options?.includeVersion !== false;
|
|
6963
|
-
const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
|
|
6964
|
-
const results = await Promise.all(
|
|
6965
|
-
cliList.map(async (cli) => {
|
|
6966
|
-
try {
|
|
6967
|
-
const explicitPath = resolveCommandPath(cli.command);
|
|
6968
|
-
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
|
|
6969
|
-
if (!pathResult) return { ...cli, installed: false };
|
|
6970
|
-
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
6971
|
-
let version;
|
|
6972
|
-
if (includeVersion) {
|
|
6973
|
-
const versionCommands = [
|
|
6974
|
-
`"${firstPath}" --version`,
|
|
6975
|
-
`"${firstPath}" -V`,
|
|
6976
|
-
`"${firstPath}" -v`,
|
|
6977
|
-
cli.versionCommand
|
|
6978
|
-
].filter((v) => !!v);
|
|
6979
|
-
try {
|
|
6980
|
-
for (const versionCommand of versionCommands) {
|
|
6981
|
-
const versionResult = await execAsync(versionCommand, 3e3);
|
|
6982
|
-
if (versionResult) {
|
|
6983
|
-
version = parseVersion(versionResult);
|
|
6984
|
-
break;
|
|
6985
|
-
}
|
|
6986
|
-
}
|
|
6987
|
-
} catch {
|
|
6988
|
-
}
|
|
6989
|
-
}
|
|
6990
|
-
return { ...cli, installed: true, version, path: firstPath };
|
|
6991
|
-
} catch {
|
|
6992
|
-
return { ...cli, installed: false };
|
|
6993
|
-
}
|
|
6994
|
-
})
|
|
6995
|
-
);
|
|
6996
|
-
return results;
|
|
6997
|
-
}
|
|
6998
|
-
async function detectCLI(cliId, providerLoader, options) {
|
|
6999
|
-
const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
|
|
7000
|
-
if (providerLoader) {
|
|
7001
|
-
const cliList = providerLoader.getCliDetectionList();
|
|
7002
|
-
const target = cliList.find((c) => c.id === resolvedId);
|
|
7003
|
-
if (target) {
|
|
7004
|
-
const platform10 = os3.platform();
|
|
7005
|
-
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
7006
|
-
try {
|
|
7007
|
-
const explicitPath = resolveCommandPath(target.command);
|
|
7008
|
-
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
|
|
7009
|
-
if (!pathResult) return null;
|
|
7010
|
-
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
7011
|
-
let version;
|
|
7012
|
-
if (options?.includeVersion !== false) {
|
|
7013
|
-
const versionCommands = [
|
|
7014
|
-
`"${firstPath}" --version`,
|
|
7015
|
-
`"${firstPath}" -V`,
|
|
7016
|
-
`"${firstPath}" -v`,
|
|
7017
|
-
target.versionCommand
|
|
7018
|
-
].filter((v) => !!v);
|
|
7019
|
-
try {
|
|
7020
|
-
for (const versionCommand of versionCommands) {
|
|
7021
|
-
const versionResult = await execAsync(versionCommand, 3e3);
|
|
7022
|
-
if (versionResult) {
|
|
7023
|
-
version = parseVersion(versionResult);
|
|
7024
|
-
break;
|
|
7025
|
-
}
|
|
7026
|
-
}
|
|
7027
|
-
} catch {
|
|
7028
|
-
}
|
|
7029
|
-
}
|
|
7030
|
-
return { ...target, installed: true, version, path: firstPath };
|
|
7031
|
-
} catch {
|
|
7032
|
-
return null;
|
|
7033
|
-
}
|
|
7034
|
-
}
|
|
7035
|
-
}
|
|
7036
|
-
const all = await detectCLIs(providerLoader, options);
|
|
7037
|
-
return all.find((c) => c.id === resolvedId && c.installed) || null;
|
|
7038
|
-
}
|
|
7437
|
+
// src/index.ts
|
|
7438
|
+
init_cli_detector();
|
|
7039
7439
|
|
|
7040
7440
|
// src/system/host-memory.ts
|
|
7041
7441
|
import * as os4 from "os";
|
|
@@ -16139,13 +16539,14 @@ var DaemonCommandHandler = class {
|
|
|
16139
16539
|
|
|
16140
16540
|
// src/commands/cli-manager.ts
|
|
16141
16541
|
init_provider_cli_adapter();
|
|
16542
|
+
init_cli_detector();
|
|
16543
|
+
init_config();
|
|
16142
16544
|
import * as os13 from "os";
|
|
16143
16545
|
import * as path18 from "path";
|
|
16144
16546
|
import * as crypto4 from "crypto";
|
|
16145
16547
|
import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
16146
16548
|
import { execFileSync } from "child_process";
|
|
16147
16549
|
import chalk from "chalk";
|
|
16148
|
-
init_config();
|
|
16149
16550
|
|
|
16150
16551
|
// src/providers/cli-provider-instance.ts
|
|
16151
16552
|
import * as os12 from "os";
|
|
@@ -21731,6 +22132,7 @@ function getAvailableIdeIds() {
|
|
|
21731
22132
|
|
|
21732
22133
|
// src/commands/router.ts
|
|
21733
22134
|
init_config();
|
|
22135
|
+
init_cli_detector();
|
|
21734
22136
|
init_logger();
|
|
21735
22137
|
|
|
21736
22138
|
// src/logging/command-log.ts
|
|
@@ -22877,6 +23279,209 @@ async function resolveProviderTypeFromPriority(args) {
|
|
|
22877
23279
|
}
|
|
22878
23280
|
return { error: `No usable provider detected for node '${args.nodeId}' from providerPriority: ${failed.join("; ")}` };
|
|
22879
23281
|
}
|
|
23282
|
+
var REFINE_VALIDATION_CATEGORIES = ["typecheck", "test", "lint", "build"];
|
|
23283
|
+
var REFINE_VALIDATION_TIMEOUT_MS = 12e4;
|
|
23284
|
+
var REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
|
|
23285
|
+
var REFINE_VALIDATION_SUMMARY_CHARS = 2e3;
|
|
23286
|
+
var REFINE_VALIDATION_MAX_COMMANDS = 4;
|
|
23287
|
+
function truncateValidationOutput(value) {
|
|
23288
|
+
const text = typeof value === "string" ? value : value == null ? "" : String(value);
|
|
23289
|
+
if (text.length <= REFINE_VALIDATION_SUMMARY_CHARS) return text;
|
|
23290
|
+
return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}
|
|
23291
|
+
[truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
|
|
23292
|
+
}
|
|
23293
|
+
function readPackageScripts(workspace) {
|
|
23294
|
+
try {
|
|
23295
|
+
const packageJsonPath = pathJoin(workspace, "package.json");
|
|
23296
|
+
const parsed = JSON.parse(fs10.readFileSync(packageJsonPath, "utf-8"));
|
|
23297
|
+
return parsed?.scripts && typeof parsed.scripts === "object" && !Array.isArray(parsed.scripts) ? parsed.scripts : {};
|
|
23298
|
+
} catch {
|
|
23299
|
+
return {};
|
|
23300
|
+
}
|
|
23301
|
+
}
|
|
23302
|
+
function tokenizeValidationCommand(command) {
|
|
23303
|
+
const trimmed = command.trim();
|
|
23304
|
+
if (!trimmed) return null;
|
|
23305
|
+
if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
|
|
23306
|
+
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
23307
|
+
if (!tokens.length) return null;
|
|
23308
|
+
if (tokens.some((token) => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
|
|
23309
|
+
return tokens;
|
|
23310
|
+
}
|
|
23311
|
+
function scriptMatchesValidationCategory(scriptName, category) {
|
|
23312
|
+
return scriptName === category || scriptName.startsWith(`${category}:`);
|
|
23313
|
+
}
|
|
23314
|
+
function parsePackageManagerValidationCommand(rawCommand, category, scripts, source) {
|
|
23315
|
+
const tokens = tokenizeValidationCommand(rawCommand);
|
|
23316
|
+
if (!tokens) {
|
|
23317
|
+
return { rejected: { command: rawCommand, category, source, reason: "unsafe command string is not allowlisted" } };
|
|
23318
|
+
}
|
|
23319
|
+
const [binary, second, third, ...rest] = tokens;
|
|
23320
|
+
let scriptName = "";
|
|
23321
|
+
let command = binary;
|
|
23322
|
+
let args = [];
|
|
23323
|
+
if ((binary === "npm" || binary === "pnpm" || binary === "bun") && second === "run" && third) {
|
|
23324
|
+
scriptName = third;
|
|
23325
|
+
args = ["run", scriptName, ...rest];
|
|
23326
|
+
} else if (binary === "npm" && second === "test" && !third) {
|
|
23327
|
+
scriptName = "test";
|
|
23328
|
+
args = ["test"];
|
|
23329
|
+
} else if (binary === "yarn" && second === "run" && third) {
|
|
23330
|
+
scriptName = third;
|
|
23331
|
+
args = ["run", scriptName, ...rest];
|
|
23332
|
+
} else if (binary === "yarn" && second && !third) {
|
|
23333
|
+
scriptName = second;
|
|
23334
|
+
args = [scriptName];
|
|
23335
|
+
} else {
|
|
23336
|
+
return { rejected: { command: rawCommand, category, source, reason: "command is not a supported package-manager script invocation" } };
|
|
23337
|
+
}
|
|
23338
|
+
if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
|
|
23339
|
+
return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script is not declared in package.json" } };
|
|
23340
|
+
}
|
|
23341
|
+
if (!scriptMatchesValidationCategory(scriptName, category)) {
|
|
23342
|
+
return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script name is outside the validation category allowlist" } };
|
|
23343
|
+
}
|
|
23344
|
+
return {
|
|
23345
|
+
command: {
|
|
23346
|
+
command,
|
|
23347
|
+
args,
|
|
23348
|
+
displayCommand: [command, ...args].join(" "),
|
|
23349
|
+
category,
|
|
23350
|
+
source
|
|
23351
|
+
}
|
|
23352
|
+
};
|
|
23353
|
+
}
|
|
23354
|
+
function collectProjectContextValidationCandidates(mesh) {
|
|
23355
|
+
const commands = mesh?.projectContext?.commands;
|
|
23356
|
+
if (!commands || typeof commands !== "object" || Array.isArray(commands)) return [];
|
|
23357
|
+
const candidates = [];
|
|
23358
|
+
for (const category of REFINE_VALIDATION_CATEGORIES) {
|
|
23359
|
+
const entries = Array.isArray(commands[category]) ? commands[category] : [];
|
|
23360
|
+
for (const entry of entries) {
|
|
23361
|
+
if (typeof entry?.command !== "string") continue;
|
|
23362
|
+
candidates.push({
|
|
23363
|
+
command: entry.command,
|
|
23364
|
+
category,
|
|
23365
|
+
source: typeof entry.sourcePath === "string" ? entry.sourcePath : "projectContext.commands",
|
|
23366
|
+
confidence: typeof entry.confidence === "string" ? entry.confidence : void 0
|
|
23367
|
+
});
|
|
23368
|
+
}
|
|
23369
|
+
}
|
|
23370
|
+
return candidates.sort((a, b) => {
|
|
23371
|
+
const rank = (value) => value === "high" ? 0 : value === "medium" ? 1 : 2;
|
|
23372
|
+
return rank(a.confidence) - rank(b.confidence);
|
|
23373
|
+
});
|
|
23374
|
+
}
|
|
23375
|
+
function collectPolicyValidationCandidates(mesh) {
|
|
23376
|
+
const policy = mesh?.policy && typeof mesh.policy === "object" && !Array.isArray(mesh.policy) ? mesh.policy : {};
|
|
23377
|
+
const configured = Array.isArray(policy.validationCommands) ? policy.validationCommands : Array.isArray(policy.validationGate?.commands) ? policy.validationGate.commands : [];
|
|
23378
|
+
return configured.map((entry) => typeof entry === "string" ? { command: entry, category: "", source: "mesh.policy.validationCommands" } : entry).filter((entry) => entry && typeof entry.command === "string").map((entry) => {
|
|
23379
|
+
const commandText = entry.command.trim();
|
|
23380
|
+
const category = REFINE_VALIDATION_CATEGORIES.find((cat) => commandText.includes(` ${cat}`)) ?? "";
|
|
23381
|
+
return { command: commandText, category, source: "mesh.policy.validationCommands" };
|
|
23382
|
+
}).filter((entry) => !!entry.category);
|
|
23383
|
+
}
|
|
23384
|
+
function selectMeshRefineValidationCommands(mesh, workspace) {
|
|
23385
|
+
const scripts = readPackageScripts(workspace);
|
|
23386
|
+
const rejectedCommands = [];
|
|
23387
|
+
const selected = [];
|
|
23388
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23389
|
+
const candidates = [
|
|
23390
|
+
...collectPolicyValidationCandidates(mesh),
|
|
23391
|
+
...collectProjectContextValidationCandidates(mesh)
|
|
23392
|
+
];
|
|
23393
|
+
for (const candidate of candidates) {
|
|
23394
|
+
const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
|
|
23395
|
+
if (parsed.rejected) {
|
|
23396
|
+
rejectedCommands.push(parsed.rejected);
|
|
23397
|
+
continue;
|
|
23398
|
+
}
|
|
23399
|
+
if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
|
|
23400
|
+
selected.push(parsed.command);
|
|
23401
|
+
seen.add(parsed.command.displayCommand);
|
|
23402
|
+
if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
|
|
23403
|
+
}
|
|
23404
|
+
if (!selected.length && candidates.length === 0) {
|
|
23405
|
+
for (const category of REFINE_VALIDATION_CATEGORIES) {
|
|
23406
|
+
if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
|
|
23407
|
+
const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, "package.json:scripts");
|
|
23408
|
+
if (fallback.command && !seen.has(fallback.command.displayCommand)) {
|
|
23409
|
+
selected.push(fallback.command);
|
|
23410
|
+
seen.add(fallback.command.displayCommand);
|
|
23411
|
+
} else if (fallback.rejected) {
|
|
23412
|
+
rejectedCommands.push(fallback.rejected);
|
|
23413
|
+
}
|
|
23414
|
+
if (selected.length >= 2) break;
|
|
23415
|
+
}
|
|
23416
|
+
}
|
|
23417
|
+
return {
|
|
23418
|
+
commands: selected,
|
|
23419
|
+
rejectedCommands,
|
|
23420
|
+
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"
|
|
23421
|
+
};
|
|
23422
|
+
}
|
|
23423
|
+
async function runMeshRefineValidationGate(mesh, workspace) {
|
|
23424
|
+
const { execFile: execFile3 } = await import("child_process");
|
|
23425
|
+
const { promisify: promisify3 } = await import("util");
|
|
23426
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
23427
|
+
const selection = selectMeshRefineValidationCommands(mesh, workspace);
|
|
23428
|
+
const summary = {
|
|
23429
|
+
status: "skipped",
|
|
23430
|
+
required: true,
|
|
23431
|
+
commandsRun: [],
|
|
23432
|
+
rejectedCommands: selection.rejectedCommands,
|
|
23433
|
+
skippedReason: void 0,
|
|
23434
|
+
timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
|
|
23435
|
+
outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES
|
|
23436
|
+
};
|
|
23437
|
+
if (!selection.commands.length) {
|
|
23438
|
+
summary.skippedReason = "validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available";
|
|
23439
|
+
return summary;
|
|
23440
|
+
}
|
|
23441
|
+
for (const candidate of selection.commands) {
|
|
23442
|
+
const startedAt = Date.now();
|
|
23443
|
+
try {
|
|
23444
|
+
const result = await execFileAsync3(candidate.command, candidate.args, {
|
|
23445
|
+
cwd: workspace,
|
|
23446
|
+
encoding: "utf8",
|
|
23447
|
+
timeout: REFINE_VALIDATION_TIMEOUT_MS,
|
|
23448
|
+
maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
|
|
23449
|
+
env: { ...process.env, CI: process.env.CI || "1" }
|
|
23450
|
+
});
|
|
23451
|
+
summary.commandsRun.push({
|
|
23452
|
+
command: candidate.command,
|
|
23453
|
+
args: candidate.args,
|
|
23454
|
+
displayCommand: candidate.displayCommand,
|
|
23455
|
+
category: candidate.category,
|
|
23456
|
+
source: candidate.source,
|
|
23457
|
+
passed: true,
|
|
23458
|
+
exitCode: 0,
|
|
23459
|
+
durationMs: Date.now() - startedAt,
|
|
23460
|
+
stdout: truncateValidationOutput(result.stdout),
|
|
23461
|
+
stderr: truncateValidationOutput(result.stderr)
|
|
23462
|
+
});
|
|
23463
|
+
} catch (error) {
|
|
23464
|
+
summary.commandsRun.push({
|
|
23465
|
+
command: candidate.command,
|
|
23466
|
+
args: candidate.args,
|
|
23467
|
+
displayCommand: candidate.displayCommand,
|
|
23468
|
+
category: candidate.category,
|
|
23469
|
+
source: candidate.source,
|
|
23470
|
+
passed: false,
|
|
23471
|
+
exitCode: typeof error?.code === "number" ? error.code : null,
|
|
23472
|
+
signal: typeof error?.signal === "string" ? error.signal : null,
|
|
23473
|
+
timedOut: error?.killed === true || /timed out/i.test(String(error?.message || "")),
|
|
23474
|
+
durationMs: Date.now() - startedAt,
|
|
23475
|
+
stdout: truncateValidationOutput(error?.stdout),
|
|
23476
|
+
stderr: truncateValidationOutput(error?.stderr || error?.message)
|
|
23477
|
+
});
|
|
23478
|
+
summary.status = "failed";
|
|
23479
|
+
return summary;
|
|
23480
|
+
}
|
|
23481
|
+
}
|
|
23482
|
+
summary.status = "passed";
|
|
23483
|
+
return summary;
|
|
23484
|
+
}
|
|
22880
23485
|
function loadYamlModule() {
|
|
22881
23486
|
return yaml;
|
|
22882
23487
|
}
|
|
@@ -23160,20 +23765,98 @@ var DaemonCommandRouter = class {
|
|
|
23160
23765
|
recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
|
|
23161
23766
|
};
|
|
23162
23767
|
}
|
|
23768
|
+
const forceFallbackConvergence = await this.getWorktreeForceCleanupConvergence({
|
|
23769
|
+
repoRoot,
|
|
23770
|
+
workspace,
|
|
23771
|
+
node: args.node
|
|
23772
|
+
});
|
|
23163
23773
|
try {
|
|
23164
|
-
const result = await removeWorktree2(repoRoot, workspace, {
|
|
23165
|
-
|
|
23774
|
+
const result = await removeWorktree2(repoRoot, workspace, {
|
|
23775
|
+
requireClean: true,
|
|
23776
|
+
allowSubmoduleForceFallback: forceFallbackConvergence.allow
|
|
23777
|
+
});
|
|
23778
|
+
return {
|
|
23779
|
+
success: true,
|
|
23780
|
+
removedPath: result.removedPath,
|
|
23781
|
+
repoRoot,
|
|
23782
|
+
...result.fallback ? {
|
|
23783
|
+
fallback: result.fallback,
|
|
23784
|
+
forced: result.forced,
|
|
23785
|
+
reason: result.reason,
|
|
23786
|
+
convergence: forceFallbackConvergence
|
|
23787
|
+
} : {}
|
|
23788
|
+
};
|
|
23166
23789
|
} catch (e) {
|
|
23167
23790
|
const message = String(e?.message || e || "worktree cleanup failed");
|
|
23168
23791
|
const dirty = message.includes("dirty worktree") || message.includes("local changes");
|
|
23792
|
+
const submoduleForceBlocked = /working trees containing submodules cannot be moved or removed/i.test(message) && !forceFallbackConvergence.allow;
|
|
23169
23793
|
return {
|
|
23170
23794
|
success: false,
|
|
23171
|
-
code: dirty ? "mesh_worktree_cleanup_dirty" : "mesh_worktree_cleanup_failed",
|
|
23172
|
-
error: message,
|
|
23173
|
-
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."
|
|
23795
|
+
code: dirty ? "mesh_worktree_cleanup_dirty" : submoduleForceBlocked ? "mesh_worktree_cleanup_force_fallback_blocked" : "mesh_worktree_cleanup_failed",
|
|
23796
|
+
error: submoduleForceBlocked ? `${message}; refusing --force fallback because convergence could not be verified: ${forceFallbackConvergence.error || "unknown convergence state"}` : message,
|
|
23797
|
+
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.",
|
|
23798
|
+
...submoduleForceBlocked ? { convergence: forceFallbackConvergence } : {}
|
|
23174
23799
|
};
|
|
23175
23800
|
}
|
|
23176
23801
|
}
|
|
23802
|
+
async getWorktreeForceCleanupConvergence(args) {
|
|
23803
|
+
const metadataStatus = typeof args.node?.branchConvergence?.status === "string" ? args.node.branchConvergence.status : "";
|
|
23804
|
+
if (metadataStatus === "merged_to_main" || metadataStatus === "cleanup_candidate") {
|
|
23805
|
+
return { allow: true, status: metadataStatus, source: "node_branch_convergence" };
|
|
23806
|
+
}
|
|
23807
|
+
const { execFile: execFile3 } = await import("child_process");
|
|
23808
|
+
const { promisify: promisify3 } = await import("util");
|
|
23809
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
23810
|
+
const runGit2 = async (gitArgs, cwd) => {
|
|
23811
|
+
const { stdout } = await execFileAsync3("git", gitArgs, {
|
|
23812
|
+
cwd,
|
|
23813
|
+
encoding: "utf8",
|
|
23814
|
+
timeout: 3e4,
|
|
23815
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
23816
|
+
windowsHide: true
|
|
23817
|
+
});
|
|
23818
|
+
return String(stdout || "").trim();
|
|
23819
|
+
};
|
|
23820
|
+
let head = "";
|
|
23821
|
+
try {
|
|
23822
|
+
head = await runGit2(["rev-parse", "HEAD"], args.workspace);
|
|
23823
|
+
} catch (e) {
|
|
23824
|
+
return { allow: false, error: `could not resolve worktree HEAD: ${e?.message || e}` };
|
|
23825
|
+
}
|
|
23826
|
+
if (!head) return { allow: false, error: "worktree HEAD is empty" };
|
|
23827
|
+
const candidateRefs = [];
|
|
23828
|
+
try {
|
|
23829
|
+
const defaultBranch = await runGit2(["branch", "--show-current"], args.repoRoot);
|
|
23830
|
+
if (defaultBranch) {
|
|
23831
|
+
candidateRefs.push(defaultBranch, `origin/${defaultBranch}`);
|
|
23832
|
+
}
|
|
23833
|
+
} catch {
|
|
23834
|
+
}
|
|
23835
|
+
candidateRefs.push("origin/main", "origin/master", "main", "master");
|
|
23836
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23837
|
+
const checkedRefs = [];
|
|
23838
|
+
for (const ref of candidateRefs) {
|
|
23839
|
+
if (!ref || seen.has(ref)) continue;
|
|
23840
|
+
seen.add(ref);
|
|
23841
|
+
let commit = "";
|
|
23842
|
+
try {
|
|
23843
|
+
commit = await runGit2(["rev-parse", "--verify", `${ref}^{commit}`], args.repoRoot);
|
|
23844
|
+
} catch {
|
|
23845
|
+
continue;
|
|
23846
|
+
}
|
|
23847
|
+
checkedRefs.push(ref);
|
|
23848
|
+
try {
|
|
23849
|
+
await runGit2(["merge-base", "--is-ancestor", head, commit], args.repoRoot);
|
|
23850
|
+
return { allow: true, status: "merged_to_default_ref", source: "git_merge_base", ref };
|
|
23851
|
+
} catch {
|
|
23852
|
+
}
|
|
23853
|
+
}
|
|
23854
|
+
return {
|
|
23855
|
+
allow: false,
|
|
23856
|
+
status: metadataStatus || void 0,
|
|
23857
|
+
error: checkedRefs.length ? `worktree HEAD is not contained in checked refs: ${checkedRefs.join(", ")}` : "no default/main refs were available for convergence verification"
|
|
23858
|
+
};
|
|
23859
|
+
}
|
|
23177
23860
|
isCompletedHostedSession(record) {
|
|
23178
23861
|
return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
|
|
23179
23862
|
}
|
|
@@ -24133,10 +24816,61 @@ var DaemonCommandRouter = class {
|
|
|
24133
24816
|
if (!branch) return { success: false, error: "Could not determine branch of the worktree node" };
|
|
24134
24817
|
const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
|
|
24135
24818
|
const baseBranch = baseBranchStdout.trim();
|
|
24819
|
+
const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
|
|
24820
|
+
if (validationSummary.status === "failed") {
|
|
24821
|
+
return {
|
|
24822
|
+
success: false,
|
|
24823
|
+
code: "validation_failed",
|
|
24824
|
+
convergenceStatus: "blocked_review",
|
|
24825
|
+
error: "Refinery validation gate failed; merge/refine was not attempted.",
|
|
24826
|
+
branch,
|
|
24827
|
+
into: baseBranch,
|
|
24828
|
+
validationSummary,
|
|
24829
|
+
finalBranchConvergenceState: {
|
|
24830
|
+
branch,
|
|
24831
|
+
baseBranch,
|
|
24832
|
+
merged: false,
|
|
24833
|
+
removed: false,
|
|
24834
|
+
validation: "failed",
|
|
24835
|
+
status: "blocked_review"
|
|
24836
|
+
}
|
|
24837
|
+
};
|
|
24838
|
+
}
|
|
24839
|
+
if (validationSummary.status === "skipped") {
|
|
24840
|
+
return {
|
|
24841
|
+
success: false,
|
|
24842
|
+
code: "validation_unavailable",
|
|
24843
|
+
convergenceStatus: "blocked_review",
|
|
24844
|
+
error: "Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.",
|
|
24845
|
+
branch,
|
|
24846
|
+
into: baseBranch,
|
|
24847
|
+
validationSummary,
|
|
24848
|
+
finalBranchConvergenceState: {
|
|
24849
|
+
branch,
|
|
24850
|
+
baseBranch,
|
|
24851
|
+
merged: false,
|
|
24852
|
+
removed: false,
|
|
24853
|
+
validation: "unavailable",
|
|
24854
|
+
status: "blocked_review"
|
|
24855
|
+
}
|
|
24856
|
+
};
|
|
24857
|
+
}
|
|
24136
24858
|
try {
|
|
24137
24859
|
await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
|
|
24138
24860
|
} catch (e) {
|
|
24139
|
-
return {
|
|
24861
|
+
return {
|
|
24862
|
+
success: false,
|
|
24863
|
+
error: `Merge failed (conflicts?): ${e.message}`,
|
|
24864
|
+
validationSummary,
|
|
24865
|
+
finalBranchConvergenceState: {
|
|
24866
|
+
branch,
|
|
24867
|
+
baseBranch,
|
|
24868
|
+
merged: false,
|
|
24869
|
+
removed: false,
|
|
24870
|
+
validation: "passed",
|
|
24871
|
+
status: "not_mergeable"
|
|
24872
|
+
}
|
|
24873
|
+
};
|
|
24140
24874
|
}
|
|
24141
24875
|
const removeResult = await this.execute("remove_mesh_node", {
|
|
24142
24876
|
meshId,
|
|
@@ -24149,11 +24883,27 @@ var DaemonCommandRouter = class {
|
|
|
24149
24883
|
appendLedgerEntry2(meshId, {
|
|
24150
24884
|
kind: "node_removed",
|
|
24151
24885
|
nodeId,
|
|
24152
|
-
payload: { refined: true, mergedBranch: branch, into: baseBranch }
|
|
24886
|
+
payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary }
|
|
24153
24887
|
});
|
|
24154
24888
|
} catch {
|
|
24155
24889
|
}
|
|
24156
|
-
return {
|
|
24890
|
+
return {
|
|
24891
|
+
success: true,
|
|
24892
|
+
merged: true,
|
|
24893
|
+
branch,
|
|
24894
|
+
into: baseBranch,
|
|
24895
|
+
removeResult,
|
|
24896
|
+
validationSummary,
|
|
24897
|
+
finalBranchConvergenceState: {
|
|
24898
|
+
branch: baseBranch,
|
|
24899
|
+
mergedBranch: branch,
|
|
24900
|
+
baseBranch,
|
|
24901
|
+
merged: true,
|
|
24902
|
+
removed: removeResult?.success !== false,
|
|
24903
|
+
validation: "passed",
|
|
24904
|
+
status: removeResult?.success === false ? "merged_cleanup_failed" : "merged"
|
|
24905
|
+
}
|
|
24906
|
+
};
|
|
24157
24907
|
} catch (e) {
|
|
24158
24908
|
return { success: false, error: e.message };
|
|
24159
24909
|
}
|
|
@@ -24208,7 +24958,10 @@ var DaemonCommandRouter = class {
|
|
|
24208
24958
|
sessionCleanupMode,
|
|
24209
24959
|
workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
|
|
24210
24960
|
daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
|
|
24211
|
-
worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0
|
|
24961
|
+
worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
|
|
24962
|
+
worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
|
|
24963
|
+
forced: worktreeCleanup?.forced === true ? true : void 0,
|
|
24964
|
+
forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
|
|
24212
24965
|
}
|
|
24213
24966
|
});
|
|
24214
24967
|
} catch {
|
|
@@ -32345,6 +33098,9 @@ function launchIDE(ide, workspacePath) {
|
|
|
32345
33098
|
}
|
|
32346
33099
|
}
|
|
32347
33100
|
|
|
33101
|
+
// src/boot/daemon-lifecycle.ts
|
|
33102
|
+
init_cli_detector();
|
|
33103
|
+
|
|
32348
33104
|
// src/sessions/registry.ts
|
|
32349
33105
|
var SessionRegistry = class {
|
|
32350
33106
|
bySessionId = /* @__PURE__ */ new Map();
|
|
@@ -32681,6 +33437,7 @@ export {
|
|
|
32681
33437
|
MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
|
|
32682
33438
|
MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
|
|
32683
33439
|
NodePtyTransportFactory,
|
|
33440
|
+
P2pRelayFailureError,
|
|
32684
33441
|
ProviderCliAdapter,
|
|
32685
33442
|
ProviderInstanceManager,
|
|
32686
33443
|
ProviderLoader,
|
|
@@ -32697,6 +33454,7 @@ export {
|
|
|
32697
33454
|
buildChatTailDeliverySignature,
|
|
32698
33455
|
buildCoordinatorSystemPrompt,
|
|
32699
33456
|
buildMachineInfo,
|
|
33457
|
+
buildP2pRelayFailurePayload,
|
|
32700
33458
|
buildPinnedGlobalInstallCommand,
|
|
32701
33459
|
buildRuntimeSystemChatMessage,
|
|
32702
33460
|
buildSessionEntries,
|
|
@@ -32711,6 +33469,7 @@ export {
|
|
|
32711
33469
|
claimNextTask,
|
|
32712
33470
|
classifyChatMessageVisibility,
|
|
32713
33471
|
classifyHotChatSessionsForSubscriptionFlush,
|
|
33472
|
+
classifyP2pRelayFailure,
|
|
32714
33473
|
clearDebugTrace,
|
|
32715
33474
|
compareGitSnapshots,
|
|
32716
33475
|
configureDebugTraceStore,
|
|
@@ -32778,6 +33537,7 @@ export {
|
|
|
32778
33537
|
isInternalChatMessage,
|
|
32779
33538
|
isManagedStatusWaiting,
|
|
32780
33539
|
isManagedStatusWorking,
|
|
33540
|
+
isP2pRelayTransportFailure,
|
|
32781
33541
|
isPathInside,
|
|
32782
33542
|
isSessionHostLiveRuntime,
|
|
32783
33543
|
isSessionHostRecoverySnapshot,
|