@adhdev/daemon-core 0.9.77-rc.43 → 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/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 -182
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +942 -183
- 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 +10 -0
- package/dist/mesh/p2p-relay-failure.d.ts +35 -0
- package/package.json +1 -1
- package/src/commands/router.ts +446 -11
- package/src/git/git-worktree.ts +35 -1
- package/src/index.d.ts +3 -0
- package/src/index.ts +14 -0
- package/src/mesh/coordinator-prompt.ts +16 -8
- package/src/mesh/mesh-events.ts +292 -5
- package/src/mesh/mesh-ledger.ts +1 -0
- package/src/mesh/mesh-work-queue.ts +27 -0
- package/src/mesh/p2p-relay-failure.ts +152 -0
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
|
-
|
|
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
|
|
|
@@ -689,7 +716,8 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
689
716
|
- **Minimize coordinator context.** The coordinator's job is routing, not implementing. Do not read source files, run commands, or analyze code directly \u2014 delegate all of that to node agents. Your context should stay lean.
|
|
690
717
|
- **Delegate analysis too.** If you need to understand a bug or explore the codebase, send that investigation as a task to the queue or a node. Do not do it yourself.
|
|
691
718
|
- **Respect explicit provider requests.** If the user names an agent/provider, pass the matching provider type to \`mesh_launch_session\`: Hermes \u2192 \`hermes-cli\`, Claude Code/Claude \u2192 \`claude-cli\`, Codex \u2192 \`codex-cli\`, Gemini \u2192 \`gemini-cli\`. Never substitute \`claude-cli\` just because the coordinator itself is Claude Code.
|
|
692
|
-
- **Front-load
|
|
719
|
+
- **Front-load new task messages.** When calling \`mesh_enqueue_task\` or \`mesh_send_task\` for a new task, include everything the agent needs: what files to touch, what the problem is, what the fix should look like. The agent won't ask follow-up questions.
|
|
720
|
+
- **Avoid context-wasting restarts.** For follow-up, retry, commit/push, preview, or cleanup work on the same issue, prefer the existing idle session and send only the delta from its last verified state. Start a fresh chat/session only for genuinely independent work, explicit provider/user request, unsafe transcript contamination, or required branch/worktree isolation.
|
|
693
721
|
- **Don't inspect code.** Treat delegated agent summaries as self-reports, not verification. Verify side effects via \`mesh_git_status\` (including related repo freshness when configured), not by reading source files.
|
|
694
722
|
- **Don't over-parallelize.** Start with 1-2 concurrent tasks. Scale up if they succeed. Never launch a duplicate session or second worker solely because \`mesh_read_chat\` has no final assistant message while the delegated session is still showing tool/terminal activity.
|
|
695
723
|
- **Handle failures with context.** If a task fails, check \`mesh_task_history\` first to see if this task was attempted before and how it failed. Read the chat to understand why, then decide: retry on the same node, reassign to a different node, or escalate to the user.
|
|
@@ -710,18 +738,24 @@ var init_coordinator_prompt = __esm({
|
|
|
710
738
|
|
|
711
739
|
| Tool | Purpose |
|
|
712
740
|
|------|---------|
|
|
713
|
-
| \`mesh_status\` | Check all nodes' health, git state,
|
|
741
|
+
| \`mesh_status\` | Check all nodes' health, git state, active sessions, and branch convergence |
|
|
714
742
|
| \`mesh_list_nodes\` | List nodes with workspace paths |
|
|
743
|
+
| \`mesh_enqueue_task\` | Add a task to the pull-based work queue; idle nodes auto-claim |
|
|
744
|
+
| \`mesh_view_queue\` | View queue status \u2014 pending, assigned, completed, failed, cancelled tasks |
|
|
745
|
+
| \`mesh_queue_cancel\` | Cancel a queue task without deleting audit history |
|
|
746
|
+
| \`mesh_queue_requeue\` | Return a task to pending for retry; clears stale session targets |
|
|
747
|
+
| \`mesh_send_task\` | Legacy push: enqueue a task targeted at a specific node |
|
|
715
748
|
| \`mesh_launch_session\` | Start a new agent session on a node |
|
|
716
|
-
| \`
|
|
717
|
-
| \`
|
|
749
|
+
| \`mesh_read_chat\` | Read recent chat messages from a delegated agent session |
|
|
750
|
+
| \`mesh_read_debug\` | Collect a daemon-side chat/parser debug bundle for a session |
|
|
718
751
|
| \`mesh_task_history\` | Read the task ledger \u2014 dispatches, completions, failures. Use to understand what has been done before deciding next steps |
|
|
719
752
|
| \`mesh_git_status\` | Check git status on a specific node |
|
|
720
753
|
| \`mesh_checkpoint\` | Create a git checkpoint on a node |
|
|
721
754
|
| \`mesh_approve\` | Approve/reject a pending agent action |
|
|
722
755
|
| \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
|
|
723
756
|
| \`mesh_refine_node\` | Validate and merge a completed worktree node back into its base branch |
|
|
724
|
-
| \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable)
|
|
757
|
+
| \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |
|
|
758
|
+
| \`mesh_cleanup_sessions\` | Manually clean up delegated session records for a node |`;
|
|
725
759
|
TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
|
|
726
760
|
|
|
727
761
|
Before doing any coordinator work, confirm that the actual callable tool list includes \`mesh_status\` and the other \`mesh_*\` tools from the table above. If this Repo Mesh coordinator prompt is present but the callable \`mesh_*\` tools are missing, the MCP server/tool manifest is stale or not injected yet. Do not substitute terminal/file/git tools, do not inspect or edit the repository directly, and do not continue as a non-mesh local coding agent. Stop immediately and tell the user to run \`/reload-mcp\` or start a fresh coordinator session so ADHDev can reconnect \`adhdev-mesh\`.`;
|
|
@@ -731,9 +765,10 @@ Before doing any coordinator work, confirm that the actual callable tool list in
|
|
|
731
765
|
2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist. If \`mesh_task_history\` shows a recent failure for a task, decide whether to retry or reassign.
|
|
732
766
|
3. **Queue / Delegate** \u2014 The Mesh uses an autonomous pull-based Work Queue:
|
|
733
767
|
a. **General Tasks**: Enqueue tasks using \`mesh_enqueue_task\`. Idle node agents will automatically pull tasks from the queue and begin working.
|
|
734
|
-
b. **Node Preparation**: Call \`mesh_launch_session\`
|
|
768
|
+
b. **Node Preparation**: Reuse an existing idle session on the correct node/provider before launching a new chat/session. Call \`mesh_launch_session\` only when no suitable session exists, when the user explicitly asks for a fresh provider/session, or when branch/worktree isolation requires it. If you need branch isolation for parallel work, call \`mesh_clone_node\` to create a worktree node first.
|
|
735
769
|
c. **Targeted Tasks**: Use \`mesh_send_task\` only when you need to bypass the queue and force a specific node to execute a task immediately.
|
|
736
|
-
d.
|
|
770
|
+
d. For the first dispatch of a new task, provide a **complete, self-contained** instruction that includes all context the agent needs (file paths, line numbers, what to change, why). Do not send partial instructions expecting future follow-up.
|
|
771
|
+
e. For a continuation of the same issue in an existing session, send a concise **delta instruction**: current verified state, the exact failed/blocked step, the newly approved action, and final reporting requirements. Do not resend the full original task or open a new chat solely to continue the same work; that wastes coordinator and worker context.
|
|
737
772
|
4. **Monitor** \u2014 Prefer event-driven completion/status notifications. Do **not** poll \`mesh_read_chat\` repeatedly. Use \`mesh_view_queue\` to see the status of all pending, assigned, completed, and failed tasks. Do not call \`mesh_read_chat\` again within a few seconds for the same generating session. Use at most one compact \`mesh_read_chat\` check after a completion/approval signal. Handle approvals via \`mesh_approve\`.
|
|
738
773
|
5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
|
|
739
774
|
6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
|
|
@@ -749,7 +784,7 @@ When a node agent stops unexpectedly, the daemon automatically enriches the syst
|
|
|
749
784
|
- A recommendation: **retry**, **reassign**, or **escalate**
|
|
750
785
|
|
|
751
786
|
Follow these recovery rules:
|
|
752
|
-
1. **If "Retry recommended"**:
|
|
787
|
+
1. **If "Retry recommended"**: Check \`mesh_view_queue\` first \u2014 the daemon may have auto-requeued. If not, re-launch the session on the same node (\`mesh_launch_session\`), then resend the original task (\`mesh_send_task\`). The system message includes the original task text.
|
|
753
788
|
2. **If "Max retries exceeded"**: Do NOT retry on the same node. Either reassign the task to a different node, or inform the user that the task requires manual intervention.
|
|
754
789
|
3. **If no recovery context**: The stop may be intentional (normal completion). Use \`mesh_read_chat\` once to verify, then move on.
|
|
755
790
|
4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user.`;
|
|
@@ -1001,6 +1036,7 @@ __export(mesh_work_queue_exports, {
|
|
|
1001
1036
|
enqueueTask: () => enqueueTask,
|
|
1002
1037
|
getMeshQueueStats: () => getMeshQueueStats,
|
|
1003
1038
|
getQueue: () => getQueue,
|
|
1039
|
+
recordTaskAutoLaunch: () => recordTaskAutoLaunch,
|
|
1004
1040
|
requeueTask: () => requeueTask,
|
|
1005
1041
|
updateSessionTaskStatus: () => updateSessionTaskStatus,
|
|
1006
1042
|
updateTaskStatus: () => updateTaskStatus
|
|
@@ -1076,6 +1112,19 @@ function updateTaskStatus(meshId, taskId, status) {
|
|
|
1076
1112
|
writeQueue(meshId, queue);
|
|
1077
1113
|
return queue[idx];
|
|
1078
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
|
+
}
|
|
1079
1128
|
function cancelTask(meshId, taskId, opts) {
|
|
1080
1129
|
const queue = readQueue(meshId);
|
|
1081
1130
|
const idx = queue.findIndex((q) => q.id === taskId);
|
|
@@ -1149,6 +1198,141 @@ var init_mesh_work_queue = __esm({
|
|
|
1149
1198
|
}
|
|
1150
1199
|
});
|
|
1151
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
|
+
|
|
1152
1336
|
// src/logging/logger.ts
|
|
1153
1337
|
function setLogLevel(level) {
|
|
1154
1338
|
currentLevel = level;
|
|
@@ -1164,13 +1348,13 @@ function getDaemonLogDir() {
|
|
|
1164
1348
|
return LOG_DIR;
|
|
1165
1349
|
}
|
|
1166
1350
|
function getCurrentDaemonLogPath(date = /* @__PURE__ */ new Date()) {
|
|
1167
|
-
return
|
|
1351
|
+
return path9.join(LOG_DIR, `daemon-${date.toISOString().slice(0, 10)}.log`);
|
|
1168
1352
|
}
|
|
1169
1353
|
function checkDateRotation() {
|
|
1170
1354
|
const today = getDateStr();
|
|
1171
1355
|
if (today !== currentDate) {
|
|
1172
1356
|
currentDate = today;
|
|
1173
|
-
currentLogFile =
|
|
1357
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
1174
1358
|
cleanOldLogs();
|
|
1175
1359
|
}
|
|
1176
1360
|
}
|
|
@@ -1184,7 +1368,7 @@ function cleanOldLogs() {
|
|
|
1184
1368
|
const dateMatch = file.match(/daemon-(\d{4}-\d{2}-\d{2})/);
|
|
1185
1369
|
if (dateMatch && dateMatch[1] < cutoffStr) {
|
|
1186
1370
|
try {
|
|
1187
|
-
fs2.unlinkSync(
|
|
1371
|
+
fs2.unlinkSync(path9.join(LOG_DIR, file));
|
|
1188
1372
|
} catch {
|
|
1189
1373
|
}
|
|
1190
1374
|
}
|
|
@@ -1300,17 +1484,17 @@ function installGlobalInterceptor() {
|
|
|
1300
1484
|
writeToFile(`Log file: ${currentLogFile}`);
|
|
1301
1485
|
writeToFile(`Log level: ${currentLevel}`);
|
|
1302
1486
|
}
|
|
1303
|
-
var fs2,
|
|
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;
|
|
1304
1488
|
var init_logger = __esm({
|
|
1305
1489
|
"src/logging/logger.ts"() {
|
|
1306
1490
|
"use strict";
|
|
1307
1491
|
fs2 = __toESM(require("fs"));
|
|
1308
|
-
|
|
1309
|
-
|
|
1492
|
+
path9 = __toESM(require("path"));
|
|
1493
|
+
os3 = __toESM(require("os"));
|
|
1310
1494
|
LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
1311
1495
|
LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
|
|
1312
1496
|
currentLevel = "info";
|
|
1313
|
-
LOG_DIR = process.platform === "win32" ?
|
|
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");
|
|
1314
1498
|
MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
1315
1499
|
MAX_LOG_DAYS = 7;
|
|
1316
1500
|
try {
|
|
@@ -1318,16 +1502,16 @@ var init_logger = __esm({
|
|
|
1318
1502
|
} catch {
|
|
1319
1503
|
}
|
|
1320
1504
|
currentDate = getDateStr();
|
|
1321
|
-
currentLogFile =
|
|
1505
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
1322
1506
|
cleanOldLogs();
|
|
1323
1507
|
try {
|
|
1324
|
-
const oldLog =
|
|
1508
|
+
const oldLog = path9.join(LOG_DIR, "daemon.log");
|
|
1325
1509
|
if (fs2.existsSync(oldLog)) {
|
|
1326
1510
|
const stat2 = fs2.statSync(oldLog);
|
|
1327
1511
|
const oldDate = stat2.mtime.toISOString().slice(0, 10);
|
|
1328
|
-
fs2.renameSync(oldLog,
|
|
1512
|
+
fs2.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
|
|
1329
1513
|
}
|
|
1330
|
-
const oldLogBackup =
|
|
1514
|
+
const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
|
|
1331
1515
|
if (fs2.existsSync(oldLogBackup)) {
|
|
1332
1516
|
fs2.unlinkSync(oldLogBackup);
|
|
1333
1517
|
}
|
|
@@ -1359,7 +1543,7 @@ var init_logger = __esm({
|
|
|
1359
1543
|
}
|
|
1360
1544
|
};
|
|
1361
1545
|
interceptorInstalled = false;
|
|
1362
|
-
LOG_PATH =
|
|
1546
|
+
LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
|
|
1363
1547
|
}
|
|
1364
1548
|
});
|
|
1365
1549
|
|
|
@@ -1431,7 +1615,235 @@ function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType)
|
|
|
1431
1615
|
});
|
|
1432
1616
|
return true;
|
|
1433
1617
|
}
|
|
1434
|
-
function
|
|
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) {
|
|
1435
1847
|
const mesh = getMeshWithCache(components, meshId);
|
|
1436
1848
|
if (!mesh) return;
|
|
1437
1849
|
const cliInstances = components.instanceManager.getByCategory("cli");
|
|
@@ -1442,9 +1854,7 @@ function triggerMeshQueue(components, meshId) {
|
|
|
1442
1854
|
if (instMeshId !== meshId) continue;
|
|
1443
1855
|
const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
1444
1856
|
if (!nodeId) continue;
|
|
1445
|
-
|
|
1446
|
-
if (["stopped", "failed", "terminated", "exited", "closed"].includes(status)) continue;
|
|
1447
|
-
if (status !== "idle" && state.activeChat?.status !== "waiting_input") continue;
|
|
1857
|
+
if (!isIdleSessionState(state)) continue;
|
|
1448
1858
|
const sessionId = state.instanceId;
|
|
1449
1859
|
const providerType = state.type || readNonEmptyString(settings.providerType);
|
|
1450
1860
|
if (providerType) {
|
|
@@ -1460,6 +1870,7 @@ function triggerMeshQueue(components, meshId) {
|
|
|
1460
1870
|
}
|
|
1461
1871
|
}
|
|
1462
1872
|
}
|
|
1873
|
+
await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
|
|
1463
1874
|
}
|
|
1464
1875
|
function buildMeshSystemMessage(args) {
|
|
1465
1876
|
const metadata = formatCompletionMetadata(args.metadataEvent);
|
|
@@ -1736,11 +2147,13 @@ function setupMeshEventForwarding(components) {
|
|
|
1736
2147
|
});
|
|
1737
2148
|
});
|
|
1738
2149
|
}
|
|
1739
|
-
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;
|
|
1740
2151
|
var init_mesh_events = __esm({
|
|
1741
2152
|
"src/mesh/mesh-events.ts"() {
|
|
1742
2153
|
"use strict";
|
|
2154
|
+
init_config();
|
|
1743
2155
|
init_mesh_config();
|
|
2156
|
+
init_cli_detector();
|
|
1744
2157
|
init_logger();
|
|
1745
2158
|
init_mesh_ledger();
|
|
1746
2159
|
init_mesh_work_queue();
|
|
@@ -1761,6 +2174,9 @@ var init_mesh_events = __esm({
|
|
|
1761
2174
|
"agent:stopped": "task_failed",
|
|
1762
2175
|
"monitor:long_generating": "task_stalled"
|
|
1763
2176
|
};
|
|
2177
|
+
autoLaunchInProgress = /* @__PURE__ */ new Set();
|
|
2178
|
+
autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
|
|
2179
|
+
AUTO_LAUNCH_COOLDOWN_MS = 5e3;
|
|
1764
2180
|
}
|
|
1765
2181
|
});
|
|
1766
2182
|
|
|
@@ -4884,6 +5300,7 @@ __export(index_exports, {
|
|
|
4884
5300
|
MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS: () => MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
|
|
4885
5301
|
MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS: () => MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
|
|
4886
5302
|
NodePtyTransportFactory: () => NodePtyTransportFactory,
|
|
5303
|
+
P2pRelayFailureError: () => P2pRelayFailureError,
|
|
4887
5304
|
ProviderCliAdapter: () => ProviderCliAdapter,
|
|
4888
5305
|
ProviderInstanceManager: () => ProviderInstanceManager,
|
|
4889
5306
|
ProviderLoader: () => ProviderLoader,
|
|
@@ -4900,6 +5317,7 @@ __export(index_exports, {
|
|
|
4900
5317
|
buildChatTailDeliverySignature: () => buildChatTailDeliverySignature,
|
|
4901
5318
|
buildCoordinatorSystemPrompt: () => buildCoordinatorSystemPrompt,
|
|
4902
5319
|
buildMachineInfo: () => buildMachineInfo,
|
|
5320
|
+
buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
|
|
4903
5321
|
buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
|
|
4904
5322
|
buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
|
|
4905
5323
|
buildSessionEntries: () => buildSessionEntries,
|
|
@@ -4914,6 +5332,7 @@ __export(index_exports, {
|
|
|
4914
5332
|
claimNextTask: () => claimNextTask,
|
|
4915
5333
|
classifyChatMessageVisibility: () => classifyChatMessageVisibility,
|
|
4916
5334
|
classifyHotChatSessionsForSubscriptionFlush: () => classifyHotChatSessionsForSubscriptionFlush,
|
|
5335
|
+
classifyP2pRelayFailure: () => classifyP2pRelayFailure,
|
|
4917
5336
|
clearDebugTrace: () => clearDebugTrace,
|
|
4918
5337
|
compareGitSnapshots: () => compareGitSnapshots,
|
|
4919
5338
|
configureDebugTraceStore: () => configureDebugTraceStore,
|
|
@@ -4981,6 +5400,7 @@ __export(index_exports, {
|
|
|
4981
5400
|
isInternalChatMessage: () => isInternalChatMessage,
|
|
4982
5401
|
isManagedStatusWaiting: () => isManagedStatusWaiting,
|
|
4983
5402
|
isManagedStatusWorking: () => isManagedStatusWorking,
|
|
5403
|
+
isP2pRelayTransportFailure: () => isP2pRelayTransportFailure,
|
|
4984
5404
|
isPathInside: () => isPathInside,
|
|
4985
5405
|
isSessionHostLiveRuntime: () => isSessionHostLiveRuntime,
|
|
4986
5406
|
isSessionHostRecoverySnapshot: () => isSessionHostRecoverySnapshot,
|
|
@@ -6946,8 +7366,118 @@ init_mesh_ledger();
|
|
|
6946
7366
|
init_mesh_work_queue();
|
|
6947
7367
|
init_mesh_events();
|
|
6948
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
|
+
|
|
6949
7479
|
// src/config/state-store.ts
|
|
6950
|
-
var
|
|
7480
|
+
var import_fs6 = require("fs");
|
|
6951
7481
|
var import_path5 = require("path");
|
|
6952
7482
|
init_config();
|
|
6953
7483
|
var DEFAULT_STATE = {
|
|
@@ -6998,11 +7528,11 @@ function normalizeState(raw) {
|
|
|
6998
7528
|
}
|
|
6999
7529
|
function loadState() {
|
|
7000
7530
|
const statePath = getStatePath();
|
|
7001
|
-
if (!(0,
|
|
7531
|
+
if (!(0, import_fs6.existsSync)(statePath)) {
|
|
7002
7532
|
return { ...DEFAULT_STATE };
|
|
7003
7533
|
}
|
|
7004
7534
|
try {
|
|
7005
|
-
const raw = (0,
|
|
7535
|
+
const raw = (0, import_fs6.readFileSync)(statePath, "utf-8");
|
|
7006
7536
|
return normalizeState(JSON.parse(raw));
|
|
7007
7537
|
} catch {
|
|
7008
7538
|
return { ...DEFAULT_STATE };
|
|
@@ -7011,17 +7541,17 @@ function loadState() {
|
|
|
7011
7541
|
function saveState(state) {
|
|
7012
7542
|
const statePath = getStatePath();
|
|
7013
7543
|
const normalized = normalizeState(state);
|
|
7014
|
-
(0,
|
|
7544
|
+
(0, import_fs6.writeFileSync)(statePath, JSON.stringify(normalized, null, 2), { encoding: "utf-8", mode: 384 });
|
|
7015
7545
|
}
|
|
7016
7546
|
function resetState() {
|
|
7017
7547
|
saveState({ ...DEFAULT_STATE });
|
|
7018
7548
|
}
|
|
7019
7549
|
|
|
7020
7550
|
// src/detection/ide-detector.ts
|
|
7021
|
-
var
|
|
7022
|
-
var
|
|
7551
|
+
var import_child_process2 = require("child_process");
|
|
7552
|
+
var import_fs7 = require("fs");
|
|
7023
7553
|
var import_os2 = require("os");
|
|
7024
|
-
var
|
|
7554
|
+
var path10 = __toESM(require("path"));
|
|
7025
7555
|
var BUILTIN_IDE_DEFINITIONS = [];
|
|
7026
7556
|
var registeredIDEs = /* @__PURE__ */ new Map();
|
|
7027
7557
|
function registerIDEDefinition(def) {
|
|
@@ -7040,13 +7570,13 @@ function getMergedDefinitions() {
|
|
|
7040
7570
|
function findCliCommand(command) {
|
|
7041
7571
|
const trimmed = String(command || "").trim();
|
|
7042
7572
|
if (!trimmed) return null;
|
|
7043
|
-
if (
|
|
7044
|
-
const candidate = trimmed.startsWith("~") ?
|
|
7045
|
-
const resolved =
|
|
7046
|
-
return (0,
|
|
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;
|
|
7047
7577
|
}
|
|
7048
7578
|
try {
|
|
7049
|
-
const result = (0,
|
|
7579
|
+
const result = (0, import_child_process2.execSync)(
|
|
7050
7580
|
(0, import_os2.platform)() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
|
|
7051
7581
|
{ encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
7052
7582
|
).trim();
|
|
@@ -7057,7 +7587,7 @@ function findCliCommand(command) {
|
|
|
7057
7587
|
}
|
|
7058
7588
|
function getIdeVersion(cliCommand) {
|
|
7059
7589
|
try {
|
|
7060
|
-
const result = (0,
|
|
7590
|
+
const result = (0, import_child_process2.execSync)(`"${cliCommand}" --version`, {
|
|
7061
7591
|
encoding: "utf-8",
|
|
7062
7592
|
timeout: 1e4,
|
|
7063
7593
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7070,13 +7600,13 @@ function getIdeVersion(cliCommand) {
|
|
|
7070
7600
|
function checkPathExists(paths) {
|
|
7071
7601
|
const home = (0, import_os2.homedir)();
|
|
7072
7602
|
for (const p of paths) {
|
|
7073
|
-
const normalized = p.startsWith("~") ?
|
|
7603
|
+
const normalized = p.startsWith("~") ? path10.join(home, p.slice(1)) : p;
|
|
7074
7604
|
if (normalized.includes("*")) {
|
|
7075
7605
|
const username = home.split(/[\\/]/).pop() || "";
|
|
7076
7606
|
const resolved = normalized.replace("*", username);
|
|
7077
|
-
if ((0,
|
|
7607
|
+
if ((0, import_fs7.existsSync)(resolved)) return resolved;
|
|
7078
7608
|
} else {
|
|
7079
|
-
if ((0,
|
|
7609
|
+
if ((0, import_fs7.existsSync)(normalized)) return normalized;
|
|
7080
7610
|
}
|
|
7081
7611
|
}
|
|
7082
7612
|
return null;
|
|
@@ -7090,7 +7620,7 @@ async function detectIDEs(providerLoader) {
|
|
|
7090
7620
|
let resolvedCli = cliPath;
|
|
7091
7621
|
if (!resolvedCli && appPath && os22 === "darwin") {
|
|
7092
7622
|
const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
|
|
7093
|
-
if ((0,
|
|
7623
|
+
if ((0, import_fs7.existsSync)(bundledCli)) resolvedCli = bundledCli;
|
|
7094
7624
|
}
|
|
7095
7625
|
if (!resolvedCli && appPath && os22 === "win32") {
|
|
7096
7626
|
const { dirname: dirname9 } = await import("path");
|
|
@@ -7103,7 +7633,7 @@ async function detectIDEs(providerLoader) {
|
|
|
7103
7633
|
`${appDir}\\\\resources\\\\app\\\\bin\\\\${def.cli}.cmd`
|
|
7104
7634
|
];
|
|
7105
7635
|
for (const c of candidates) {
|
|
7106
|
-
if ((0,
|
|
7636
|
+
if ((0, import_fs7.existsSync)(c)) {
|
|
7107
7637
|
resolvedCli = c;
|
|
7108
7638
|
break;
|
|
7109
7639
|
}
|
|
@@ -7125,134 +7655,8 @@ async function detectIDEs(providerLoader) {
|
|
|
7125
7655
|
return results;
|
|
7126
7656
|
}
|
|
7127
7657
|
|
|
7128
|
-
// src/
|
|
7129
|
-
|
|
7130
|
-
var os3 = __toESM(require("os"));
|
|
7131
|
-
var path10 = __toESM(require("path"));
|
|
7132
|
-
var import_fs7 = require("fs");
|
|
7133
|
-
function parseVersion(raw) {
|
|
7134
|
-
const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
|
|
7135
|
-
return match ? match[1] : raw.split("\n")[0].slice(0, 100);
|
|
7136
|
-
}
|
|
7137
|
-
function shellQuote(value) {
|
|
7138
|
-
if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
7139
|
-
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
7140
|
-
}
|
|
7141
|
-
function expandHome(value) {
|
|
7142
|
-
const trimmed = value.trim();
|
|
7143
|
-
if (!trimmed.startsWith("~")) return trimmed;
|
|
7144
|
-
return path10.join(os3.homedir(), trimmed.slice(1));
|
|
7145
|
-
}
|
|
7146
|
-
function isExplicitCommandPath(command) {
|
|
7147
|
-
const trimmed = command.trim();
|
|
7148
|
-
return path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
|
|
7149
|
-
}
|
|
7150
|
-
function resolveCommandPath(command) {
|
|
7151
|
-
const trimmed = command.trim();
|
|
7152
|
-
if (!trimmed) return null;
|
|
7153
|
-
if (isExplicitCommandPath(trimmed)) {
|
|
7154
|
-
const expanded = expandHome(trimmed);
|
|
7155
|
-
const candidate = path10.isAbsolute(expanded) ? expanded : path10.resolve(expanded);
|
|
7156
|
-
return (0, import_fs7.existsSync)(candidate) ? candidate : null;
|
|
7157
|
-
}
|
|
7158
|
-
return null;
|
|
7159
|
-
}
|
|
7160
|
-
function execAsync(cmd, timeoutMs = 5e3) {
|
|
7161
|
-
return new Promise((resolve16) => {
|
|
7162
|
-
const child = (0, import_child_process2.exec)(cmd, {
|
|
7163
|
-
encoding: "utf-8",
|
|
7164
|
-
timeout: timeoutMs,
|
|
7165
|
-
...process.platform === "win32" ? { windowsHide: true } : {}
|
|
7166
|
-
}, (err, stdout) => {
|
|
7167
|
-
if (err || !stdout?.trim()) {
|
|
7168
|
-
resolve16(null);
|
|
7169
|
-
} else {
|
|
7170
|
-
resolve16(stdout.trim());
|
|
7171
|
-
}
|
|
7172
|
-
});
|
|
7173
|
-
child.on("error", () => resolve16(null));
|
|
7174
|
-
});
|
|
7175
|
-
}
|
|
7176
|
-
async function detectCLIs(providerLoader, options) {
|
|
7177
|
-
const platform10 = os3.platform();
|
|
7178
|
-
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
7179
|
-
const includeVersion = options?.includeVersion !== false;
|
|
7180
|
-
const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
|
|
7181
|
-
const results = await Promise.all(
|
|
7182
|
-
cliList.map(async (cli) => {
|
|
7183
|
-
try {
|
|
7184
|
-
const explicitPath = resolveCommandPath(cli.command);
|
|
7185
|
-
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
|
|
7186
|
-
if (!pathResult) return { ...cli, installed: false };
|
|
7187
|
-
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
7188
|
-
let version;
|
|
7189
|
-
if (includeVersion) {
|
|
7190
|
-
const versionCommands = [
|
|
7191
|
-
`"${firstPath}" --version`,
|
|
7192
|
-
`"${firstPath}" -V`,
|
|
7193
|
-
`"${firstPath}" -v`,
|
|
7194
|
-
cli.versionCommand
|
|
7195
|
-
].filter((v) => !!v);
|
|
7196
|
-
try {
|
|
7197
|
-
for (const versionCommand of versionCommands) {
|
|
7198
|
-
const versionResult = await execAsync(versionCommand, 3e3);
|
|
7199
|
-
if (versionResult) {
|
|
7200
|
-
version = parseVersion(versionResult);
|
|
7201
|
-
break;
|
|
7202
|
-
}
|
|
7203
|
-
}
|
|
7204
|
-
} catch {
|
|
7205
|
-
}
|
|
7206
|
-
}
|
|
7207
|
-
return { ...cli, installed: true, version, path: firstPath };
|
|
7208
|
-
} catch {
|
|
7209
|
-
return { ...cli, installed: false };
|
|
7210
|
-
}
|
|
7211
|
-
})
|
|
7212
|
-
);
|
|
7213
|
-
return results;
|
|
7214
|
-
}
|
|
7215
|
-
async function detectCLI(cliId, providerLoader, options) {
|
|
7216
|
-
const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
|
|
7217
|
-
if (providerLoader) {
|
|
7218
|
-
const cliList = providerLoader.getCliDetectionList();
|
|
7219
|
-
const target = cliList.find((c) => c.id === resolvedId);
|
|
7220
|
-
if (target) {
|
|
7221
|
-
const platform10 = os3.platform();
|
|
7222
|
-
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
7223
|
-
try {
|
|
7224
|
-
const explicitPath = resolveCommandPath(target.command);
|
|
7225
|
-
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
|
|
7226
|
-
if (!pathResult) return null;
|
|
7227
|
-
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
7228
|
-
let version;
|
|
7229
|
-
if (options?.includeVersion !== false) {
|
|
7230
|
-
const versionCommands = [
|
|
7231
|
-
`"${firstPath}" --version`,
|
|
7232
|
-
`"${firstPath}" -V`,
|
|
7233
|
-
`"${firstPath}" -v`,
|
|
7234
|
-
target.versionCommand
|
|
7235
|
-
].filter((v) => !!v);
|
|
7236
|
-
try {
|
|
7237
|
-
for (const versionCommand of versionCommands) {
|
|
7238
|
-
const versionResult = await execAsync(versionCommand, 3e3);
|
|
7239
|
-
if (versionResult) {
|
|
7240
|
-
version = parseVersion(versionResult);
|
|
7241
|
-
break;
|
|
7242
|
-
}
|
|
7243
|
-
}
|
|
7244
|
-
} catch {
|
|
7245
|
-
}
|
|
7246
|
-
}
|
|
7247
|
-
return { ...target, installed: true, version, path: firstPath };
|
|
7248
|
-
} catch {
|
|
7249
|
-
return null;
|
|
7250
|
-
}
|
|
7251
|
-
}
|
|
7252
|
-
}
|
|
7253
|
-
const all = await detectCLIs(providerLoader, options);
|
|
7254
|
-
return all.find((c) => c.id === resolvedId && c.installed) || null;
|
|
7255
|
-
}
|
|
7658
|
+
// src/index.ts
|
|
7659
|
+
init_cli_detector();
|
|
7256
7660
|
|
|
7257
7661
|
// src/system/host-memory.ts
|
|
7258
7662
|
var os4 = __toESM(require("os"));
|
|
@@ -16362,6 +16766,7 @@ var import_fs8 = require("fs");
|
|
|
16362
16766
|
var import_child_process6 = require("child_process");
|
|
16363
16767
|
var import_chalk = __toESM(require("chalk"));
|
|
16364
16768
|
init_provider_cli_adapter();
|
|
16769
|
+
init_cli_detector();
|
|
16365
16770
|
init_config();
|
|
16366
16771
|
|
|
16367
16772
|
// src/providers/cli-provider-instance.ts
|
|
@@ -21943,6 +22348,7 @@ function getAvailableIdeIds() {
|
|
|
21943
22348
|
|
|
21944
22349
|
// src/commands/router.ts
|
|
21945
22350
|
init_config();
|
|
22351
|
+
init_cli_detector();
|
|
21946
22352
|
init_logger();
|
|
21947
22353
|
|
|
21948
22354
|
// src/logging/command-log.ts
|
|
@@ -23089,6 +23495,209 @@ async function resolveProviderTypeFromPriority(args) {
|
|
|
23089
23495
|
}
|
|
23090
23496
|
return { error: `No usable provider detected for node '${args.nodeId}' from providerPriority: ${failed.join("; ")}` };
|
|
23091
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
|
+
}
|
|
23092
23701
|
function loadYamlModule() {
|
|
23093
23702
|
return yaml;
|
|
23094
23703
|
}
|
|
@@ -23372,20 +23981,98 @@ var DaemonCommandRouter = class {
|
|
|
23372
23981
|
recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
|
|
23373
23982
|
};
|
|
23374
23983
|
}
|
|
23984
|
+
const forceFallbackConvergence = await this.getWorktreeForceCleanupConvergence({
|
|
23985
|
+
repoRoot,
|
|
23986
|
+
workspace,
|
|
23987
|
+
node: args.node
|
|
23988
|
+
});
|
|
23375
23989
|
try {
|
|
23376
|
-
const result = await removeWorktree2(repoRoot, workspace, {
|
|
23377
|
-
|
|
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
|
+
};
|
|
23378
24005
|
} catch (e) {
|
|
23379
24006
|
const message = String(e?.message || e || "worktree cleanup failed");
|
|
23380
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;
|
|
23381
24009
|
return {
|
|
23382
24010
|
success: false,
|
|
23383
|
-
code: dirty ? "mesh_worktree_cleanup_dirty" : "mesh_worktree_cleanup_failed",
|
|
23384
|
-
error: message,
|
|
23385
|
-
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 } : {}
|
|
23386
24015
|
};
|
|
23387
24016
|
}
|
|
23388
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
|
+
}
|
|
23389
24076
|
isCompletedHostedSession(record) {
|
|
23390
24077
|
return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
|
|
23391
24078
|
}
|
|
@@ -24345,10 +25032,61 @@ var DaemonCommandRouter = class {
|
|
|
24345
25032
|
if (!branch) return { success: false, error: "Could not determine branch of the worktree node" };
|
|
24346
25033
|
const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
|
|
24347
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
|
+
}
|
|
24348
25074
|
try {
|
|
24349
25075
|
await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
|
|
24350
25076
|
} catch (e) {
|
|
24351
|
-
return {
|
|
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
|
+
};
|
|
24352
25090
|
}
|
|
24353
25091
|
const removeResult = await this.execute("remove_mesh_node", {
|
|
24354
25092
|
meshId,
|
|
@@ -24361,11 +25099,27 @@ var DaemonCommandRouter = class {
|
|
|
24361
25099
|
appendLedgerEntry2(meshId, {
|
|
24362
25100
|
kind: "node_removed",
|
|
24363
25101
|
nodeId,
|
|
24364
|
-
payload: { refined: true, mergedBranch: branch, into: baseBranch }
|
|
25102
|
+
payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary }
|
|
24365
25103
|
});
|
|
24366
25104
|
} catch {
|
|
24367
25105
|
}
|
|
24368
|
-
return {
|
|
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
|
+
};
|
|
24369
25123
|
} catch (e) {
|
|
24370
25124
|
return { success: false, error: e.message };
|
|
24371
25125
|
}
|
|
@@ -24420,7 +25174,10 @@ var DaemonCommandRouter = class {
|
|
|
24420
25174
|
sessionCleanupMode,
|
|
24421
25175
|
workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
|
|
24422
25176
|
daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
|
|
24423
|
-
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
|
|
24424
25181
|
}
|
|
24425
25182
|
});
|
|
24426
25183
|
} catch {
|
|
@@ -32552,6 +33309,9 @@ function launchIDE(ide, workspacePath) {
|
|
|
32552
33309
|
}
|
|
32553
33310
|
}
|
|
32554
33311
|
|
|
33312
|
+
// src/boot/daemon-lifecycle.ts
|
|
33313
|
+
init_cli_detector();
|
|
33314
|
+
|
|
32555
33315
|
// src/sessions/registry.ts
|
|
32556
33316
|
var SessionRegistry = class {
|
|
32557
33317
|
bySessionId = /* @__PURE__ */ new Map();
|
|
@@ -32889,6 +33649,7 @@ async function shutdownDaemonComponents(components) {
|
|
|
32889
33649
|
MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
|
|
32890
33650
|
MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
|
|
32891
33651
|
NodePtyTransportFactory,
|
|
33652
|
+
P2pRelayFailureError,
|
|
32892
33653
|
ProviderCliAdapter,
|
|
32893
33654
|
ProviderInstanceManager,
|
|
32894
33655
|
ProviderLoader,
|
|
@@ -32905,6 +33666,7 @@ async function shutdownDaemonComponents(components) {
|
|
|
32905
33666
|
buildChatTailDeliverySignature,
|
|
32906
33667
|
buildCoordinatorSystemPrompt,
|
|
32907
33668
|
buildMachineInfo,
|
|
33669
|
+
buildP2pRelayFailurePayload,
|
|
32908
33670
|
buildPinnedGlobalInstallCommand,
|
|
32909
33671
|
buildRuntimeSystemChatMessage,
|
|
32910
33672
|
buildSessionEntries,
|
|
@@ -32919,6 +33681,7 @@ async function shutdownDaemonComponents(components) {
|
|
|
32919
33681
|
claimNextTask,
|
|
32920
33682
|
classifyChatMessageVisibility,
|
|
32921
33683
|
classifyHotChatSessionsForSubscriptionFlush,
|
|
33684
|
+
classifyP2pRelayFailure,
|
|
32922
33685
|
clearDebugTrace,
|
|
32923
33686
|
compareGitSnapshots,
|
|
32924
33687
|
configureDebugTraceStore,
|
|
@@ -32986,6 +33749,7 @@ async function shutdownDaemonComponents(components) {
|
|
|
32986
33749
|
isInternalChatMessage,
|
|
32987
33750
|
isManagedStatusWaiting,
|
|
32988
33751
|
isManagedStatusWorking,
|
|
33752
|
+
isP2pRelayTransportFailure,
|
|
32989
33753
|
isPathInside,
|
|
32990
33754
|
isSessionHostLiveRuntime,
|
|
32991
33755
|
isSessionHostRecoverySnapshot,
|