@adhdev/daemon-standalone 0.9.77-rc.5 → 0.9.77-rc.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2675 -10123
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/public/assets/{index-6WEc6L46.js → index-BWLH33S7.js} +27 -27
- package/public/assets/index-BdY6qPu0.css +1 -0
- package/public/assets/{terminal-BeBmUW3m.js → terminal-B-dmfv31.js} +11 -11
- package/public/index.html +2 -2
- package/vendor/mcp-server/index.js +3086 -363
- package/vendor/mcp-server/index.js.map +1 -1
- package/public/assets/index-DftJ2WZr.css +0 -1
|
@@ -25090,10 +25090,12 @@ __export(dist_exports, {
|
|
|
25090
25090
|
IdeProviderInstance: () => IdeProviderInstance,
|
|
25091
25091
|
InMemoryGitSnapshotStore: () => InMemoryGitSnapshotStore,
|
|
25092
25092
|
LOG: () => LOG,
|
|
25093
|
+
MAX_LEDGER_SLICE_LIMIT: () => MAX_LEDGER_SLICE_LIMIT,
|
|
25093
25094
|
MIN_GIT_WORKSPACE_POLL_INTERVAL_MS: () => MIN_GIT_WORKSPACE_POLL_INTERVAL_MS,
|
|
25094
25095
|
MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS: () => MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS,
|
|
25095
25096
|
MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS: () => MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
|
|
25096
25097
|
NodePtyTransportFactory: () => NodePtyTransportFactory,
|
|
25098
|
+
P2pRelayFailureError: () => P2pRelayFailureError,
|
|
25097
25099
|
ProviderCliAdapter: () => ProviderCliAdapter,
|
|
25098
25100
|
ProviderInstanceManager: () => ProviderInstanceManager,
|
|
25099
25101
|
ProviderLoader: () => ProviderLoader,
|
|
@@ -25104,12 +25106,16 @@ __export(dist_exports, {
|
|
|
25104
25106
|
addNode: () => addNode,
|
|
25105
25107
|
appendLedgerEntry: () => appendLedgerEntry,
|
|
25106
25108
|
appendRecentActivity: () => appendRecentActivity,
|
|
25109
|
+
appendRemoteLedgerEntries: () => appendRemoteLedgerEntries,
|
|
25107
25110
|
buildAssistantChatMessage: () => buildAssistantChatMessage,
|
|
25108
25111
|
buildChatMessage: () => buildChatMessage,
|
|
25109
25112
|
buildChatMessageSignature: () => buildChatMessageSignature,
|
|
25110
25113
|
buildChatTailDeliverySignature: () => buildChatTailDeliverySignature,
|
|
25111
25114
|
buildCoordinatorSystemPrompt: () => buildCoordinatorSystemPrompt,
|
|
25112
25115
|
buildMachineInfo: () => buildMachineInfo,
|
|
25116
|
+
buildMeshLedgerReconciliationEvidence: () => buildMeshLedgerReconciliationEvidence,
|
|
25117
|
+
buildMeshLedgerReplicaEvidence: () => buildMeshLedgerReplicaEvidence,
|
|
25118
|
+
buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
|
|
25113
25119
|
buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
|
|
25114
25120
|
buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
|
|
25115
25121
|
buildSessionEntries: () => buildSessionEntries,
|
|
@@ -25120,9 +25126,11 @@ __export(dist_exports, {
|
|
|
25120
25126
|
buildThoughtChatMessage: () => buildThoughtChatMessage,
|
|
25121
25127
|
buildToolChatMessage: () => buildToolChatMessage,
|
|
25122
25128
|
buildUserChatMessage: () => buildUserChatMessage,
|
|
25129
|
+
cancelTask: () => cancelTask,
|
|
25123
25130
|
claimNextTask: () => claimNextTask,
|
|
25124
25131
|
classifyChatMessageVisibility: () => classifyChatMessageVisibility,
|
|
25125
25132
|
classifyHotChatSessionsForSubscriptionFlush: () => classifyHotChatSessionsForSubscriptionFlush,
|
|
25133
|
+
classifyP2pRelayFailure: () => classifyP2pRelayFailure,
|
|
25126
25134
|
clearDebugTrace: () => clearDebugTrace,
|
|
25127
25135
|
compareGitSnapshots: () => compareGitSnapshots,
|
|
25128
25136
|
configureDebugTraceStore: () => configureDebugTraceStore,
|
|
@@ -25163,6 +25171,7 @@ __export(dist_exports, {
|
|
|
25163
25171
|
getLogLevel: () => getLogLevel,
|
|
25164
25172
|
getMesh: () => getMesh,
|
|
25165
25173
|
getMeshByRepo: () => getMeshByRepo,
|
|
25174
|
+
getMeshQueueStats: () => getMeshQueueStats,
|
|
25166
25175
|
getNpmExecOptions: () => getNpmExecOptions,
|
|
25167
25176
|
getQueue: () => getQueue,
|
|
25168
25177
|
getRecentActivity: () => getRecentActivity,
|
|
@@ -25189,6 +25198,7 @@ __export(dist_exports, {
|
|
|
25189
25198
|
isInternalChatMessage: () => isInternalChatMessage,
|
|
25190
25199
|
isManagedStatusWaiting: () => isManagedStatusWaiting,
|
|
25191
25200
|
isManagedStatusWorking: () => isManagedStatusWorking,
|
|
25201
|
+
isP2pRelayTransportFailure: () => isP2pRelayTransportFailure,
|
|
25192
25202
|
isPathInside: () => isPathInside,
|
|
25193
25203
|
isSessionHostLiveRuntime: () => isSessionHostLiveRuntime,
|
|
25194
25204
|
isSessionHostRecoverySnapshot: () => isSessionHostRecoverySnapshot,
|
|
@@ -25227,10 +25237,12 @@ __export(dist_exports, {
|
|
|
25227
25237
|
probeCdpPort: () => probeCdpPort,
|
|
25228
25238
|
readChatHistory: () => readChatHistory,
|
|
25229
25239
|
readLedgerEntries: () => readLedgerEntries,
|
|
25240
|
+
readLedgerSlice: () => readLedgerSlice,
|
|
25230
25241
|
recordDebugTrace: () => recordDebugTrace,
|
|
25231
25242
|
registerExtensionProviders: () => registerExtensionProviders,
|
|
25232
25243
|
removeNode: () => removeNode,
|
|
25233
25244
|
removeWorktree: () => removeWorktree,
|
|
25245
|
+
requeueTask: () => requeueTask,
|
|
25234
25246
|
resetConfig: () => resetConfig,
|
|
25235
25247
|
resetDebugRuntimeConfig: () => resetDebugRuntimeConfig,
|
|
25236
25248
|
resetState: () => resetState,
|
|
@@ -25301,13 +25313,25 @@ async function createWorktree(opts) {
|
|
|
25301
25313
|
branch
|
|
25302
25314
|
};
|
|
25303
25315
|
}
|
|
25304
|
-
async function removeWorktree(repoRoot, worktreePath) {
|
|
25316
|
+
async function removeWorktree(repoRoot, worktreePath, opts = {}) {
|
|
25305
25317
|
if (!(0, import_fs3.existsSync)(worktreePath)) {
|
|
25306
25318
|
await pruneWorktrees(repoRoot);
|
|
25307
25319
|
return { success: true, removedPath: worktreePath };
|
|
25308
25320
|
}
|
|
25321
|
+
if (opts.requireClean) {
|
|
25322
|
+
const { stdout } = await execFileAsync2("git", ["status", "--porcelain"], {
|
|
25323
|
+
cwd: worktreePath,
|
|
25324
|
+
encoding: "utf8",
|
|
25325
|
+
timeout: GIT_TIMEOUT_MS,
|
|
25326
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
25327
|
+
windowsHide: true
|
|
25328
|
+
});
|
|
25329
|
+
if (stdout.trim()) {
|
|
25330
|
+
throw new Error(`Refusing to remove dirty worktree: ${worktreePath}`);
|
|
25331
|
+
}
|
|
25332
|
+
}
|
|
25309
25333
|
try {
|
|
25310
|
-
await execFileAsync2("git", ["worktree", "remove", worktreePath
|
|
25334
|
+
await execFileAsync2("git", ["worktree", "remove", worktreePath], {
|
|
25311
25335
|
cwd: repoRoot,
|
|
25312
25336
|
encoding: "utf8",
|
|
25313
25337
|
timeout: GIT_TIMEOUT_MS,
|
|
@@ -25316,7 +25340,33 @@ async function removeWorktree(repoRoot, worktreePath) {
|
|
|
25316
25340
|
});
|
|
25317
25341
|
} catch (error48) {
|
|
25318
25342
|
const stderr = typeof error48.stderr === "string" ? error48.stderr : "";
|
|
25319
|
-
|
|
25343
|
+
const stdout = typeof error48.stdout === "string" ? error48.stdout : "";
|
|
25344
|
+
const detail = `${stderr}
|
|
25345
|
+
${stdout}
|
|
25346
|
+
${error48.message || ""}`;
|
|
25347
|
+
if (opts.allowSubmoduleForceFallback && SUBMODULE_WORKTREE_REMOVE_RE.test(detail)) {
|
|
25348
|
+
try {
|
|
25349
|
+
await execFileAsync2("git", ["worktree", "remove", "--force", worktreePath], {
|
|
25350
|
+
cwd: repoRoot,
|
|
25351
|
+
encoding: "utf8",
|
|
25352
|
+
timeout: GIT_TIMEOUT_MS,
|
|
25353
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
25354
|
+
windowsHide: true
|
|
25355
|
+
});
|
|
25356
|
+
} catch (forceError) {
|
|
25357
|
+
const forceStderr = typeof forceError.stderr === "string" ? forceError.stderr : "";
|
|
25358
|
+
const forceStdout = typeof forceError.stdout === "string" ? forceError.stdout : "";
|
|
25359
|
+
throw new Error(`git worktree remove --force fallback failed: ${forceStderr.trim() || forceStdout.trim() || forceError.message}`);
|
|
25360
|
+
}
|
|
25361
|
+
return {
|
|
25362
|
+
success: true,
|
|
25363
|
+
removedPath: worktreePath,
|
|
25364
|
+
fallback: "git_worktree_remove_force_submodule",
|
|
25365
|
+
forced: true,
|
|
25366
|
+
reason: "working_trees_containing_submodules"
|
|
25367
|
+
};
|
|
25368
|
+
}
|
|
25369
|
+
throw new Error(`git worktree remove failed: ${stderr.trim() || stdout.trim() || error48.message}`);
|
|
25320
25370
|
}
|
|
25321
25371
|
return { success: true, removedPath: worktreePath };
|
|
25322
25372
|
}
|
|
@@ -25458,7 +25508,8 @@ function ensureMachineId(config2) {
|
|
|
25458
25508
|
};
|
|
25459
25509
|
}
|
|
25460
25510
|
function getConfigDir() {
|
|
25461
|
-
const
|
|
25511
|
+
const override = process.env.ADHDEV_CONFIG_DIR;
|
|
25512
|
+
const dir = override && override.trim() ? override.trim() : (0, import_path.join)((0, import_os2.homedir)(), ".adhdev");
|
|
25462
25513
|
if (!(0, import_fs4.existsSync)(dir)) {
|
|
25463
25514
|
(0, import_fs4.mkdirSync)(dir, { recursive: true });
|
|
25464
25515
|
}
|
|
@@ -25671,6 +25722,7 @@ function addNode(meshId, opts) {
|
|
|
25671
25722
|
workspace: opts.workspace.trim(),
|
|
25672
25723
|
repoRoot: opts.repoRoot,
|
|
25673
25724
|
daemonId: opts.daemonId,
|
|
25725
|
+
machineId: opts.machineId,
|
|
25674
25726
|
userOverrides: opts.userOverrides || {},
|
|
25675
25727
|
policy: opts.policy || {},
|
|
25676
25728
|
isLocalWorktree: opts.isLocalWorktree,
|
|
@@ -25782,7 +25834,8 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
25782
25834
|
- **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.
|
|
25783
25835
|
- **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.
|
|
25784
25836
|
- **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.
|
|
25785
|
-
- **Front-load
|
|
25837
|
+
- **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.
|
|
25838
|
+
- **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.
|
|
25786
25839
|
- **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.
|
|
25787
25840
|
- **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.
|
|
25788
25841
|
- **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.
|
|
@@ -25791,8 +25844,14 @@ function buildRulesSection(coordinatorCliType) {
|
|
|
25791
25844
|
- **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
|
|
25792
25845
|
- **Never fabricate tool results.** Always call the actual tool; never pretend you did.
|
|
25793
25846
|
- **Clean up worktree nodes.** After a worktree task completes and its changes are merged or checkpointed, call \`mesh_remove_node\` to free resources.
|
|
25847
|
+
- **Do not strand completed branches.** A checkpointed or clean feature/worktree branch is not done by itself. Merge/refine it to the mesh default branch, or explicitly report one of \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\` with the next action.
|
|
25794
25848
|
- **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
|
|
25795
25849
|
}
|
|
25850
|
+
function isIntentionalCleanupStopEntry(entry) {
|
|
25851
|
+
if (entry.kind !== "session_stopped" && entry.kind !== "task_failed" && entry.kind !== "task_stalled") return false;
|
|
25852
|
+
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
25853
|
+
return payload.intentional === true && (payload.reason === "operator_cleanup" || payload.intentionalStopReason === "operator_cleanup" || payload.source === "mesh_cleanup_sessions" || payload.source === "mesh_remove_node");
|
|
25854
|
+
}
|
|
25796
25855
|
function getLedgerDir() {
|
|
25797
25856
|
const dir = (0, import_path3.join)(getConfigDir(), LEDGER_DIR_NAME);
|
|
25798
25857
|
if (!(0, import_fs6.existsSync)(dir)) {
|
|
@@ -25808,6 +25867,37 @@ function getRotatedPath(meshId, index) {
|
|
|
25808
25867
|
const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
25809
25868
|
return (0, import_path3.join)(getLedgerDir(), `${safe}.${index}.jsonl`);
|
|
25810
25869
|
}
|
|
25870
|
+
function buildTaskCompletionEvidence(opts) {
|
|
25871
|
+
const providerSessionId = opts.providerSessionId?.trim() || void 0;
|
|
25872
|
+
const providerType = opts.providerType?.trim() || void 0;
|
|
25873
|
+
return {
|
|
25874
|
+
source: "agent_status_event",
|
|
25875
|
+
event: opts.event,
|
|
25876
|
+
nodeId: opts.nodeId,
|
|
25877
|
+
sessionId: opts.sessionId,
|
|
25878
|
+
providerType,
|
|
25879
|
+
completedAt: opts.completedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
25880
|
+
transcriptHandle: {
|
|
25881
|
+
kind: providerSessionId ? "provider_session" : "runtime_session",
|
|
25882
|
+
sessionId: opts.sessionId,
|
|
25883
|
+
providerSessionId,
|
|
25884
|
+
finalSummaryAvailable: typeof opts.finalSummary === "string" && opts.finalSummary.trim().length > 0
|
|
25885
|
+
},
|
|
25886
|
+
git: {
|
|
25887
|
+
status: "deferred",
|
|
25888
|
+
reason: "ordinary_completion_git_status_not_checked"
|
|
25889
|
+
},
|
|
25890
|
+
validation: {
|
|
25891
|
+
status: "deferred",
|
|
25892
|
+
commandsRun: [],
|
|
25893
|
+
reason: "ordinary_completion_validation_not_run"
|
|
25894
|
+
},
|
|
25895
|
+
checkpoint: {
|
|
25896
|
+
attempted: false,
|
|
25897
|
+
reason: "not_attempted_for_ordinary_completion"
|
|
25898
|
+
}
|
|
25899
|
+
};
|
|
25900
|
+
}
|
|
25811
25901
|
function appendLedgerEntry(meshId, partial2) {
|
|
25812
25902
|
const entry = {
|
|
25813
25903
|
id: (0, import_crypto4.randomUUID)(),
|
|
@@ -25834,15 +25924,49 @@ function appendLedgerEntry(meshId, partial2) {
|
|
|
25834
25924
|
throw new Error(`Failed to append to ledger for mesh ${meshId}: ${e.message}`);
|
|
25835
25925
|
}
|
|
25836
25926
|
}
|
|
25927
|
+
function clampLedgerSliceLimit(limit) {
|
|
25928
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) return DEFAULT_LEDGER_SLICE_LIMIT;
|
|
25929
|
+
return Math.max(1, Math.min(MAX_LEDGER_SLICE_LIMIT, Math.floor(limit)));
|
|
25930
|
+
}
|
|
25931
|
+
function isValidRemoteLedgerEntry(meshId, value) {
|
|
25932
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
25933
|
+
const entry = value;
|
|
25934
|
+
if (typeof entry.id !== "string" || !entry.id.trim()) return false;
|
|
25935
|
+
if (entry.meshId !== meshId) return false;
|
|
25936
|
+
if (typeof entry.timestamp !== "string" || Number.isNaN(new Date(entry.timestamp).getTime())) return false;
|
|
25937
|
+
if (typeof entry.kind !== "string" || !entry.kind.trim()) return false;
|
|
25938
|
+
if (!entry.payload || typeof entry.payload !== "object" || Array.isArray(entry.payload)) return false;
|
|
25939
|
+
return true;
|
|
25940
|
+
}
|
|
25837
25941
|
function appendRemoteLedgerEntries(meshId, entries) {
|
|
25838
|
-
if (entries.length === 0) return;
|
|
25942
|
+
if (entries.length === 0) return { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
|
|
25839
25943
|
const ledgerPath = getLedgerPath(meshId);
|
|
25840
25944
|
const existing = new Set(readLedgerEntries(meshId).map((e) => e.id));
|
|
25841
|
-
const
|
|
25842
|
-
|
|
25945
|
+
const validEntries = [];
|
|
25946
|
+
let rejectedInvalid = 0;
|
|
25947
|
+
let skippedDuplicate = 0;
|
|
25948
|
+
for (const entry of entries) {
|
|
25949
|
+
if (!isValidRemoteLedgerEntry(meshId, entry)) {
|
|
25950
|
+
rejectedInvalid++;
|
|
25951
|
+
continue;
|
|
25952
|
+
}
|
|
25953
|
+
if (existing.has(entry.id)) {
|
|
25954
|
+
skippedDuplicate++;
|
|
25955
|
+
continue;
|
|
25956
|
+
}
|
|
25957
|
+
existing.add(entry.id);
|
|
25958
|
+
validEntries.push(entry);
|
|
25959
|
+
}
|
|
25960
|
+
if (validEntries.length === 0) {
|
|
25961
|
+
return { accepted: 0, skippedDuplicate, rejectedInvalid, entries: [] };
|
|
25962
|
+
}
|
|
25843
25963
|
try {
|
|
25844
|
-
const lines =
|
|
25964
|
+
const lines = validEntries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
25845
25965
|
(0, import_fs6.appendFileSync)(ledgerPath, lines, { encoding: "utf-8", mode: 384 });
|
|
25966
|
+
for (const entry of validEntries) {
|
|
25967
|
+
meshLedgerEvents.emit("append", meshId, entry);
|
|
25968
|
+
}
|
|
25969
|
+
return { accepted: validEntries.length, skippedDuplicate, rejectedInvalid, entries: validEntries };
|
|
25846
25970
|
} catch (e) {
|
|
25847
25971
|
throw new Error(`Failed to append remote ledger entries for mesh ${meshId}: ${e.message}`);
|
|
25848
25972
|
}
|
|
@@ -25881,6 +26005,34 @@ function readLedgerEntries(meshId, opts) {
|
|
|
25881
26005
|
}
|
|
25882
26006
|
return entries;
|
|
25883
26007
|
}
|
|
26008
|
+
function readLedgerSlice(meshId, opts) {
|
|
26009
|
+
const limit = clampLedgerSliceLimit(opts?.limit);
|
|
26010
|
+
let entries = readLedgerEntries(meshId, { since: opts?.since, kind: opts?.kind });
|
|
26011
|
+
const afterId = typeof opts?.afterId === "string" && opts.afterId.trim() ? opts.afterId.trim() : null;
|
|
26012
|
+
if (afterId) {
|
|
26013
|
+
const index = entries.findIndex((entry) => entry.id === afterId);
|
|
26014
|
+
entries = index >= 0 ? entries.slice(index + 1) : entries;
|
|
26015
|
+
}
|
|
26016
|
+
const bounded = entries.slice(0, limit);
|
|
26017
|
+
return {
|
|
26018
|
+
protocol: "adhdev.mesh.ledger.slice.v1",
|
|
26019
|
+
meshId,
|
|
26020
|
+
entries: bounded,
|
|
26021
|
+
cursor: {
|
|
26022
|
+
afterId,
|
|
26023
|
+
nextAfterId: bounded.length ? bounded[bounded.length - 1].id : afterId,
|
|
26024
|
+
limit,
|
|
26025
|
+
hasMore: entries.length > bounded.length
|
|
26026
|
+
},
|
|
26027
|
+
summary: getLedgerSummary(meshId),
|
|
26028
|
+
sourceOfTruth: {
|
|
26029
|
+
kind: "local_jsonl",
|
|
26030
|
+
path: getLedgerPath(meshId),
|
|
26031
|
+
bounded: true,
|
|
26032
|
+
maxLimit: MAX_LEDGER_SLICE_LIMIT
|
|
26033
|
+
}
|
|
26034
|
+
};
|
|
26035
|
+
}
|
|
25884
26036
|
function getLedgerSummary(meshId) {
|
|
25885
26037
|
const entries = readLedgerEntries(meshId);
|
|
25886
26038
|
const now = Date.now();
|
|
@@ -25906,15 +26058,17 @@ function getLedgerSummary(meshId) {
|
|
|
25906
26058
|
summary.taskCompleted++;
|
|
25907
26059
|
break;
|
|
25908
26060
|
case "task_failed": {
|
|
26061
|
+
if (isIntentionalCleanupStopEntry(entry)) break;
|
|
25909
26062
|
summary.taskFailed++;
|
|
25910
26063
|
if (new Date(entry.timestamp).getTime() >= recentFailureCutoff) {
|
|
25911
26064
|
summary.recentFailures++;
|
|
25912
26065
|
}
|
|
25913
26066
|
break;
|
|
25914
26067
|
}
|
|
25915
|
-
case "task_stalled":
|
|
25916
|
-
summary.taskStalled++;
|
|
26068
|
+
case "task_stalled": {
|
|
26069
|
+
if (!isIntentionalCleanupStopEntry(entry)) summary.taskStalled++;
|
|
25917
26070
|
break;
|
|
26071
|
+
}
|
|
25918
26072
|
case "session_launched":
|
|
25919
26073
|
summary.sessionLaunched++;
|
|
25920
26074
|
break;
|
|
@@ -25953,6 +26107,7 @@ function getSessionRecoveryContext(meshId, opts) {
|
|
|
25953
26107
|
if (new Date(e.timestamp).getTime() < recentWindow) break;
|
|
25954
26108
|
if (opts.nodeId && e.nodeId !== opts.nodeId) continue;
|
|
25955
26109
|
if (e.kind === "task_failed") {
|
|
26110
|
+
if (isIntentionalCleanupStopEntry(e)) continue;
|
|
25956
26111
|
consecutiveNodeFailures++;
|
|
25957
26112
|
} else if (e.kind === "task_completed" || e.kind === "task_dispatched") {
|
|
25958
26113
|
break;
|
|
@@ -26029,6 +26184,7 @@ function enqueueTask(meshId, message, opts) {
|
|
|
26029
26184
|
message,
|
|
26030
26185
|
status: "pending",
|
|
26031
26186
|
targetNodeId: opts?.targetNodeId,
|
|
26187
|
+
targetSessionId: opts?.targetSessionId,
|
|
26032
26188
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26033
26189
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
26034
26190
|
};
|
|
@@ -26046,9 +26202,14 @@ function getQueue(meshId, opts) {
|
|
|
26046
26202
|
}
|
|
26047
26203
|
function claimNextTask(meshId, nodeId, sessionId) {
|
|
26048
26204
|
const queue = readQueue(meshId);
|
|
26049
|
-
|
|
26205
|
+
const hasActiveAssignment = queue.some((q) => q.status === "assigned" && (q.assignedSessionId === sessionId || q.assignedNodeId === nodeId));
|
|
26206
|
+
if (hasActiveAssignment) return null;
|
|
26207
|
+
let targetIdx = queue.findIndex((q) => q.status === "pending" && q.targetSessionId === sessionId);
|
|
26208
|
+
if (targetIdx === -1) {
|
|
26209
|
+
targetIdx = queue.findIndex((q) => q.status === "pending" && q.targetNodeId === nodeId && !q.targetSessionId);
|
|
26210
|
+
}
|
|
26050
26211
|
if (targetIdx === -1) {
|
|
26051
|
-
targetIdx = queue.findIndex((q) => q.status === "pending" && !q.targetNodeId);
|
|
26212
|
+
targetIdx = queue.findIndex((q) => q.status === "pending" && !q.targetNodeId && !q.targetSessionId);
|
|
26052
26213
|
}
|
|
26053
26214
|
if (targetIdx === -1) return null;
|
|
26054
26215
|
const entry = queue[targetIdx];
|
|
@@ -26068,6 +26229,53 @@ function updateTaskStatus(meshId, taskId, status) {
|
|
|
26068
26229
|
writeQueue(meshId, queue);
|
|
26069
26230
|
return queue[idx];
|
|
26070
26231
|
}
|
|
26232
|
+
function recordTaskAutoLaunch(meshId, taskId, autoLaunch) {
|
|
26233
|
+
const queue = readQueue(meshId);
|
|
26234
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
26235
|
+
if (idx === -1) return null;
|
|
26236
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
26237
|
+
queue[idx].autoLaunch = {
|
|
26238
|
+
...autoLaunch,
|
|
26239
|
+
updatedAt: now
|
|
26240
|
+
};
|
|
26241
|
+
queue[idx].updatedAt = now;
|
|
26242
|
+
writeQueue(meshId, queue);
|
|
26243
|
+
return queue[idx];
|
|
26244
|
+
}
|
|
26245
|
+
function cancelTask(meshId, taskId, opts) {
|
|
26246
|
+
const queue = readQueue(meshId);
|
|
26247
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
26248
|
+
if (idx === -1) return null;
|
|
26249
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
26250
|
+
queue[idx].status = "cancelled";
|
|
26251
|
+
queue[idx].updatedAt = now;
|
|
26252
|
+
queue[idx].cancelledAt = now;
|
|
26253
|
+
if (opts?.reason) queue[idx].cancelReason = opts.reason;
|
|
26254
|
+
writeQueue(meshId, queue);
|
|
26255
|
+
return queue[idx];
|
|
26256
|
+
}
|
|
26257
|
+
function requeueTask(meshId, taskId, opts) {
|
|
26258
|
+
const queue = readQueue(meshId);
|
|
26259
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
26260
|
+
if (idx === -1) return null;
|
|
26261
|
+
const entry = queue[idx];
|
|
26262
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
26263
|
+
entry.status = "pending";
|
|
26264
|
+
delete entry.assignedNodeId;
|
|
26265
|
+
delete entry.assignedSessionId;
|
|
26266
|
+
delete entry.cancelledAt;
|
|
26267
|
+
delete entry.cancelReason;
|
|
26268
|
+
if (opts?.clearTargetNode) delete entry.targetNodeId;
|
|
26269
|
+
if (typeof opts?.targetNodeId === "string") entry.targetNodeId = opts.targetNodeId;
|
|
26270
|
+
if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
|
|
26271
|
+
if (typeof opts?.targetSessionId === "string") entry.targetSessionId = opts.targetSessionId;
|
|
26272
|
+
entry.updatedAt = now;
|
|
26273
|
+
entry.requeuedAt = now;
|
|
26274
|
+
entry.requeueCount = (entry.requeueCount || 0) + 1;
|
|
26275
|
+
if (opts?.reason) entry.requeueReason = opts.reason;
|
|
26276
|
+
writeQueue(meshId, queue);
|
|
26277
|
+
return entry;
|
|
26278
|
+
}
|
|
26071
26279
|
function updateSessionTaskStatus(meshId, sessionId, status) {
|
|
26072
26280
|
const queue = readQueue(meshId);
|
|
26073
26281
|
for (let i = queue.length - 1; i >= 0; i--) {
|
|
@@ -26082,13 +26290,160 @@ function updateSessionTaskStatus(meshId, sessionId, status) {
|
|
|
26082
26290
|
}
|
|
26083
26291
|
function getMeshQueueStats(meshId) {
|
|
26084
26292
|
const queue = readQueue(meshId);
|
|
26293
|
+
const pending = queue.filter((q) => q.status === "pending").length;
|
|
26294
|
+
const assigned = queue.filter((q) => q.status === "assigned").length;
|
|
26295
|
+
const completed = queue.filter((q) => q.status === "completed").length;
|
|
26296
|
+
const failed = queue.filter((q) => q.status === "failed").length;
|
|
26297
|
+
const cancelled = queue.filter((q) => q.status === "cancelled").length;
|
|
26085
26298
|
return {
|
|
26086
|
-
|
|
26087
|
-
|
|
26088
|
-
|
|
26089
|
-
|
|
26299
|
+
total: queue.length,
|
|
26300
|
+
active: pending + assigned,
|
|
26301
|
+
historical: completed + failed + cancelled,
|
|
26302
|
+
pending,
|
|
26303
|
+
assigned,
|
|
26304
|
+
completed,
|
|
26305
|
+
failed,
|
|
26306
|
+
cancelled,
|
|
26307
|
+
activeCounts: {
|
|
26308
|
+
pending,
|
|
26309
|
+
assigned
|
|
26310
|
+
},
|
|
26311
|
+
historicalCounts: {
|
|
26312
|
+
completed,
|
|
26313
|
+
failed,
|
|
26314
|
+
cancelled
|
|
26315
|
+
},
|
|
26316
|
+
activeAssignments: queue.filter((q) => q.status === "assigned").map((q) => ({
|
|
26317
|
+
id: q.id,
|
|
26318
|
+
nodeId: q.assignedNodeId,
|
|
26319
|
+
sessionId: q.assignedSessionId,
|
|
26320
|
+
message: q.message
|
|
26321
|
+
}))
|
|
26090
26322
|
};
|
|
26091
26323
|
}
|
|
26324
|
+
function parseVersion(raw) {
|
|
26325
|
+
const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
|
|
26326
|
+
return match ? match[1] : raw.split("\n")[0].slice(0, 100);
|
|
26327
|
+
}
|
|
26328
|
+
function shellQuote(value) {
|
|
26329
|
+
if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
26330
|
+
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
26331
|
+
}
|
|
26332
|
+
function expandHome(value) {
|
|
26333
|
+
const trimmed = value.trim();
|
|
26334
|
+
if (!trimmed.startsWith("~")) return trimmed;
|
|
26335
|
+
return path8.join(os22.homedir(), trimmed.slice(1));
|
|
26336
|
+
}
|
|
26337
|
+
function isExplicitCommandPath(command) {
|
|
26338
|
+
const trimmed = command.trim();
|
|
26339
|
+
return path8.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
|
|
26340
|
+
}
|
|
26341
|
+
function resolveCommandPath(command) {
|
|
26342
|
+
const trimmed = command.trim();
|
|
26343
|
+
if (!trimmed) return null;
|
|
26344
|
+
if (isExplicitCommandPath(trimmed)) {
|
|
26345
|
+
const expanded = expandHome(trimmed);
|
|
26346
|
+
const candidate = path8.isAbsolute(expanded) ? expanded : path8.resolve(expanded);
|
|
26347
|
+
return (0, import_fs8.existsSync)(candidate) ? candidate : null;
|
|
26348
|
+
}
|
|
26349
|
+
return null;
|
|
26350
|
+
}
|
|
26351
|
+
function execAsync(cmd, timeoutMs = 5e3) {
|
|
26352
|
+
return new Promise((resolve162) => {
|
|
26353
|
+
const child = (0, import_child_process2.exec)(cmd, {
|
|
26354
|
+
encoding: "utf-8",
|
|
26355
|
+
timeout: timeoutMs,
|
|
26356
|
+
...process.platform === "win32" ? { windowsHide: true } : {}
|
|
26357
|
+
}, (err, stdout) => {
|
|
26358
|
+
if (err || !stdout?.trim()) {
|
|
26359
|
+
resolve162(null);
|
|
26360
|
+
} else {
|
|
26361
|
+
resolve162(stdout.trim());
|
|
26362
|
+
}
|
|
26363
|
+
});
|
|
26364
|
+
child.on("error", () => resolve162(null));
|
|
26365
|
+
});
|
|
26366
|
+
}
|
|
26367
|
+
async function detectCLIs(providerLoader, options) {
|
|
26368
|
+
const platform10 = os22.platform();
|
|
26369
|
+
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
26370
|
+
const includeVersion = options?.includeVersion !== false;
|
|
26371
|
+
const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
|
|
26372
|
+
const results = await Promise.all(
|
|
26373
|
+
cliList.map(async (cli) => {
|
|
26374
|
+
try {
|
|
26375
|
+
const explicitPath = resolveCommandPath(cli.command);
|
|
26376
|
+
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
|
|
26377
|
+
if (!pathResult) return { ...cli, installed: false };
|
|
26378
|
+
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
26379
|
+
let version2;
|
|
26380
|
+
if (includeVersion) {
|
|
26381
|
+
const versionCommands = [
|
|
26382
|
+
`"${firstPath}" --version`,
|
|
26383
|
+
`"${firstPath}" -V`,
|
|
26384
|
+
`"${firstPath}" -v`,
|
|
26385
|
+
cli.versionCommand
|
|
26386
|
+
].filter((v) => !!v);
|
|
26387
|
+
try {
|
|
26388
|
+
for (const versionCommand of versionCommands) {
|
|
26389
|
+
const versionResult = await execAsync(versionCommand, 3e3);
|
|
26390
|
+
if (versionResult) {
|
|
26391
|
+
version2 = parseVersion(versionResult);
|
|
26392
|
+
break;
|
|
26393
|
+
}
|
|
26394
|
+
}
|
|
26395
|
+
} catch {
|
|
26396
|
+
}
|
|
26397
|
+
}
|
|
26398
|
+
return { ...cli, installed: true, version: version2, path: firstPath };
|
|
26399
|
+
} catch {
|
|
26400
|
+
return { ...cli, installed: false };
|
|
26401
|
+
}
|
|
26402
|
+
})
|
|
26403
|
+
);
|
|
26404
|
+
return results;
|
|
26405
|
+
}
|
|
26406
|
+
async function detectCLI(cliId, providerLoader, options) {
|
|
26407
|
+
const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
|
|
26408
|
+
if (providerLoader) {
|
|
26409
|
+
const cliList = providerLoader.getCliDetectionList();
|
|
26410
|
+
const target = cliList.find((c) => c.id === resolvedId);
|
|
26411
|
+
if (target) {
|
|
26412
|
+
const platform10 = os22.platform();
|
|
26413
|
+
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
26414
|
+
try {
|
|
26415
|
+
const explicitPath = resolveCommandPath(target.command);
|
|
26416
|
+
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
|
|
26417
|
+
if (!pathResult) return null;
|
|
26418
|
+
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
26419
|
+
let version2;
|
|
26420
|
+
if (options?.includeVersion !== false) {
|
|
26421
|
+
const versionCommands = [
|
|
26422
|
+
`"${firstPath}" --version`,
|
|
26423
|
+
`"${firstPath}" -V`,
|
|
26424
|
+
`"${firstPath}" -v`,
|
|
26425
|
+
target.versionCommand
|
|
26426
|
+
].filter((v) => !!v);
|
|
26427
|
+
try {
|
|
26428
|
+
for (const versionCommand of versionCommands) {
|
|
26429
|
+
const versionResult = await execAsync(versionCommand, 3e3);
|
|
26430
|
+
if (versionResult) {
|
|
26431
|
+
version2 = parseVersion(versionResult);
|
|
26432
|
+
break;
|
|
26433
|
+
}
|
|
26434
|
+
}
|
|
26435
|
+
} catch {
|
|
26436
|
+
}
|
|
26437
|
+
}
|
|
26438
|
+
return { ...target, installed: true, version: version2, path: firstPath };
|
|
26439
|
+
} catch {
|
|
26440
|
+
return null;
|
|
26441
|
+
}
|
|
26442
|
+
}
|
|
26443
|
+
}
|
|
26444
|
+
const all = await detectCLIs(providerLoader, options);
|
|
26445
|
+
return all.find((c) => c.id === resolvedId && c.installed) || null;
|
|
26446
|
+
}
|
|
26092
26447
|
function setLogLevel(level) {
|
|
26093
26448
|
currentLevel = level;
|
|
26094
26449
|
daemonLog("Logger", `Log level set to: ${level}`, "info");
|
|
@@ -26103,13 +26458,13 @@ function getDaemonLogDir() {
|
|
|
26103
26458
|
return LOG_DIR;
|
|
26104
26459
|
}
|
|
26105
26460
|
function getCurrentDaemonLogPath(date5 = /* @__PURE__ */ new Date()) {
|
|
26106
|
-
return
|
|
26461
|
+
return path9.join(LOG_DIR, `daemon-${date5.toISOString().slice(0, 10)}.log`);
|
|
26107
26462
|
}
|
|
26108
26463
|
function checkDateRotation() {
|
|
26109
26464
|
const today = getDateStr();
|
|
26110
26465
|
if (today !== currentDate) {
|
|
26111
26466
|
currentDate = today;
|
|
26112
|
-
currentLogFile =
|
|
26467
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
26113
26468
|
cleanOldLogs();
|
|
26114
26469
|
}
|
|
26115
26470
|
}
|
|
@@ -26123,7 +26478,7 @@ function cleanOldLogs() {
|
|
|
26123
26478
|
const dateMatch = file2.match(/daemon-(\d{4}-\d{2}-\d{2})/);
|
|
26124
26479
|
if (dateMatch && dateMatch[1] < cutoffStr) {
|
|
26125
26480
|
try {
|
|
26126
|
-
fs2.unlinkSync(
|
|
26481
|
+
fs2.unlinkSync(path9.join(LOG_DIR, file2));
|
|
26127
26482
|
} catch {
|
|
26128
26483
|
}
|
|
26129
26484
|
}
|
|
@@ -26245,6 +26600,9 @@ function drainPendingMeshCoordinatorEvents() {
|
|
|
26245
26600
|
function readNonEmptyString(value) {
|
|
26246
26601
|
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
26247
26602
|
}
|
|
26603
|
+
function resolveEventSessionId(event, fallback) {
|
|
26604
|
+
return readNonEmptyString(event.targetSessionId) || readNonEmptyString(event.sessionId) || readNonEmptyString(event.instanceId) || readNonEmptyString(fallback);
|
|
26605
|
+
}
|
|
26248
26606
|
function isMeshCoordinatorEvent(eventName) {
|
|
26249
26607
|
return typeof eventName === "string" && MESH_COORDINATOR_EVENTS.has(eventName);
|
|
26250
26608
|
}
|
|
@@ -26256,38 +26614,323 @@ function formatCompletionMetadata(event) {
|
|
|
26256
26614
|
].filter(Boolean);
|
|
26257
26615
|
return parts.length > 0 ? ` (${parts.join("; ")})` : "";
|
|
26258
26616
|
}
|
|
26617
|
+
function getMeshWithCache(components, meshId) {
|
|
26618
|
+
const localMesh = getMesh(meshId);
|
|
26619
|
+
if (localMesh) return localMesh;
|
|
26620
|
+
return components.router?.getCachedInlineMesh(meshId);
|
|
26621
|
+
}
|
|
26622
|
+
function isIntentionalCleanupStopMetadata(event) {
|
|
26623
|
+
return event.intentional === true || event.intentionalStop === true || event.operatorCleanup === true || event.reason === "operator_cleanup" || event.stopReason === "operator_cleanup" || event.cleanupReason === "operator_cleanup" || event.source === "mesh_cleanup_sessions" || event.source === "mesh_remove_node";
|
|
26624
|
+
}
|
|
26625
|
+
function hasRecentIntentionalCleanupStop(meshId, sessionId, nodeId) {
|
|
26626
|
+
if (!sessionId && !nodeId) return false;
|
|
26627
|
+
const cutoff = Date.now() - INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS;
|
|
26628
|
+
const entries = readLedgerEntries(meshId);
|
|
26629
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
26630
|
+
const entry = entries[i];
|
|
26631
|
+
const timestamp2 = new Date(entry.timestamp).getTime();
|
|
26632
|
+
if (!Number.isNaN(timestamp2) && timestamp2 < cutoff) break;
|
|
26633
|
+
if (!isIntentionalCleanupStopEntry(entry)) continue;
|
|
26634
|
+
if (sessionId && entry.sessionId === sessionId) return true;
|
|
26635
|
+
if (!sessionId && nodeId && entry.nodeId === nodeId) return true;
|
|
26636
|
+
}
|
|
26637
|
+
return false;
|
|
26638
|
+
}
|
|
26639
|
+
function shouldSuppressIntentionalCleanupStop(args) {
|
|
26640
|
+
if (args.event !== "agent:stopped" && args.event !== "monitor:long_generating") return false;
|
|
26641
|
+
if (isIntentionalCleanupStopMetadata(args.metadataEvent)) return true;
|
|
26642
|
+
return hasRecentIntentionalCleanupStop(args.meshId, args.sessionId, args.nodeId);
|
|
26643
|
+
}
|
|
26259
26644
|
function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
|
|
26260
26645
|
const task = claimNextTask(meshId, nodeId, sessionId);
|
|
26261
|
-
if (!task)
|
|
26646
|
+
if (!task) {
|
|
26647
|
+
return false;
|
|
26648
|
+
}
|
|
26262
26649
|
LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
|
|
26650
|
+
const mesh = getMeshWithCache(components, meshId);
|
|
26651
|
+
const node = mesh?.nodes.find((n) => n.id === nodeId);
|
|
26652
|
+
if (node?.daemonId && components.dispatchMeshCommand) {
|
|
26653
|
+
const isLocalNode = components.cliManager.adapters.has(sessionId);
|
|
26654
|
+
if (!isLocalNode) {
|
|
26655
|
+
components.dispatchMeshCommand(node.daemonId, "agent_command", {
|
|
26656
|
+
targetSessionId: sessionId,
|
|
26657
|
+
cliType: providerType,
|
|
26658
|
+
action: "send_chat",
|
|
26659
|
+
message: task.message
|
|
26660
|
+
}).catch((e) => {
|
|
26661
|
+
LOG.error("MeshQueue", `Failed to dispatch task via P2P to remote node ${nodeId}: ${e?.message}`);
|
|
26662
|
+
updateTaskStatus(meshId, task.id, "failed");
|
|
26663
|
+
});
|
|
26664
|
+
return true;
|
|
26665
|
+
}
|
|
26666
|
+
}
|
|
26263
26667
|
components.cliManager.handleCliCommand("agent_command", {
|
|
26264
26668
|
targetSessionId: sessionId,
|
|
26265
26669
|
cliType: providerType,
|
|
26266
26670
|
action: "send_chat",
|
|
26267
|
-
|
|
26671
|
+
message: task.message
|
|
26268
26672
|
}).catch((e) => {
|
|
26269
|
-
LOG.error("MeshQueue", `Failed to dispatch task to node ${nodeId}: ${e?.message}`);
|
|
26673
|
+
LOG.error("MeshQueue", `Failed to dispatch task locally to node ${nodeId}: ${e?.message}`);
|
|
26674
|
+
updateTaskStatus(meshId, task.id, "failed");
|
|
26270
26675
|
});
|
|
26271
26676
|
return true;
|
|
26272
26677
|
}
|
|
26273
|
-
function
|
|
26274
|
-
const
|
|
26678
|
+
function normalizeProviderPriority(policy) {
|
|
26679
|
+
const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
|
|
26680
|
+
if (!Array.isArray(raw)) return [];
|
|
26681
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26682
|
+
return raw.map((type2) => typeof type2 === "string" ? type2.trim() : "").filter(Boolean).filter((type2) => {
|
|
26683
|
+
if (seen.has(type2)) return false;
|
|
26684
|
+
seen.add(type2);
|
|
26685
|
+
return true;
|
|
26686
|
+
});
|
|
26687
|
+
}
|
|
26688
|
+
function isTerminalSessionStatus(status) {
|
|
26689
|
+
return ["stopped", "failed", "terminated", "exited", "closed"].includes(status);
|
|
26690
|
+
}
|
|
26691
|
+
function isIdleSessionState(state) {
|
|
26692
|
+
const status = readNonEmptyString(state?.status).toLowerCase();
|
|
26693
|
+
if (isTerminalSessionStatus(status)) return false;
|
|
26694
|
+
return status === "idle" || state?.activeChat?.status === "waiting_input";
|
|
26695
|
+
}
|
|
26696
|
+
function isDirtyNode(node) {
|
|
26697
|
+
return node?.health === "dirty" || node?.git?.dirty === true;
|
|
26698
|
+
}
|
|
26699
|
+
function isLaunchableNode(node) {
|
|
26700
|
+
if (!node || node.status === "disabled" || node.status === "removed") return false;
|
|
26701
|
+
const health = readNonEmptyString(node.health).toLowerCase();
|
|
26702
|
+
if (!health) return true;
|
|
26703
|
+
return health === "online" || health === "unknown";
|
|
26704
|
+
}
|
|
26705
|
+
function localAutoLaunchSkipReason(node) {
|
|
26706
|
+
const daemonId = readNonEmptyString(node?.daemonId);
|
|
26707
|
+
const machineId = readNonEmptyString(node?.machineId);
|
|
26708
|
+
const appConfig = loadConfig();
|
|
26709
|
+
const localMachineId = readNonEmptyString(appConfig.machineId) || readNonEmptyString(appConfig.registeredMachineId);
|
|
26710
|
+
const cloudDaemonId = localMachineId ? `daemon_${localMachineId}` : "";
|
|
26711
|
+
const standaloneDaemonId = localMachineId ? `standalone_${localMachineId}` : "";
|
|
26712
|
+
const daemonMatchesLocal = !daemonId || daemonId === cloudDaemonId || daemonId === standaloneDaemonId;
|
|
26713
|
+
const machineMatchesLocal = !machineId || localMachineId && machineId === localMachineId;
|
|
26714
|
+
if (node?.isLocalWorktree === true) {
|
|
26715
|
+
return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
|
|
26716
|
+
}
|
|
26717
|
+
if (daemonId || machineId) {
|
|
26718
|
+
return daemonMatchesLocal && machineMatchesLocal ? null : "remote_auto_launch_unsupported";
|
|
26719
|
+
}
|
|
26720
|
+
return null;
|
|
26721
|
+
}
|
|
26722
|
+
function activeAssignedCount(meshId) {
|
|
26723
|
+
return getQueue(meshId, { status: ["assigned"] }).length;
|
|
26724
|
+
}
|
|
26725
|
+
function nodeHasActiveAssignment(meshId, nodeId) {
|
|
26726
|
+
return getQueue(meshId, { status: ["assigned"] }).some((task) => task.assignedNodeId === nodeId);
|
|
26727
|
+
}
|
|
26728
|
+
function liveSessionCountForNode(components, meshId, nodeId) {
|
|
26729
|
+
return components.instanceManager.getByCategory("cli").filter((inst) => {
|
|
26730
|
+
const state = inst.getState();
|
|
26731
|
+
const settings = state.settings || {};
|
|
26732
|
+
if (readNonEmptyString(settings.meshNodeFor) !== meshId) return false;
|
|
26733
|
+
const instNodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
26734
|
+
if (instNodeId !== nodeId) return false;
|
|
26735
|
+
const status = readNonEmptyString(state.status).toLowerCase();
|
|
26736
|
+
return !isTerminalSessionStatus(status);
|
|
26737
|
+
}).length;
|
|
26738
|
+
}
|
|
26739
|
+
function recordAutoLaunchEvent(meshId, args) {
|
|
26740
|
+
try {
|
|
26741
|
+
appendLedgerEntry(meshId, {
|
|
26742
|
+
kind: "session_auto_launch",
|
|
26743
|
+
nodeId: args.nodeId,
|
|
26744
|
+
sessionId: args.sessionId,
|
|
26745
|
+
providerType: args.providerType,
|
|
26746
|
+
payload: {
|
|
26747
|
+
phase: args.phase,
|
|
26748
|
+
taskId: args.taskId,
|
|
26749
|
+
reason: args.reason,
|
|
26750
|
+
error: args.error
|
|
26751
|
+
}
|
|
26752
|
+
});
|
|
26753
|
+
} catch (e) {
|
|
26754
|
+
LOG.warn("MeshQueue", `Failed to record auto-launch ledger event: ${e?.message || e}`);
|
|
26755
|
+
}
|
|
26756
|
+
}
|
|
26757
|
+
function markAutoLaunch(meshId, taskId, args) {
|
|
26758
|
+
recordTaskAutoLaunch(meshId, taskId, {
|
|
26759
|
+
status: args.status,
|
|
26760
|
+
reason: args.reason || args.error,
|
|
26761
|
+
nodeId: args.nodeId,
|
|
26762
|
+
providerType: args.providerType,
|
|
26763
|
+
sessionId: args.sessionId
|
|
26764
|
+
});
|
|
26765
|
+
recordAutoLaunchEvent(meshId, {
|
|
26766
|
+
phase: args.status,
|
|
26767
|
+
taskId,
|
|
26768
|
+
nodeId: args.nodeId,
|
|
26769
|
+
providerType: args.providerType,
|
|
26770
|
+
sessionId: args.sessionId,
|
|
26771
|
+
reason: args.reason,
|
|
26772
|
+
error: args.error
|
|
26773
|
+
});
|
|
26774
|
+
}
|
|
26775
|
+
async function resolveUsableProvider(components, nodeId, node) {
|
|
26776
|
+
const providerPriority = normalizeProviderPriority(node?.policy);
|
|
26777
|
+
if (!providerPriority.length) return { reason: "missing_provider_priority" };
|
|
26778
|
+
const providerLoader = components.providerLoader;
|
|
26779
|
+
if (!providerLoader) return { reason: "provider_loader_unavailable" };
|
|
26780
|
+
const failed = [];
|
|
26781
|
+
for (const requestedType of providerPriority) {
|
|
26782
|
+
const normalizedType = typeof providerLoader.resolveAlias === "function" ? providerLoader.resolveAlias(requestedType) : requestedType;
|
|
26783
|
+
if (typeof providerLoader.isMachineProviderEnabled === "function" && !providerLoader.isMachineProviderEnabled(normalizedType)) {
|
|
26784
|
+
failed.push(`${requestedType}: disabled`);
|
|
26785
|
+
continue;
|
|
26786
|
+
}
|
|
26787
|
+
let detected;
|
|
26788
|
+
try {
|
|
26789
|
+
detected = await detectCLI(normalizedType, providerLoader, { includeVersion: false });
|
|
26790
|
+
} catch (e) {
|
|
26791
|
+
failed.push(`${requestedType}: detect failed: ${e?.message || e}`);
|
|
26792
|
+
continue;
|
|
26793
|
+
}
|
|
26794
|
+
if (typeof providerLoader.setCliDetectionResults === "function") {
|
|
26795
|
+
providerLoader.setCliDetectionResults([{
|
|
26796
|
+
id: normalizedType,
|
|
26797
|
+
installed: !!detected,
|
|
26798
|
+
path: detected?.path
|
|
26799
|
+
}], false);
|
|
26800
|
+
}
|
|
26801
|
+
components.onStatusChange?.();
|
|
26802
|
+
if (detected) return { providerType: normalizedType };
|
|
26803
|
+
failed.push(`${requestedType}: not detected`);
|
|
26804
|
+
}
|
|
26805
|
+
return { reason: `provider_priority_unusable: ${failed.join("; ") || nodeId}` };
|
|
26806
|
+
}
|
|
26807
|
+
async function maybeAutoLaunchOneQueueSession(components, meshId, mesh) {
|
|
26808
|
+
const queue = getQueue(meshId);
|
|
26809
|
+
const pending = queue.filter((task) => task.status === "pending");
|
|
26810
|
+
if (!pending.length) return false;
|
|
26811
|
+
const maxParallelTasks = Math.max(1, Math.floor(Number(mesh?.policy?.maxParallelTasks) || 2));
|
|
26812
|
+
for (const task of pending) {
|
|
26813
|
+
if (activeAssignedCount(meshId) >= maxParallelTasks) {
|
|
26814
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_parallel_tasks_reached" });
|
|
26815
|
+
return false;
|
|
26816
|
+
}
|
|
26817
|
+
if (task.targetSessionId) {
|
|
26818
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_constraint" });
|
|
26819
|
+
continue;
|
|
26820
|
+
}
|
|
26821
|
+
const candidateNodes = Array.isArray(mesh?.nodes) ? mesh.nodes.filter((node) => task.targetNodeId ? node?.id === task.targetNodeId : true) : [];
|
|
26822
|
+
if (!candidateNodes.length) {
|
|
26823
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "no_matching_node", nodeId: task.targetNodeId });
|
|
26824
|
+
continue;
|
|
26825
|
+
}
|
|
26826
|
+
for (const node of candidateNodes) {
|
|
26827
|
+
const nodeId = readNonEmptyString(node?.id);
|
|
26828
|
+
if (!nodeId) continue;
|
|
26829
|
+
const launchKey = `${meshId}:${nodeId}`;
|
|
26830
|
+
const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
|
|
26831
|
+
if (autoLaunchInProgress.has(launchKey)) {
|
|
26832
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_in_progress", nodeId });
|
|
26833
|
+
continue;
|
|
26834
|
+
}
|
|
26835
|
+
if (Date.now() < cooldownUntil) {
|
|
26836
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "auto_launch_cooldown", nodeId });
|
|
26837
|
+
continue;
|
|
26838
|
+
}
|
|
26839
|
+
if (isDirtyNode(node)) {
|
|
26840
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "dirty_workspace", nodeId });
|
|
26841
|
+
continue;
|
|
26842
|
+
}
|
|
26843
|
+
if (!isLaunchableNode(node)) {
|
|
26844
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_not_launch_ready", nodeId });
|
|
26845
|
+
continue;
|
|
26846
|
+
}
|
|
26847
|
+
const localSkipReason = localAutoLaunchSkipReason(node);
|
|
26848
|
+
if (localSkipReason) {
|
|
26849
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: localSkipReason, nodeId });
|
|
26850
|
+
continue;
|
|
26851
|
+
}
|
|
26852
|
+
if (nodeHasActiveAssignment(meshId, nodeId)) {
|
|
26853
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "node_has_active_assignment", nodeId });
|
|
26854
|
+
continue;
|
|
26855
|
+
}
|
|
26856
|
+
const maxConcurrentSessions = Number(node?.policy?.maxConcurrentSessions);
|
|
26857
|
+
if (Number.isFinite(maxConcurrentSessions) && maxConcurrentSessions >= 0 && liveSessionCountForNode(components, meshId, nodeId) >= maxConcurrentSessions) {
|
|
26858
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_concurrent_sessions_reached", nodeId });
|
|
26859
|
+
continue;
|
|
26860
|
+
}
|
|
26861
|
+
autoLaunchInProgress.add(launchKey);
|
|
26862
|
+
try {
|
|
26863
|
+
const resolved = await resolveUsableProvider(components, nodeId, node);
|
|
26864
|
+
if (!resolved.providerType) {
|
|
26865
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: resolved.reason || "provider_unusable", nodeId });
|
|
26866
|
+
continue;
|
|
26867
|
+
}
|
|
26868
|
+
markAutoLaunch(meshId, task.id, { status: "started", nodeId, providerType: resolved.providerType });
|
|
26869
|
+
const launchResult = await components.cliManager.handleCliCommand("launch_cli", {
|
|
26870
|
+
cliType: resolved.providerType,
|
|
26871
|
+
dir: node.workspace,
|
|
26872
|
+
settings: {
|
|
26873
|
+
meshNodeFor: meshId,
|
|
26874
|
+
meshNodeId: nodeId,
|
|
26875
|
+
spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || "hidden",
|
|
26876
|
+
launchedByCoordinator: true,
|
|
26877
|
+
autoLaunchedForQueueTaskId: task.id
|
|
26878
|
+
}
|
|
26879
|
+
});
|
|
26880
|
+
if (!launchResult?.success) {
|
|
26881
|
+
const reason = launchResult?.error || "launch_cli_failed";
|
|
26882
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
|
|
26883
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
26884
|
+
return false;
|
|
26885
|
+
}
|
|
26886
|
+
const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
|
|
26887
|
+
if (!sessionId) {
|
|
26888
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason: "launch_missing_session_id", nodeId, providerType: resolved.providerType });
|
|
26889
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
26890
|
+
return false;
|
|
26891
|
+
}
|
|
26892
|
+
markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId });
|
|
26893
|
+
tryAssignQueueTask(components, meshId, nodeId, sessionId, resolved.providerType);
|
|
26894
|
+
return true;
|
|
26895
|
+
} catch (e) {
|
|
26896
|
+
markAutoLaunch(meshId, task.id, { status: "failed", error: e?.message || String(e), nodeId });
|
|
26897
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
26898
|
+
return false;
|
|
26899
|
+
} finally {
|
|
26900
|
+
autoLaunchInProgress.delete(launchKey);
|
|
26901
|
+
}
|
|
26902
|
+
}
|
|
26903
|
+
}
|
|
26904
|
+
return false;
|
|
26905
|
+
}
|
|
26906
|
+
async function triggerMeshQueue(components, meshId) {
|
|
26907
|
+
const mesh = getMeshWithCache(components, meshId);
|
|
26275
26908
|
if (!mesh) return;
|
|
26276
26909
|
const cliInstances = components.instanceManager.getByCategory("cli");
|
|
26277
26910
|
for (const inst of cliInstances) {
|
|
26278
26911
|
const state = inst.getState();
|
|
26279
26912
|
const settings = state.settings || {};
|
|
26280
26913
|
const instMeshId = readNonEmptyString(settings.meshNodeFor);
|
|
26281
|
-
if (instMeshId !== meshId
|
|
26914
|
+
if (instMeshId !== meshId) continue;
|
|
26282
26915
|
const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
26283
26916
|
if (!nodeId) continue;
|
|
26284
|
-
if (state
|
|
26917
|
+
if (!isIdleSessionState(state)) continue;
|
|
26285
26918
|
const sessionId = state.instanceId;
|
|
26286
26919
|
const providerType = state.type || readNonEmptyString(settings.providerType);
|
|
26287
26920
|
if (providerType) {
|
|
26288
26921
|
tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType);
|
|
26289
26922
|
}
|
|
26290
26923
|
}
|
|
26924
|
+
for (const [key, idle] of remoteIdleSessions.entries()) {
|
|
26925
|
+
const node = mesh.nodes.find((n) => n.id === idle.nodeId);
|
|
26926
|
+
if (node) {
|
|
26927
|
+
const assigned = tryAssignQueueTask(components, meshId, idle.nodeId, idle.sessionId, idle.providerType);
|
|
26928
|
+
if (assigned) {
|
|
26929
|
+
remoteIdleSessions.delete(key);
|
|
26930
|
+
}
|
|
26931
|
+
}
|
|
26932
|
+
}
|
|
26933
|
+
await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
|
|
26291
26934
|
}
|
|
26292
26935
|
function buildMeshSystemMessage(args) {
|
|
26293
26936
|
const metadata = formatCompletionMetadata(args.metadataEvent);
|
|
@@ -26334,20 +26977,91 @@ Do NOT retry on this node. Consider reassigning to a different node or asking th
|
|
|
26334
26977
|
return "";
|
|
26335
26978
|
}
|
|
26336
26979
|
function injectMeshSystemMessage(components, args) {
|
|
26980
|
+
const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
26981
|
+
const eventNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
26982
|
+
const intentionalCleanupStop = shouldSuppressIntentionalCleanupStop({
|
|
26983
|
+
event: args.event,
|
|
26984
|
+
meshId: args.meshId,
|
|
26985
|
+
metadataEvent: args.metadataEvent,
|
|
26986
|
+
sessionId: eventSessionId || void 0,
|
|
26987
|
+
nodeId: eventNodeId || void 0
|
|
26988
|
+
});
|
|
26989
|
+
if (intentionalCleanupStop) {
|
|
26990
|
+
if (eventSessionId && eventNodeId) {
|
|
26991
|
+
remoteIdleSessions.delete(`${eventNodeId}:${eventSessionId}`);
|
|
26992
|
+
}
|
|
26993
|
+
LOG.info("MeshEvents", `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || "(unknown session)"}`);
|
|
26994
|
+
return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
|
|
26995
|
+
}
|
|
26996
|
+
let completedTaskForLedger = null;
|
|
26337
26997
|
if (args.event === "agent:generating_completed") {
|
|
26338
|
-
const sessionId =
|
|
26339
|
-
const nodeId = readNonEmptyString(args.
|
|
26998
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
26999
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
26340
27000
|
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
26341
27001
|
if (sessionId) {
|
|
26342
|
-
updateSessionTaskStatus(args.meshId, sessionId, "completed");
|
|
27002
|
+
const completedTask = updateSessionTaskStatus(args.meshId, sessionId, "completed");
|
|
27003
|
+
completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
|
|
26343
27004
|
if (nodeId && providerType) {
|
|
26344
27005
|
setTimeout(() => {
|
|
26345
27006
|
tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
26346
27007
|
}, 500);
|
|
26347
27008
|
}
|
|
26348
27009
|
}
|
|
27010
|
+
} else if (args.event === "agent:ready") {
|
|
27011
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
27012
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
27013
|
+
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
27014
|
+
const completedTask = sessionId ? updateSessionTaskStatus(args.meshId, sessionId, "completed") : null;
|
|
27015
|
+
if (completedTask) {
|
|
27016
|
+
completedTaskForLedger = { id: completedTask.id };
|
|
27017
|
+
try {
|
|
27018
|
+
appendLedgerEntry(args.meshId, {
|
|
27019
|
+
kind: "task_completed",
|
|
27020
|
+
nodeId: nodeId || void 0,
|
|
27021
|
+
sessionId,
|
|
27022
|
+
providerType: providerType || void 0,
|
|
27023
|
+
payload: {
|
|
27024
|
+
event: args.event,
|
|
27025
|
+
nodeLabel: args.nodeLabel,
|
|
27026
|
+
taskId: completedTask.id,
|
|
27027
|
+
completedViaReady: true,
|
|
27028
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
27029
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
|
|
27030
|
+
evidence: buildTaskCompletionEvidence({
|
|
27031
|
+
event: "agent:ready",
|
|
27032
|
+
nodeId,
|
|
27033
|
+
sessionId,
|
|
27034
|
+
providerType: providerType || void 0,
|
|
27035
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
27036
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
27037
|
+
})
|
|
27038
|
+
}
|
|
27039
|
+
});
|
|
27040
|
+
} catch (e) {
|
|
27041
|
+
LOG.warn("MeshLedger", `Failed to record task_completed from ready: ${e?.message || e}`);
|
|
27042
|
+
}
|
|
27043
|
+
}
|
|
27044
|
+
if (sessionId && nodeId && providerType) {
|
|
27045
|
+
remoteIdleSessions.set(`${nodeId}:${sessionId}`, { nodeId, sessionId, providerType });
|
|
27046
|
+
setTimeout(() => {
|
|
27047
|
+
const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
27048
|
+
if (assigned) {
|
|
27049
|
+
remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
27050
|
+
}
|
|
27051
|
+
}, 500);
|
|
27052
|
+
}
|
|
27053
|
+
} else if (args.event === "agent:generating_started") {
|
|
27054
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
27055
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
27056
|
+
if (sessionId && nodeId) {
|
|
27057
|
+
remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
27058
|
+
}
|
|
26349
27059
|
} else if (args.event === "agent:stopped") {
|
|
26350
|
-
const sessionId =
|
|
27060
|
+
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
27061
|
+
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
27062
|
+
if (sessionId && nodeId) {
|
|
27063
|
+
remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
|
|
27064
|
+
}
|
|
26351
27065
|
if (sessionId) {
|
|
26352
27066
|
updateSessionTaskStatus(args.meshId, sessionId, "failed");
|
|
26353
27067
|
}
|
|
@@ -26355,15 +27069,29 @@ function injectMeshSystemMessage(components, args) {
|
|
|
26355
27069
|
const ledgerKind = EVENT_TO_LEDGER_KIND[args.event];
|
|
26356
27070
|
if (ledgerKind) {
|
|
26357
27071
|
try {
|
|
27072
|
+
const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0;
|
|
27073
|
+
const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0;
|
|
27074
|
+
const ledgerProviderType = readNonEmptyString(args.metadataEvent.providerType) || void 0;
|
|
27075
|
+
const completionEvidence = ledgerKind === "task_completed" && ledgerNodeId && ledgerSessionId ? buildTaskCompletionEvidence({
|
|
27076
|
+
event: "agent:generating_completed",
|
|
27077
|
+
nodeId: ledgerNodeId,
|
|
27078
|
+
sessionId: ledgerSessionId,
|
|
27079
|
+
providerType: ledgerProviderType,
|
|
27080
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
27081
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
27082
|
+
}) : void 0;
|
|
26358
27083
|
appendLedgerEntry(args.meshId, {
|
|
26359
27084
|
kind: ledgerKind,
|
|
26360
|
-
nodeId:
|
|
26361
|
-
sessionId:
|
|
26362
|
-
providerType:
|
|
27085
|
+
nodeId: ledgerNodeId,
|
|
27086
|
+
sessionId: ledgerSessionId,
|
|
27087
|
+
providerType: ledgerProviderType,
|
|
26363
27088
|
payload: {
|
|
26364
27089
|
event: args.event,
|
|
26365
27090
|
nodeLabel: args.nodeLabel,
|
|
26366
|
-
|
|
27091
|
+
taskId: completedTaskForLedger?.id || void 0,
|
|
27092
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
27093
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
|
|
27094
|
+
evidence: completionEvidence
|
|
26367
27095
|
}
|
|
26368
27096
|
});
|
|
26369
27097
|
} catch (e) {
|
|
@@ -26376,8 +27104,8 @@ function injectMeshSystemMessage(components, args) {
|
|
|
26376
27104
|
const mesh = getMesh(args.meshId);
|
|
26377
27105
|
const maxRetries = mesh?.policy?.maxTaskRetries ?? 1;
|
|
26378
27106
|
recoveryContext = getSessionRecoveryContext(args.meshId, {
|
|
26379
|
-
sessionId:
|
|
26380
|
-
nodeId: readNonEmptyString(args.
|
|
27107
|
+
sessionId: resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0,
|
|
27108
|
+
nodeId: readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
|
|
26381
27109
|
maxRetries
|
|
26382
27110
|
});
|
|
26383
27111
|
recoveryContext.failedProviderType = readNonEmptyString(args.metadataEvent.providerType) || null;
|
|
@@ -26472,12 +27200,21 @@ function handleMeshForwardEvent(components, payload) {
|
|
|
26472
27200
|
const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : "Remote agent";
|
|
26473
27201
|
return injectMeshSystemMessage(components, {
|
|
26474
27202
|
meshId,
|
|
27203
|
+
nodeId,
|
|
26475
27204
|
nodeLabel,
|
|
26476
27205
|
event: eventName,
|
|
26477
27206
|
metadataEvent: {
|
|
26478
|
-
targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId),
|
|
27207
|
+
targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.instanceId),
|
|
26479
27208
|
providerType: readNonEmptyString(payload.providerType),
|
|
26480
|
-
providerSessionId: readNonEmptyString(payload.providerSessionId)
|
|
27209
|
+
providerSessionId: readNonEmptyString(payload.providerSessionId),
|
|
27210
|
+
finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
|
|
27211
|
+
intentional: payload.intentional === true,
|
|
27212
|
+
intentionalStop: payload.intentionalStop === true,
|
|
27213
|
+
operatorCleanup: payload.operatorCleanup === true,
|
|
27214
|
+
reason: readNonEmptyString(payload.reason),
|
|
27215
|
+
stopReason: readNonEmptyString(payload.stopReason),
|
|
27216
|
+
cleanupReason: readNonEmptyString(payload.cleanupReason),
|
|
27217
|
+
source: readNonEmptyString(payload.source)
|
|
26481
27218
|
}
|
|
26482
27219
|
});
|
|
26483
27220
|
}
|
|
@@ -26496,15 +27233,17 @@ function setupMeshEventForwarding(components) {
|
|
|
26496
27233
|
const meshIdFromRuntime = readNonEmptyString(settings.meshNodeFor);
|
|
26497
27234
|
const isMeshDelegate = Boolean(meshIdFromRuntime || settings.launchedByCoordinator);
|
|
26498
27235
|
if (!isMeshDelegate) return;
|
|
26499
|
-
const mesh = meshIdFromRuntime ?
|
|
27236
|
+
const mesh = meshIdFromRuntime ? getMeshWithCache(components, meshIdFromRuntime) : getMeshByRepo(workspace);
|
|
26500
27237
|
const meshId = meshIdFromRuntime || readNonEmptyString(mesh?.id);
|
|
26501
27238
|
if (!meshId) return;
|
|
26502
27239
|
const targetNode = mesh?.nodes?.find((n) => n.workspace === workspace);
|
|
26503
27240
|
const runtimeNodeId = readNonEmptyString(settings.meshNodeId);
|
|
27241
|
+
const resolvedNodeId = targetNode?.id || runtimeNodeId;
|
|
26504
27242
|
const nodeLabel = targetNode ? `Node '${targetNode.id}'` : runtimeNodeId ? `Node '${runtimeNodeId}'` : `Agent at ${workspace}`;
|
|
26505
27243
|
injectMeshSystemMessage(components, {
|
|
26506
27244
|
meshId,
|
|
26507
27245
|
sourceInstanceId: instanceId,
|
|
27246
|
+
nodeId: resolvedNodeId,
|
|
26508
27247
|
nodeLabel,
|
|
26509
27248
|
event: event.event,
|
|
26510
27249
|
metadataEvent: event
|
|
@@ -26742,7 +27481,7 @@ function findBinary(name) {
|
|
|
26742
27481
|
const isWin = os9.platform() === "win32";
|
|
26743
27482
|
try {
|
|
26744
27483
|
const cmd = isWin ? `where ${trimmed}` : `which ${trimmed}`;
|
|
26745
|
-
return (0,
|
|
27484
|
+
return (0, import_child_process3.execSync)(cmd, {
|
|
26746
27485
|
encoding: "utf-8",
|
|
26747
27486
|
timeout: 5e3,
|
|
26748
27487
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -27117,7 +27856,7 @@ async function validateWorkspace(workspace) {
|
|
|
27117
27856
|
cwd: normalizedWorkspace
|
|
27118
27857
|
});
|
|
27119
27858
|
}
|
|
27120
|
-
await (0, import_promises5.access)(normalizedWorkspace,
|
|
27859
|
+
await (0, import_promises5.access)(normalizedWorkspace, import_fs9.constants.R_OK);
|
|
27121
27860
|
} catch (error48) {
|
|
27122
27861
|
if (error48 instanceof GitCommandError) throw error48;
|
|
27123
27862
|
throw new GitCommandError("invalid_args", "Workspace must be an existing directory", {
|
|
@@ -28246,7 +28985,7 @@ function addWorkspaceEntry(config2, rawPath, label, options) {
|
|
|
28246
28985
|
}
|
|
28247
28986
|
}
|
|
28248
28987
|
const v = validateWorkspacePath(abs);
|
|
28249
|
-
if (
|
|
28988
|
+
if (v.ok !== true) return { error: v.error };
|
|
28250
28989
|
const list = [...config2.workspaces || []];
|
|
28251
28990
|
if (list.some((w) => path5.resolve(w.path) === abs)) {
|
|
28252
28991
|
return { error: "Workspace already in list" };
|
|
@@ -28610,25 +29349,143 @@ async function syncMeshes(transport) {
|
|
|
28610
29349
|
}
|
|
28611
29350
|
}
|
|
28612
29351
|
}
|
|
28613
|
-
if (transport.syncMeshLedger) {
|
|
28614
|
-
for (const local of localMeshes) {
|
|
28615
|
-
try {
|
|
28616
|
-
await syncMeshLedger(local.id, transport);
|
|
28617
|
-
} catch (e) {
|
|
28618
|
-
result.errors.push(`Ledger sync failed for "${local.name}": ${e.message}`);
|
|
28619
|
-
}
|
|
28620
|
-
}
|
|
28621
|
-
}
|
|
28622
29352
|
return result;
|
|
28623
29353
|
}
|
|
28624
|
-
|
|
28625
|
-
|
|
28626
|
-
|
|
28627
|
-
|
|
28628
|
-
|
|
28629
|
-
|
|
28630
|
-
|
|
29354
|
+
function lastTimestamp(slice) {
|
|
29355
|
+
const entries = Array.isArray(slice?.entries) ? slice.entries : [];
|
|
29356
|
+
return entries.length ? entries[entries.length - 1].timestamp : null;
|
|
29357
|
+
}
|
|
29358
|
+
function buildMeshLedgerReplicaEvidence(args) {
|
|
29359
|
+
const entriesReceived = Array.isArray(args.slice?.entries) ? args.slice.entries.length : 0;
|
|
29360
|
+
return {
|
|
29361
|
+
nodeId: args.nodeId,
|
|
29362
|
+
...args.daemonId ? { daemonId: args.daemonId } : {},
|
|
29363
|
+
status: args.status ?? (args.importResult && args.importResult.accepted > 0 ? "imported" : "queried"),
|
|
29364
|
+
transport: args.transport,
|
|
29365
|
+
protocol: "adhdev.mesh.ledger.slice.v1",
|
|
29366
|
+
entriesReceived,
|
|
29367
|
+
entriesImported: args.importResult?.accepted ?? 0,
|
|
29368
|
+
skippedDuplicate: args.importResult?.skippedDuplicate ?? 0,
|
|
29369
|
+
rejectedInvalid: args.importResult?.rejectedInvalid ?? 0,
|
|
29370
|
+
hasMore: args.slice?.cursor?.hasMore === true,
|
|
29371
|
+
nextAfterId: args.slice?.cursor?.nextAfterId ?? null,
|
|
29372
|
+
lastTimestamp: lastTimestamp(args.slice),
|
|
29373
|
+
...args.slice?.summary ? { summary: args.slice.summary } : {},
|
|
29374
|
+
...args.error ? {
|
|
29375
|
+
error: args.error,
|
|
29376
|
+
noFallbackReason: "Ledger reconciliation is P2P/local-first only; Cloud/D1 ledger sync is intentionally disabled."
|
|
29377
|
+
} : {}
|
|
29378
|
+
};
|
|
29379
|
+
}
|
|
29380
|
+
function buildMeshLedgerReconciliationEvidence(meshId, replicas) {
|
|
29381
|
+
const failedNodes = replicas.filter((replica) => replica.status === "failed").map((replica) => replica.nodeId);
|
|
29382
|
+
const pendingNodes = replicas.filter((replica) => replica.hasMore && replica.status !== "failed").map((replica) => replica.nodeId);
|
|
29383
|
+
return {
|
|
29384
|
+
protocol: "adhdev.mesh.ledger.reconciliation.v1",
|
|
29385
|
+
meshId,
|
|
29386
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
29387
|
+
sourceOfTruth: {
|
|
29388
|
+
kind: "coordinator_local_jsonl",
|
|
29389
|
+
p2pOnly: true,
|
|
29390
|
+
cloudD1LedgerSync: false,
|
|
29391
|
+
notes: "Coordinator reconciles bounded slices from daemon-local JSONL ledgers over P2P DataChannel; Cloud/D1 is not a ledger source of truth."
|
|
29392
|
+
},
|
|
29393
|
+
replicas,
|
|
29394
|
+
totals: {
|
|
29395
|
+
replicas: replicas.length,
|
|
29396
|
+
queried: replicas.filter((replica) => replica.status !== "failed").length,
|
|
29397
|
+
failed: failedNodes.length,
|
|
29398
|
+
entriesReceived: replicas.reduce((sum, replica) => sum + replica.entriesReceived, 0),
|
|
29399
|
+
entriesImported: replicas.reduce((sum, replica) => sum + replica.entriesImported, 0),
|
|
29400
|
+
skippedDuplicate: replicas.reduce((sum, replica) => sum + replica.skippedDuplicate, 0),
|
|
29401
|
+
rejectedInvalid: replicas.reduce((sum, replica) => sum + replica.rejectedInvalid, 0)
|
|
29402
|
+
},
|
|
29403
|
+
convergence: {
|
|
29404
|
+
complete: failedNodes.length === 0 && pendingNodes.length === 0,
|
|
29405
|
+
pendingNodes,
|
|
29406
|
+
failedNodes
|
|
29407
|
+
}
|
|
29408
|
+
};
|
|
29409
|
+
}
|
|
29410
|
+
function messageFromError(error48) {
|
|
29411
|
+
if (error48 instanceof Error) return error48.message;
|
|
29412
|
+
if (typeof error48 === "string") return error48;
|
|
29413
|
+
if (error48 && typeof error48 === "object") {
|
|
29414
|
+
const candidate = error48.error ?? error48.message ?? error48.reason;
|
|
29415
|
+
if (typeof candidate === "string") return candidate;
|
|
29416
|
+
}
|
|
29417
|
+
return String(error48 || "mesh relay command failed");
|
|
29418
|
+
}
|
|
29419
|
+
function classifyP2pRelayFailure(error48, _context = {}) {
|
|
29420
|
+
const message = messageFromError(error48);
|
|
29421
|
+
const lower = message.toLowerCase();
|
|
29422
|
+
const hasP2pSignal = /p2p|datachannel|node-datachannel|webrtc|ice|mesh_relay_command|daemon_mesh_p2p_transport/i.test(message);
|
|
29423
|
+
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);
|
|
29424
|
+
if (/requires targetdaemonid and command|providerpriority|no inference provider|permission denied|read-only|not a member/i.test(message)) {
|
|
29425
|
+
return {
|
|
29426
|
+
code: "mesh_logic_or_provider_failure",
|
|
29427
|
+
reason: "mesh_logic_or_provider_failure",
|
|
29428
|
+
transport: "unknown",
|
|
29429
|
+
recoverable: false,
|
|
29430
|
+
retryRecommended: false,
|
|
29431
|
+
nextAction: NON_P2P_NEXT_ACTION,
|
|
29432
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
29433
|
+
};
|
|
29434
|
+
}
|
|
29435
|
+
let code = null;
|
|
29436
|
+
let reason = "";
|
|
29437
|
+
if (/timeout|timed out/i.test(message) && (hasP2pSignal || /mesh transport/i.test(message))) {
|
|
29438
|
+
code = "p2p_timeout";
|
|
29439
|
+
reason = "daemon_mesh_p2p_timeout";
|
|
29440
|
+
} else if (/no route|route unavailable/i.test(message)) {
|
|
29441
|
+
code = "p2p_no_route";
|
|
29442
|
+
reason = "daemon_mesh_p2p_no_route";
|
|
29443
|
+
} else if (/offline|not owned|not found|not connected to server/i.test(message) && /daemon|peer|target/i.test(message)) {
|
|
29444
|
+
code = "p2p_daemon_offline";
|
|
29445
|
+
reason = "daemon_mesh_target_offline";
|
|
29446
|
+
} else if (/closed|disconnected/i.test(message) && (hasP2pSignal || /state changed/i.test(message))) {
|
|
29447
|
+
code = "p2p_datachannel_closed";
|
|
29448
|
+
reason = "daemon_mesh_p2p_datachannel_closed";
|
|
29449
|
+
} else if (/not connected|cannot send|cannot establish/i.test(message) && hasP2pSignal) {
|
|
29450
|
+
code = "p2p_not_connected";
|
|
29451
|
+
reason = "daemon_mesh_p2p_not_connected";
|
|
29452
|
+
} else if (hasP2pSignal && hasFailureSignal) {
|
|
29453
|
+
code = "p2p_unavailable";
|
|
29454
|
+
reason = "daemon_mesh_p2p_transport_unavailable";
|
|
29455
|
+
}
|
|
29456
|
+
if (!code) {
|
|
29457
|
+
return {
|
|
29458
|
+
code: "mesh_logic_or_provider_failure",
|
|
29459
|
+
reason: "mesh_logic_or_provider_failure",
|
|
29460
|
+
transport: "unknown",
|
|
29461
|
+
recoverable: false,
|
|
29462
|
+
retryRecommended: false,
|
|
29463
|
+
nextAction: NON_P2P_NEXT_ACTION,
|
|
29464
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
29465
|
+
};
|
|
28631
29466
|
}
|
|
29467
|
+
return {
|
|
29468
|
+
code,
|
|
29469
|
+
reason,
|
|
29470
|
+
transport: "p2p",
|
|
29471
|
+
recoverable: true,
|
|
29472
|
+
retryRecommended: true,
|
|
29473
|
+
nextAction: P2P_NEXT_ACTION,
|
|
29474
|
+
noFallbackReason: NO_FALLBACK_REASON
|
|
29475
|
+
};
|
|
29476
|
+
}
|
|
29477
|
+
function isP2pRelayTransportFailure(error48) {
|
|
29478
|
+
return classifyP2pRelayFailure(error48).recoverable === true;
|
|
29479
|
+
}
|
|
29480
|
+
function buildP2pRelayFailurePayload(error48, context = {}) {
|
|
29481
|
+
const classification = classifyP2pRelayFailure(error48, context);
|
|
29482
|
+
return {
|
|
29483
|
+
success: false,
|
|
29484
|
+
...classification,
|
|
29485
|
+
error: messageFromError(error48),
|
|
29486
|
+
...context.command ? { command: context.command } : {},
|
|
29487
|
+
...context.targetDaemonId ? { targetDaemonId: context.targetDaemonId } : {}
|
|
29488
|
+
};
|
|
28632
29489
|
}
|
|
28633
29490
|
function isPlainObject22(value) {
|
|
28634
29491
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -28670,11 +29527,11 @@ function normalizeState(raw) {
|
|
|
28670
29527
|
}
|
|
28671
29528
|
function loadState() {
|
|
28672
29529
|
const statePath = getStatePath();
|
|
28673
|
-
if (!(0,
|
|
29530
|
+
if (!(0, import_fs10.existsSync)(statePath)) {
|
|
28674
29531
|
return { ...DEFAULT_STATE };
|
|
28675
29532
|
}
|
|
28676
29533
|
try {
|
|
28677
|
-
const raw = (0,
|
|
29534
|
+
const raw = (0, import_fs10.readFileSync)(statePath, "utf-8");
|
|
28678
29535
|
return normalizeState(JSON.parse(raw));
|
|
28679
29536
|
} catch {
|
|
28680
29537
|
return { ...DEFAULT_STATE };
|
|
@@ -28683,7 +29540,7 @@ function loadState() {
|
|
|
28683
29540
|
function saveState(state) {
|
|
28684
29541
|
const statePath = getStatePath();
|
|
28685
29542
|
const normalized = normalizeState(state);
|
|
28686
|
-
(0,
|
|
29543
|
+
(0, import_fs10.writeFileSync)(statePath, JSON.stringify(normalized, null, 2), { encoding: "utf-8", mode: 384 });
|
|
28687
29544
|
}
|
|
28688
29545
|
function resetState() {
|
|
28689
29546
|
saveState({ ...DEFAULT_STATE });
|
|
@@ -28704,13 +29561,13 @@ function getMergedDefinitions() {
|
|
|
28704
29561
|
function findCliCommand(command) {
|
|
28705
29562
|
const trimmed = String(command || "").trim();
|
|
28706
29563
|
if (!trimmed) return null;
|
|
28707
|
-
if (
|
|
28708
|
-
const candidate = trimmed.startsWith("~") ?
|
|
28709
|
-
const resolved =
|
|
28710
|
-
return (0,
|
|
29564
|
+
if (path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~")) {
|
|
29565
|
+
const candidate = trimmed.startsWith("~") ? path10.join((0, import_os3.homedir)(), trimmed.slice(1)) : trimmed;
|
|
29566
|
+
const resolved = path10.isAbsolute(candidate) ? candidate : path10.resolve(candidate);
|
|
29567
|
+
return (0, import_fs11.existsSync)(resolved) ? resolved : null;
|
|
28711
29568
|
}
|
|
28712
29569
|
try {
|
|
28713
|
-
const result = (0,
|
|
29570
|
+
const result = (0, import_child_process5.execSync)(
|
|
28714
29571
|
(0, import_os3.platform)() === "win32" ? `where ${trimmed}` : `which ${trimmed}`,
|
|
28715
29572
|
{ encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
|
|
28716
29573
|
).trim();
|
|
@@ -28721,7 +29578,7 @@ function findCliCommand(command) {
|
|
|
28721
29578
|
}
|
|
28722
29579
|
function getIdeVersion(cliCommand) {
|
|
28723
29580
|
try {
|
|
28724
|
-
const result = (0,
|
|
29581
|
+
const result = (0, import_child_process5.execSync)(`"${cliCommand}" --version`, {
|
|
28725
29582
|
encoding: "utf-8",
|
|
28726
29583
|
timeout: 1e4,
|
|
28727
29584
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -28734,13 +29591,13 @@ function getIdeVersion(cliCommand) {
|
|
|
28734
29591
|
function checkPathExists(paths) {
|
|
28735
29592
|
const home = (0, import_os3.homedir)();
|
|
28736
29593
|
for (const p of paths) {
|
|
28737
|
-
const normalized = p.startsWith("~") ?
|
|
29594
|
+
const normalized = p.startsWith("~") ? path10.join(home, p.slice(1)) : p;
|
|
28738
29595
|
if (normalized.includes("*")) {
|
|
28739
29596
|
const username = home.split(/[\\/]/).pop() || "";
|
|
28740
29597
|
const resolved = normalized.replace("*", username);
|
|
28741
|
-
if ((0,
|
|
29598
|
+
if ((0, import_fs11.existsSync)(resolved)) return resolved;
|
|
28742
29599
|
} else {
|
|
28743
|
-
if ((0,
|
|
29600
|
+
if ((0, import_fs11.existsSync)(normalized)) return normalized;
|
|
28744
29601
|
}
|
|
28745
29602
|
}
|
|
28746
29603
|
return null;
|
|
@@ -28754,7 +29611,7 @@ async function detectIDEs(providerLoader) {
|
|
|
28754
29611
|
let resolvedCli = cliPath;
|
|
28755
29612
|
if (!resolvedCli && appPath && os222 === "darwin") {
|
|
28756
29613
|
const bundledCli = `${appPath}/Contents/Resources/app/bin/${def.cli}`;
|
|
28757
|
-
if ((0,
|
|
29614
|
+
if ((0, import_fs11.existsSync)(bundledCli)) resolvedCli = bundledCli;
|
|
28758
29615
|
}
|
|
28759
29616
|
if (!resolvedCli && appPath && os222 === "win32") {
|
|
28760
29617
|
const { dirname: dirname92 } = await import("path");
|
|
@@ -28767,7 +29624,7 @@ async function detectIDEs(providerLoader) {
|
|
|
28767
29624
|
`${appDir}\\\\resources\\\\app\\\\bin\\\\${def.cli}.cmd`
|
|
28768
29625
|
];
|
|
28769
29626
|
for (const c of candidates) {
|
|
28770
|
-
if ((0,
|
|
29627
|
+
if ((0, import_fs11.existsSync)(c)) {
|
|
28771
29628
|
resolvedCli = c;
|
|
28772
29629
|
break;
|
|
28773
29630
|
}
|
|
@@ -28788,129 +29645,6 @@ async function detectIDEs(providerLoader) {
|
|
|
28788
29645
|
}
|
|
28789
29646
|
return results;
|
|
28790
29647
|
}
|
|
28791
|
-
function parseVersion(raw) {
|
|
28792
|
-
const match = raw.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?)/);
|
|
28793
|
-
return match ? match[1] : raw.split("\n")[0].slice(0, 100);
|
|
28794
|
-
}
|
|
28795
|
-
function shellQuote(value) {
|
|
28796
|
-
if (/^[a-zA-Z0-9_./:@%+=,-]+$/.test(value)) return value;
|
|
28797
|
-
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
28798
|
-
}
|
|
28799
|
-
function expandHome(value) {
|
|
28800
|
-
const trimmed = value.trim();
|
|
28801
|
-
if (!trimmed.startsWith("~")) return trimmed;
|
|
28802
|
-
return path10.join(os32.homedir(), trimmed.slice(1));
|
|
28803
|
-
}
|
|
28804
|
-
function isExplicitCommandPath(command) {
|
|
28805
|
-
const trimmed = command.trim();
|
|
28806
|
-
return path10.isAbsolute(trimmed) || trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
|
|
28807
|
-
}
|
|
28808
|
-
function resolveCommandPath(command) {
|
|
28809
|
-
const trimmed = command.trim();
|
|
28810
|
-
if (!trimmed) return null;
|
|
28811
|
-
if (isExplicitCommandPath(trimmed)) {
|
|
28812
|
-
const expanded = expandHome(trimmed);
|
|
28813
|
-
const candidate = path10.isAbsolute(expanded) ? expanded : path10.resolve(expanded);
|
|
28814
|
-
return (0, import_fs11.existsSync)(candidate) ? candidate : null;
|
|
28815
|
-
}
|
|
28816
|
-
return null;
|
|
28817
|
-
}
|
|
28818
|
-
function execAsync(cmd, timeoutMs = 5e3) {
|
|
28819
|
-
return new Promise((resolve162) => {
|
|
28820
|
-
const child = (0, import_child_process5.exec)(cmd, {
|
|
28821
|
-
encoding: "utf-8",
|
|
28822
|
-
timeout: timeoutMs,
|
|
28823
|
-
...process.platform === "win32" ? { windowsHide: true } : {}
|
|
28824
|
-
}, (err, stdout) => {
|
|
28825
|
-
if (err || !stdout?.trim()) {
|
|
28826
|
-
resolve162(null);
|
|
28827
|
-
} else {
|
|
28828
|
-
resolve162(stdout.trim());
|
|
28829
|
-
}
|
|
28830
|
-
});
|
|
28831
|
-
child.on("error", () => resolve162(null));
|
|
28832
|
-
});
|
|
28833
|
-
}
|
|
28834
|
-
async function detectCLIs(providerLoader, options) {
|
|
28835
|
-
const platform10 = os32.platform();
|
|
28836
|
-
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
28837
|
-
const includeVersion = options?.includeVersion !== false;
|
|
28838
|
-
const cliList = providerLoader ? providerLoader.getCliDetectionList() : [];
|
|
28839
|
-
const results = await Promise.all(
|
|
28840
|
-
cliList.map(async (cli) => {
|
|
28841
|
-
try {
|
|
28842
|
-
const explicitPath = resolveCommandPath(cli.command);
|
|
28843
|
-
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(cli.command)}`);
|
|
28844
|
-
if (!pathResult) return { ...cli, installed: false };
|
|
28845
|
-
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
28846
|
-
let version2;
|
|
28847
|
-
if (includeVersion) {
|
|
28848
|
-
const versionCommands = [
|
|
28849
|
-
`"${firstPath}" --version`,
|
|
28850
|
-
`"${firstPath}" -V`,
|
|
28851
|
-
`"${firstPath}" -v`,
|
|
28852
|
-
cli.versionCommand
|
|
28853
|
-
].filter((v) => !!v);
|
|
28854
|
-
try {
|
|
28855
|
-
for (const versionCommand of versionCommands) {
|
|
28856
|
-
const versionResult = await execAsync(versionCommand, 3e3);
|
|
28857
|
-
if (versionResult) {
|
|
28858
|
-
version2 = parseVersion(versionResult);
|
|
28859
|
-
break;
|
|
28860
|
-
}
|
|
28861
|
-
}
|
|
28862
|
-
} catch {
|
|
28863
|
-
}
|
|
28864
|
-
}
|
|
28865
|
-
return { ...cli, installed: true, version: version2, path: firstPath };
|
|
28866
|
-
} catch {
|
|
28867
|
-
return { ...cli, installed: false };
|
|
28868
|
-
}
|
|
28869
|
-
})
|
|
28870
|
-
);
|
|
28871
|
-
return results;
|
|
28872
|
-
}
|
|
28873
|
-
async function detectCLI(cliId, providerLoader, options) {
|
|
28874
|
-
const resolvedId = providerLoader ? providerLoader.resolveAlias(cliId) : cliId;
|
|
28875
|
-
if (providerLoader) {
|
|
28876
|
-
const cliList = providerLoader.getCliDetectionList();
|
|
28877
|
-
const target = cliList.find((c) => c.id === resolvedId);
|
|
28878
|
-
if (target) {
|
|
28879
|
-
const platform10 = os32.platform();
|
|
28880
|
-
const whichCmd = platform10 === "win32" ? "where" : "which";
|
|
28881
|
-
try {
|
|
28882
|
-
const explicitPath = resolveCommandPath(target.command);
|
|
28883
|
-
const pathResult = explicitPath || await execAsync(`${whichCmd} ${shellQuote(target.command)}`);
|
|
28884
|
-
if (!pathResult) return null;
|
|
28885
|
-
const firstPath = explicitPath || pathResult.split("\n")[0];
|
|
28886
|
-
let version2;
|
|
28887
|
-
if (options?.includeVersion !== false) {
|
|
28888
|
-
const versionCommands = [
|
|
28889
|
-
`"${firstPath}" --version`,
|
|
28890
|
-
`"${firstPath}" -V`,
|
|
28891
|
-
`"${firstPath}" -v`,
|
|
28892
|
-
target.versionCommand
|
|
28893
|
-
].filter((v) => !!v);
|
|
28894
|
-
try {
|
|
28895
|
-
for (const versionCommand of versionCommands) {
|
|
28896
|
-
const versionResult = await execAsync(versionCommand, 3e3);
|
|
28897
|
-
if (versionResult) {
|
|
28898
|
-
version2 = parseVersion(versionResult);
|
|
28899
|
-
break;
|
|
28900
|
-
}
|
|
28901
|
-
}
|
|
28902
|
-
} catch {
|
|
28903
|
-
}
|
|
28904
|
-
}
|
|
28905
|
-
return { ...target, installed: true, version: version2, path: firstPath };
|
|
28906
|
-
} catch {
|
|
28907
|
-
return null;
|
|
28908
|
-
}
|
|
28909
|
-
}
|
|
28910
|
-
}
|
|
28911
|
-
const all = await detectCLIs(providerLoader, options);
|
|
28912
|
-
return all.find((c) => c.id === resolvedId && c.installed) || null;
|
|
28913
|
-
}
|
|
28914
29648
|
function parseDarwinAvailableBytes(totalMem) {
|
|
28915
29649
|
if (os42.platform() !== "darwin") return null;
|
|
28916
29650
|
try {
|
|
@@ -33701,11 +34435,13 @@ async function handleOpenPanel(h, args) {
|
|
|
33701
34435
|
async function handlePtyInput(h, args) {
|
|
33702
34436
|
const { cliType, data, targetSessionId } = args || {};
|
|
33703
34437
|
if (!data) return { success: false, error: "data required" };
|
|
34438
|
+
const cleanData = typeof data === "string" ? data.replace(/\x1b\[[?>][0-9;]*c/g, "") : data;
|
|
34439
|
+
if (!cleanData) return { success: true };
|
|
33704
34440
|
const adapter = h.getCliAdapter(targetSessionId || cliType);
|
|
33705
34441
|
if (!adapter || typeof adapter.writeRaw !== "function") {
|
|
33706
34442
|
return { success: false, error: `CLI adapter not found: ${targetSessionId || cliType || "unknown"}` };
|
|
33707
34443
|
}
|
|
33708
|
-
await adapter.writeRaw(
|
|
34444
|
+
await adapter.writeRaw(cleanData);
|
|
33709
34445
|
return { success: true };
|
|
33710
34446
|
}
|
|
33711
34447
|
function handlePtyResize(_h, args) {
|
|
@@ -34245,6 +34981,13 @@ function cleanupStaleMaterializedImages(dir) {
|
|
|
34245
34981
|
} catch {
|
|
34246
34982
|
}
|
|
34247
34983
|
}
|
|
34984
|
+
function hasNonEmptyCliModalButtons(activeModal) {
|
|
34985
|
+
const buttons = activeModal?.buttons;
|
|
34986
|
+
return Array.isArray(buttons) && buttons.some((button) => String(button || "").trim().length > 0);
|
|
34987
|
+
}
|
|
34988
|
+
function isCliGeneratingLikeStatus(status) {
|
|
34989
|
+
return status === "generating" || status === "streaming" || status === "long_generating" || status === "starting";
|
|
34990
|
+
}
|
|
34248
34991
|
function buildCliStructuredInputPrompt(input, options = {}) {
|
|
34249
34992
|
const promptParts = [];
|
|
34250
34993
|
const imageRefs = [];
|
|
@@ -34532,9 +35275,6 @@ function buildCoordinatorDelegatedCliLaunchOptions(input) {
|
|
|
34532
35275
|
const cliType = String(input.cliType || "").trim();
|
|
34533
35276
|
const cliArgs = Array.isArray(input.cliArgs) ? [...input.cliArgs] : [];
|
|
34534
35277
|
const env2 = { ...input.env || {}, ...COORDINATOR_DELEGATED_ENV_UNSETS };
|
|
34535
|
-
if (cliType === "hermes-cli" && !hasCliArg(cliArgs, "--ignore-user-config")) {
|
|
34536
|
-
cliArgs.unshift("--ignore-user-config");
|
|
34537
|
-
}
|
|
34538
35278
|
if (cliType === "claude-cli" && !hasCliArg(cliArgs, "--mcp-config")) {
|
|
34539
35279
|
cliArgs.unshift("--mcp-config", ensureEmptyDelegatedMcpConfig(input.workspace));
|
|
34540
35280
|
}
|
|
@@ -35478,7 +36218,9 @@ function resolveHermesMeshCoordinatorSetup(options) {
|
|
|
35478
36218
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
35479
36219
|
meshId: options.meshId,
|
|
35480
36220
|
nodeExecutable: options.nodeExecutable,
|
|
35481
|
-
adhdevMcpEntryPath: options.adhdevMcpEntryPath
|
|
36221
|
+
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
36222
|
+
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
36223
|
+
adhdevMcpPort: options.adhdevMcpPort
|
|
35482
36224
|
});
|
|
35483
36225
|
if (!mcpServer) {
|
|
35484
36226
|
return {
|
|
@@ -35545,7 +36287,9 @@ function resolveMeshCoordinatorSetup(options) {
|
|
|
35545
36287
|
const mcpServer = resolveAdhdevMcpServerLaunch({
|
|
35546
36288
|
meshId,
|
|
35547
36289
|
nodeExecutable: options.nodeExecutable,
|
|
35548
|
-
adhdevMcpEntryPath: options.adhdevMcpEntryPath
|
|
36290
|
+
adhdevMcpEntryPath: options.adhdevMcpEntryPath,
|
|
36291
|
+
adhdevMcpTransport: options.adhdevMcpTransport,
|
|
36292
|
+
adhdevMcpPort: options.adhdevMcpPort
|
|
35549
36293
|
});
|
|
35550
36294
|
if (!mcpServer) {
|
|
35551
36295
|
return {
|
|
@@ -35567,6 +36311,22 @@ function resolveMeshCoordinatorSetup(options) {
|
|
|
35567
36311
|
if (!instructions || !template?.trim()) {
|
|
35568
36312
|
return { kind: "unsupported", reason: "Provider manual MCP setup is missing instructions or template" };
|
|
35569
36313
|
}
|
|
36314
|
+
const renderedTemplate = renderMeshCoordinatorTemplate(template, {
|
|
36315
|
+
meshId,
|
|
36316
|
+
workspace,
|
|
36317
|
+
serverName,
|
|
36318
|
+
adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
|
|
36319
|
+
});
|
|
36320
|
+
const isCliCommand = !renderedTemplate.trim().includes("\n") && !renderedTemplate.trim().startsWith("{");
|
|
36321
|
+
if (isCliCommand) {
|
|
36322
|
+
return {
|
|
36323
|
+
kind: "cli_command",
|
|
36324
|
+
serverName,
|
|
36325
|
+
command: renderedTemplate.trim(),
|
|
36326
|
+
requiresRestart: mcpConfig.requiresRestart === true,
|
|
36327
|
+
instructions
|
|
36328
|
+
};
|
|
36329
|
+
}
|
|
35570
36330
|
return {
|
|
35571
36331
|
kind: "manual",
|
|
35572
36332
|
serverName,
|
|
@@ -35574,12 +36334,7 @@ function resolveMeshCoordinatorSetup(options) {
|
|
|
35574
36334
|
configPathCommand: mcpConfig.configPathCommand,
|
|
35575
36335
|
requiresRestart: mcpConfig.requiresRestart === true,
|
|
35576
36336
|
instructions,
|
|
35577
|
-
template:
|
|
35578
|
-
meshId,
|
|
35579
|
-
workspace,
|
|
35580
|
-
serverName,
|
|
35581
|
-
adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
|
|
35582
|
-
})
|
|
36337
|
+
template: renderedTemplate
|
|
35583
36338
|
};
|
|
35584
36339
|
}
|
|
35585
36340
|
return {
|
|
@@ -35608,11 +36363,27 @@ function resolveAdhdevMcpServerLaunch(options) {
|
|
|
35608
36363
|
if (!entryPath) return null;
|
|
35609
36364
|
const nodeExecutable = resolveMcpNodeExecutable(options.nodeExecutable);
|
|
35610
36365
|
if (!nodeExecutable) return null;
|
|
36366
|
+
const transport = resolveMcpTransport(options.adhdevMcpTransport);
|
|
36367
|
+
const args = [entryPath, "--mode", transport, "--repo-mesh", options.meshId];
|
|
36368
|
+
const port = resolveMcpPort(options.adhdevMcpPort);
|
|
36369
|
+
if (port !== void 0) args.push("--port", String(port));
|
|
35611
36370
|
return {
|
|
35612
36371
|
command: nodeExecutable,
|
|
35613
|
-
args
|
|
36372
|
+
args
|
|
35614
36373
|
};
|
|
35615
36374
|
}
|
|
36375
|
+
function resolveMcpTransport(explicitTransport) {
|
|
36376
|
+
if (explicitTransport === "local" || explicitTransport === "ipc") return explicitTransport;
|
|
36377
|
+
const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim();
|
|
36378
|
+
return envTransport === "local" ? "local" : "ipc";
|
|
36379
|
+
}
|
|
36380
|
+
function resolveMcpPort(explicitPort) {
|
|
36381
|
+
if (typeof explicitPort === "number" && Number.isInteger(explicitPort) && explicitPort > 0) return explicitPort;
|
|
36382
|
+
const raw = process.env.ADHDEV_COORDINATOR_MCP_PORT?.trim();
|
|
36383
|
+
if (!raw) return void 0;
|
|
36384
|
+
const parsed = Number(raw);
|
|
36385
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
36386
|
+
}
|
|
35616
36387
|
function resolveMcpNodeExecutable(explicitExecutable) {
|
|
35617
36388
|
const explicit = explicitExecutable?.trim();
|
|
35618
36389
|
if (explicit) return explicit;
|
|
@@ -36395,6 +37166,204 @@ async function resolveProviderTypeFromPriority(args) {
|
|
|
36395
37166
|
}
|
|
36396
37167
|
return { error: `No usable provider detected for node '${args.nodeId}' from providerPriority: ${failed.join("; ")}` };
|
|
36397
37168
|
}
|
|
37169
|
+
function truncateValidationOutput(value) {
|
|
37170
|
+
const text = typeof value === "string" ? value : value == null ? "" : String(value);
|
|
37171
|
+
if (text.length <= REFINE_VALIDATION_SUMMARY_CHARS) return text;
|
|
37172
|
+
return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}
|
|
37173
|
+
[truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
|
|
37174
|
+
}
|
|
37175
|
+
function readPackageScripts(workspace) {
|
|
37176
|
+
try {
|
|
37177
|
+
const packageJsonPath = (0, import_path7.join)(workspace, "package.json");
|
|
37178
|
+
const parsed = JSON.parse(fs10.readFileSync(packageJsonPath, "utf-8"));
|
|
37179
|
+
return parsed?.scripts && typeof parsed.scripts === "object" && !Array.isArray(parsed.scripts) ? parsed.scripts : {};
|
|
37180
|
+
} catch {
|
|
37181
|
+
return {};
|
|
37182
|
+
}
|
|
37183
|
+
}
|
|
37184
|
+
function tokenizeValidationCommand(command) {
|
|
37185
|
+
const trimmed = command.trim();
|
|
37186
|
+
if (!trimmed) return null;
|
|
37187
|
+
if (/[;&|<>`$\\\n\r'\"]/.test(trimmed)) return null;
|
|
37188
|
+
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
37189
|
+
if (!tokens.length) return null;
|
|
37190
|
+
if (tokens.some((token) => !/^[A-Za-z0-9_@./:=+-]+$/.test(token))) return null;
|
|
37191
|
+
return tokens;
|
|
37192
|
+
}
|
|
37193
|
+
function scriptMatchesValidationCategory(scriptName, category) {
|
|
37194
|
+
return scriptName === category || scriptName.startsWith(`${category}:`);
|
|
37195
|
+
}
|
|
37196
|
+
function parsePackageManagerValidationCommand(rawCommand, category, scripts, source) {
|
|
37197
|
+
const tokens = tokenizeValidationCommand(rawCommand);
|
|
37198
|
+
if (!tokens) {
|
|
37199
|
+
return { rejected: { command: rawCommand, category, source, reason: "unsafe command string is not allowlisted" } };
|
|
37200
|
+
}
|
|
37201
|
+
const [binary2, second, third, ...rest] = tokens;
|
|
37202
|
+
let scriptName = "";
|
|
37203
|
+
let command = binary2;
|
|
37204
|
+
let args = [];
|
|
37205
|
+
if ((binary2 === "npm" || binary2 === "pnpm" || binary2 === "bun") && second === "run" && third) {
|
|
37206
|
+
scriptName = third;
|
|
37207
|
+
args = ["run", scriptName, ...rest];
|
|
37208
|
+
} else if (binary2 === "npm" && second === "test" && !third) {
|
|
37209
|
+
scriptName = "test";
|
|
37210
|
+
args = ["test"];
|
|
37211
|
+
} else if (binary2 === "yarn" && second === "run" && third) {
|
|
37212
|
+
scriptName = third;
|
|
37213
|
+
args = ["run", scriptName, ...rest];
|
|
37214
|
+
} else if (binary2 === "yarn" && second && !third) {
|
|
37215
|
+
scriptName = second;
|
|
37216
|
+
args = [scriptName];
|
|
37217
|
+
} else {
|
|
37218
|
+
return { rejected: { command: rawCommand, category, source, reason: "command is not a supported package-manager script invocation" } };
|
|
37219
|
+
}
|
|
37220
|
+
if (!scriptName || !Object.prototype.hasOwnProperty.call(scripts, scriptName)) {
|
|
37221
|
+
return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script is not declared in package.json" } };
|
|
37222
|
+
}
|
|
37223
|
+
if (!scriptMatchesValidationCategory(scriptName, category)) {
|
|
37224
|
+
return { rejected: { command: rawCommand, category, source, script: scriptName, reason: "script name is outside the validation category allowlist" } };
|
|
37225
|
+
}
|
|
37226
|
+
return {
|
|
37227
|
+
command: {
|
|
37228
|
+
command,
|
|
37229
|
+
args,
|
|
37230
|
+
displayCommand: [command, ...args].join(" "),
|
|
37231
|
+
category,
|
|
37232
|
+
source
|
|
37233
|
+
}
|
|
37234
|
+
};
|
|
37235
|
+
}
|
|
37236
|
+
function collectProjectContextValidationCandidates(mesh) {
|
|
37237
|
+
const commands = mesh?.projectContext?.commands;
|
|
37238
|
+
if (!commands || typeof commands !== "object" || Array.isArray(commands)) return [];
|
|
37239
|
+
const candidates = [];
|
|
37240
|
+
for (const category of REFINE_VALIDATION_CATEGORIES) {
|
|
37241
|
+
const entries = Array.isArray(commands[category]) ? commands[category] : [];
|
|
37242
|
+
for (const entry of entries) {
|
|
37243
|
+
if (typeof entry?.command !== "string") continue;
|
|
37244
|
+
candidates.push({
|
|
37245
|
+
command: entry.command,
|
|
37246
|
+
category,
|
|
37247
|
+
source: typeof entry.sourcePath === "string" ? entry.sourcePath : "projectContext.commands",
|
|
37248
|
+
confidence: typeof entry.confidence === "string" ? entry.confidence : void 0
|
|
37249
|
+
});
|
|
37250
|
+
}
|
|
37251
|
+
}
|
|
37252
|
+
return candidates.sort((a, b) => {
|
|
37253
|
+
const rank = (value) => value === "high" ? 0 : value === "medium" ? 1 : 2;
|
|
37254
|
+
return rank(a.confidence) - rank(b.confidence);
|
|
37255
|
+
});
|
|
37256
|
+
}
|
|
37257
|
+
function collectPolicyValidationCandidates(mesh) {
|
|
37258
|
+
const policy = mesh?.policy && typeof mesh.policy === "object" && !Array.isArray(mesh.policy) ? mesh.policy : {};
|
|
37259
|
+
const configured = Array.isArray(policy.validationCommands) ? policy.validationCommands : Array.isArray(policy.validationGate?.commands) ? policy.validationGate.commands : [];
|
|
37260
|
+
return configured.map((entry) => typeof entry === "string" ? { command: entry, category: "", source: "mesh.policy.validationCommands" } : entry).filter((entry) => entry && typeof entry.command === "string").map((entry) => {
|
|
37261
|
+
const commandText = entry.command.trim();
|
|
37262
|
+
const category = REFINE_VALIDATION_CATEGORIES.find((cat) => commandText.includes(` ${cat}`)) ?? "";
|
|
37263
|
+
return { command: commandText, category, source: "mesh.policy.validationCommands" };
|
|
37264
|
+
}).filter((entry) => !!entry.category);
|
|
37265
|
+
}
|
|
37266
|
+
function selectMeshRefineValidationCommands(mesh, workspace) {
|
|
37267
|
+
const scripts = readPackageScripts(workspace);
|
|
37268
|
+
const rejectedCommands = [];
|
|
37269
|
+
const selected = [];
|
|
37270
|
+
const seen = /* @__PURE__ */ new Set();
|
|
37271
|
+
const candidates = [
|
|
37272
|
+
...collectPolicyValidationCandidates(mesh),
|
|
37273
|
+
...collectProjectContextValidationCandidates(mesh)
|
|
37274
|
+
];
|
|
37275
|
+
for (const candidate of candidates) {
|
|
37276
|
+
const parsed = parsePackageManagerValidationCommand(candidate.command, candidate.category, scripts, candidate.source);
|
|
37277
|
+
if (parsed.rejected) {
|
|
37278
|
+
rejectedCommands.push(parsed.rejected);
|
|
37279
|
+
continue;
|
|
37280
|
+
}
|
|
37281
|
+
if (!parsed.command || seen.has(parsed.command.displayCommand)) continue;
|
|
37282
|
+
selected.push(parsed.command);
|
|
37283
|
+
seen.add(parsed.command.displayCommand);
|
|
37284
|
+
if (selected.length >= REFINE_VALIDATION_MAX_COMMANDS) break;
|
|
37285
|
+
}
|
|
37286
|
+
if (!selected.length && candidates.length === 0) {
|
|
37287
|
+
for (const category of REFINE_VALIDATION_CATEGORIES) {
|
|
37288
|
+
if (!Object.prototype.hasOwnProperty.call(scripts, category)) continue;
|
|
37289
|
+
const fallback = parsePackageManagerValidationCommand(`npm run ${category}`, category, scripts, "package.json:scripts");
|
|
37290
|
+
if (fallback.command && !seen.has(fallback.command.displayCommand)) {
|
|
37291
|
+
selected.push(fallback.command);
|
|
37292
|
+
seen.add(fallback.command.displayCommand);
|
|
37293
|
+
} else if (fallback.rejected) {
|
|
37294
|
+
rejectedCommands.push(fallback.rejected);
|
|
37295
|
+
}
|
|
37296
|
+
if (selected.length >= 2) break;
|
|
37297
|
+
}
|
|
37298
|
+
}
|
|
37299
|
+
return {
|
|
37300
|
+
commands: selected,
|
|
37301
|
+
rejectedCommands,
|
|
37302
|
+
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"
|
|
37303
|
+
};
|
|
37304
|
+
}
|
|
37305
|
+
async function runMeshRefineValidationGate(mesh, workspace) {
|
|
37306
|
+
const { execFile: execFile3 } = await import("child_process");
|
|
37307
|
+
const { promisify: promisify3 } = await import("util");
|
|
37308
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
37309
|
+
const selection = selectMeshRefineValidationCommands(mesh, workspace);
|
|
37310
|
+
const summary = {
|
|
37311
|
+
status: "skipped",
|
|
37312
|
+
required: true,
|
|
37313
|
+
commandsRun: [],
|
|
37314
|
+
rejectedCommands: selection.rejectedCommands,
|
|
37315
|
+
skippedReason: void 0,
|
|
37316
|
+
timeoutMs: REFINE_VALIDATION_TIMEOUT_MS,
|
|
37317
|
+
outputLimitBytes: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES
|
|
37318
|
+
};
|
|
37319
|
+
if (!selection.commands.length) {
|
|
37320
|
+
summary.skippedReason = "validation_unavailable: no allowlisted projectContext, mesh policy, or package.json build/test/typecheck/lint command was available";
|
|
37321
|
+
return summary;
|
|
37322
|
+
}
|
|
37323
|
+
for (const candidate of selection.commands) {
|
|
37324
|
+
const startedAt = Date.now();
|
|
37325
|
+
try {
|
|
37326
|
+
const result = await execFileAsync3(candidate.command, candidate.args, {
|
|
37327
|
+
cwd: workspace,
|
|
37328
|
+
encoding: "utf8",
|
|
37329
|
+
timeout: REFINE_VALIDATION_TIMEOUT_MS,
|
|
37330
|
+
maxBuffer: REFINE_VALIDATION_OUTPUT_LIMIT_BYTES,
|
|
37331
|
+
env: { ...process.env, CI: process.env.CI || "1" }
|
|
37332
|
+
});
|
|
37333
|
+
summary.commandsRun.push({
|
|
37334
|
+
command: candidate.command,
|
|
37335
|
+
args: candidate.args,
|
|
37336
|
+
displayCommand: candidate.displayCommand,
|
|
37337
|
+
category: candidate.category,
|
|
37338
|
+
source: candidate.source,
|
|
37339
|
+
passed: true,
|
|
37340
|
+
exitCode: 0,
|
|
37341
|
+
durationMs: Date.now() - startedAt,
|
|
37342
|
+
stdout: truncateValidationOutput(result.stdout),
|
|
37343
|
+
stderr: truncateValidationOutput(result.stderr)
|
|
37344
|
+
});
|
|
37345
|
+
} catch (error48) {
|
|
37346
|
+
summary.commandsRun.push({
|
|
37347
|
+
command: candidate.command,
|
|
37348
|
+
args: candidate.args,
|
|
37349
|
+
displayCommand: candidate.displayCommand,
|
|
37350
|
+
category: candidate.category,
|
|
37351
|
+
source: candidate.source,
|
|
37352
|
+
passed: false,
|
|
37353
|
+
exitCode: typeof error48?.code === "number" ? error48.code : null,
|
|
37354
|
+
signal: typeof error48?.signal === "string" ? error48.signal : null,
|
|
37355
|
+
timedOut: error48?.killed === true || /timed out/i.test(String(error48?.message || "")),
|
|
37356
|
+
durationMs: Date.now() - startedAt,
|
|
37357
|
+
stdout: truncateValidationOutput(error48?.stdout),
|
|
37358
|
+
stderr: truncateValidationOutput(error48?.stderr || error48?.message)
|
|
37359
|
+
});
|
|
37360
|
+
summary.status = "failed";
|
|
37361
|
+
return summary;
|
|
37362
|
+
}
|
|
37363
|
+
}
|
|
37364
|
+
summary.status = "passed";
|
|
37365
|
+
return summary;
|
|
37366
|
+
}
|
|
36398
37367
|
function loadYamlModule() {
|
|
36399
37368
|
return js_yaml_exports;
|
|
36400
37369
|
}
|
|
@@ -36424,6 +37393,34 @@ function loadHermesCoordinatorBaseConfig(targetConfigPath) {
|
|
|
36424
37393
|
const { mcp_servers: _mcpServers, ...baseConfig } = parsed;
|
|
36425
37394
|
return { config: baseConfig, sourceHome, sourceConfigPath };
|
|
36426
37395
|
}
|
|
37396
|
+
function stripHermesCoordinatorTempModelProviderOverrides(config2) {
|
|
37397
|
+
const {
|
|
37398
|
+
model: _model,
|
|
37399
|
+
provider: _provider,
|
|
37400
|
+
default_model: _defaultModel,
|
|
37401
|
+
defaultProvider: _defaultProvider,
|
|
37402
|
+
default_provider: _defaultProviderSnake,
|
|
37403
|
+
modelProvider: _modelProvider,
|
|
37404
|
+
model_provider: _modelProviderSnake,
|
|
37405
|
+
...sanitized
|
|
37406
|
+
} = config2;
|
|
37407
|
+
const delegation = sanitized.delegation;
|
|
37408
|
+
if (delegation && typeof delegation === "object" && !Array.isArray(delegation)) {
|
|
37409
|
+
const {
|
|
37410
|
+
model: _delegationModel,
|
|
37411
|
+
provider: _delegationProvider,
|
|
37412
|
+
modelProvider: _delegationModelProvider,
|
|
37413
|
+
model_provider: _delegationModelProviderSnake,
|
|
37414
|
+
...delegationRest
|
|
37415
|
+
} = delegation;
|
|
37416
|
+
if (Object.keys(delegationRest).length > 0) {
|
|
37417
|
+
sanitized.delegation = delegationRest;
|
|
37418
|
+
} else {
|
|
37419
|
+
delete sanitized.delegation;
|
|
37420
|
+
}
|
|
37421
|
+
}
|
|
37422
|
+
return sanitized;
|
|
37423
|
+
}
|
|
36427
37424
|
function copyHermesCoordinatorCredentialFiles(sourceHome, targetHome) {
|
|
36428
37425
|
if ((0, import_path7.resolve)(sourceHome) === (0, import_path7.resolve)(targetHome)) return;
|
|
36429
37426
|
for (const fileName of [".env", "auth.json"]) {
|
|
@@ -40855,7 +41852,8 @@ async function initDaemonComponents(config2) {
|
|
|
40855
41852
|
cdpManagers,
|
|
40856
41853
|
sessionRegistry,
|
|
40857
41854
|
detectedIdes: detectedIdesRef,
|
|
40858
|
-
refreshProviderAvailability
|
|
41855
|
+
refreshProviderAvailability,
|
|
41856
|
+
dispatchMeshCommand: config2.dispatchMeshCommand
|
|
40859
41857
|
};
|
|
40860
41858
|
setupMeshEventForwarding(components);
|
|
40861
41859
|
return components;
|
|
@@ -40913,7 +41911,7 @@ async function shutdownDaemonComponents(components) {
|
|
|
40913
41911
|
}
|
|
40914
41912
|
cdpManagers.clear();
|
|
40915
41913
|
}
|
|
40916
|
-
var path4, import_promises4, import_fs3, import_child_process, import_util3, import_os2, import_path, import_fs4, import_crypto2, import_fs5, import_path2, import_crypto3, import_fs6, import_path3, import_crypto4, import_events2, import_fs7, import_path4, import_crypto5,
|
|
41914
|
+
var path4, import_promises4, import_fs3, import_child_process, import_util3, import_os2, import_path, import_fs4, import_crypto2, import_fs5, import_path2, import_crypto3, import_fs6, import_path3, import_crypto4, import_events2, import_fs7, import_path4, import_crypto5, import_child_process2, os22, path8, import_fs8, fs2, path9, os32, os8, os9, path14, import_child_process3, os10, path15, os11, import_child_process4, import_fs9, import_promises5, path, import_util4, import_promises6, path22, path32, fs, os4, path5, import_crypto6, path6, path7, import_fs10, import_path5, import_child_process5, import_fs11, import_os3, path10, os42, import_child_process6, http, crypto2, fs3, path11, os5, fs4, os6, path12, import_crypto7, fs5, path13, os7, os13, path18, crypto4, import_fs12, import_child_process7, os12, path16, crypto3, fs6, import_module, path17, import_stream2, import_child_process8, import_child_process9, net2, os15, path20, fs7, path19, os14, fs8, path21, os16, import_child_process10, import_crypto8, import_fs13, import_module2, os17, import_path6, os18, import_child_process11, import_child_process12, fs9, os19, path222, import_os4, import_path7, fs10, fs11, path23, os20, import_child_process13, import_os5, http2, fs15, path27, fs12, path24, fs13, path25, fs14, path26, os21, import_child_process14, __defProp2, __getOwnPropDesc2, __getOwnPropNames2, __hasOwnProp2, __require2, __esm2, __export2, __copyProps2, __toCommonJS2, DEFAULT_MESH_POLICY, init_repo_mesh_types, git_worktree_exports, execFileAsync2, WORKTREE_DIR_NAME, GIT_TIMEOUT_MS, GIT_MAX_BUFFER, SUBMODULE_WORKTREE_REMOVE_RE, init_git_worktree, config_exports, DEFAULT_CONFIG, MACHINE_ID_PREFIX, init_config, mesh_config_exports, SESSION_CLEANUP_MODES, SPAWNED_SESSION_VISIBILITY_MODES, init_mesh_config, coordinator_prompt_exports, TOOLS_SECTION, TOOL_EXPOSURE_PREFLIGHT_SECTION, WORKFLOW_SECTION, init_coordinator_prompt, mesh_ledger_exports, LEDGER_DIR_NAME, MAX_FILE_SIZE_BYTES, RECENT_FAILURE_WINDOW_MS, DEFAULT_LEDGER_SLICE_LIMIT, MAX_LEDGER_SLICE_LIMIT, meshLedgerEvents, init_mesh_ledger, mesh_work_queue_exports, ACTIVE_MESH_QUEUE_STATUSES, HISTORICAL_MESH_QUEUE_STATUSES, init_mesh_work_queue, init_cli_detector, 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, init_logger, mesh_events_exports, remoteIdleSessions, MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS, init_mesh_events, NORMAL_TRACE_BUFFER_SIZE, DEV_TRACE_BUFFER_SIZE, DEFAULT_CONFIG2, currentConfig, init_debug_config, DEFAULT_BINDING_CANDIDATES, cachedBinding, cachedBindingError, GhosttyVtTerminalBackend, init_ghostty_vt_backend, TerminalCtor, XtermTerminalBackend, init_xterm_backend, DEFAULT_SCROLLBACK, loggedTerminalBackends, TerminalScreen, init_terminal_screen, init_spawn_env, cachedPty, NodePtyRuntimeTransport, NodePtyTransportFactory, init_pty_transport, TerminalTranscriptAccumulator, buildCliSpawnEnv, init_provider_cli_shared, init_provider_cli_parse, init_provider_cli_config, init_provider_cli_runtime, provider_cli_adapter_exports, ProviderCliAdapter, init_provider_cli_adapter, execFileAsync, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_BUFFER, GitCommandError, DEFAULT_MAX_FILES, DEFAULT_MAX_BYTES, summarizeGitStatus, InMemoryGitSnapshotStore, DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS, MIN_GIT_WORKSPACE_POLL_INTERVAL_MS, GitWorkspaceMonitor, GIT_COMMAND_NAMES, SNAPSHOT_REASONS, FAILURE_REASONS, defaultSnapshotStore, defaultGitCommandServices, BUSY_STATUSES, TERMINAL_STATUSES, TurnSnapshotTracker, MAX_WORKSPACES, MAX_ACTIVITY, MAX_SAVED_SESSIONS, NO_FALLBACK_REASON, P2P_NEXT_ACTION, NON_P2P_NEXT_ACTION, P2pRelayFailureError, DEFAULT_STATE, BUILTIN_IDE_DEFINITIONS, registeredIDEs, LIVE_LIFECYCLES, DEFAULT_ACTIVE_CHAT_POLL_STATUSES, DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS, LIVE_RUNTIME_LIFECYCLES, DaemonCdpManager, CdpDomHandlers, DEFAULT_MONITOR_CONFIG, StatusMonitor, BUILTIN_CHAT_MESSAGE_KINDS, CHAT_MESSAGE_VISIBILITIES, CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES, CHAT_MESSAGE_AUDIENCES, CHAT_MESSAGE_SOURCES, CHAT_MESSAGE_ACTIVITY_SOURCES, CHAT_MESSAGE_INTERNAL_SOURCES, KNOWN_CHAT_MESSAGE_KINDS, CHAT_MESSAGE_KIND_ALIASES, EXPLICIT_HIDDEN_VISIBILITIES, EXPLICIT_VISIBLE_VISIBILITIES, HIDDEN_AUDIENCES, ACTIVITY_SOURCE_SET, INTERNAL_SOURCE_SET, HISTORY_DIR, RETAIN_DAYS, SAVED_HISTORY_INDEX_VERSION, SAVED_HISTORY_INDEX_FILE, SAVED_HISTORY_INDEX_LOCK_SUFFIX, SAVED_HISTORY_INDEX_LOCK_WAIT_MS, SAVED_HISTORY_INDEX_LOCK_STALE_MS, SAVED_HISTORY_INDEX_LOCK_POLL_MS, SAVED_HISTORY_ROLLUP_THRESHOLD_BYTES, savedHistorySessionCache, savedHistoryFileSummaryCache, savedHistoryBackgroundRefresh, savedHistoryRollupInFlight, ChatHistoryWriter, IDE_PROVIDER_SESSION_CAPABILITIES_BASE, EXTENSION_PROVIDER_SESSION_CAPABILITIES_BASE, ExtensionProviderInstance, VALID_STATUSES, VALID_ROLES, VALID_BUBBLE_STATES, VALID_TURN_STATUSES, DEFAULT_APPROVAL_POSITIVE_HINTS, IdeProviderInstance, DEFAULT_CDP_SCAN_INTERVAL_MS, DEFAULT_CDP_DISCOVERY_INTERVAL_MS, DEFAULT_STATUS_INITIAL_REPORT_DELAY_MS, DEFAULT_STATUS_SERVER_REPORT_INTERVAL_MS, DEFAULT_STATUS_P2P_REPORT_INTERVAL_MS, MIN_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS, DEFAULT_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS, MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS, DEFAULT_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS, DEFAULT_SESSION_HOST_READY_TIMEOUT_MS, STANDALONE_CDP_SCAN_INTERVAL_MS, DaemonCdpScanner, DaemonCdpInitializer, WORKING_STATUSES, FULL_STATUS_ACTIVE_CHAT_OPTIONS, LIVE_STATUS_ACTIVE_CHAT_OPTIONS, STATUS_MODAL_MESSAGE_LIMIT, STATUS_MODAL_BUTTON_LIMIT, VALID_INPUT_MEDIA_TYPES, VALID_INPUT_STRATEGIES, TEXT_ONLY_MESSAGE_INPUT_SUPPORT, IDE_SESSION_CAPABILITIES, EXTENSION_SESSION_CAPABILITIES, PTY_SESSION_CAPABILITIES, CLI_CHAT_SESSION_CAPABILITIES, ACP_SESSION_CAPABILITIES, globalStore, RECENT_SEND_WINDOW_MS, READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS, HERMES_CLI_STARTING_SEND_SETTLE_MS, recentSendByTarget, DEFAULT_DEBUG_SANITIZE_OPTIONS, SECRET_KEY_PATTERN, KEY_TO_VK, COMMAND_DEBUG_LEVELS, DaemonCommandHandler, COMPLETED_FINALIZATION_RETRY_MS, COMPLETED_FINALIZATION_MAX_WAIT_MS, IMAGE_MIME_EXTENSIONS, MATERIALIZED_IMAGE_MAX_AGE_MS, MATERIALIZED_IMAGE_CLEANUP_INTERVAL_MS, lastMaterializedImageCleanupAt, CachedDatabaseSync, CliProviderInstance, AcpProviderInstance, chalkModule, chalkApi, COORDINATOR_DELEGATED_ENV_UNSETS, DaemonCliManager, VALID_CAPABILITY_MEDIA_TYPES, VALID_INPUT_STRATEGIES2, KNOWN_PROVIDER_FIELDS, VALUE_CONTROL_TYPES, ProviderLoader, _providerLoader, LOG_DIR2, MAX_FILE_SIZE, MAX_DAYS, SENSITIVE_KEYS, currentDate2, currentFile, writeCount2, SKIP_COMMANDS, DEFAULT_SERVER_NAME, DEFAULT_ADHDEV_MCP_COMMAND, HERMES_CLI_TYPE, HERMES_MCP_CONFIG_PATH, READ_DEBUG_ENABLED, recentReadDebugSignatureBySession, UPGRADE_HELPER_ENV, CHANNEL_NPM_TAG, CHANNEL_SERVER_URL, REFINE_VALIDATION_CATEGORIES, REFINE_VALIDATION_TIMEOUT_MS, REFINE_VALIDATION_OUTPUT_LIMIT_BYTES, REFINE_VALIDATION_SUMMARY_CHARS, REFINE_VALIDATION_MAX_COMMANDS, CHAT_COMMANDS, READ_DEBUG_ENABLED2, DaemonCommandRouter, DaemonStatusReporter, DEFAULT_DAEMON_PORT, DAEMON_WS_PATH, ProviderStreamAdapter, DaemonAgentStreamManager, AgentStreamPoller, ProviderInstanceManager, ARCHIVE_PATH, MAX_ENTRIES_PER_PROVIDER, VersionArchive, DEV_SERVER_PORT, DevServer, SessionHostRuntimeTransport, SessionHostPtyTransportFactory, DEFAULT_SESSION_HOST_APP_NAME, DEFAULT_STANDALONE_SESSION_HOST_APP_NAME, STARTUP_TIMEOUT_MS, STARTUP_POLL_MS, SessionHostCompatibilityError, EXTENSION_CATALOG, SessionRegistry;
|
|
40917
41915
|
var init_dist2 = __esm({
|
|
40918
41916
|
"../daemon-core/dist/index.mjs"() {
|
|
40919
41917
|
"use strict";
|
|
@@ -40936,20 +41934,24 @@ var init_dist2 = __esm({
|
|
|
40936
41934
|
import_fs7 = require("fs");
|
|
40937
41935
|
import_path4 = require("path");
|
|
40938
41936
|
import_crypto5 = require("crypto");
|
|
40939
|
-
|
|
40940
|
-
path8 = __toESM(require("path"), 1);
|
|
41937
|
+
import_child_process2 = require("child_process");
|
|
40941
41938
|
os22 = __toESM(require("os"), 1);
|
|
41939
|
+
path8 = __toESM(require("path"), 1);
|
|
41940
|
+
import_fs8 = require("fs");
|
|
41941
|
+
fs2 = __toESM(require("fs"), 1);
|
|
41942
|
+
path9 = __toESM(require("path"), 1);
|
|
41943
|
+
os32 = __toESM(require("os"), 1);
|
|
40942
41944
|
init_dist();
|
|
40943
41945
|
os8 = __toESM(require("os"), 1);
|
|
40944
41946
|
os9 = __toESM(require("os"), 1);
|
|
40945
41947
|
path14 = __toESM(require("path"), 1);
|
|
40946
|
-
|
|
41948
|
+
import_child_process3 = require("child_process");
|
|
40947
41949
|
os10 = __toESM(require("os"), 1);
|
|
40948
41950
|
path15 = __toESM(require("path"), 1);
|
|
40949
41951
|
init_dist();
|
|
40950
41952
|
os11 = __toESM(require("os"), 1);
|
|
40951
|
-
|
|
40952
|
-
|
|
41953
|
+
import_child_process4 = require("child_process");
|
|
41954
|
+
import_fs9 = require("fs");
|
|
40953
41955
|
import_promises5 = require("fs/promises");
|
|
40954
41956
|
path = __toESM(require("path"), 1);
|
|
40955
41957
|
import_util4 = require("util");
|
|
@@ -40962,16 +41964,12 @@ var init_dist2 = __esm({
|
|
|
40962
41964
|
import_crypto6 = require("crypto");
|
|
40963
41965
|
path6 = __toESM(require("path"), 1);
|
|
40964
41966
|
path7 = __toESM(require("path"), 1);
|
|
40965
|
-
import_fs9 = require("fs");
|
|
40966
|
-
import_path5 = require("path");
|
|
40967
|
-
import_child_process4 = require("child_process");
|
|
40968
41967
|
import_fs10 = require("fs");
|
|
40969
|
-
|
|
40970
|
-
path9 = __toESM(require("path"), 1);
|
|
41968
|
+
import_path5 = require("path");
|
|
40971
41969
|
import_child_process5 = require("child_process");
|
|
40972
|
-
os32 = __toESM(require("os"), 1);
|
|
40973
|
-
path10 = __toESM(require("path"), 1);
|
|
40974
41970
|
import_fs11 = require("fs");
|
|
41971
|
+
import_os3 = require("os");
|
|
41972
|
+
path10 = __toESM(require("path"), 1);
|
|
40975
41973
|
os42 = __toESM(require("os"), 1);
|
|
40976
41974
|
import_child_process6 = require("child_process");
|
|
40977
41975
|
init_wrapper();
|
|
@@ -41104,6 +42102,7 @@ var init_dist2 = __esm({
|
|
|
41104
42102
|
WORKTREE_DIR_NAME = ".adhdev-worktrees";
|
|
41105
42103
|
GIT_TIMEOUT_MS = 3e4;
|
|
41106
42104
|
GIT_MAX_BUFFER = 4 * 1024 * 1024;
|
|
42105
|
+
SUBMODULE_WORKTREE_REMOVE_RE = /working trees containing submodules cannot be moved or removed/i;
|
|
41107
42106
|
}
|
|
41108
42107
|
});
|
|
41109
42108
|
config_exports = {};
|
|
@@ -41183,17 +42182,24 @@ var init_dist2 = __esm({
|
|
|
41183
42182
|
|
|
41184
42183
|
| Tool | Purpose |
|
|
41185
42184
|
|------|---------|
|
|
41186
|
-
| \`mesh_status\` | Check all nodes' health, git state,
|
|
42185
|
+
| \`mesh_status\` | Check all nodes' health, git state, active sessions, and branch convergence |
|
|
41187
42186
|
| \`mesh_list_nodes\` | List nodes with workspace paths |
|
|
42187
|
+
| \`mesh_enqueue_task\` | Add a task to the pull-based work queue; idle nodes auto-claim |
|
|
42188
|
+
| \`mesh_view_queue\` | View queue status \u2014 pending, assigned, completed, failed, cancelled tasks |
|
|
42189
|
+
| \`mesh_queue_cancel\` | Cancel a queue task without deleting audit history |
|
|
42190
|
+
| \`mesh_queue_requeue\` | Return a task to pending for retry; clears stale session targets |
|
|
42191
|
+
| \`mesh_send_task\` | Legacy push: enqueue a task targeted at a specific node |
|
|
41188
42192
|
| \`mesh_launch_session\` | Start a new agent session on a node |
|
|
41189
|
-
| \`
|
|
41190
|
-
| \`
|
|
42193
|
+
| \`mesh_read_chat\` | Read recent chat messages from a delegated agent session |
|
|
42194
|
+
| \`mesh_read_debug\` | Collect a daemon-side chat/parser debug bundle for a session |
|
|
41191
42195
|
| \`mesh_task_history\` | Read the task ledger \u2014 dispatches, completions, failures. Use to understand what has been done before deciding next steps |
|
|
41192
42196
|
| \`mesh_git_status\` | Check git status on a specific node |
|
|
41193
42197
|
| \`mesh_checkpoint\` | Create a git checkpoint on a node |
|
|
41194
42198
|
| \`mesh_approve\` | Approve/reject a pending agent action |
|
|
41195
42199
|
| \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
|
|
41196
|
-
| \`
|
|
42200
|
+
| \`mesh_refine_node\` | Validate and merge a completed worktree node back into its base branch |
|
|
42201
|
+
| \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |
|
|
42202
|
+
| \`mesh_cleanup_sessions\` | Manually clean up delegated session records for a node |`;
|
|
41197
42203
|
TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
|
|
41198
42204
|
|
|
41199
42205
|
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\`.`;
|
|
@@ -41203,14 +42209,16 @@ Before doing any coordinator work, confirm that the actual callable tool list in
|
|
|
41203
42209
|
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.
|
|
41204
42210
|
3. **Queue / Delegate** \u2014 The Mesh uses an autonomous pull-based Work Queue:
|
|
41205
42211
|
a. **General Tasks**: Enqueue tasks using \`mesh_enqueue_task\`. Idle node agents will automatically pull tasks from the queue and begin working.
|
|
41206
|
-
b. **Node Preparation**: Call \`mesh_launch_session\`
|
|
42212
|
+
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.
|
|
41207
42213
|
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.
|
|
41208
|
-
d.
|
|
42214
|
+
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.
|
|
42215
|
+
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.
|
|
41209
42216
|
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\`.
|
|
41210
42217
|
5. **Verify** \u2014 When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
|
|
41211
42218
|
6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
|
|
41212
|
-
7. **
|
|
41213
|
-
8. **
|
|
42219
|
+
7. **Converge branches** \u2014 Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary and \`mesh_refine_node\` for clean worktree branches when safe. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
|
|
42220
|
+
8. **Clean up** \u2014 Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
|
|
42221
|
+
9. **Report** \u2014 Summarize what was done, what changed, any issues, and the branch convergence state.
|
|
41214
42222
|
|
|
41215
42223
|
## Failure Recovery
|
|
41216
42224
|
|
|
@@ -41220,7 +42228,7 @@ When a node agent stops unexpectedly, the daemon automatically enriches the syst
|
|
|
41220
42228
|
- A recommendation: **retry**, **reassign**, or **escalate**
|
|
41221
42229
|
|
|
41222
42230
|
Follow these recovery rules:
|
|
41223
|
-
1. **If "Retry recommended"**:
|
|
42231
|
+
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.
|
|
41224
42232
|
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.
|
|
41225
42233
|
3. **If no recovery context**: The stop may be intentional (normal completion). Use \`mesh_read_chat\` once to verify, then move on.
|
|
41226
42234
|
4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user.`;
|
|
@@ -41228,13 +42236,17 @@ Follow these recovery rules:
|
|
|
41228
42236
|
});
|
|
41229
42237
|
mesh_ledger_exports = {};
|
|
41230
42238
|
__export2(mesh_ledger_exports, {
|
|
42239
|
+
MAX_LEDGER_SLICE_LIMIT: () => MAX_LEDGER_SLICE_LIMIT,
|
|
41231
42240
|
appendLedgerEntry: () => appendLedgerEntry,
|
|
41232
42241
|
appendRemoteLedgerEntries: () => appendRemoteLedgerEntries,
|
|
42242
|
+
buildTaskCompletionEvidence: () => buildTaskCompletionEvidence,
|
|
41233
42243
|
getLedgerDir: () => getLedgerDir,
|
|
41234
42244
|
getLedgerSummary: () => getLedgerSummary,
|
|
41235
42245
|
getSessionRecoveryContext: () => getSessionRecoveryContext,
|
|
42246
|
+
isIntentionalCleanupStopEntry: () => isIntentionalCleanupStopEntry,
|
|
41236
42247
|
meshLedgerEvents: () => meshLedgerEvents,
|
|
41237
|
-
readLedgerEntries: () => readLedgerEntries
|
|
42248
|
+
readLedgerEntries: () => readLedgerEntries,
|
|
42249
|
+
readLedgerSlice: () => readLedgerSlice
|
|
41238
42250
|
});
|
|
41239
42251
|
init_mesh_ledger = __esm2({
|
|
41240
42252
|
"src/mesh/mesh-ledger.ts"() {
|
|
@@ -41243,13 +42255,36 @@ Follow these recovery rules:
|
|
|
41243
42255
|
LEDGER_DIR_NAME = "mesh-ledger";
|
|
41244
42256
|
MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
41245
42257
|
RECENT_FAILURE_WINDOW_MS = 30 * 60 * 1e3;
|
|
42258
|
+
DEFAULT_LEDGER_SLICE_LIMIT = 100;
|
|
42259
|
+
MAX_LEDGER_SLICE_LIMIT = 500;
|
|
41246
42260
|
meshLedgerEvents = new import_events2.EventEmitter();
|
|
41247
42261
|
}
|
|
41248
42262
|
});
|
|
42263
|
+
mesh_work_queue_exports = {};
|
|
42264
|
+
__export2(mesh_work_queue_exports, {
|
|
42265
|
+
ACTIVE_MESH_QUEUE_STATUSES: () => ACTIVE_MESH_QUEUE_STATUSES,
|
|
42266
|
+
HISTORICAL_MESH_QUEUE_STATUSES: () => HISTORICAL_MESH_QUEUE_STATUSES,
|
|
42267
|
+
cancelTask: () => cancelTask,
|
|
42268
|
+
claimNextTask: () => claimNextTask,
|
|
42269
|
+
enqueueTask: () => enqueueTask,
|
|
42270
|
+
getMeshQueueStats: () => getMeshQueueStats,
|
|
42271
|
+
getQueue: () => getQueue,
|
|
42272
|
+
recordTaskAutoLaunch: () => recordTaskAutoLaunch,
|
|
42273
|
+
requeueTask: () => requeueTask,
|
|
42274
|
+
updateSessionTaskStatus: () => updateSessionTaskStatus,
|
|
42275
|
+
updateTaskStatus: () => updateTaskStatus
|
|
42276
|
+
});
|
|
41249
42277
|
init_mesh_work_queue = __esm2({
|
|
41250
42278
|
"src/mesh/mesh-work-queue.ts"() {
|
|
41251
42279
|
"use strict";
|
|
41252
42280
|
init_mesh_ledger();
|
|
42281
|
+
ACTIVE_MESH_QUEUE_STATUSES = ["pending", "assigned"];
|
|
42282
|
+
HISTORICAL_MESH_QUEUE_STATUSES = ["completed", "failed", "cancelled"];
|
|
42283
|
+
}
|
|
42284
|
+
});
|
|
42285
|
+
init_cli_detector = __esm2({
|
|
42286
|
+
"src/detection/cli-detector.ts"() {
|
|
42287
|
+
"use strict";
|
|
41253
42288
|
}
|
|
41254
42289
|
});
|
|
41255
42290
|
init_logger = __esm2({
|
|
@@ -41258,7 +42293,7 @@ Follow these recovery rules:
|
|
|
41258
42293
|
LEVEL_NUM = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
41259
42294
|
LEVEL_LABEL = { debug: "DBG", info: "INF", warn: "WRN", error: "ERR" };
|
|
41260
42295
|
currentLevel = "info";
|
|
41261
|
-
LOG_DIR = process.platform === "win32" ?
|
|
42296
|
+
LOG_DIR = process.platform === "win32" ? path9.join(process.env.LOCALAPPDATA || process.env.APPDATA || path9.join(os32.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path9.join(os32.homedir(), "Library", "Logs", "adhdev") : path9.join(os32.homedir(), ".local", "share", "adhdev", "logs");
|
|
41262
42297
|
MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
41263
42298
|
MAX_LOG_DAYS = 7;
|
|
41264
42299
|
try {
|
|
@@ -41266,16 +42301,16 @@ Follow these recovery rules:
|
|
|
41266
42301
|
} catch {
|
|
41267
42302
|
}
|
|
41268
42303
|
currentDate = getDateStr();
|
|
41269
|
-
currentLogFile =
|
|
42304
|
+
currentLogFile = path9.join(LOG_DIR, `daemon-${currentDate}.log`);
|
|
41270
42305
|
cleanOldLogs();
|
|
41271
42306
|
try {
|
|
41272
|
-
const oldLog =
|
|
42307
|
+
const oldLog = path9.join(LOG_DIR, "daemon.log");
|
|
41273
42308
|
if (fs2.existsSync(oldLog)) {
|
|
41274
42309
|
const stat22 = fs2.statSync(oldLog);
|
|
41275
42310
|
const oldDate = stat22.mtime.toISOString().slice(0, 10);
|
|
41276
|
-
fs2.renameSync(oldLog,
|
|
42311
|
+
fs2.renameSync(oldLog, path9.join(LOG_DIR, `daemon-${oldDate}.log`));
|
|
41277
42312
|
}
|
|
41278
|
-
const oldLogBackup =
|
|
42313
|
+
const oldLogBackup = path9.join(LOG_DIR, "daemon.log.old");
|
|
41279
42314
|
if (fs2.existsSync(oldLogBackup)) {
|
|
41280
42315
|
fs2.unlinkSync(oldLogBackup);
|
|
41281
42316
|
}
|
|
@@ -41307,7 +42342,7 @@ Follow these recovery rules:
|
|
|
41307
42342
|
}
|
|
41308
42343
|
};
|
|
41309
42344
|
interceptorInstalled = false;
|
|
41310
|
-
LOG_PATH =
|
|
42345
|
+
LOG_PATH = path9.join(LOG_DIR, `daemon-${getDateStr()}.log`);
|
|
41311
42346
|
}
|
|
41312
42347
|
});
|
|
41313
42348
|
mesh_events_exports = {};
|
|
@@ -41321,16 +42356,21 @@ Follow these recovery rules:
|
|
|
41321
42356
|
init_mesh_events = __esm2({
|
|
41322
42357
|
"src/mesh/mesh-events.ts"() {
|
|
41323
42358
|
"use strict";
|
|
42359
|
+
init_config();
|
|
41324
42360
|
init_mesh_config();
|
|
42361
|
+
init_cli_detector();
|
|
41325
42362
|
init_logger();
|
|
41326
42363
|
init_mesh_ledger();
|
|
41327
42364
|
init_mesh_work_queue();
|
|
42365
|
+
remoteIdleSessions = /* @__PURE__ */ new Map();
|
|
41328
42366
|
MAX_PENDING_EVENTS = 50;
|
|
41329
42367
|
pendingMeshCoordinatorEvents = [];
|
|
41330
42368
|
MESH_COORDINATOR_EVENTS = /* @__PURE__ */ new Set([
|
|
42369
|
+
"agent:generating_started",
|
|
41331
42370
|
"agent:generating_completed",
|
|
41332
42371
|
"agent:waiting_approval",
|
|
41333
42372
|
"agent:stopped",
|
|
42373
|
+
"agent:ready",
|
|
41334
42374
|
"monitor:long_generating"
|
|
41335
42375
|
]);
|
|
41336
42376
|
EVENT_TO_LEDGER_KIND = {
|
|
@@ -41339,6 +42379,10 @@ Follow these recovery rules:
|
|
|
41339
42379
|
"agent:stopped": "task_failed",
|
|
41340
42380
|
"monitor:long_generating": "task_stalled"
|
|
41341
42381
|
};
|
|
42382
|
+
INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS = 30 * 60 * 1e3;
|
|
42383
|
+
autoLaunchInProgress = /* @__PURE__ */ new Set();
|
|
42384
|
+
autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
|
|
42385
|
+
AUTO_LAUNCH_COOLDOWN_MS = 5e3;
|
|
41342
42386
|
}
|
|
41343
42387
|
});
|
|
41344
42388
|
init_debug_config = __esm2({
|
|
@@ -41783,6 +42827,7 @@ Follow these recovery rules:
|
|
|
41783
42827
|
this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
|
|
41784
42828
|
this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
|
|
41785
42829
|
this.cliScripts = provider.scripts || {};
|
|
42830
|
+
this.scriptState = typeof this.cliScripts.createState === "function" ? this.cliScripts.createState() ?? null : null;
|
|
41786
42831
|
const scriptNames = listCliScriptNames(this.cliScripts);
|
|
41787
42832
|
if (scriptNames.length > 0) {
|
|
41788
42833
|
LOG.info("CLI", `[${this.cliType}] CLI scripts: [${scriptNames.join(", ")}]`);
|
|
@@ -41865,6 +42910,8 @@ Follow these recovery rules:
|
|
|
41865
42910
|
statusHistory = [];
|
|
41866
42911
|
// ─── CLI Scripts (script-based parsing) ───
|
|
41867
42912
|
cliScripts;
|
|
42913
|
+
/** Per-session opaque state object created by cliScripts.createState(), reset on stop. */
|
|
42914
|
+
scriptState = null;
|
|
41868
42915
|
runtimeSettings = {};
|
|
41869
42916
|
/** Full accumulated rendered PTY transcript for parser/readback use */
|
|
41870
42917
|
accumulatedBuffer = "";
|
|
@@ -41941,9 +42988,13 @@ ${lastSnapshot}`;
|
|
|
41941
42988
|
this.lastScreenChangeAt = 0;
|
|
41942
42989
|
this.lastScreenSnapshotReadAt = Number.NEGATIVE_INFINITY;
|
|
41943
42990
|
}
|
|
42991
|
+
getAccumulatedRawBufferCacheKey() {
|
|
42992
|
+
return this.accumulatedRawBuffer.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
42993
|
+
}
|
|
41944
42994
|
getFreshParsedStatusCache() {
|
|
41945
42995
|
const cached2 = this.parsedStatusCache;
|
|
41946
|
-
|
|
42996
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
42997
|
+
if (cached2 && cached2.responseBuffer === this.responseBuffer && cached2.currentTurnScope === this.currentTurnScope && cached2.recentOutputBuffer === this.recentOutputBuffer && cached2.accumulatedBuffer === this.accumulatedBuffer && cached2.accumulatedRawBufferKey === accumulatedRawBufferKey && cached2.screenText === this.lastScreenText && cached2.currentStatus === this.currentStatus && cached2.activeModal === this.activeModal && cached2.cliName === this.cliName) {
|
|
41947
42998
|
return cached2.result;
|
|
41948
42999
|
}
|
|
41949
43000
|
return null;
|
|
@@ -42046,6 +43097,7 @@ ${lastSnapshot}`;
|
|
|
42046
43097
|
this.cliScripts = scripts;
|
|
42047
43098
|
this.parsedStatusCache = null;
|
|
42048
43099
|
this.parseErrorMessage = null;
|
|
43100
|
+
this.scriptState = typeof scripts.createState === "function" ? scripts.createState() ?? null : null;
|
|
42049
43101
|
const scriptNames = listCliScriptNames(scripts);
|
|
42050
43102
|
LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
|
|
42051
43103
|
}
|
|
@@ -42163,6 +43215,7 @@ ${lastSnapshot}`;
|
|
|
42163
43215
|
this.ready = false;
|
|
42164
43216
|
this.startupParseGate = false;
|
|
42165
43217
|
this.spawnAt = 0;
|
|
43218
|
+
this.scriptState = null;
|
|
42166
43219
|
this.onStatusChange?.();
|
|
42167
43220
|
});
|
|
42168
43221
|
this.spawnAt = Date.now();
|
|
@@ -42916,6 +43969,11 @@ ${lastSnapshot}`;
|
|
|
42916
43969
|
};
|
|
42917
43970
|
}
|
|
42918
43971
|
// ─── Script Execution ──────────────────────────
|
|
43972
|
+
invokeCliScript(script, input) {
|
|
43973
|
+
const hasStateFactory = typeof this.cliScripts?.createState === "function";
|
|
43974
|
+
const expectsStateArgument = hasStateFactory || this.scriptState !== null || script.length >= 2;
|
|
43975
|
+
return expectsStateArgument ? script(this.scriptState, input) : script(input);
|
|
43976
|
+
}
|
|
42919
43977
|
runParseSession() {
|
|
42920
43978
|
if (typeof this.cliScripts?.parseSession !== "function") {
|
|
42921
43979
|
this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
|
|
@@ -42936,7 +43994,10 @@ ${lastSnapshot}`;
|
|
|
42936
43994
|
scope: this.currentTurnScope,
|
|
42937
43995
|
runtimeSettings: this.runtimeSettings
|
|
42938
43996
|
});
|
|
42939
|
-
const session = this.
|
|
43997
|
+
const session = this.invokeCliScript(
|
|
43998
|
+
this.cliScripts.parseSession,
|
|
43999
|
+
{ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) }
|
|
44000
|
+
);
|
|
42940
44001
|
this.parseErrorMessage = null;
|
|
42941
44002
|
return session && typeof session === "object" ? session : null;
|
|
42942
44003
|
} catch (e) {
|
|
@@ -42950,7 +44011,7 @@ ${lastSnapshot}`;
|
|
|
42950
44011
|
if (!this.cliScripts?.detectStatus) return null;
|
|
42951
44012
|
try {
|
|
42952
44013
|
const screenText = this.terminalScreen.getText();
|
|
42953
|
-
const status = this.cliScripts.detectStatus
|
|
44014
|
+
const status = this.invokeCliScript(this.cliScripts.detectStatus, {
|
|
42954
44015
|
tail: text.slice(-500),
|
|
42955
44016
|
screenText,
|
|
42956
44017
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -42969,7 +44030,7 @@ ${lastSnapshot}`;
|
|
|
42969
44030
|
try {
|
|
42970
44031
|
const screenText = this.terminalScreen.getText();
|
|
42971
44032
|
const buffer = screenText || this.accumulatedBuffer;
|
|
42972
|
-
return this.cliScripts.parseApproval
|
|
44033
|
+
return this.invokeCliScript(this.cliScripts.parseApproval, {
|
|
42973
44034
|
buffer,
|
|
42974
44035
|
screenText,
|
|
42975
44036
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -43025,7 +44086,8 @@ ${lastSnapshot}`;
|
|
|
43025
44086
|
const screenText = this.readTerminalScreenText();
|
|
43026
44087
|
const parseScreenText = this.getParseScreenText(screenText);
|
|
43027
44088
|
const cached2 = this.parsedStatusCache;
|
|
43028
|
-
|
|
44089
|
+
const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
|
|
44090
|
+
if (cached2 && cached2.responseBuffer === this.responseBuffer && cached2.currentTurnScope === this.currentTurnScope && cached2.recentOutputBuffer === this.recentOutputBuffer && cached2.accumulatedBuffer === this.accumulatedBuffer && cached2.accumulatedRawBufferKey === accumulatedRawBufferKey && cached2.screenText === parseScreenText && cached2.currentStatus === this.currentStatus && cached2.activeModal === this.activeModal && cached2.cliName === this.cliName) {
|
|
43029
44091
|
return cached2.result;
|
|
43030
44092
|
}
|
|
43031
44093
|
const parsed = this.runParseSession();
|
|
@@ -43053,6 +44115,7 @@ ${lastSnapshot}`;
|
|
|
43053
44115
|
currentTurnScope: this.currentTurnScope,
|
|
43054
44116
|
recentOutputBuffer: this.recentOutputBuffer,
|
|
43055
44117
|
accumulatedBuffer: this.accumulatedBuffer,
|
|
44118
|
+
accumulatedRawBufferKey,
|
|
43056
44119
|
screenText: parseScreenText,
|
|
43057
44120
|
currentStatus: this.currentStatus,
|
|
43058
44121
|
activeModal: this.activeModal,
|
|
@@ -43077,7 +44140,7 @@ ${lastSnapshot}`;
|
|
|
43077
44140
|
scope: this.currentTurnScope,
|
|
43078
44141
|
runtimeSettings: this.runtimeSettings
|
|
43079
44142
|
});
|
|
43080
|
-
return await Promise.resolve(fn
|
|
44143
|
+
return await Promise.resolve(this.invokeCliScript(fn, {
|
|
43081
44144
|
...input,
|
|
43082
44145
|
args: args && typeof args === "object" ? { ...args } : {}
|
|
43083
44146
|
}));
|
|
@@ -43813,7 +44876,7 @@ ${lastSnapshot}`;
|
|
|
43813
44876
|
}
|
|
43814
44877
|
});
|
|
43815
44878
|
init_repo_mesh_types();
|
|
43816
|
-
execFileAsync = (0, import_util4.promisify)(
|
|
44879
|
+
execFileAsync = (0, import_util4.promisify)(import_child_process4.execFile);
|
|
43817
44880
|
DEFAULT_TIMEOUT_MS = 5e3;
|
|
43818
44881
|
DEFAULT_MAX_BUFFER = 1024 * 1024;
|
|
43819
44882
|
GitCommandError = class extends Error {
|
|
@@ -44072,6 +45135,34 @@ ${lastSnapshot}`;
|
|
|
44072
45135
|
init_mesh_ledger();
|
|
44073
45136
|
init_mesh_work_queue();
|
|
44074
45137
|
init_mesh_events();
|
|
45138
|
+
NO_FALLBACK_REASON = "Repo Mesh command/data-plane is P2P-only; WS/REST command fallback is intentionally disabled to preserve the transport boundary.";
|
|
45139
|
+
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.";
|
|
45140
|
+
NON_P2P_NEXT_ACTION = "Inspect the provider/command error and fix the underlying logic or configuration before retrying.";
|
|
45141
|
+
P2pRelayFailureError = class extends Error {
|
|
45142
|
+
code;
|
|
45143
|
+
reason;
|
|
45144
|
+
transport;
|
|
45145
|
+
recoverable;
|
|
45146
|
+
retryRecommended;
|
|
45147
|
+
nextAction;
|
|
45148
|
+
noFallbackReason;
|
|
45149
|
+
command;
|
|
45150
|
+
targetDaemonId;
|
|
45151
|
+
constructor(message, context = {}) {
|
|
45152
|
+
super(message);
|
|
45153
|
+
this.name = "P2pRelayFailureError";
|
|
45154
|
+
const payload = buildP2pRelayFailurePayload(message, context);
|
|
45155
|
+
this.code = payload.code;
|
|
45156
|
+
this.reason = payload.reason;
|
|
45157
|
+
this.transport = payload.transport;
|
|
45158
|
+
this.recoverable = payload.recoverable;
|
|
45159
|
+
this.retryRecommended = payload.retryRecommended;
|
|
45160
|
+
this.nextAction = payload.nextAction;
|
|
45161
|
+
this.noFallbackReason = payload.noFallbackReason;
|
|
45162
|
+
this.command = context.command;
|
|
45163
|
+
this.targetDaemonId = context.targetDaemonId;
|
|
45164
|
+
}
|
|
45165
|
+
};
|
|
44075
45166
|
init_config();
|
|
44076
45167
|
DEFAULT_STATE = {
|
|
44077
45168
|
recentActivity: [],
|
|
@@ -44083,6 +45174,7 @@ ${lastSnapshot}`;
|
|
|
44083
45174
|
};
|
|
44084
45175
|
BUILTIN_IDE_DEFINITIONS = [];
|
|
44085
45176
|
registeredIDEs = /* @__PURE__ */ new Map();
|
|
45177
|
+
init_cli_detector();
|
|
44086
45178
|
LIVE_LIFECYCLES = /* @__PURE__ */ new Set(["starting", "running", "stopping", "interrupted"]);
|
|
44087
45179
|
DEFAULT_ACTIVE_CHAT_POLL_STATUSES = /* @__PURE__ */ new Set([
|
|
44088
45180
|
"generating",
|
|
@@ -47814,9 +48906,12 @@ ${effect.notification.body || ""}`.trim();
|
|
|
47814
48906
|
}
|
|
47815
48907
|
};
|
|
47816
48908
|
init_provider_cli_adapter();
|
|
48909
|
+
init_cli_detector();
|
|
47817
48910
|
init_config();
|
|
47818
48911
|
init_provider_cli_adapter();
|
|
47819
48912
|
init_logger();
|
|
48913
|
+
COMPLETED_FINALIZATION_RETRY_MS = 1e3;
|
|
48914
|
+
COMPLETED_FINALIZATION_MAX_WAIT_MS = 3e4;
|
|
47820
48915
|
IMAGE_MIME_EXTENSIONS = {
|
|
47821
48916
|
"image/png": ".png",
|
|
47822
48917
|
"image/jpeg": ".jpg",
|
|
@@ -48002,10 +49097,12 @@ ${effect.notification.body || ""}`.trim();
|
|
|
48002
49097
|
const mergedMessages = this.mergeConversationMessages(parsedMessages);
|
|
48003
49098
|
const canonicalBackedHistory = this.syncCanonicalSavedHistoryIfNeeded();
|
|
48004
49099
|
const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
|
|
49100
|
+
const parsedChatStatus = typeof parsedStatus?.status === "string" && parsedStatus.status.trim() ? parsedStatus.status.trim() : void 0;
|
|
49101
|
+
const suppressStaleParsedBusyStatus = this.shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus);
|
|
48005
49102
|
if (parsedMessages.length > 0) {
|
|
48006
49103
|
const shouldSkipReplayPersist = this.suppressIdleHistoryReplay && adapterStatus.status === "idle" && parsedStatus?.status === "idle";
|
|
48007
49104
|
let messagesToSave = parsedMessages;
|
|
48008
|
-
if (
|
|
49105
|
+
if (!suppressStaleParsedBusyStatus && (parsedChatStatus === "generating" || parsedChatStatus === "long_generating")) {
|
|
48009
49106
|
const lastIdx = messagesToSave.length - 1;
|
|
48010
49107
|
if (lastIdx >= 0 && messagesToSave[lastIdx]?.role === "assistant") {
|
|
48011
49108
|
messagesToSave = messagesToSave.slice(0, lastIdx);
|
|
@@ -48039,6 +49136,7 @@ ${effect.notification.body || ""}`.trim();
|
|
|
48039
49136
|
summaryMetadata: this.summaryMetadata,
|
|
48040
49137
|
controlValues: this.controlValues
|
|
48041
49138
|
});
|
|
49139
|
+
const activeChatStatus = parseErrorMessage ? "error" : autoApproveActive && parsedStatus?.status === "waiting_approval" ? "generating" : adapterStatus.status !== "idle" ? visibleStatus : suppressStaleParsedBusyStatus ? visibleStatus : parsedChatStatus || visibleStatus;
|
|
48042
49140
|
return {
|
|
48043
49141
|
type: this.type,
|
|
48044
49142
|
name: this.provider.name,
|
|
@@ -48048,7 +49146,7 @@ ${effect.notification.body || ""}`.trim();
|
|
|
48048
49146
|
activeChat: {
|
|
48049
49147
|
id: `${this.type}_${this.workingDir}`,
|
|
48050
49148
|
title: parsedStatus?.title || dirName,
|
|
48051
|
-
status:
|
|
49149
|
+
status: activeChatStatus,
|
|
48052
49150
|
messages: mergedMessages,
|
|
48053
49151
|
activeModal: autoApproveActive ? null : parsedStatus?.activeModal ?? adapterStatus.activeModal,
|
|
48054
49152
|
inputContent: ""
|
|
@@ -48178,6 +49276,102 @@ ${effect.notification.body || ""}`.trim();
|
|
|
48178
49276
|
}
|
|
48179
49277
|
this.applyProviderResponse(parsed.payload, { phase: "immediate" });
|
|
48180
49278
|
}
|
|
49279
|
+
completionHasFinalAssistantMessage(messages) {
|
|
49280
|
+
const visibleMessages = (Array.isArray(messages) ? messages : []).filter((message) => isUserFacingChatMessage(message));
|
|
49281
|
+
const lastVisible = visibleMessages[visibleMessages.length - 1];
|
|
49282
|
+
const role = typeof lastVisible?.role === "string" ? lastVisible.role.trim().toLowerCase() : "";
|
|
49283
|
+
const content = lastVisible ? flattenContent(lastVisible.content).trim() : "";
|
|
49284
|
+
return role === "assistant" && !!content;
|
|
49285
|
+
}
|
|
49286
|
+
hasAdapterPendingResponse() {
|
|
49287
|
+
const adapterAny = this.adapter;
|
|
49288
|
+
if (adapterAny?.isWaitingForResponse === true) return true;
|
|
49289
|
+
if (adapterAny?.currentTurnScope) return true;
|
|
49290
|
+
try {
|
|
49291
|
+
if (typeof this.adapter.isProcessing === "function" && this.adapter.isProcessing()) return true;
|
|
49292
|
+
} catch {
|
|
49293
|
+
}
|
|
49294
|
+
try {
|
|
49295
|
+
const partial2 = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
49296
|
+
if (typeof partial2 === "string" && partial2.trim()) return true;
|
|
49297
|
+
} catch {
|
|
49298
|
+
}
|
|
49299
|
+
return false;
|
|
49300
|
+
}
|
|
49301
|
+
shouldSuppressStaleParsedBusyStatus(parsedStatus, adapterStatus) {
|
|
49302
|
+
const parsedRawStatus = typeof parsedStatus?.status === "string" ? parsedStatus.status.trim() : "";
|
|
49303
|
+
const adapterRawStatus = typeof adapterStatus?.status === "string" ? adapterStatus.status.trim() : "";
|
|
49304
|
+
if (!isCliGeneratingLikeStatus(parsedRawStatus)) return false;
|
|
49305
|
+
if (adapterRawStatus !== "idle") return false;
|
|
49306
|
+
if (hasNonEmptyCliModalButtons(parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
|
|
49307
|
+
return !this.hasAdapterPendingResponse();
|
|
49308
|
+
}
|
|
49309
|
+
getCompletedFinalizationBlockReason(latestVisibleStatus) {
|
|
49310
|
+
if (latestVisibleStatus !== "idle") return `status:${latestVisibleStatus}`;
|
|
49311
|
+
const adapterAny = this.adapter;
|
|
49312
|
+
if (adapterAny?.isWaitingForResponse === true) return "adapter_waiting_for_response";
|
|
49313
|
+
if (adapterAny?.currentTurnScope) return "adapter_turn_scope_active";
|
|
49314
|
+
const partial2 = typeof this.adapter.getPartialResponse === "function" ? this.adapter.getPartialResponse() : "";
|
|
49315
|
+
if (typeof partial2 === "string" && partial2.trim()) return "partial_response_pending";
|
|
49316
|
+
let parsed;
|
|
49317
|
+
try {
|
|
49318
|
+
parsed = this.adapter.getScriptParsedStatus();
|
|
49319
|
+
} catch (error48) {
|
|
49320
|
+
return `parse_error:${error48?.message || String(error48)}`;
|
|
49321
|
+
}
|
|
49322
|
+
const parsedStatus = typeof parsed?.status === "string" ? parsed.status : "unknown";
|
|
49323
|
+
if (parsedStatus !== "idle") return `parsed_status:${parsedStatus}`;
|
|
49324
|
+
if (parsed?.activeModal || parsed?.modal) return "parsed_modal_active";
|
|
49325
|
+
if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return "missing_final_assistant";
|
|
49326
|
+
return null;
|
|
49327
|
+
}
|
|
49328
|
+
scheduleCompletedDebounceFlush(delayMs) {
|
|
49329
|
+
if (this.completedDebounceTimer) clearTimeout(this.completedDebounceTimer);
|
|
49330
|
+
this.completedDebounceTimer = setTimeout(() => this.flushCompletedDebounceIfFinalized(), delayMs);
|
|
49331
|
+
}
|
|
49332
|
+
flushCompletedDebounceIfFinalized() {
|
|
49333
|
+
const pending = this.completedDebouncePending;
|
|
49334
|
+
if (!pending) {
|
|
49335
|
+
this.completedDebounceTimer = null;
|
|
49336
|
+
return;
|
|
49337
|
+
}
|
|
49338
|
+
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
49339
|
+
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
49340
|
+
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
49341
|
+
if (latestVisibleStatus !== "idle") {
|
|
49342
|
+
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
49343
|
+
this.completedDebouncePending = null;
|
|
49344
|
+
this.completedDebounceTimer = null;
|
|
49345
|
+
return;
|
|
49346
|
+
}
|
|
49347
|
+
const blockReason = this.getCompletedFinalizationBlockReason(latestVisibleStatus);
|
|
49348
|
+
if (blockReason) {
|
|
49349
|
+
const waitedMs = Date.now() - pending.firstObservedAt;
|
|
49350
|
+
if (waitedMs < COMPLETED_FINALIZATION_MAX_WAIT_MS) {
|
|
49351
|
+
if (pending.loggedBlockReason !== blockReason) {
|
|
49352
|
+
LOG.info("CLI", `[${this.type}] waiting to emit completed until transcript finalizes (${blockReason})`);
|
|
49353
|
+
pending.loggedBlockReason = blockReason;
|
|
49354
|
+
}
|
|
49355
|
+
this.scheduleCompletedDebounceFlush(COMPLETED_FINALIZATION_RETRY_MS);
|
|
49356
|
+
return;
|
|
49357
|
+
}
|
|
49358
|
+
LOG.warn("CLI", `[${this.type}] suppressed completed event after ${waitedMs}ms without finalized assistant turn (${blockReason})`);
|
|
49359
|
+
this.completedDebouncePending = null;
|
|
49360
|
+
this.completedDebounceTimer = null;
|
|
49361
|
+
this.generatingStartedAt = 0;
|
|
49362
|
+
return;
|
|
49363
|
+
}
|
|
49364
|
+
LOG.info("CLI", `[${this.type}] completed in ${pending.duration}s`);
|
|
49365
|
+
this.pushEvent({
|
|
49366
|
+
event: "agent:generating_completed",
|
|
49367
|
+
chatTitle: pending.chatTitle,
|
|
49368
|
+
duration: pending.duration,
|
|
49369
|
+
timestamp: pending.timestamp
|
|
49370
|
+
});
|
|
49371
|
+
this.completedDebouncePending = null;
|
|
49372
|
+
this.completedDebounceTimer = null;
|
|
49373
|
+
this.generatingStartedAt = 0;
|
|
49374
|
+
}
|
|
48181
49375
|
maybeAutoApproveStatus(adapterStatus, now = Date.now()) {
|
|
48182
49376
|
const autoApproveActive = adapterStatus?.status === "waiting_approval" && this.shouldAutoApprove();
|
|
48183
49377
|
if (autoApproveActive && !this.autoApproveBusy) {
|
|
@@ -48275,27 +49469,11 @@ ${effect.notification.body || ""}`.trim();
|
|
|
48275
49469
|
this.generatingDebouncePending = null;
|
|
48276
49470
|
this.generatingStartedAt = 0;
|
|
48277
49471
|
} else {
|
|
48278
|
-
|
|
48279
|
-
this.
|
|
48280
|
-
this.completedDebounceTimer = setTimeout(() => {
|
|
48281
|
-
if (this.completedDebouncePending) {
|
|
48282
|
-
const latestStatus = this.adapter.getStatus({ allowParse: false });
|
|
48283
|
-
const latestAutoApproveActive = latestStatus.status === "waiting_approval" && this.shouldAutoApprove();
|
|
48284
|
-
const latestVisibleStatus = latestAutoApproveActive ? "generating" : latestStatus.status;
|
|
48285
|
-
if (latestVisibleStatus !== "idle") {
|
|
48286
|
-
LOG.info("CLI", `[${this.type}] cancelled pending completed (resumed ${latestVisibleStatus})`);
|
|
48287
|
-
this.completedDebouncePending = null;
|
|
48288
|
-
this.completedDebounceTimer = null;
|
|
48289
|
-
return;
|
|
48290
|
-
}
|
|
48291
|
-
LOG.info("CLI", `[${this.type}] completed in ${this.completedDebouncePending.duration}s`);
|
|
48292
|
-
this.pushEvent({ event: "agent:generating_completed", ...this.completedDebouncePending });
|
|
48293
|
-
this.completedDebouncePending = null;
|
|
48294
|
-
this.generatingStartedAt = 0;
|
|
48295
|
-
}
|
|
48296
|
-
this.completedDebounceTimer = null;
|
|
48297
|
-
}, 3e3);
|
|
49472
|
+
this.completedDebouncePending = { chatTitle, duration: duration3, timestamp: now, firstObservedAt: now };
|
|
49473
|
+
this.scheduleCompletedDebounceFlush(3e3);
|
|
48298
49474
|
}
|
|
49475
|
+
} else if (newStatus === "idle" && this.lastStatus === "starting") {
|
|
49476
|
+
this.pushEvent({ event: "agent:ready", chatTitle, timestamp: now });
|
|
48299
49477
|
} else if (newStatus === "stopped") {
|
|
48300
49478
|
if (this.generatingDebounceTimer) {
|
|
48301
49479
|
clearTimeout(this.generatingDebounceTimer);
|
|
@@ -52080,6 +53258,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
52080
53258
|
};
|
|
52081
53259
|
_providerLoader = null;
|
|
52082
53260
|
init_config();
|
|
53261
|
+
init_cli_detector();
|
|
52083
53262
|
init_logger();
|
|
52084
53263
|
LOG_DIR2 = process.platform === "win32" ? path21.join(process.env.LOCALAPPDATA || process.env.APPDATA || path21.join(os16.homedir(), "AppData", "Local"), "adhdev", "logs") : process.platform === "darwin" ? path21.join(os16.homedir(), "Library", "Logs", "adhdev") : path21.join(os16.homedir(), ".local", "share", "adhdev", "logs");
|
|
52085
53264
|
MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
@@ -52128,6 +53307,11 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
52128
53307
|
stable: "https://api.adhf.dev",
|
|
52129
53308
|
preview: "https://api-preview.adhf.dev"
|
|
52130
53309
|
};
|
|
53310
|
+
REFINE_VALIDATION_CATEGORIES = ["typecheck", "test", "lint", "build"];
|
|
53311
|
+
REFINE_VALIDATION_TIMEOUT_MS = 12e4;
|
|
53312
|
+
REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
|
|
53313
|
+
REFINE_VALIDATION_SUMMARY_CHARS = 2e3;
|
|
53314
|
+
REFINE_VALIDATION_MAX_COMMANDS = 4;
|
|
52131
53315
|
CHAT_COMMANDS = [
|
|
52132
53316
|
"send_chat",
|
|
52133
53317
|
"new_chat",
|
|
@@ -52191,9 +53375,191 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
52191
53375
|
if (record2?.meta?.meshNodeId === nodeId) return true;
|
|
52192
53376
|
return false;
|
|
52193
53377
|
}
|
|
53378
|
+
async cleanupLocalWorktreeNode(args) {
|
|
53379
|
+
const workspace = typeof args.node?.workspace === "string" ? args.node.workspace.trim() : "";
|
|
53380
|
+
if (!workspace) {
|
|
53381
|
+
return {
|
|
53382
|
+
success: false,
|
|
53383
|
+
code: "mesh_worktree_cleanup_missing_workspace",
|
|
53384
|
+
error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
|
|
53385
|
+
recoveryHint: "Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains."
|
|
53386
|
+
};
|
|
53387
|
+
}
|
|
53388
|
+
const worktreeExists = fs10.existsSync(workspace);
|
|
53389
|
+
const sourceNode = args.node?.clonedFromNodeId ? args.mesh?.nodes?.find((n) => n.id === args.node.clonedFromNodeId || n.nodeId === args.node.clonedFromNodeId) : args.mesh?.nodes?.find((n) => !n.isLocalWorktree);
|
|
53390
|
+
const repoRoot = typeof sourceNode?.repoRoot === "string" && sourceNode.repoRoot.trim() ? sourceNode.repoRoot.trim() : typeof sourceNode?.workspace === "string" && sourceNode.workspace.trim() ? sourceNode.workspace.trim() : "";
|
|
53391
|
+
if (!worktreeExists) {
|
|
53392
|
+
return { success: true, skipped: true, removedPath: workspace, repoRoot: repoRoot || void 0, reason: "worktree_path_missing" };
|
|
53393
|
+
}
|
|
53394
|
+
if (!repoRoot || !fs10.existsSync(repoRoot)) {
|
|
53395
|
+
return {
|
|
53396
|
+
success: false,
|
|
53397
|
+
code: "mesh_worktree_cleanup_missing_source_repo",
|
|
53398
|
+
error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
|
|
53399
|
+
recoveryHint: "Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying."
|
|
53400
|
+
};
|
|
53401
|
+
}
|
|
53402
|
+
if (typeof args.node?.worktreeBranch !== "string" || !args.node.worktreeBranch.trim()) {
|
|
53403
|
+
return {
|
|
53404
|
+
success: false,
|
|
53405
|
+
code: "mesh_worktree_cleanup_missing_branch",
|
|
53406
|
+
error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
|
|
53407
|
+
recoveryHint: "Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata."
|
|
53408
|
+
};
|
|
53409
|
+
}
|
|
53410
|
+
const { resolveWorktreePath: resolveWorktreePath2, listWorktrees: listWorktrees2, removeWorktree: removeWorktree2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
53411
|
+
const normalizePath2 = (value) => {
|
|
53412
|
+
const resolved = (0, import_path7.resolve)(value);
|
|
53413
|
+
try {
|
|
53414
|
+
return fs10.realpathSync(resolved);
|
|
53415
|
+
} catch {
|
|
53416
|
+
return resolved;
|
|
53417
|
+
}
|
|
53418
|
+
};
|
|
53419
|
+
const expectedPath = normalizePath2(resolveWorktreePath2(repoRoot, String(args.mesh?.name || args.mesh?.id || "mesh"), args.node.worktreeBranch));
|
|
53420
|
+
const actualPath = normalizePath2(workspace);
|
|
53421
|
+
if (actualPath !== expectedPath) {
|
|
53422
|
+
return {
|
|
53423
|
+
success: false,
|
|
53424
|
+
code: "mesh_worktree_cleanup_unexpected_path",
|
|
53425
|
+
error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
|
|
53426
|
+
recoveryHint: "Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree."
|
|
53427
|
+
};
|
|
53428
|
+
}
|
|
53429
|
+
const entries = await listWorktrees2(repoRoot);
|
|
53430
|
+
const managedEntry = entries.find((entry) => normalizePath2(entry.path) === actualPath);
|
|
53431
|
+
if (!managedEntry) {
|
|
53432
|
+
return {
|
|
53433
|
+
success: false,
|
|
53434
|
+
code: "mesh_worktree_cleanup_not_registered",
|
|
53435
|
+
error: `Refusing to remove '${workspace}' because it is not registered in git worktree list for '${repoRoot}'`,
|
|
53436
|
+
recoveryHint: "Inspect git worktree list --porcelain from the source repo. If the path was already removed, prune git worktrees before retrying."
|
|
53437
|
+
};
|
|
53438
|
+
}
|
|
53439
|
+
if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
|
|
53440
|
+
return {
|
|
53441
|
+
success: false,
|
|
53442
|
+
code: "mesh_worktree_cleanup_branch_mismatch",
|
|
53443
|
+
error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
|
|
53444
|
+
recoveryHint: "Inspect the worktree branch and mesh metadata before retrying cleanup."
|
|
53445
|
+
};
|
|
53446
|
+
}
|
|
53447
|
+
const forceFallbackConvergence = await this.getWorktreeForceCleanupConvergence({
|
|
53448
|
+
repoRoot,
|
|
53449
|
+
workspace,
|
|
53450
|
+
node: args.node
|
|
53451
|
+
});
|
|
53452
|
+
try {
|
|
53453
|
+
const result = await removeWorktree2(repoRoot, workspace, {
|
|
53454
|
+
requireClean: true,
|
|
53455
|
+
allowSubmoduleForceFallback: forceFallbackConvergence.allow
|
|
53456
|
+
});
|
|
53457
|
+
return {
|
|
53458
|
+
success: true,
|
|
53459
|
+
removedPath: result.removedPath,
|
|
53460
|
+
repoRoot,
|
|
53461
|
+
...result.fallback ? {
|
|
53462
|
+
fallback: result.fallback,
|
|
53463
|
+
forced: result.forced,
|
|
53464
|
+
reason: result.reason,
|
|
53465
|
+
convergence: forceFallbackConvergence
|
|
53466
|
+
} : {}
|
|
53467
|
+
};
|
|
53468
|
+
} catch (e) {
|
|
53469
|
+
const message = String(e?.message || e || "worktree cleanup failed");
|
|
53470
|
+
const dirty = message.includes("dirty worktree") || message.includes("local changes");
|
|
53471
|
+
const submoduleForceBlocked = /working trees containing submodules cannot be moved or removed/i.test(message) && !forceFallbackConvergence.allow;
|
|
53472
|
+
return {
|
|
53473
|
+
success: false,
|
|
53474
|
+
code: dirty ? "mesh_worktree_cleanup_dirty" : submoduleForceBlocked ? "mesh_worktree_cleanup_force_fallback_blocked" : "mesh_worktree_cleanup_failed",
|
|
53475
|
+
error: submoduleForceBlocked ? `${message}; refusing --force fallback because convergence could not be verified: ${forceFallbackConvergence.error || "unknown convergence state"}` : message,
|
|
53476
|
+
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.",
|
|
53477
|
+
...submoduleForceBlocked ? { convergence: forceFallbackConvergence } : {}
|
|
53478
|
+
};
|
|
53479
|
+
}
|
|
53480
|
+
}
|
|
53481
|
+
async getWorktreeForceCleanupConvergence(args) {
|
|
53482
|
+
const metadataStatus = typeof args.node?.branchConvergence?.status === "string" ? args.node.branchConvergence.status : "";
|
|
53483
|
+
if (metadataStatus === "merged_to_main" || metadataStatus === "cleanup_candidate") {
|
|
53484
|
+
return { allow: true, status: metadataStatus, source: "node_branch_convergence" };
|
|
53485
|
+
}
|
|
53486
|
+
const { execFile: execFile3 } = await import("child_process");
|
|
53487
|
+
const { promisify: promisify3 } = await import("util");
|
|
53488
|
+
const execFileAsync3 = promisify3(execFile3);
|
|
53489
|
+
const runGit2 = async (gitArgs, cwd) => {
|
|
53490
|
+
const { stdout } = await execFileAsync3("git", gitArgs, {
|
|
53491
|
+
cwd,
|
|
53492
|
+
encoding: "utf8",
|
|
53493
|
+
timeout: 3e4,
|
|
53494
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
53495
|
+
windowsHide: true
|
|
53496
|
+
});
|
|
53497
|
+
return String(stdout || "").trim();
|
|
53498
|
+
};
|
|
53499
|
+
let head = "";
|
|
53500
|
+
try {
|
|
53501
|
+
head = await runGit2(["rev-parse", "HEAD"], args.workspace);
|
|
53502
|
+
} catch (e) {
|
|
53503
|
+
return { allow: false, error: `could not resolve worktree HEAD: ${e?.message || e}` };
|
|
53504
|
+
}
|
|
53505
|
+
if (!head) return { allow: false, error: "worktree HEAD is empty" };
|
|
53506
|
+
const candidateRefs = [];
|
|
53507
|
+
try {
|
|
53508
|
+
const defaultBranch = await runGit2(["branch", "--show-current"], args.repoRoot);
|
|
53509
|
+
if (defaultBranch) {
|
|
53510
|
+
candidateRefs.push(defaultBranch, `origin/${defaultBranch}`);
|
|
53511
|
+
}
|
|
53512
|
+
} catch {
|
|
53513
|
+
}
|
|
53514
|
+
candidateRefs.push("origin/main", "origin/master", "main", "master");
|
|
53515
|
+
const seen = /* @__PURE__ */ new Set();
|
|
53516
|
+
const checkedRefs = [];
|
|
53517
|
+
for (const ref of candidateRefs) {
|
|
53518
|
+
if (!ref || seen.has(ref)) continue;
|
|
53519
|
+
seen.add(ref);
|
|
53520
|
+
let commit = "";
|
|
53521
|
+
try {
|
|
53522
|
+
commit = await runGit2(["rev-parse", "--verify", `${ref}^{commit}`], args.repoRoot);
|
|
53523
|
+
} catch {
|
|
53524
|
+
continue;
|
|
53525
|
+
}
|
|
53526
|
+
checkedRefs.push(ref);
|
|
53527
|
+
try {
|
|
53528
|
+
await runGit2(["merge-base", "--is-ancestor", head, commit], args.repoRoot);
|
|
53529
|
+
return { allow: true, status: "merged_to_default_ref", source: "git_merge_base", ref };
|
|
53530
|
+
} catch {
|
|
53531
|
+
}
|
|
53532
|
+
}
|
|
53533
|
+
return {
|
|
53534
|
+
allow: false,
|
|
53535
|
+
status: metadataStatus || void 0,
|
|
53536
|
+
error: checkedRefs.length ? `worktree HEAD is not contained in checked refs: ${checkedRefs.join(", ")}` : "no default/main refs were available for convergence verification"
|
|
53537
|
+
};
|
|
53538
|
+
}
|
|
52194
53539
|
isCompletedHostedSession(record2) {
|
|
52195
53540
|
return record2?.lifecycle === "stopped" || record2?.lifecycle === "failed" || record2?.lifecycle === "interrupted";
|
|
52196
53541
|
}
|
|
53542
|
+
async recordIntentionalMeshSessionStop(args) {
|
|
53543
|
+
try {
|
|
53544
|
+
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
53545
|
+
appendLedgerEntry2(args.meshId, {
|
|
53546
|
+
kind: "session_stopped",
|
|
53547
|
+
nodeId: args.nodeId,
|
|
53548
|
+
sessionId: args.sessionId,
|
|
53549
|
+
payload: {
|
|
53550
|
+
intentional: true,
|
|
53551
|
+
reason: "operator_cleanup",
|
|
53552
|
+
intentionalStopReason: "operator_cleanup",
|
|
53553
|
+
source: args.source,
|
|
53554
|
+
cleanupMode: args.mode,
|
|
53555
|
+
action: args.action,
|
|
53556
|
+
workspace: typeof args.node?.workspace === "string" ? args.node.workspace : void 0
|
|
53557
|
+
}
|
|
53558
|
+
});
|
|
53559
|
+
} catch (e) {
|
|
53560
|
+
LOG.warn("MeshCleanup", `Failed to record intentional cleanup stop for ${args.sessionId}: ${e?.message || e}`);
|
|
53561
|
+
}
|
|
53562
|
+
}
|
|
52197
53563
|
async cleanupMeshSessions(args) {
|
|
52198
53564
|
if (args.mode === "preserve") {
|
|
52199
53565
|
return { success: true, mode: "preserve", matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
|
|
@@ -52210,6 +53576,21 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
52210
53576
|
const deleteUnsupportedSessionIds = [];
|
|
52211
53577
|
const recordsRemainSessionIds = [];
|
|
52212
53578
|
const errors = [];
|
|
53579
|
+
const cleanupSource = args.source || "mesh_cleanup_sessions";
|
|
53580
|
+
const markedIntentionalStopSessionIds = /* @__PURE__ */ new Set();
|
|
53581
|
+
const markIntentionalStop = async (sessionId, action) => {
|
|
53582
|
+
if (args.dryRun || markedIntentionalStopSessionIds.has(sessionId)) return;
|
|
53583
|
+
markedIntentionalStopSessionIds.add(sessionId);
|
|
53584
|
+
await this.recordIntentionalMeshSessionStop({
|
|
53585
|
+
meshId: args.meshId,
|
|
53586
|
+
nodeId: args.nodeId,
|
|
53587
|
+
node: args.node,
|
|
53588
|
+
sessionId,
|
|
53589
|
+
mode: args.mode,
|
|
53590
|
+
source: cleanupSource,
|
|
53591
|
+
action
|
|
53592
|
+
});
|
|
53593
|
+
};
|
|
52213
53594
|
const matchedBySurfaceKind = {
|
|
52214
53595
|
live_runtime: 0,
|
|
52215
53596
|
recovery_snapshot: 0,
|
|
@@ -52232,7 +53613,10 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
52232
53613
|
try {
|
|
52233
53614
|
if (args.mode === "stop") {
|
|
52234
53615
|
if (!completed) {
|
|
52235
|
-
if (!args.dryRun)
|
|
53616
|
+
if (!args.dryRun) {
|
|
53617
|
+
await markIntentionalStop(sessionId, "stop_session");
|
|
53618
|
+
await this.deps.sessionHostControl.stopSession(sessionId);
|
|
53619
|
+
}
|
|
52236
53620
|
stoppedSessionIds.push(sessionId);
|
|
52237
53621
|
} else {
|
|
52238
53622
|
skippedSessionIds.push(sessionId);
|
|
@@ -52249,6 +53633,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
52249
53633
|
continue;
|
|
52250
53634
|
}
|
|
52251
53635
|
if (args.mode === "stop_and_delete") {
|
|
53636
|
+
if (!completed) await markIntentionalStop(sessionId, "delete_session_force");
|
|
52252
53637
|
if (!args.dryRun) await this.deps.sessionHostControl.deleteSession(sessionId, { force: true });
|
|
52253
53638
|
deletedSessionIds.push(sessionId);
|
|
52254
53639
|
continue;
|
|
@@ -52260,6 +53645,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
52260
53645
|
recordsRemainSessionIds.push(sessionId);
|
|
52261
53646
|
if (args.mode === "stop_and_delete" && !completed) {
|
|
52262
53647
|
try {
|
|
53648
|
+
await markIntentionalStop(sessionId, "stop_session");
|
|
52263
53649
|
await this.deps.sessionHostControl.stopSession(sessionId);
|
|
52264
53650
|
stoppedSessionIds.push(sessionId);
|
|
52265
53651
|
} catch (stopError) {
|
|
@@ -53014,6 +54400,91 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53014
54400
|
return { success: false, error: e.message };
|
|
53015
54401
|
}
|
|
53016
54402
|
}
|
|
54403
|
+
case "get_mesh_ledger_slice": {
|
|
54404
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
54405
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
54406
|
+
try {
|
|
54407
|
+
const { readLedgerSlice: readLedgerSlice2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
54408
|
+
const kind = Array.isArray(args?.kind) ? args.kind.filter((k) => typeof k === "string") : void 0;
|
|
54409
|
+
const slice = readLedgerSlice2(meshId, {
|
|
54410
|
+
afterId: typeof args?.afterId === "string" ? args.afterId : void 0,
|
|
54411
|
+
since: typeof args?.since === "string" ? args.since : void 0,
|
|
54412
|
+
kind,
|
|
54413
|
+
limit: typeof args?.limit === "number" ? args.limit : void 0
|
|
54414
|
+
});
|
|
54415
|
+
return { success: true, slice };
|
|
54416
|
+
} catch (e) {
|
|
54417
|
+
return { success: false, error: e.message };
|
|
54418
|
+
}
|
|
54419
|
+
}
|
|
54420
|
+
case "import_mesh_ledger_slice": {
|
|
54421
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
54422
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
54423
|
+
try {
|
|
54424
|
+
const { appendRemoteLedgerEntries: appendRemoteLedgerEntries2, getLedgerSummary: getLedgerSummary2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
54425
|
+
const entries = Array.isArray(args?.entries) ? args.entries : Array.isArray(args?.slice?.entries) ? args.slice.entries : [];
|
|
54426
|
+
const result = appendRemoteLedgerEntries2(meshId, entries);
|
|
54427
|
+
return { success: true, result, summary: getLedgerSummary2(meshId) };
|
|
54428
|
+
} catch (e) {
|
|
54429
|
+
return { success: false, error: e.message };
|
|
54430
|
+
}
|
|
54431
|
+
}
|
|
54432
|
+
case "get_mesh_queue": {
|
|
54433
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
54434
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
54435
|
+
try {
|
|
54436
|
+
const { getMeshQueueStats: getMeshQueueStats2, getQueue: getQueue2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
54437
|
+
const status = Array.isArray(args?.status) ? args.status.map((s) => typeof s === "string" ? s.trim() : "").filter(Boolean) : void 0;
|
|
54438
|
+
const queue = getQueue2(meshId, { status });
|
|
54439
|
+
const summary = getMeshQueueStats2(meshId);
|
|
54440
|
+
return {
|
|
54441
|
+
success: true,
|
|
54442
|
+
queue,
|
|
54443
|
+
summary,
|
|
54444
|
+
sourceOfTruth: {
|
|
54445
|
+
kind: "mesh_work_queue_file",
|
|
54446
|
+
activeStatuses: ["pending", "assigned"],
|
|
54447
|
+
historicalStatuses: ["completed", "failed", "cancelled"],
|
|
54448
|
+
notes: "pending/assigned are active work; completed/failed/cancelled are historical records."
|
|
54449
|
+
}
|
|
54450
|
+
};
|
|
54451
|
+
} catch (e) {
|
|
54452
|
+
return { success: false, error: e.message };
|
|
54453
|
+
}
|
|
54454
|
+
}
|
|
54455
|
+
case "cancel_mesh_queue_task": {
|
|
54456
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
54457
|
+
const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
|
|
54458
|
+
if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
|
|
54459
|
+
try {
|
|
54460
|
+
const { cancelTask: cancelTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
54461
|
+
const reason = typeof args?.reason === "string" ? args.reason : void 0;
|
|
54462
|
+
const task = cancelTask2(meshId, taskId, { reason });
|
|
54463
|
+
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
54464
|
+
return { success: true, task };
|
|
54465
|
+
} catch (e) {
|
|
54466
|
+
return { success: false, error: e.message };
|
|
54467
|
+
}
|
|
54468
|
+
}
|
|
54469
|
+
case "requeue_mesh_queue_task": {
|
|
54470
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
54471
|
+
const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
|
|
54472
|
+
if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
|
|
54473
|
+
try {
|
|
54474
|
+
const { requeueTask: requeueTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
54475
|
+
const task = requeueTask2(meshId, taskId, {
|
|
54476
|
+
reason: typeof args?.reason === "string" ? args.reason : void 0,
|
|
54477
|
+
targetNodeId: typeof args?.targetNodeId === "string" ? args.targetNodeId.trim() : void 0,
|
|
54478
|
+
targetSessionId: typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : void 0,
|
|
54479
|
+
clearTargetNode: args?.clearTargetNode === true,
|
|
54480
|
+
clearTargetSession: args?.clearTargetSession !== false
|
|
54481
|
+
});
|
|
54482
|
+
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
54483
|
+
return { success: true, task };
|
|
54484
|
+
} catch (e) {
|
|
54485
|
+
return { success: false, error: e.message };
|
|
54486
|
+
}
|
|
54487
|
+
}
|
|
53017
54488
|
case "add_mesh_node": {
|
|
53018
54489
|
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
53019
54490
|
const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
|
|
@@ -53075,7 +54546,8 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53075
54546
|
node,
|
|
53076
54547
|
mode,
|
|
53077
54548
|
sessionIds,
|
|
53078
|
-
dryRun: args?.dryRun === true
|
|
54549
|
+
dryRun: args?.dryRun === true,
|
|
54550
|
+
source: "mesh_cleanup_sessions"
|
|
53079
54551
|
});
|
|
53080
54552
|
return result;
|
|
53081
54553
|
} catch (e) {
|
|
@@ -53105,10 +54577,61 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53105
54577
|
if (!branch) return { success: false, error: "Could not determine branch of the worktree node" };
|
|
53106
54578
|
const { stdout: baseBranchStdout } = await execFileAsync3("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
|
|
53107
54579
|
const baseBranch = baseBranchStdout.trim();
|
|
54580
|
+
const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
|
|
54581
|
+
if (validationSummary.status === "failed") {
|
|
54582
|
+
return {
|
|
54583
|
+
success: false,
|
|
54584
|
+
code: "validation_failed",
|
|
54585
|
+
convergenceStatus: "blocked_review",
|
|
54586
|
+
error: "Refinery validation gate failed; merge/refine was not attempted.",
|
|
54587
|
+
branch,
|
|
54588
|
+
into: baseBranch,
|
|
54589
|
+
validationSummary,
|
|
54590
|
+
finalBranchConvergenceState: {
|
|
54591
|
+
branch,
|
|
54592
|
+
baseBranch,
|
|
54593
|
+
merged: false,
|
|
54594
|
+
removed: false,
|
|
54595
|
+
validation: "failed",
|
|
54596
|
+
status: "blocked_review"
|
|
54597
|
+
}
|
|
54598
|
+
};
|
|
54599
|
+
}
|
|
54600
|
+
if (validationSummary.status === "skipped") {
|
|
54601
|
+
return {
|
|
54602
|
+
success: false,
|
|
54603
|
+
code: "validation_unavailable",
|
|
54604
|
+
convergenceStatus: "blocked_review",
|
|
54605
|
+
error: "Refinery validation gate is required but no allowlisted validation command was available; merge/refine was not attempted.",
|
|
54606
|
+
branch,
|
|
54607
|
+
into: baseBranch,
|
|
54608
|
+
validationSummary,
|
|
54609
|
+
finalBranchConvergenceState: {
|
|
54610
|
+
branch,
|
|
54611
|
+
baseBranch,
|
|
54612
|
+
merged: false,
|
|
54613
|
+
removed: false,
|
|
54614
|
+
validation: "unavailable",
|
|
54615
|
+
status: "blocked_review"
|
|
54616
|
+
}
|
|
54617
|
+
};
|
|
54618
|
+
}
|
|
53108
54619
|
try {
|
|
53109
54620
|
await execFileAsync3("git", ["merge", "--no-ff", branch, "-m", `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: "utf8" });
|
|
53110
54621
|
} catch (e) {
|
|
53111
|
-
return {
|
|
54622
|
+
return {
|
|
54623
|
+
success: false,
|
|
54624
|
+
error: `Merge failed (conflicts?): ${e.message}`,
|
|
54625
|
+
validationSummary,
|
|
54626
|
+
finalBranchConvergenceState: {
|
|
54627
|
+
branch,
|
|
54628
|
+
baseBranch,
|
|
54629
|
+
merged: false,
|
|
54630
|
+
removed: false,
|
|
54631
|
+
validation: "passed",
|
|
54632
|
+
status: "not_mergeable"
|
|
54633
|
+
}
|
|
54634
|
+
};
|
|
53112
54635
|
}
|
|
53113
54636
|
const removeResult = await this.execute("remove_mesh_node", {
|
|
53114
54637
|
meshId,
|
|
@@ -53121,11 +54644,27 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53121
54644
|
appendLedgerEntry2(meshId, {
|
|
53122
54645
|
kind: "node_removed",
|
|
53123
54646
|
nodeId,
|
|
53124
|
-
payload: { refined: true, mergedBranch: branch, into: baseBranch }
|
|
54647
|
+
payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary }
|
|
53125
54648
|
});
|
|
53126
54649
|
} catch {
|
|
53127
54650
|
}
|
|
53128
|
-
return {
|
|
54651
|
+
return {
|
|
54652
|
+
success: true,
|
|
54653
|
+
merged: true,
|
|
54654
|
+
branch,
|
|
54655
|
+
into: baseBranch,
|
|
54656
|
+
removeResult,
|
|
54657
|
+
validationSummary,
|
|
54658
|
+
finalBranchConvergenceState: {
|
|
54659
|
+
branch: baseBranch,
|
|
54660
|
+
mergedBranch: branch,
|
|
54661
|
+
baseBranch,
|
|
54662
|
+
merged: true,
|
|
54663
|
+
removed: removeResult?.success !== false,
|
|
54664
|
+
validation: "passed",
|
|
54665
|
+
status: removeResult?.success === false ? "merged_cleanup_failed" : "merged"
|
|
54666
|
+
}
|
|
54667
|
+
};
|
|
53129
54668
|
} catch (e) {
|
|
53130
54669
|
return { success: false, error: e.message };
|
|
53131
54670
|
}
|
|
@@ -53143,20 +54682,24 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53143
54682
|
);
|
|
53144
54683
|
let sessionCleanup;
|
|
53145
54684
|
if (node && sessionCleanupMode !== "preserve") {
|
|
53146
|
-
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
|
|
54685
|
+
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode, source: "mesh_remove_node" });
|
|
53147
54686
|
if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
|
|
53148
54687
|
}
|
|
53149
|
-
|
|
53150
|
-
|
|
53151
|
-
|
|
53152
|
-
|
|
53153
|
-
|
|
53154
|
-
|
|
53155
|
-
|
|
53156
|
-
|
|
53157
|
-
|
|
53158
|
-
|
|
54688
|
+
let worktreeCleanup;
|
|
54689
|
+
if (node?.isLocalWorktree) {
|
|
54690
|
+
const cleanupResult = await this.cleanupLocalWorktreeNode({ mesh, node, nodeId });
|
|
54691
|
+
if (cleanupResult.success === false) {
|
|
54692
|
+
return {
|
|
54693
|
+
success: false,
|
|
54694
|
+
removed: false,
|
|
54695
|
+
code: cleanupResult.code,
|
|
54696
|
+
error: cleanupResult.error,
|
|
54697
|
+
recoveryHint: cleanupResult.recoveryHint,
|
|
54698
|
+
...sessionCleanup ? { sessionCleanup } : {},
|
|
54699
|
+
worktreeCleanup: cleanupResult
|
|
54700
|
+
};
|
|
53159
54701
|
}
|
|
54702
|
+
worktreeCleanup = cleanupResult;
|
|
53160
54703
|
}
|
|
53161
54704
|
let removed = false;
|
|
53162
54705
|
if (meshRecord?.inline) {
|
|
@@ -53171,12 +54714,21 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53171
54714
|
appendLedgerEntry2(meshId, {
|
|
53172
54715
|
kind: "node_removed",
|
|
53173
54716
|
nodeId,
|
|
53174
|
-
payload: {
|
|
54717
|
+
payload: {
|
|
54718
|
+
worktree: !!node?.isLocalWorktree,
|
|
54719
|
+
sessionCleanupMode,
|
|
54720
|
+
workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
|
|
54721
|
+
daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
|
|
54722
|
+
worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0,
|
|
54723
|
+
worktreeCleanupFallback: typeof worktreeCleanup?.fallback === "string" ? worktreeCleanup.fallback : void 0,
|
|
54724
|
+
forced: worktreeCleanup?.forced === true ? true : void 0,
|
|
54725
|
+
forceFallbackReason: typeof worktreeCleanup?.reason === "string" ? worktreeCleanup.reason : void 0
|
|
54726
|
+
}
|
|
53175
54727
|
});
|
|
53176
54728
|
} catch {
|
|
53177
54729
|
}
|
|
53178
54730
|
}
|
|
53179
|
-
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {} };
|
|
54731
|
+
return { success: true, removed, ...sessionCleanup ? { sessionCleanup } : {}, ...worktreeCleanup ? { worktreeCleanup } : {} };
|
|
53180
54732
|
} catch (e) {
|
|
53181
54733
|
return { success: false, error: e.message };
|
|
53182
54734
|
}
|
|
@@ -53211,6 +54763,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53211
54763
|
workspace: result.worktreePath,
|
|
53212
54764
|
repoRoot: result.worktreePath,
|
|
53213
54765
|
daemonId: sourceNode.daemonId,
|
|
54766
|
+
machineId: sourceNode.machineId ?? sourceNode.machine_id,
|
|
53214
54767
|
userOverrides: { ...sourceNode.userOverrides || {} },
|
|
53215
54768
|
policy: { ...sourceNode.policy || {} },
|
|
53216
54769
|
isLocalWorktree: true,
|
|
@@ -53224,6 +54777,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53224
54777
|
workspace: result.worktreePath,
|
|
53225
54778
|
repoRoot: result.worktreePath,
|
|
53226
54779
|
daemonId: sourceNode.daemonId,
|
|
54780
|
+
machineId: sourceNode.machineId ?? sourceNode.machine_id,
|
|
53227
54781
|
userOverrides: { ...sourceNode.userOverrides || {} },
|
|
53228
54782
|
isLocalWorktree: true,
|
|
53229
54783
|
worktreeBranch: result.branch,
|
|
@@ -53342,6 +54896,93 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53342
54896
|
meshCoordinatorSetup: coordinatorSetup
|
|
53343
54897
|
};
|
|
53344
54898
|
}
|
|
54899
|
+
if (coordinatorSetup.kind === "cli_command") {
|
|
54900
|
+
let cliCmdSystemPrompt = "";
|
|
54901
|
+
try {
|
|
54902
|
+
cliCmdSystemPrompt = buildCoordinatorSystemPrompt2({ mesh, coordinatorCliType: cliType });
|
|
54903
|
+
} catch (error48) {
|
|
54904
|
+
const message = error48?.message || String(error48);
|
|
54905
|
+
LOG.error("MeshCoordinator", `Failed to build coordinator prompt: ${message}`);
|
|
54906
|
+
return {
|
|
54907
|
+
success: false,
|
|
54908
|
+
code: "mesh_coordinator_prompt_failed",
|
|
54909
|
+
error: `Failed to build Repo Mesh coordinator prompt: ${message}`,
|
|
54910
|
+
meshId,
|
|
54911
|
+
cliType,
|
|
54912
|
+
workspace
|
|
54913
|
+
};
|
|
54914
|
+
}
|
|
54915
|
+
try {
|
|
54916
|
+
const { execFileSync: execCmdSync } = await import("child_process");
|
|
54917
|
+
const cmdParts = coordinatorSetup.command.trim().split(/\s+/);
|
|
54918
|
+
const [regCmd, ...regArgs] = cmdParts;
|
|
54919
|
+
LOG.info("MeshCoordinator", `Running MCP registration: ${coordinatorSetup.command}`);
|
|
54920
|
+
execCmdSync(regCmd, regArgs, { stdio: "pipe", timeout: 15e3 });
|
|
54921
|
+
} catch (error48) {
|
|
54922
|
+
LOG.warn("MeshCoordinator", `MCP registration command failed (may be pre-registered): ${error48?.message || error48}`);
|
|
54923
|
+
}
|
|
54924
|
+
const cliCmdArgs = [];
|
|
54925
|
+
const cliCmdEnv = {};
|
|
54926
|
+
if (cliCmdSystemPrompt) {
|
|
54927
|
+
if (cliType === "codex-cli") {
|
|
54928
|
+
cliCmdArgs.push("-c", `developer_instructions=${JSON.stringify(cliCmdSystemPrompt)}`);
|
|
54929
|
+
} else if (cliType === "gemini-cli") {
|
|
54930
|
+
try {
|
|
54931
|
+
const { writeFileSync: wfs, existsSync: efs, readFileSync: rfs } = await import("fs");
|
|
54932
|
+
const geminiMdPath = `${workspace}/GEMINI.md`;
|
|
54933
|
+
const marker = "<!-- adhdev-mesh-coordinator-prompt -->";
|
|
54934
|
+
const markerEnd = "<!-- /adhdev-mesh-coordinator-prompt -->";
|
|
54935
|
+
const block = `${marker}
|
|
54936
|
+
${cliCmdSystemPrompt}
|
|
54937
|
+
${markerEnd}`;
|
|
54938
|
+
if (efs(geminiMdPath)) {
|
|
54939
|
+
const existing = rfs(geminiMdPath, "utf-8");
|
|
54940
|
+
const replaced = existing.replace(
|
|
54941
|
+
new RegExp(`${marker}[\\s\\S]*?${markerEnd}`, "g"),
|
|
54942
|
+
block
|
|
54943
|
+
);
|
|
54944
|
+
wfs(geminiMdPath, replaced.includes(marker) ? replaced : `${existing}
|
|
54945
|
+
|
|
54946
|
+
${block}`);
|
|
54947
|
+
} else {
|
|
54948
|
+
wfs(geminiMdPath, block);
|
|
54949
|
+
}
|
|
54950
|
+
LOG.info("MeshCoordinator", `Wrote coordinator prompt to ${workspace}/GEMINI.md`);
|
|
54951
|
+
} catch (e) {
|
|
54952
|
+
LOG.warn("MeshCoordinator", `Could not write GEMINI.md: ${e?.message || e}`);
|
|
54953
|
+
}
|
|
54954
|
+
}
|
|
54955
|
+
}
|
|
54956
|
+
const cliCmdLaunch = await this.deps.cliManager.handleCliCommand("launch_cli", {
|
|
54957
|
+
cliType,
|
|
54958
|
+
dir: workspace,
|
|
54959
|
+
cliArgs: cliCmdArgs.length > 0 ? cliCmdArgs : void 0,
|
|
54960
|
+
env: Object.keys(cliCmdEnv).length > 0 ? cliCmdEnv : void 0,
|
|
54961
|
+
settings: { meshCoordinatorFor: meshId }
|
|
54962
|
+
});
|
|
54963
|
+
if (!cliCmdLaunch?.success) {
|
|
54964
|
+
return { success: false, error: cliCmdLaunch?.error || "Failed to launch CLI session" };
|
|
54965
|
+
}
|
|
54966
|
+
LOG.info("MeshCoordinator", `Launched ${cliType} coordinator (cli_command) for mesh ${meshId}`);
|
|
54967
|
+
try {
|
|
54968
|
+
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
54969
|
+
appendLedgerEntry2(meshId, {
|
|
54970
|
+
kind: "coordinator_started",
|
|
54971
|
+
sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
|
|
54972
|
+
providerType: cliType,
|
|
54973
|
+
payload: { workspace }
|
|
54974
|
+
});
|
|
54975
|
+
} catch {
|
|
54976
|
+
}
|
|
54977
|
+
return {
|
|
54978
|
+
success: true,
|
|
54979
|
+
meshId,
|
|
54980
|
+
cliType,
|
|
54981
|
+
workspace,
|
|
54982
|
+
sessionId: cliCmdLaunch.sessionId || cliCmdLaunch.id,
|
|
54983
|
+
mcpRegistered: true
|
|
54984
|
+
};
|
|
54985
|
+
}
|
|
53345
54986
|
const configFormat = coordinatorSetup.configFormat;
|
|
53346
54987
|
if (configFormat !== "claude_mcp_json" && configFormat !== "hermes_config_yaml") {
|
|
53347
54988
|
return {
|
|
@@ -53396,9 +55037,11 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53396
55037
|
args: coordinatorSetup.mcpServer.args
|
|
53397
55038
|
};
|
|
53398
55039
|
if (args?.inlineMesh) {
|
|
55040
|
+
const modeArgIndex = coordinatorSetup.mcpServer.args.findIndex((value) => value === "--mode");
|
|
55041
|
+
const mcpTransport = modeArgIndex >= 0 ? coordinatorSetup.mcpServer.args[modeArgIndex + 1] : "ipc";
|
|
53399
55042
|
mcpServerEntry.env = {
|
|
53400
55043
|
ADHDEV_INLINE_MESH: JSON.stringify(mesh),
|
|
53401
|
-
ADHDEV_MCP_TRANSPORT: "ipc"
|
|
55044
|
+
ADHDEV_MCP_TRANSPORT: mcpTransport === "local" ? "local" : "ipc"
|
|
53402
55045
|
};
|
|
53403
55046
|
}
|
|
53404
55047
|
try {
|
|
@@ -53417,7 +55060,8 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53417
55060
|
if (hadExistingMcpConfig) {
|
|
53418
55061
|
try {
|
|
53419
55062
|
const parsedExistingMcpConfig = parseMeshCoordinatorMcpConfig(readFileSync17(mcpConfigPath, "utf-8"), configFormat);
|
|
53420
|
-
|
|
55063
|
+
const existingCoordinatorConfig = hermesManualFallback ? stripHermesCoordinatorTempModelProviderOverrides(parsedExistingMcpConfig) : parsedExistingMcpConfig;
|
|
55064
|
+
existingMcpConfig = { ...existingMcpConfig, ...existingCoordinatorConfig };
|
|
53421
55065
|
copyFileSync4(mcpConfigPath, mcpConfigPath + ".backup");
|
|
53422
55066
|
} catch (error48) {
|
|
53423
55067
|
LOG.error("MeshCoordinator", `Failed to parse existing MCP config ${mcpConfigPath}: ${error48?.message || error48}`);
|
|
@@ -56975,6 +58619,7 @@ data: ${JSON.stringify(msg.data)}
|
|
|
56975
58619
|
apiKeyName: "OpenAI/Anthropic API key"
|
|
56976
58620
|
}
|
|
56977
58621
|
];
|
|
58622
|
+
init_cli_detector();
|
|
56978
58623
|
SessionRegistry = class {
|
|
56979
58624
|
bySessionId = /* @__PURE__ */ new Map();
|
|
56980
58625
|
byManagerKey = /* @__PURE__ */ new Map();
|
|
@@ -57240,6 +58885,14 @@ function annotateRapidReadChatAdvisory(payload, options) {
|
|
|
57240
58885
|
// src/tools/mesh-tools.ts
|
|
57241
58886
|
init_dist2();
|
|
57242
58887
|
var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
58888
|
+
function readString(value) {
|
|
58889
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
58890
|
+
}
|
|
58891
|
+
var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
|
|
58892
|
+
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
58893
|
+
var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
58894
|
+
var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
|
|
58895
|
+
var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
57243
58896
|
async function refreshMeshFromDaemon(ctx) {
|
|
57244
58897
|
if (!(ctx.transport instanceof IpcTransport)) return;
|
|
57245
58898
|
try {
|
|
@@ -57260,6 +58913,302 @@ async function findNodeWithRefresh(ctx, nodeId) {
|
|
|
57260
58913
|
if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
|
|
57261
58914
|
return refreshed;
|
|
57262
58915
|
}
|
|
58916
|
+
async function findOptionalNodeWithRefresh(ctx, nodeId) {
|
|
58917
|
+
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
58918
|
+
if (hit) return hit;
|
|
58919
|
+
await refreshMeshFromDaemon(ctx);
|
|
58920
|
+
return ctx.mesh.nodes.find((n) => n.id === nodeId) ?? null;
|
|
58921
|
+
}
|
|
58922
|
+
function hasRecentDuplicateDispatch(ctx, args) {
|
|
58923
|
+
const now = Date.now();
|
|
58924
|
+
const normalizedMessage = args.message.trim();
|
|
58925
|
+
for (const task of getQueue(ctx.mesh.id)) {
|
|
58926
|
+
const timestamp2 = new Date(task.updatedAt || task.createdAt).getTime();
|
|
58927
|
+
if (!Number.isFinite(timestamp2) || now - timestamp2 > DUPLICATE_DISPATCH_WINDOW_MS) continue;
|
|
58928
|
+
if (task.targetNodeId && task.targetNodeId !== args.node_id) continue;
|
|
58929
|
+
if (task.assignedNodeId && task.assignedNodeId !== args.node_id) continue;
|
|
58930
|
+
if (args.session_id && task.targetSessionId !== args.session_id && task.assignedSessionId !== args.session_id) continue;
|
|
58931
|
+
if (task.message?.trim() === normalizedMessage) {
|
|
58932
|
+
return { duplicate: true, entry: task, source: "queue" };
|
|
58933
|
+
}
|
|
58934
|
+
}
|
|
58935
|
+
const entries = readLedgerEntries(ctx.mesh.id, { tail: 200 });
|
|
58936
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
58937
|
+
const entry = entries[i];
|
|
58938
|
+
const timestamp2 = new Date(entry.timestamp).getTime();
|
|
58939
|
+
if (Number.isFinite(timestamp2) && now - timestamp2 > DUPLICATE_DISPATCH_WINDOW_MS) break;
|
|
58940
|
+
if (entry.kind !== "task_dispatched") continue;
|
|
58941
|
+
if (entry.nodeId !== args.node_id) continue;
|
|
58942
|
+
if (args.session_id && entry.sessionId !== args.session_id) continue;
|
|
58943
|
+
if (typeof entry.payload?.message !== "string") continue;
|
|
58944
|
+
if (entry.payload.message.trim() === normalizedMessage) {
|
|
58945
|
+
return { duplicate: true, entry, source: "ledger" };
|
|
58946
|
+
}
|
|
58947
|
+
}
|
|
58948
|
+
return { duplicate: false };
|
|
58949
|
+
}
|
|
58950
|
+
function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
58951
|
+
const entries = readLedgerEntries(ctx.mesh.id, { tail: 300 });
|
|
58952
|
+
const relatedEntries = entries.filter((entry) => entry.nodeId === args.node_id || entry.sessionId === args.session_id);
|
|
58953
|
+
const completedEntries = relatedEntries.filter((entry) => entry.kind === "task_completed");
|
|
58954
|
+
const lastDispatch = [...relatedEntries].reverse().find((entry) => entry.kind === "task_dispatched");
|
|
58955
|
+
const lastTerminal = [...relatedEntries].reverse().find((entry) => entry.kind === "task_completed" || entry.kind === "task_failed" || entry.kind === "task_stalled");
|
|
58956
|
+
const lastRemoved = [...relatedEntries].reverse().find((entry) => entry.kind === "node_removed");
|
|
58957
|
+
const lastLaunch = [...relatedEntries].reverse().find((entry) => entry.kind === "session_launched");
|
|
58958
|
+
const providerSessionId = args.provider_session_id || readString(lastTerminal?.payload?.providerSessionId) || readString(lastLaunch?.payload?.providerSessionId) || readString(lastDispatch?.payload?.providerSessionId);
|
|
58959
|
+
const finalSummary = readString(lastTerminal?.payload?.finalSummary) || readString(lastTerminal?.payload?.compactSummary) || readString(lastTerminal?.payload?.summary);
|
|
58960
|
+
const ledger = {
|
|
58961
|
+
taskCompletedFound: completedEntries.length > 0,
|
|
58962
|
+
nodeRemovedFound: !!lastRemoved,
|
|
58963
|
+
providerType: lastTerminal?.providerType || lastLaunch?.providerType || lastDispatch?.providerType,
|
|
58964
|
+
providerSessionId,
|
|
58965
|
+
nodeRemovedAt: lastRemoved?.timestamp,
|
|
58966
|
+
sessionCleanupMode: readString(lastRemoved?.payload?.sessionCleanupMode),
|
|
58967
|
+
readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
|
|
58968
|
+
};
|
|
58969
|
+
if (finalSummary) {
|
|
58970
|
+
return {
|
|
58971
|
+
success: true,
|
|
58972
|
+
compact: args.compact === true,
|
|
58973
|
+
recoveredFromLedger: true,
|
|
58974
|
+
nodeId: args.node_id,
|
|
58975
|
+
sessionId: args.session_id,
|
|
58976
|
+
summary: finalSummary,
|
|
58977
|
+
ledger,
|
|
58978
|
+
messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
|
|
58979
|
+
};
|
|
58980
|
+
}
|
|
58981
|
+
return {
|
|
58982
|
+
success: false,
|
|
58983
|
+
recoverable: true,
|
|
58984
|
+
code: "mesh_removed_node_transcript_unavailable",
|
|
58985
|
+
error: `Node '${args.node_id}' is not a current member of mesh '${ctx.mesh.name}'.`,
|
|
58986
|
+
nodeId: args.node_id,
|
|
58987
|
+
sessionId: args.session_id,
|
|
58988
|
+
providerSessionId,
|
|
58989
|
+
reason: "node_not_in_current_mesh_snapshot",
|
|
58990
|
+
ledger,
|
|
58991
|
+
completedSessionSeenInLedger: ledger.taskCompletedFound,
|
|
58992
|
+
lastDispatch: lastDispatch ? {
|
|
58993
|
+
timestamp: lastDispatch.timestamp,
|
|
58994
|
+
sessionId: lastDispatch.sessionId,
|
|
58995
|
+
providerType: lastDispatch.providerType,
|
|
58996
|
+
taskId: typeof lastDispatch.payload?.taskId === "string" ? lastDispatch.payload.taskId : void 0,
|
|
58997
|
+
messagePreview: typeof lastDispatch.payload?.message === "string" ? lastDispatch.payload.message.slice(0, 500) : void 0
|
|
58998
|
+
} : null,
|
|
58999
|
+
lastTerminalEvent: lastTerminal ? {
|
|
59000
|
+
kind: lastTerminal.kind,
|
|
59001
|
+
timestamp: lastTerminal.timestamp,
|
|
59002
|
+
sessionId: lastTerminal.sessionId,
|
|
59003
|
+
providerType: lastTerminal.providerType,
|
|
59004
|
+
taskId: typeof lastTerminal.payload?.taskId === "string" ? lastTerminal.payload.taskId : void 0,
|
|
59005
|
+
payload: lastTerminal.payload
|
|
59006
|
+
} : null,
|
|
59007
|
+
nextSteps: [
|
|
59008
|
+
providerSessionId ? `Retry mesh_read_chat with provider_session_id='${providerSessionId}' on a current live node for the same daemon if one exists.` : "If the node UI shows a provider transcript id, retry mesh_read_chat/mesh_read_debug with provider_session_id.",
|
|
59009
|
+
"Use mesh_read_debug with the provider_session_id or daemon-side debug bundle locator if available.",
|
|
59010
|
+
"Check mesh_task_history for task_completed and node_removed entries before redispatching; do not resend solely because transcript recovery failed.",
|
|
59011
|
+
"If this node was removed with stop_and_delete, the runtime transcript may be gone; rely on the ledger summary/locator or ask the operator for the saved UI output."
|
|
59012
|
+
],
|
|
59013
|
+
recoveryHints: [
|
|
59014
|
+
"The worktree/node may have been removed or the mesh snapshot may be stale after task completion.",
|
|
59015
|
+
"If you have a provider_session_id, retry mesh_read_chat with that value while targeting a live node for the same daemon if available.",
|
|
59016
|
+
"Use mesh_read_debug with provider_session_id, or inspect the daemon/session-host history locator if the transcript has already been archived.",
|
|
59017
|
+
"Avoid redispatching the same task solely because read_chat could not recover the transcript; check task_history and git status first."
|
|
59018
|
+
]
|
|
59019
|
+
};
|
|
59020
|
+
}
|
|
59021
|
+
function readSessionRecordId(session) {
|
|
59022
|
+
return readString(session?.id) || readString(session?.sessionId) || readString(session?.session_id) || readString(session?.runtimeSessionId) || readString(session?.runtime_session_id) || readString(session?.instanceId) || readString(session?.instance_id);
|
|
59023
|
+
}
|
|
59024
|
+
function addSessionRecord(target, session) {
|
|
59025
|
+
if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
|
|
59026
|
+
const sessionId = readSessionRecordId(session);
|
|
59027
|
+
if (sessionId) target.add(sessionId);
|
|
59028
|
+
}
|
|
59029
|
+
function collectNodeSessionIds(node) {
|
|
59030
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
59031
|
+
const sessionArrays = [
|
|
59032
|
+
node?.sessions,
|
|
59033
|
+
node?.activeSessions,
|
|
59034
|
+
node?.active_sessions,
|
|
59035
|
+
node?.lastProbe?.sessions,
|
|
59036
|
+
node?.last_probe?.sessions,
|
|
59037
|
+
node?.lastProbe?.status?.sessions,
|
|
59038
|
+
node?.last_probe?.status?.sessions
|
|
59039
|
+
];
|
|
59040
|
+
for (const value of sessionArrays) {
|
|
59041
|
+
if (Array.isArray(value)) value.forEach((session) => addSessionRecord(sessions, session));
|
|
59042
|
+
}
|
|
59043
|
+
const sessionRecords = [
|
|
59044
|
+
node?.activeSession,
|
|
59045
|
+
node?.active_session,
|
|
59046
|
+
node?.currentSession,
|
|
59047
|
+
node?.current_session,
|
|
59048
|
+
node?.runtimeSession,
|
|
59049
|
+
node?.runtime_session,
|
|
59050
|
+
node?.session,
|
|
59051
|
+
node?.lastProbe?.activeSession,
|
|
59052
|
+
node?.last_probe?.active_session,
|
|
59053
|
+
node?.lastProbe?.currentSession,
|
|
59054
|
+
node?.last_probe?.current_session,
|
|
59055
|
+
node?.lastProbe?.session,
|
|
59056
|
+
node?.last_probe?.session
|
|
59057
|
+
];
|
|
59058
|
+
sessionRecords.forEach((session) => addSessionRecord(sessions, session));
|
|
59059
|
+
return sessions;
|
|
59060
|
+
}
|
|
59061
|
+
function buildQueueLivenessIndex(mesh) {
|
|
59062
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
59063
|
+
const nodeSessionIds = /* @__PURE__ */ new Map();
|
|
59064
|
+
for (const node of Array.isArray(mesh?.nodes) ? mesh.nodes : []) {
|
|
59065
|
+
const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
|
|
59066
|
+
if (!nodeId) continue;
|
|
59067
|
+
nodeIds.add(nodeId);
|
|
59068
|
+
const sessions = collectNodeSessionIds(node);
|
|
59069
|
+
if (sessions.size > 0) nodeSessionIds.set(nodeId, sessions);
|
|
59070
|
+
}
|
|
59071
|
+
return { nodeIds, nodeSessionIds };
|
|
59072
|
+
}
|
|
59073
|
+
function queueAssignmentStaleReason(task, liveness) {
|
|
59074
|
+
if (task?.status !== "assigned") return void 0;
|
|
59075
|
+
const nodeId = readString(task.assignedNodeId) || readString(task.nodeId) || readString(task.node_id) || readString(task.targetNodeId);
|
|
59076
|
+
const sessionId = readString(task.assignedSessionId) || readString(task.sessionId) || readString(task.session_id) || readString(task.targetSessionId);
|
|
59077
|
+
if (nodeId && liveness.nodeIds.size > 0 && !liveness.nodeIds.has(nodeId)) {
|
|
59078
|
+
return "assigned node is not present in the current mesh snapshot";
|
|
59079
|
+
}
|
|
59080
|
+
if (nodeId && sessionId && liveness.nodeSessionIds.has(nodeId) && !liveness.nodeSessionIds.get(nodeId).has(sessionId)) {
|
|
59081
|
+
return "assigned session is not live on the assigned node";
|
|
59082
|
+
}
|
|
59083
|
+
const updatedAt = new Date(task.updatedAt).getTime();
|
|
59084
|
+
const ageMs = Number.isFinite(updatedAt) ? Date.now() - updatedAt : null;
|
|
59085
|
+
if (!nodeId && ageMs !== null && ageMs >= STALE_ASSIGNED_QUEUE_MS) {
|
|
59086
|
+
return "assigned task has no assigned node metadata";
|
|
59087
|
+
}
|
|
59088
|
+
return void 0;
|
|
59089
|
+
}
|
|
59090
|
+
function buildQueueStatusSummary(queue) {
|
|
59091
|
+
const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
|
|
59092
|
+
for (const task of queue) {
|
|
59093
|
+
const status = typeof task?.status === "string" ? task.status : void 0;
|
|
59094
|
+
if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
|
|
59095
|
+
counts[status] += 1;
|
|
59096
|
+
}
|
|
59097
|
+
}
|
|
59098
|
+
return {
|
|
59099
|
+
totalCount: queue.length,
|
|
59100
|
+
activeCount: counts.pending + counts.assigned,
|
|
59101
|
+
historicalCount: counts.completed + counts.failed + counts.cancelled,
|
|
59102
|
+
counts,
|
|
59103
|
+
activeCounts: {
|
|
59104
|
+
pending: counts.pending,
|
|
59105
|
+
assigned: counts.assigned
|
|
59106
|
+
},
|
|
59107
|
+
historicalCounts: {
|
|
59108
|
+
completed: counts.completed,
|
|
59109
|
+
failed: counts.failed,
|
|
59110
|
+
cancelled: counts.cancelled
|
|
59111
|
+
}
|
|
59112
|
+
};
|
|
59113
|
+
}
|
|
59114
|
+
function normalizeQueueViewMode(value) {
|
|
59115
|
+
return value === "active" || value === "historical" || value === "all" ? value : "all";
|
|
59116
|
+
}
|
|
59117
|
+
function sanitizeQueueStatusFilter(value) {
|
|
59118
|
+
if (!Array.isArray(value)) return void 0;
|
|
59119
|
+
const statuses = value.map((item) => typeof item === "string" ? item.trim() : "").filter((status) => ACTIVE_QUEUE_STATUSES.has(status) || HISTORICAL_QUEUE_STATUSES.has(status));
|
|
59120
|
+
return statuses.length ? Array.from(new Set(statuses)) : void 0;
|
|
59121
|
+
}
|
|
59122
|
+
function filterQueueForView(queue, view, statuses) {
|
|
59123
|
+
if (statuses?.length) {
|
|
59124
|
+
const allowed = new Set(statuses);
|
|
59125
|
+
return queue.filter((task) => allowed.has(String(task?.status || "")));
|
|
59126
|
+
}
|
|
59127
|
+
if (view === "active") return queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
59128
|
+
if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
59129
|
+
return queue;
|
|
59130
|
+
}
|
|
59131
|
+
function slimQueueTask(task) {
|
|
59132
|
+
return {
|
|
59133
|
+
id: task?.id,
|
|
59134
|
+
status: task?.status,
|
|
59135
|
+
assignedNodeId: task?.assignedNodeId,
|
|
59136
|
+
assignedSessionId: task?.assignedSessionId,
|
|
59137
|
+
targetNodeId: task?.targetNodeId,
|
|
59138
|
+
targetSessionId: task?.targetSessionId,
|
|
59139
|
+
updatedAt: task?.updatedAt,
|
|
59140
|
+
staleAssigned: task?.staleAssigned === true,
|
|
59141
|
+
staleReason: task?.staleReason
|
|
59142
|
+
};
|
|
59143
|
+
}
|
|
59144
|
+
function buildQueueMaintenanceReport(queue) {
|
|
59145
|
+
const now = Date.now();
|
|
59146
|
+
const staleAssignedTasks = queue.filter((task) => task?.status === "assigned" && task?.staleAssigned === true).map(slimQueueTask);
|
|
59147
|
+
const historicalTasks = queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
59148
|
+
const oldHistoricalTasks = historicalTasks.filter((task) => {
|
|
59149
|
+
const updatedAt = new Date(task?.updatedAt).getTime();
|
|
59150
|
+
return Number.isFinite(updatedAt) && now - updatedAt >= OLD_HISTORICAL_QUEUE_RECORD_MS;
|
|
59151
|
+
}).map((task) => ({
|
|
59152
|
+
...slimQueueTask(task),
|
|
59153
|
+
cleanupClass: "old_historical_record",
|
|
59154
|
+
reason: "terminal queue record is older than the read-only maintenance threshold"
|
|
59155
|
+
}));
|
|
59156
|
+
const cleanupCandidates = [
|
|
59157
|
+
...staleAssignedTasks.map((task) => ({
|
|
59158
|
+
...task,
|
|
59159
|
+
cleanupClass: "stale_assigned",
|
|
59160
|
+
reason: typeof task.staleReason === "string" ? task.staleReason : "active assigned task does not match current live mesh node/session state",
|
|
59161
|
+
suggestedOperation: "operator_review_then_requeue_or_cancel"
|
|
59162
|
+
})),
|
|
59163
|
+
...oldHistoricalTasks.map((task) => ({
|
|
59164
|
+
...task,
|
|
59165
|
+
suggestedOperation: "operator_review_then_archive_or_keep"
|
|
59166
|
+
}))
|
|
59167
|
+
];
|
|
59168
|
+
return {
|
|
59169
|
+
readOnly: true,
|
|
59170
|
+
mutationPerformed: false,
|
|
59171
|
+
sourceOfTruth: "mesh_work_queue_file",
|
|
59172
|
+
staleAssignedDefinition: "Only active assigned queue rows are stale candidates, and only when the assigned node/session is absent from the current live mesh snapshot.",
|
|
59173
|
+
historicalDefinition: "completed/failed/cancelled rows are historical ledger records and never active assignments.",
|
|
59174
|
+
staleAssignedTasks,
|
|
59175
|
+
staleAssignedCount: staleAssignedTasks.length,
|
|
59176
|
+
historicalRecordCount: historicalTasks.length,
|
|
59177
|
+
oldHistoricalRecordCount: oldHistoricalTasks.length,
|
|
59178
|
+
cleanupCandidates,
|
|
59179
|
+
cleanupCandidateCount: cleanupCandidates.length
|
|
59180
|
+
};
|
|
59181
|
+
}
|
|
59182
|
+
function annotateQueueStaleness(queue, mesh) {
|
|
59183
|
+
const liveness = buildQueueLivenessIndex(mesh);
|
|
59184
|
+
const now = Date.now();
|
|
59185
|
+
return queue.map((task) => {
|
|
59186
|
+
const taskStatus = typeof task?.status === "string" ? task.status : void 0;
|
|
59187
|
+
const annotated = {
|
|
59188
|
+
...task,
|
|
59189
|
+
taskStatus,
|
|
59190
|
+
isActive: taskStatus ? ACTIVE_QUEUE_STATUSES.has(taskStatus) : false,
|
|
59191
|
+
isHistorical: taskStatus ? HISTORICAL_QUEUE_STATUSES.has(taskStatus) : false,
|
|
59192
|
+
dispatchedAt: task?.createdAt,
|
|
59193
|
+
...taskStatus === "assigned" ? { activeTaskId: task.id } : {},
|
|
59194
|
+
...taskStatus === "completed" || taskStatus === "failed" ? {
|
|
59195
|
+
completedAt: task.updatedAt
|
|
59196
|
+
} : {}
|
|
59197
|
+
};
|
|
59198
|
+
if (taskStatus !== "assigned") return annotated;
|
|
59199
|
+
const updatedAt = new Date(task.updatedAt).getTime();
|
|
59200
|
+
const ageMs = Number.isFinite(updatedAt) ? now - updatedAt : null;
|
|
59201
|
+
const staleReason = queueAssignmentStaleReason(task, liveness);
|
|
59202
|
+
if (!staleReason) return annotated;
|
|
59203
|
+
return {
|
|
59204
|
+
...annotated,
|
|
59205
|
+
stale: true,
|
|
59206
|
+
staleAssigned: true,
|
|
59207
|
+
staleReason,
|
|
59208
|
+
...ageMs !== null ? { assignedAgeMs: ageMs } : {}
|
|
59209
|
+
};
|
|
59210
|
+
});
|
|
59211
|
+
}
|
|
57263
59212
|
function unwrapCommandPayload(value) {
|
|
57264
59213
|
let current = value;
|
|
57265
59214
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -57272,6 +59221,26 @@ function unwrapCommandPayload(value) {
|
|
|
57272
59221
|
}
|
|
57273
59222
|
return current;
|
|
57274
59223
|
}
|
|
59224
|
+
function isTerminalSessionRecord(session) {
|
|
59225
|
+
const status = typeof session?.status === "string" ? session.status.toLowerCase() : "";
|
|
59226
|
+
const lifecycle = typeof session?.lifecycle === "string" ? session.lifecycle.toLowerCase() : "";
|
|
59227
|
+
const state = typeof session?.state === "string" ? session.state.toLowerCase() : "";
|
|
59228
|
+
return [status, lifecycle, state].some((value) => ["stopped", "failed", "terminated", "exited", "closed"].includes(value));
|
|
59229
|
+
}
|
|
59230
|
+
function isIdleSessionRecord(session) {
|
|
59231
|
+
if (isTerminalSessionRecord(session)) return false;
|
|
59232
|
+
const status = typeof session?.status === "string" ? session.status.toLowerCase() : "";
|
|
59233
|
+
const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
|
|
59234
|
+
return status === "idle" || chatStatus === "waiting_input";
|
|
59235
|
+
}
|
|
59236
|
+
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
59237
|
+
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
59238
|
+
const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
|
|
59239
|
+
const meshSessions = live.filter(
|
|
59240
|
+
(session) => session?.settings?.meshNodeFor === meshId || session?.settings?.meshNodeId === nodeId
|
|
59241
|
+
);
|
|
59242
|
+
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || live.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || live.find(matchingProvider) || live.find(isIdleSessionRecord) || live[0];
|
|
59243
|
+
}
|
|
57275
59244
|
function findNestedPayload(value, predicate) {
|
|
57276
59245
|
const seen = /* @__PURE__ */ new Set();
|
|
57277
59246
|
const stack = [{ payload: value, depth: 0 }];
|
|
@@ -57300,17 +59269,225 @@ function extractGitDiff(value) {
|
|
|
57300
59269
|
function extractLaunchPayload(value) {
|
|
57301
59270
|
return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
|
|
57302
59271
|
}
|
|
59272
|
+
function classifyMeshLaunchFailure(error48) {
|
|
59273
|
+
const message = error48 instanceof Error ? error48.message : String(error48 || "launch failed");
|
|
59274
|
+
const lower = message.toLowerCase();
|
|
59275
|
+
const p2pClassification = classifyP2pRelayFailure(error48, { command: "launch_cli" });
|
|
59276
|
+
if (p2pClassification.recoverable) {
|
|
59277
|
+
return p2pClassification;
|
|
59278
|
+
}
|
|
59279
|
+
if (lower.includes("cannot connect to daemon ipc") || lower.includes("daemon ipc command")) {
|
|
59280
|
+
return {
|
|
59281
|
+
code: "local_ipc_unavailable",
|
|
59282
|
+
reason: "local_daemon_ipc_unavailable",
|
|
59283
|
+
transport: "local_ipc",
|
|
59284
|
+
recoverable: true,
|
|
59285
|
+
retryRecommended: true,
|
|
59286
|
+
nextAction: "Check the local daemon IPC connection, then retry mesh_launch_session once after the daemon is reachable."
|
|
59287
|
+
};
|
|
59288
|
+
}
|
|
59289
|
+
if (lower.includes("timed out") || lower.includes("timeout")) {
|
|
59290
|
+
return {
|
|
59291
|
+
code: "mesh_transport_timeout",
|
|
59292
|
+
reason: "mesh_transport_timeout",
|
|
59293
|
+
transport: "mesh_transport",
|
|
59294
|
+
recoverable: true,
|
|
59295
|
+
retryRecommended: true,
|
|
59296
|
+
nextAction: "Check mesh transport health, then do one bounded retry before requeueing or relaunching the task."
|
|
59297
|
+
};
|
|
59298
|
+
}
|
|
59299
|
+
return {
|
|
59300
|
+
code: "mesh_launch_failed",
|
|
59301
|
+
reason: "provider_launch_failed",
|
|
59302
|
+
transport: "mesh_transport",
|
|
59303
|
+
recoverable: false,
|
|
59304
|
+
retryRecommended: false,
|
|
59305
|
+
nextAction: "Inspect the provider launch error and fix the underlying provider/configuration issue before retrying."
|
|
59306
|
+
};
|
|
59307
|
+
}
|
|
59308
|
+
function buildWorktreeCleanupHint(node) {
|
|
59309
|
+
if (!node.isLocalWorktree) return void 0;
|
|
59310
|
+
return {
|
|
59311
|
+
tool: "mesh_remove_node",
|
|
59312
|
+
args: { node_id: node.id, session_cleanup_mode: "preserve" },
|
|
59313
|
+
hint: `If the worktree is no longer needed, remove the orphan worktree node with mesh_remove_node(node_id: "${node.id}").`
|
|
59314
|
+
};
|
|
59315
|
+
}
|
|
59316
|
+
function buildRecoverableLaunchFailure(ctx, node, providerType, error48) {
|
|
59317
|
+
const message = error48 instanceof Error ? error48.message : String(error48 || "launch failed");
|
|
59318
|
+
const classified = classifyMeshLaunchFailure(error48);
|
|
59319
|
+
const cleanup = buildWorktreeCleanupHint(node);
|
|
59320
|
+
return {
|
|
59321
|
+
success: false,
|
|
59322
|
+
recoverable: classified.recoverable,
|
|
59323
|
+
code: classified.code,
|
|
59324
|
+
reason: classified.reason,
|
|
59325
|
+
transport: classified.transport,
|
|
59326
|
+
retryRecommended: classified.retryRecommended,
|
|
59327
|
+
nextAction: classified.nextAction,
|
|
59328
|
+
...classified.noFallbackReason ? { noFallbackReason: classified.noFallbackReason } : {},
|
|
59329
|
+
error: message,
|
|
59330
|
+
meshId: ctx.mesh.id,
|
|
59331
|
+
nodeId: node.id,
|
|
59332
|
+
daemonId: node.daemonId,
|
|
59333
|
+
workspace: node.workspace,
|
|
59334
|
+
isLocalWorktree: node.isLocalWorktree === true,
|
|
59335
|
+
worktreeBranch: node.worktreeBranch,
|
|
59336
|
+
clonedFromNodeId: node.clonedFromNodeId,
|
|
59337
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
59338
|
+
retryHint: `Retry mesh_launch_session(node_id: "${node.id}"${providerType ? `, type: "${providerType}"` : ""}) after daemon mesh transport/P2P is healthy.`,
|
|
59339
|
+
...cleanup ? { cleanup } : {},
|
|
59340
|
+
nextStepHints: [
|
|
59341
|
+
`Retry mesh_launch_session(node_id: "${node.id}"${providerType ? `, type: "${providerType}"` : ""}) after checking daemon/P2P health.`,
|
|
59342
|
+
...cleanup ? [`Cleanup orphan worktree node with mesh_remove_node(node_id: "${node.id}") if retry is not desired.`] : [],
|
|
59343
|
+
"Run mesh_status to see the degraded reason and recovery hints before redispatching work."
|
|
59344
|
+
]
|
|
59345
|
+
};
|
|
59346
|
+
}
|
|
59347
|
+
function recordRecoverableLaunchFailure(ctx, node, providerType, error48) {
|
|
59348
|
+
const failure2 = buildRecoverableLaunchFailure(ctx, node, providerType, error48);
|
|
59349
|
+
try {
|
|
59350
|
+
appendLedgerEntry(ctx.mesh.id, {
|
|
59351
|
+
kind: "recovery_attempted",
|
|
59352
|
+
nodeId: node.id,
|
|
59353
|
+
providerType,
|
|
59354
|
+
payload: {
|
|
59355
|
+
event: "session_launch_failed",
|
|
59356
|
+
...failure2
|
|
59357
|
+
}
|
|
59358
|
+
});
|
|
59359
|
+
} catch {
|
|
59360
|
+
}
|
|
59361
|
+
return failure2;
|
|
59362
|
+
}
|
|
59363
|
+
function getLatestActiveLaunchFailure(meshId, nodeId) {
|
|
59364
|
+
const entries = readLedgerEntries(meshId, { tail: 200 });
|
|
59365
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
59366
|
+
const entry = entries[i];
|
|
59367
|
+
if (entry.nodeId !== nodeId) continue;
|
|
59368
|
+
if (entry.kind === "session_launched" || entry.kind === "node_removed") return null;
|
|
59369
|
+
if (entry.kind === "recovery_attempted" && entry.payload?.event === "session_launch_failed") {
|
|
59370
|
+
return { timestamp: entry.timestamp, ...entry.payload };
|
|
59371
|
+
}
|
|
59372
|
+
}
|
|
59373
|
+
return null;
|
|
59374
|
+
}
|
|
59375
|
+
function buildCoordinatorP2pRelayFailure(error48, context) {
|
|
59376
|
+
const payload = buildP2pRelayFailurePayload(error48, {
|
|
59377
|
+
command: context.command,
|
|
59378
|
+
targetDaemonId: context.targetDaemonId
|
|
59379
|
+
});
|
|
59380
|
+
return {
|
|
59381
|
+
...payload,
|
|
59382
|
+
...context.nodeId ? { nodeId: context.nodeId } : {},
|
|
59383
|
+
...context.sessionId ? { sessionId: context.sessionId } : {},
|
|
59384
|
+
retryHint: payload.retryRecommended ? payload.nextAction : "Do not retry as a P2P transport recovery; inspect the command/provider error first."
|
|
59385
|
+
};
|
|
59386
|
+
}
|
|
59387
|
+
async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
59388
|
+
const transport = ctx.transport;
|
|
59389
|
+
const daemonId = node.daemonId;
|
|
59390
|
+
let sessionId = args.session_id?.trim() || "";
|
|
59391
|
+
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
59392
|
+
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
59393
|
+
if (!sessionId) {
|
|
59394
|
+
try {
|
|
59395
|
+
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
59396
|
+
const innerResult = relayResult?.result ?? relayResult;
|
|
59397
|
+
const statusObj = innerResult?.status ?? innerResult;
|
|
59398
|
+
const sessions = Array.isArray(statusObj?.sessions) ? statusObj.sessions : [];
|
|
59399
|
+
const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
|
|
59400
|
+
if (targetSession?.id || targetSession?.sessionId) {
|
|
59401
|
+
sessionId = targetSession.id || targetSession.sessionId;
|
|
59402
|
+
if (!resolvedProviderType) {
|
|
59403
|
+
resolvedProviderType = targetSession.providerType || targetSession.cliType || "";
|
|
59404
|
+
}
|
|
59405
|
+
}
|
|
59406
|
+
} catch (e) {
|
|
59407
|
+
}
|
|
59408
|
+
}
|
|
59409
|
+
if (!resolvedProviderType) {
|
|
59410
|
+
return { success: false, error: `Cannot dispatch to remote node '${node.id}': providerType unknown. Set providerPriority on the node policy or call mesh_launch_session first.` };
|
|
59411
|
+
}
|
|
59412
|
+
try {
|
|
59413
|
+
const dispatchResult = await transport.meshCommand(daemonId, "agent_command", {
|
|
59414
|
+
...sessionId ? { targetSessionId: sessionId } : {},
|
|
59415
|
+
agentType: resolvedProviderType,
|
|
59416
|
+
cliType: resolvedProviderType,
|
|
59417
|
+
action: "send_chat",
|
|
59418
|
+
message: args.message
|
|
59419
|
+
});
|
|
59420
|
+
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
59421
|
+
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
59422
|
+
const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
|
|
59423
|
+
const errorMessage = dispatchPayload?.error || dispatchResult?.error || "agent_command rejected the task";
|
|
59424
|
+
return {
|
|
59425
|
+
...buildCoordinatorP2pRelayFailure(source?.error || errorMessage, {
|
|
59426
|
+
command: "agent_command",
|
|
59427
|
+
targetDaemonId: daemonId,
|
|
59428
|
+
nodeId: node.id,
|
|
59429
|
+
sessionId
|
|
59430
|
+
}),
|
|
59431
|
+
...source && typeof source === "object" ? source : {},
|
|
59432
|
+
success: false,
|
|
59433
|
+
error: `P2P dispatch failed: ${errorMessage}`
|
|
59434
|
+
};
|
|
59435
|
+
}
|
|
59436
|
+
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
|
|
59437
|
+
} catch (e) {
|
|
59438
|
+
const errorMessage = e?.message || String(e);
|
|
59439
|
+
return {
|
|
59440
|
+
...buildCoordinatorP2pRelayFailure(e, {
|
|
59441
|
+
command: "agent_command",
|
|
59442
|
+
targetDaemonId: daemonId,
|
|
59443
|
+
nodeId: node.id,
|
|
59444
|
+
sessionId
|
|
59445
|
+
}),
|
|
59446
|
+
error: `P2P dispatch failed: ${errorMessage}`
|
|
59447
|
+
};
|
|
59448
|
+
}
|
|
59449
|
+
}
|
|
57303
59450
|
function resolveCoordinatorNode(ctx) {
|
|
57304
59451
|
const preferredNodeId = typeof ctx.mesh.coordinator?.preferredNodeId === "string" ? ctx.mesh.coordinator.preferredNodeId.trim() : "";
|
|
57305
59452
|
if (preferredNodeId) {
|
|
57306
59453
|
const preferred = ctx.mesh.nodes.find((n) => n.id === preferredNodeId && typeof n.daemonId === "string" && n.daemonId.trim());
|
|
57307
59454
|
if (preferred) return preferred;
|
|
57308
59455
|
}
|
|
59456
|
+
if (ctx.localMachineId) {
|
|
59457
|
+
const byMachine = ctx.mesh.nodes.find((n) => readNodeMachineId(n) === ctx.localMachineId);
|
|
59458
|
+
if (byMachine) return byMachine;
|
|
59459
|
+
}
|
|
57309
59460
|
if (ctx.localDaemonId) {
|
|
57310
|
-
return ctx.mesh.nodes.find((n) => n
|
|
59461
|
+
return ctx.mesh.nodes.find((n) => readNodeDaemonId(n) === ctx.localDaemonId);
|
|
57311
59462
|
}
|
|
57312
59463
|
return void 0;
|
|
57313
59464
|
}
|
|
59465
|
+
function readNodeMachineId(node) {
|
|
59466
|
+
return readString(node.machineId) || readString(node.machine_id);
|
|
59467
|
+
}
|
|
59468
|
+
function readNodeDaemonId(node) {
|
|
59469
|
+
return readString(node.daemonId) || readString(node.daemon_id);
|
|
59470
|
+
}
|
|
59471
|
+
function isDirectLocalNode(ctx, node) {
|
|
59472
|
+
const machineId = readNodeMachineId(node);
|
|
59473
|
+
const daemonId = readNodeDaemonId(node);
|
|
59474
|
+
return Boolean(
|
|
59475
|
+
ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId
|
|
59476
|
+
);
|
|
59477
|
+
}
|
|
59478
|
+
function findClonedFromNode(ctx, node) {
|
|
59479
|
+
const clonedFromNodeId = readString(node.clonedFromNodeId) || readString(node.cloned_from_node_id);
|
|
59480
|
+
if (!clonedFromNodeId) return void 0;
|
|
59481
|
+
return ctx.mesh.nodes.find((n) => n.id === clonedFromNodeId || n.nodeId === clonedFromNodeId || n.node_id === clonedFromNodeId);
|
|
59482
|
+
}
|
|
59483
|
+
function isLocalControlPlaneNode(ctx, node) {
|
|
59484
|
+
if (isDirectLocalNode(ctx, node)) return true;
|
|
59485
|
+
if (node.isLocalWorktree === true) {
|
|
59486
|
+
const sourceNode = findClonedFromNode(ctx, node);
|
|
59487
|
+
if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return true;
|
|
59488
|
+
}
|
|
59489
|
+
return false;
|
|
59490
|
+
}
|
|
57314
59491
|
function meshSessionCacheKey(nodeId, runtimeSessionId) {
|
|
57315
59492
|
return `${nodeId}:${runtimeSessionId}`;
|
|
57316
59493
|
}
|
|
@@ -57394,8 +59571,116 @@ function getNodeLaunchReadiness(node) {
|
|
|
57394
59571
|
launchBlockedMessage: missingProviderPriorityMessage(node.id)
|
|
57395
59572
|
};
|
|
57396
59573
|
}
|
|
59574
|
+
function readNumeric(value, fallback = 0) {
|
|
59575
|
+
const parsed = Number(value);
|
|
59576
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
59577
|
+
}
|
|
59578
|
+
function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
59579
|
+
const defaultBranch = readString(mesh.defaultBranch) ?? "main";
|
|
59580
|
+
const branch = readString(status?.branch) ?? readString(node.worktreeBranch) ?? null;
|
|
59581
|
+
const ahead = readNumeric(status?.ahead);
|
|
59582
|
+
const behind = readNumeric(status?.behind);
|
|
59583
|
+
const upstream = readString(status?.upstream) ?? null;
|
|
59584
|
+
const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
|
|
59585
|
+
const base = {
|
|
59586
|
+
defaultBranch,
|
|
59587
|
+
branch,
|
|
59588
|
+
upstream,
|
|
59589
|
+
ahead,
|
|
59590
|
+
behind,
|
|
59591
|
+
isWorktree: node.isLocalWorktree === true,
|
|
59592
|
+
isDefaultBranch: branch === defaultBranch
|
|
59593
|
+
};
|
|
59594
|
+
if (status?.isGitRepo !== true) {
|
|
59595
|
+
return {
|
|
59596
|
+
...base,
|
|
59597
|
+
status: "blocked_review",
|
|
59598
|
+
needsConvergence: true,
|
|
59599
|
+
reason: "git_status_unavailable",
|
|
59600
|
+
nextStep: `Resolve git status for node '${node.id}' before marking the task complete.`
|
|
59601
|
+
};
|
|
59602
|
+
}
|
|
59603
|
+
if (!branch) {
|
|
59604
|
+
return {
|
|
59605
|
+
...base,
|
|
59606
|
+
status: "blocked_review",
|
|
59607
|
+
needsConvergence: true,
|
|
59608
|
+
reason: "branch_unknown",
|
|
59609
|
+
nextStep: `Inspect node '${node.id}' git branch before deciding whether it is merged to ${defaultBranch}.`
|
|
59610
|
+
};
|
|
59611
|
+
}
|
|
59612
|
+
if (hasConflicts || dirty || uncommittedChanges > 0) {
|
|
59613
|
+
return {
|
|
59614
|
+
...base,
|
|
59615
|
+
status: "not_mergeable",
|
|
59616
|
+
needsConvergence: true,
|
|
59617
|
+
reason: hasConflicts ? "conflicts_present" : "dirty_workspace",
|
|
59618
|
+
nextStep: `Commit, checkpoint, or resolve node '${node.id}' before any main convergence step.`
|
|
59619
|
+
};
|
|
59620
|
+
}
|
|
59621
|
+
if (branch === defaultBranch) {
|
|
59622
|
+
if (ahead > 0 || behind > 0) {
|
|
59623
|
+
return {
|
|
59624
|
+
...base,
|
|
59625
|
+
status: "blocked_review",
|
|
59626
|
+
needsConvergence: true,
|
|
59627
|
+
reason: "default_branch_not_even_with_upstream",
|
|
59628
|
+
nextStep: `Bring ${defaultBranch} even with its upstream before declaring convergence complete.`
|
|
59629
|
+
};
|
|
59630
|
+
}
|
|
59631
|
+
return {
|
|
59632
|
+
...base,
|
|
59633
|
+
status: "merged_to_main",
|
|
59634
|
+
needsConvergence: false,
|
|
59635
|
+
reason: "clean_default_branch",
|
|
59636
|
+
nextStep: null
|
|
59637
|
+
};
|
|
59638
|
+
}
|
|
59639
|
+
if (node.isLocalWorktree) {
|
|
59640
|
+
return {
|
|
59641
|
+
...base,
|
|
59642
|
+
status: "cleanup_candidate",
|
|
59643
|
+
needsConvergence: true,
|
|
59644
|
+
reason: "clean_non_default_worktree_branch",
|
|
59645
|
+
nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
|
|
59646
|
+
};
|
|
59647
|
+
}
|
|
59648
|
+
if (!upstream || ahead > 0 || behind > 0) {
|
|
59649
|
+
return {
|
|
59650
|
+
...base,
|
|
59651
|
+
status: "blocked_review",
|
|
59652
|
+
needsConvergence: true,
|
|
59653
|
+
reason: !upstream ? "feature_branch_missing_upstream" : "feature_branch_not_even_with_upstream",
|
|
59654
|
+
nextStep: `Push or reconcile branch '${branch}', then merge it into ${defaultBranch} or mark it not_mergeable with a reason.`
|
|
59655
|
+
};
|
|
59656
|
+
}
|
|
59657
|
+
return {
|
|
59658
|
+
...base,
|
|
59659
|
+
status: "pushed_feature_branch_needs_merge",
|
|
59660
|
+
needsConvergence: true,
|
|
59661
|
+
reason: "clean_non_default_branch",
|
|
59662
|
+
nextStep: `Review and merge branch '${branch}' into ${defaultBranch}; do not report the task as fully complete while it remains off main.`
|
|
59663
|
+
};
|
|
59664
|
+
}
|
|
59665
|
+
function summarizeBranchConvergence(nodes) {
|
|
59666
|
+
const followUps = nodes.filter((node) => node?.branchConvergence?.needsConvergence === true).map((node) => ({
|
|
59667
|
+
nodeId: node.nodeId,
|
|
59668
|
+
workspace: node.workspace,
|
|
59669
|
+
branch: node.branchConvergence.branch,
|
|
59670
|
+
status: node.branchConvergence.status,
|
|
59671
|
+
reason: node.branchConvergence.reason,
|
|
59672
|
+
nextStep: node.branchConvergence.nextStep
|
|
59673
|
+
}));
|
|
59674
|
+
return {
|
|
59675
|
+
needsFollowUp: followUps.length > 0,
|
|
59676
|
+
unresolvedCount: followUps.length,
|
|
59677
|
+
requiredFinalStates: ["merged_to_main", "pushed_feature_branch_needs_merge", "blocked_review", "cleanup_candidate", "not_mergeable"],
|
|
59678
|
+
followUps
|
|
59679
|
+
};
|
|
59680
|
+
}
|
|
57397
59681
|
async function commandForNode(ctx, node, command, args = {}) {
|
|
57398
|
-
|
|
59682
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
59683
|
+
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
57399
59684
|
return ctx.transport.meshCommand(node.daemonId, command, args);
|
|
57400
59685
|
}
|
|
57401
59686
|
if (isLocalTransport(ctx.transport)) {
|
|
@@ -57403,12 +59688,25 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
57403
59688
|
}
|
|
57404
59689
|
throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
|
|
57405
59690
|
}
|
|
59691
|
+
function isP2pTransportUnavailableError(error48) {
|
|
59692
|
+
return isP2pRelayTransportFailure(error48);
|
|
59693
|
+
}
|
|
59694
|
+
function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
|
|
59695
|
+
return {
|
|
59696
|
+
meshId: ctx.mesh.id,
|
|
59697
|
+
nodeId,
|
|
59698
|
+
...sessionCleanupMode ? { sessionCleanupMode } : {},
|
|
59699
|
+
inlineMesh: ctx.mesh
|
|
59700
|
+
};
|
|
59701
|
+
}
|
|
57406
59702
|
var MESH_STATUS_TOOL = {
|
|
57407
59703
|
name: "mesh_status",
|
|
57408
|
-
description: "Get the current status of all nodes in the repo mesh \u2014 health, git state, active sessions. Use this to decide which node to send work to.",
|
|
59704
|
+
description: "Get the current status of all nodes in the repo mesh \u2014 health, git state, active sessions, recovery hints, and recommended next steps. Use this to decide which node to send work to or how to recover from failures.",
|
|
57409
59705
|
inputSchema: {
|
|
57410
59706
|
type: "object",
|
|
57411
|
-
properties: {
|
|
59707
|
+
properties: {
|
|
59708
|
+
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
|
|
59709
|
+
}
|
|
57412
59710
|
}
|
|
57413
59711
|
};
|
|
57414
59712
|
var MESH_LIST_NODES_TOOL = {
|
|
@@ -57416,7 +59714,9 @@ var MESH_LIST_NODES_TOOL = {
|
|
|
57416
59714
|
description: "List all nodes in the mesh with their capabilities, platform, and workspace paths.",
|
|
57417
59715
|
inputSchema: {
|
|
57418
59716
|
type: "object",
|
|
57419
|
-
properties: {
|
|
59717
|
+
properties: {
|
|
59718
|
+
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
|
|
59719
|
+
}
|
|
57420
59720
|
}
|
|
57421
59721
|
};
|
|
57422
59722
|
var MESH_ENQUEUE_TASK_TOOL = {
|
|
@@ -57432,18 +59732,51 @@ var MESH_ENQUEUE_TASK_TOOL = {
|
|
|
57432
59732
|
};
|
|
57433
59733
|
var MESH_VIEW_QUEUE_TOOL = {
|
|
57434
59734
|
name: "mesh_view_queue",
|
|
57435
|
-
description: "View the
|
|
59735
|
+
description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records.",
|
|
57436
59736
|
inputSchema: {
|
|
57437
59737
|
type: "object",
|
|
57438
59738
|
properties: {
|
|
57439
59739
|
status: {
|
|
57440
59740
|
type: "array",
|
|
57441
59741
|
items: { type: "string" },
|
|
57442
|
-
description: "
|
|
59742
|
+
description: "Explicit row filter by task status: pending, assigned, completed, failed, cancelled. Source-of-truth counts remain unfiltered; visible* counts describe returned rows."
|
|
59743
|
+
},
|
|
59744
|
+
view: {
|
|
59745
|
+
type: "string",
|
|
59746
|
+
enum: ["all", "active", "historical"],
|
|
59747
|
+
description: "Optional row view. active returns pending/assigned rows, historical returns completed/failed/cancelled rows, all returns every persisted queue row. Defaults to all for compatibility."
|
|
57443
59748
|
}
|
|
57444
59749
|
}
|
|
57445
59750
|
}
|
|
57446
59751
|
};
|
|
59752
|
+
var MESH_QUEUE_CANCEL_TOOL = {
|
|
59753
|
+
name: "mesh_queue_cancel",
|
|
59754
|
+
description: "Cancel a pending/assigned/completed/failed mesh queue task without deleting audit history. Use this to retire stale queue items that target dead sessions.",
|
|
59755
|
+
inputSchema: {
|
|
59756
|
+
type: "object",
|
|
59757
|
+
properties: {
|
|
59758
|
+
task_id: { type: "string", description: "Queue task ID to cancel." },
|
|
59759
|
+
reason: { type: "string", description: "Optional operator-visible reason for cancellation." }
|
|
59760
|
+
},
|
|
59761
|
+
required: ["task_id"]
|
|
59762
|
+
}
|
|
59763
|
+
};
|
|
59764
|
+
var MESH_QUEUE_REQUEUE_TOOL = {
|
|
59765
|
+
name: "mesh_queue_requeue",
|
|
59766
|
+
description: "Return a mesh queue task to pending for retry. By default clears stale assigned owner and target session so another live session can claim it.",
|
|
59767
|
+
inputSchema: {
|
|
59768
|
+
type: "object",
|
|
59769
|
+
properties: {
|
|
59770
|
+
task_id: { type: "string", description: "Queue task ID to requeue." },
|
|
59771
|
+
reason: { type: "string", description: "Optional operator-visible reason for requeueing." },
|
|
59772
|
+
target_node_id: { type: "string", description: "Optional replacement target node ID." },
|
|
59773
|
+
target_session_id: { type: "string", description: "Optional replacement target runtime session ID." },
|
|
59774
|
+
clear_target_node: { type: "boolean", description: "When true, remove any existing target node constraint." },
|
|
59775
|
+
keep_target_session: { type: "boolean", description: "When true, preserve an existing target session if target_session_id is not provided. Defaults false to avoid stale session targets." }
|
|
59776
|
+
},
|
|
59777
|
+
required: ["task_id"]
|
|
59778
|
+
}
|
|
59779
|
+
};
|
|
57447
59780
|
var MESH_SEND_TASK_TOOL = {
|
|
57448
59781
|
name: "mesh_send_task",
|
|
57449
59782
|
description: "Legacy push-based task assignment. Enqueues a task specifically targeted at a given node. The node will pull it immediately if idle.",
|
|
@@ -57597,6 +59930,20 @@ var MESH_TASK_HISTORY_TOOL = {
|
|
|
57597
59930
|
}
|
|
57598
59931
|
}
|
|
57599
59932
|
};
|
|
59933
|
+
var MESH_RECONCILE_LEDGER_TOOL = {
|
|
59934
|
+
name: "mesh_reconcile_ledger",
|
|
59935
|
+
description: "Reconcile daemon-local mesh ledgers by querying bounded ledger slices over P2P/DataChannel and importing missing entries into the coordinator local JSONL ledger. Cloud/D1 is not used as a ledger source of truth.",
|
|
59936
|
+
inputSchema: {
|
|
59937
|
+
type: "object",
|
|
59938
|
+
properties: {
|
|
59939
|
+
node_ids: { type: "array", items: { type: "string" }, description: "Optional node IDs to query. Defaults to all mesh nodes." },
|
|
59940
|
+
limit: { type: "number", description: "Bounded slice size per node. Defaults to 100 and is clamped by daemon-core." },
|
|
59941
|
+
after_id: { type: "string", description: "Optional cursor entry ID; remote slices return entries strictly after this ID when present." },
|
|
59942
|
+
since: { type: "string", description: "Optional ISO timestamp lower bound for queried entries." },
|
|
59943
|
+
import_entries: { type: "boolean", description: "When false, query and report evidence without importing remote entries. Defaults true." }
|
|
59944
|
+
}
|
|
59945
|
+
}
|
|
59946
|
+
};
|
|
57600
59947
|
var MESH_REFINE_NODE_TOOL = {
|
|
57601
59948
|
name: "mesh_refine_node",
|
|
57602
59949
|
description: "The Refinery: Automatically validate and merge a completed worktree node back into its base branch. This tool automates the validation gate and merge queue step. It will merge the node's branch into its base branch and cleanly remove the worktree node and its sessions.",
|
|
@@ -57613,6 +59960,8 @@ var ALL_MESH_TOOLS = [
|
|
|
57613
59960
|
MESH_LIST_NODES_TOOL,
|
|
57614
59961
|
MESH_ENQUEUE_TASK_TOOL,
|
|
57615
59962
|
MESH_VIEW_QUEUE_TOOL,
|
|
59963
|
+
MESH_QUEUE_CANCEL_TOOL,
|
|
59964
|
+
MESH_QUEUE_REQUEUE_TOOL,
|
|
57616
59965
|
MESH_SEND_TASK_TOOL,
|
|
57617
59966
|
MESH_READ_CHAT_TOOL,
|
|
57618
59967
|
MESH_READ_DEBUG_TOOL,
|
|
@@ -57624,12 +59973,14 @@ var ALL_MESH_TOOLS = [
|
|
|
57624
59973
|
MESH_REMOVE_NODE_TOOL,
|
|
57625
59974
|
MESH_REFINE_NODE_TOOL,
|
|
57626
59975
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
57627
|
-
MESH_TASK_HISTORY_TOOL
|
|
59976
|
+
MESH_TASK_HISTORY_TOOL,
|
|
59977
|
+
MESH_RECONCILE_LEDGER_TOOL
|
|
57628
59978
|
];
|
|
57629
59979
|
async function meshStatus(ctx) {
|
|
57630
59980
|
await refreshMeshFromDaemon(ctx);
|
|
57631
59981
|
const { mesh, transport } = ctx;
|
|
57632
59982
|
const results = [];
|
|
59983
|
+
const ledgerSummary = getLedgerSummary(mesh.id);
|
|
57633
59984
|
for (const node of mesh.nodes) {
|
|
57634
59985
|
const entry = {
|
|
57635
59986
|
nodeId: node.id,
|
|
@@ -57646,6 +59997,7 @@ async function meshStatus(ctx) {
|
|
|
57646
59997
|
entry.branch = status?.branch;
|
|
57647
59998
|
entry.isDirty = dirty;
|
|
57648
59999
|
entry.uncommittedChanges = uncommittedChanges;
|
|
60000
|
+
entry.branchConvergence = buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges);
|
|
57649
60001
|
} else if (isLocalTransport(transport)) {
|
|
57650
60002
|
const statusResult = await commandForNode(ctx, node, "git_status", { workspace: node.workspace });
|
|
57651
60003
|
const status = extractGitStatus(statusResult);
|
|
@@ -57655,13 +60007,70 @@ async function meshStatus(ctx) {
|
|
|
57655
60007
|
entry.branch = status?.branch;
|
|
57656
60008
|
entry.isDirty = dirty;
|
|
57657
60009
|
entry.uncommittedChanges = uncommittedChanges;
|
|
60010
|
+
entry.branchConvergence = buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges);
|
|
57658
60011
|
} else {
|
|
57659
60012
|
entry.health = "unknown";
|
|
57660
60013
|
entry.note = "No daemonId available for cloud status probe";
|
|
57661
60014
|
}
|
|
57662
60015
|
} catch (e) {
|
|
60016
|
+
const failure2 = buildCoordinatorP2pRelayFailure(e, {
|
|
60017
|
+
command: "git_status",
|
|
60018
|
+
targetDaemonId: node.daemonId,
|
|
60019
|
+
nodeId: node.id
|
|
60020
|
+
});
|
|
57663
60021
|
entry.health = "degraded";
|
|
57664
|
-
entry.error =
|
|
60022
|
+
entry.error = failure2.error;
|
|
60023
|
+
entry.degradedReason = failure2.recoverable ? "p2p_relay_failure" : "git_status_unavailable";
|
|
60024
|
+
Object.assign(entry, {
|
|
60025
|
+
code: failure2.code,
|
|
60026
|
+
transport: failure2.transport,
|
|
60027
|
+
recoverable: failure2.recoverable,
|
|
60028
|
+
retryRecommended: failure2.retryRecommended,
|
|
60029
|
+
nextAction: failure2.nextAction,
|
|
60030
|
+
noFallbackReason: failure2.noFallbackReason
|
|
60031
|
+
});
|
|
60032
|
+
}
|
|
60033
|
+
const recoveryContext = getSessionRecoveryContext(mesh.id, { nodeId: node.id });
|
|
60034
|
+
if (recoveryContext.consecutiveNodeFailures > 0) {
|
|
60035
|
+
entry.recoveryHints = {
|
|
60036
|
+
consecutiveFailures: recoveryContext.consecutiveNodeFailures,
|
|
60037
|
+
lastTaskMessage: recoveryContext.lastTaskMessage,
|
|
60038
|
+
advice: recoveryContext.advice,
|
|
60039
|
+
retryRecommended: recoveryContext.retryRecommended
|
|
60040
|
+
};
|
|
60041
|
+
}
|
|
60042
|
+
const activeLaunchFailure = getLatestActiveLaunchFailure(mesh.id, node.id);
|
|
60043
|
+
if (activeLaunchFailure && node.isLocalWorktree) {
|
|
60044
|
+
entry.health = "degraded";
|
|
60045
|
+
entry.degradedReason = "worktree_launch_failed";
|
|
60046
|
+
entry.launchReady = false;
|
|
60047
|
+
entry.launchBlockedReason = activeLaunchFailure.code || "mesh_launch_failed";
|
|
60048
|
+
entry.launchBlockedMessage = activeLaunchFailure.error || "Previous worktree session launch failed";
|
|
60049
|
+
entry.lastLaunchFailure = activeLaunchFailure;
|
|
60050
|
+
}
|
|
60051
|
+
const nextStepHints = [];
|
|
60052
|
+
if (entry.degradedReason === "worktree_launch_failed") {
|
|
60053
|
+
nextStepHints.push(`Retry mesh_launch_session(node_id: "${node.id}") after daemon mesh transport/P2P is healthy.`);
|
|
60054
|
+
nextStepHints.push(`If retry is not desired, cleanup the orphan worktree node with mesh_remove_node(node_id: "${node.id}").`);
|
|
60055
|
+
} else if (entry.health === "online" && node.isLocalWorktree) {
|
|
60056
|
+
nextStepHints.push(`Merge worktree to base via mesh_refine_node(node_id: "${node.id}")`);
|
|
60057
|
+
} else if (entry.health === "dirty") {
|
|
60058
|
+
nextStepHints.push(`Commit changes via mesh_checkpoint(node_id: "${node.id}", message: "...")`);
|
|
60059
|
+
} else if (entry.health === "degraded" && entry.error?.includes("git")) {
|
|
60060
|
+
nextStepHints.push("Initialize git repository or check workspace path.");
|
|
60061
|
+
}
|
|
60062
|
+
if (entry.branchConvergence?.needsConvergence === true && entry.branchConvergence.nextStep) {
|
|
60063
|
+
nextStepHints.push(String(entry.branchConvergence.nextStep));
|
|
60064
|
+
}
|
|
60065
|
+
if (recoveryContext.consecutiveNodeFailures > 0) {
|
|
60066
|
+
if (recoveryContext.retryRecommended) {
|
|
60067
|
+
nextStepHints.push(`Retry task on this node or launch a fresh session.`);
|
|
60068
|
+
} else {
|
|
60069
|
+
nextStepHints.push(`Consider reassigning work to a different node.`);
|
|
60070
|
+
}
|
|
60071
|
+
}
|
|
60072
|
+
if (nextStepHints.length > 0) {
|
|
60073
|
+
entry.nextStepHints = nextStepHints;
|
|
57665
60074
|
}
|
|
57666
60075
|
const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
|
|
57667
60076
|
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
@@ -57673,10 +60082,11 @@ async function meshStatus(ctx) {
|
|
|
57673
60082
|
repoIdentity: mesh.repoIdentity,
|
|
57674
60083
|
policy: mesh.policy,
|
|
57675
60084
|
refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
57676
|
-
nodes: results
|
|
60085
|
+
nodes: results,
|
|
60086
|
+
branchConvergenceSummary: summarizeBranchConvergence(results)
|
|
57677
60087
|
};
|
|
57678
60088
|
try {
|
|
57679
|
-
response.ledgerSummary =
|
|
60089
|
+
response.ledgerSummary = ledgerSummary;
|
|
57680
60090
|
} catch {
|
|
57681
60091
|
}
|
|
57682
60092
|
if (ctx.transport instanceof IpcTransport) {
|
|
@@ -57699,6 +60109,84 @@ async function meshTaskHistory(ctx, args) {
|
|
|
57699
60109
|
const summary = getLedgerSummary(mesh.id);
|
|
57700
60110
|
return JSON.stringify({ meshId: mesh.id, entries, summary }, null, 2);
|
|
57701
60111
|
}
|
|
60112
|
+
async function meshReconcileLedger(ctx, args) {
|
|
60113
|
+
await refreshMeshFromDaemon(ctx);
|
|
60114
|
+
const requestedNodeIds = Array.isArray(args.node_ids) ? new Set(args.node_ids.map((id) => typeof id === "string" ? id.trim() : "").filter(Boolean)) : null;
|
|
60115
|
+
const nodes = ctx.mesh.nodes.filter((node) => !requestedNodeIds || requestedNodeIds.has(node.id));
|
|
60116
|
+
const replicas = [];
|
|
60117
|
+
const shouldImport = args.import_entries !== false;
|
|
60118
|
+
const queryArgs = {
|
|
60119
|
+
meshId: ctx.mesh.id,
|
|
60120
|
+
...typeof args.limit === "number" ? { limit: args.limit } : {},
|
|
60121
|
+
...typeof args.after_id === "string" && args.after_id.trim() ? { afterId: args.after_id.trim() } : {},
|
|
60122
|
+
...typeof args.since === "string" && args.since.trim() ? { since: args.since.trim() } : {}
|
|
60123
|
+
};
|
|
60124
|
+
for (const node of nodes) {
|
|
60125
|
+
try {
|
|
60126
|
+
if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
|
|
60127
|
+
const slice2 = readLedgerSlice(ctx.mesh.id, queryArgs);
|
|
60128
|
+
replicas.push(buildMeshLedgerReplicaEvidence({
|
|
60129
|
+
nodeId: node.id,
|
|
60130
|
+
daemonId: node.daemonId,
|
|
60131
|
+
transport: "local",
|
|
60132
|
+
slice: slice2,
|
|
60133
|
+
status: "local"
|
|
60134
|
+
}));
|
|
60135
|
+
continue;
|
|
60136
|
+
}
|
|
60137
|
+
const result = await commandForNode(ctx, node, "get_mesh_ledger_slice", queryArgs);
|
|
60138
|
+
const payload = unwrapCommandPayload(result);
|
|
60139
|
+
if (payload?.success === false) {
|
|
60140
|
+
throw new Error(payload.error || "remote get_mesh_ledger_slice failed");
|
|
60141
|
+
}
|
|
60142
|
+
const slice = payload?.slice ?? payload;
|
|
60143
|
+
if (slice?.protocol !== "adhdev.mesh.ledger.slice.v1" || !Array.isArray(slice.entries)) {
|
|
60144
|
+
throw new Error("remote daemon returned an invalid ledger slice payload");
|
|
60145
|
+
}
|
|
60146
|
+
const importResult = shouldImport ? appendRemoteLedgerEntries(ctx.mesh.id, slice.entries) : { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
|
|
60147
|
+
replicas.push(buildMeshLedgerReplicaEvidence({
|
|
60148
|
+
nodeId: node.id,
|
|
60149
|
+
daemonId: node.daemonId,
|
|
60150
|
+
transport: "p2p_datachannel",
|
|
60151
|
+
slice,
|
|
60152
|
+
importResult
|
|
60153
|
+
}));
|
|
60154
|
+
if (shouldImport && importResult.accepted > 0) {
|
|
60155
|
+
appendLedgerEntry(ctx.mesh.id, {
|
|
60156
|
+
kind: "ledger_replicated",
|
|
60157
|
+
nodeId: node.id,
|
|
60158
|
+
payload: {
|
|
60159
|
+
protocol: "adhdev.mesh.ledger.slice.v1",
|
|
60160
|
+
imported: importResult.accepted,
|
|
60161
|
+
skippedDuplicate: importResult.skippedDuplicate,
|
|
60162
|
+
rejectedInvalid: importResult.rejectedInvalid,
|
|
60163
|
+
nextAfterId: slice.cursor?.nextAfterId ?? null,
|
|
60164
|
+
via: "p2p_datachannel"
|
|
60165
|
+
}
|
|
60166
|
+
});
|
|
60167
|
+
}
|
|
60168
|
+
} catch (e) {
|
|
60169
|
+
replicas.push(buildMeshLedgerReplicaEvidence({
|
|
60170
|
+
nodeId: node.id,
|
|
60171
|
+
daemonId: node.daemonId,
|
|
60172
|
+
transport: node.daemonId ? "p2p_datachannel" : "local",
|
|
60173
|
+
status: "failed",
|
|
60174
|
+
error: e?.message ?? String(e)
|
|
60175
|
+
}));
|
|
60176
|
+
}
|
|
60177
|
+
}
|
|
60178
|
+
const evidence = buildMeshLedgerReconciliationEvidence(ctx.mesh.id, replicas);
|
|
60179
|
+
appendLedgerEntry(ctx.mesh.id, {
|
|
60180
|
+
kind: "ledger_reconciled",
|
|
60181
|
+
payload: {
|
|
60182
|
+
protocol: evidence.protocol,
|
|
60183
|
+
sourceOfTruth: evidence.sourceOfTruth,
|
|
60184
|
+
totals: evidence.totals,
|
|
60185
|
+
convergence: evidence.convergence
|
|
60186
|
+
}
|
|
60187
|
+
});
|
|
60188
|
+
return JSON.stringify({ success: true, evidence }, null, 2);
|
|
60189
|
+
}
|
|
57702
60190
|
async function meshListNodes(ctx) {
|
|
57703
60191
|
await refreshMeshFromDaemon(ctx);
|
|
57704
60192
|
const { mesh } = ctx;
|
|
@@ -57720,12 +60208,38 @@ async function meshListNodes(ctx) {
|
|
|
57720
60208
|
async function meshEnqueueTask(ctx, args) {
|
|
57721
60209
|
try {
|
|
57722
60210
|
const task = enqueueTask(ctx.mesh.id, args.message);
|
|
57723
|
-
if (ctx.transport
|
|
57724
|
-
ctx.transport.
|
|
60211
|
+
if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
|
|
60212
|
+
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
57725
60213
|
});
|
|
57726
|
-
|
|
60214
|
+
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
60215
|
+
}
|
|
60216
|
+
if (ctx.transport instanceof IpcTransport) {
|
|
57727
60217
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
57728
60218
|
});
|
|
60219
|
+
const dispatchPromises = [];
|
|
60220
|
+
for (const node of ctx.mesh.nodes) {
|
|
60221
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
60222
|
+
if (isLocalNode || !node.daemonId) continue;
|
|
60223
|
+
dispatchPromises.push(
|
|
60224
|
+
ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
|
|
60225
|
+
if (result.success) {
|
|
60226
|
+
try {
|
|
60227
|
+
appendLedgerEntry(ctx.mesh.id, {
|
|
60228
|
+
kind: "task_dispatched",
|
|
60229
|
+
nodeId: node.id,
|
|
60230
|
+
sessionId: result.sessionId,
|
|
60231
|
+
payload: { message: args.message, via: "p2p_direct", taskId: task.id }
|
|
60232
|
+
});
|
|
60233
|
+
} catch {
|
|
60234
|
+
}
|
|
60235
|
+
}
|
|
60236
|
+
}).catch(() => {
|
|
60237
|
+
})
|
|
60238
|
+
);
|
|
60239
|
+
}
|
|
60240
|
+
Promise.all(dispatchPromises).catch(() => {
|
|
60241
|
+
});
|
|
60242
|
+
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
57729
60243
|
}
|
|
57730
60244
|
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
57731
60245
|
} catch (e) {
|
|
@@ -57734,8 +60248,88 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
57734
60248
|
}
|
|
57735
60249
|
async function meshViewQueue(ctx, args) {
|
|
57736
60250
|
try {
|
|
57737
|
-
const
|
|
57738
|
-
|
|
60251
|
+
const statusFilter = sanitizeQueueStatusFilter(args.status);
|
|
60252
|
+
const view = normalizeQueueViewMode(args.view);
|
|
60253
|
+
const fullQueue = annotateQueueStaleness(getQueue(ctx.mesh.id), ctx.mesh);
|
|
60254
|
+
const queue = filterQueueForView(fullQueue, view, statusFilter);
|
|
60255
|
+
const summary = buildQueueStatusSummary(fullQueue);
|
|
60256
|
+
const visibleSummary = buildQueueStatusSummary(queue);
|
|
60257
|
+
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
60258
|
+
const staleAssignedTasks = maintenance.staleAssignedTasks || [];
|
|
60259
|
+
const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
60260
|
+
return JSON.stringify({
|
|
60261
|
+
success: true,
|
|
60262
|
+
sourceOfTruth: {
|
|
60263
|
+
kind: "mesh_work_queue_file",
|
|
60264
|
+
activeStatuses: ["pending", "assigned"],
|
|
60265
|
+
historicalStatuses: ["completed", "failed", "cancelled"],
|
|
60266
|
+
notes: "pending/assigned are active work; completed/failed/cancelled are historical ledger records and never stale assignments."
|
|
60267
|
+
},
|
|
60268
|
+
filter: {
|
|
60269
|
+
view,
|
|
60270
|
+
statuses: statusFilter,
|
|
60271
|
+
filtered: Boolean(statusFilter?.length) || view !== "all"
|
|
60272
|
+
},
|
|
60273
|
+
queue,
|
|
60274
|
+
visibleQueue: queue,
|
|
60275
|
+
visibleSummary,
|
|
60276
|
+
summary,
|
|
60277
|
+
activeCounts: summary.activeCounts,
|
|
60278
|
+
historicalCounts: summary.historicalCounts,
|
|
60279
|
+
activeCount: summary.activeCount,
|
|
60280
|
+
historicalCount: summary.historicalCount,
|
|
60281
|
+
visibleActiveCounts: visibleSummary.activeCounts,
|
|
60282
|
+
visibleHistoricalCounts: visibleSummary.historicalCounts,
|
|
60283
|
+
visibleActiveCount: visibleSummary.activeCount,
|
|
60284
|
+
visibleHistoricalCount: visibleSummary.historicalCount,
|
|
60285
|
+
staleAssignedTasks,
|
|
60286
|
+
staleAssignedCount: maintenance.staleAssignedCount,
|
|
60287
|
+
queueMaintenance: maintenance,
|
|
60288
|
+
cleanupDryRun: maintenance,
|
|
60289
|
+
...view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status)) ? {
|
|
60290
|
+
activeQueue: queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")))
|
|
60291
|
+
} : {},
|
|
60292
|
+
...view === "historical" || requestedHistoricalRows ? {
|
|
60293
|
+
historicalQueue: queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")))
|
|
60294
|
+
} : {},
|
|
60295
|
+
// Back-compat alias for callers already reading the first hardening payload.
|
|
60296
|
+
staleAssignments: staleAssignedTasks
|
|
60297
|
+
}, null, 2);
|
|
60298
|
+
} catch (e) {
|
|
60299
|
+
return JSON.stringify({ success: false, error: e.message });
|
|
60300
|
+
}
|
|
60301
|
+
}
|
|
60302
|
+
async function meshQueueCancel(ctx, args) {
|
|
60303
|
+
try {
|
|
60304
|
+
const taskId = (args.task_id || args.taskId || "").trim();
|
|
60305
|
+
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
60306
|
+
const task = cancelTask(ctx.mesh.id, taskId, { reason: args.reason });
|
|
60307
|
+
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
60308
|
+
return JSON.stringify({ success: true, task }, null, 2);
|
|
60309
|
+
} catch (e) {
|
|
60310
|
+
return JSON.stringify({ success: false, error: e.message });
|
|
60311
|
+
}
|
|
60312
|
+
}
|
|
60313
|
+
async function meshQueueRequeue(ctx, args) {
|
|
60314
|
+
try {
|
|
60315
|
+
const taskId = (args.task_id || args.taskId || "").trim();
|
|
60316
|
+
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
60317
|
+
const targetNodeId = (args.target_node_id || args.targetNodeId || "").trim() || void 0;
|
|
60318
|
+
const targetSessionId = (args.target_session_id || args.targetSessionId || "").trim() || void 0;
|
|
60319
|
+
const keepTargetSession = args.keep_target_session === true || args.keepTargetSession === true;
|
|
60320
|
+
const task = requeueTask(ctx.mesh.id, taskId, {
|
|
60321
|
+
reason: args.reason,
|
|
60322
|
+
targetNodeId,
|
|
60323
|
+
targetSessionId,
|
|
60324
|
+
clearTargetNode: args.clear_target_node === true || args.clearTargetNode === true,
|
|
60325
|
+
clearTargetSession: targetSessionId ? false : !keepTargetSession
|
|
60326
|
+
});
|
|
60327
|
+
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
60328
|
+
if (isLocalTransport(ctx.transport)) {
|
|
60329
|
+
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
60330
|
+
});
|
|
60331
|
+
}
|
|
60332
|
+
return JSON.stringify({ success: true, task }, null, 2);
|
|
57739
60333
|
} catch (e) {
|
|
57740
60334
|
return JSON.stringify({ success: false, error: e.message });
|
|
57741
60335
|
}
|
|
@@ -57745,6 +60339,24 @@ async function meshSendTask(ctx, args) {
|
|
|
57745
60339
|
if (node.policy?.readOnly) {
|
|
57746
60340
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
57747
60341
|
}
|
|
60342
|
+
const duplicate = hasRecentDuplicateDispatch(ctx, args);
|
|
60343
|
+
if (duplicate.duplicate) {
|
|
60344
|
+
return JSON.stringify({
|
|
60345
|
+
success: true,
|
|
60346
|
+
duplicate: true,
|
|
60347
|
+
dispatched: false,
|
|
60348
|
+
warning: "Duplicate mesh_send_task suppressed: the same node/session/message was dispatched recently.",
|
|
60349
|
+
nodeId: args.node_id,
|
|
60350
|
+
sessionId: args.session_id,
|
|
60351
|
+
source: duplicate.source,
|
|
60352
|
+
previousDispatch: duplicate.entry ? {
|
|
60353
|
+
id: duplicate.entry.id,
|
|
60354
|
+
timestamp: duplicate.entry.timestamp || duplicate.entry.updatedAt || duplicate.entry.createdAt,
|
|
60355
|
+
nodeId: duplicate.entry.nodeId || duplicate.entry.targetNodeId || duplicate.entry.assignedNodeId,
|
|
60356
|
+
sessionId: duplicate.entry.sessionId || duplicate.entry.targetSessionId || duplicate.entry.assignedSessionId
|
|
60357
|
+
} : void 0
|
|
60358
|
+
});
|
|
60359
|
+
}
|
|
57748
60360
|
try {
|
|
57749
60361
|
if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
57750
60362
|
const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
|
|
@@ -57754,21 +60366,85 @@ async function meshSendTask(ctx, args) {
|
|
|
57754
60366
|
});
|
|
57755
60367
|
return JSON.stringify(res);
|
|
57756
60368
|
}
|
|
57757
|
-
const
|
|
57758
|
-
if (ctx.transport instanceof IpcTransport && node.daemonId &&
|
|
57759
|
-
|
|
60369
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
60370
|
+
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
60371
|
+
const cached2 = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id || ""));
|
|
60372
|
+
const result = await ipcDispatchToRemoteAgent(ctx, node, {
|
|
60373
|
+
session_id: args.session_id,
|
|
60374
|
+
message: args.message,
|
|
60375
|
+
providerType: cached2?.providerType
|
|
57760
60376
|
});
|
|
57761
|
-
|
|
60377
|
+
if (result.success) {
|
|
60378
|
+
const dispatchedSessionId = args.session_id || result.sessionId;
|
|
60379
|
+
try {
|
|
60380
|
+
appendLedgerEntry(ctx.mesh.id, {
|
|
60381
|
+
kind: "task_dispatched",
|
|
60382
|
+
nodeId: args.node_id,
|
|
60383
|
+
sessionId: dispatchedSessionId,
|
|
60384
|
+
payload: {
|
|
60385
|
+
message: args.message,
|
|
60386
|
+
via: "p2p_direct",
|
|
60387
|
+
...dispatchedSessionId ? { targetSessionId: dispatchedSessionId } : {}
|
|
60388
|
+
}
|
|
60389
|
+
});
|
|
60390
|
+
} catch {
|
|
60391
|
+
}
|
|
60392
|
+
}
|
|
60393
|
+
return JSON.stringify({ ...result, nodeId: args.node_id, dispatched: result.success === true });
|
|
60394
|
+
}
|
|
60395
|
+
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
60396
|
+
const cached2 = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
60397
|
+
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
60398
|
+
targetSessionId: args.session_id,
|
|
60399
|
+
...cached2?.providerType ? { agentType: cached2.providerType, cliType: cached2.providerType, providerType: cached2.providerType } : {},
|
|
60400
|
+
action: "send_chat",
|
|
60401
|
+
message: args.message
|
|
60402
|
+
});
|
|
60403
|
+
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
60404
|
+
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
60405
|
+
return JSON.stringify({
|
|
60406
|
+
success: false,
|
|
60407
|
+
nodeId: args.node_id,
|
|
60408
|
+
sessionId: args.session_id,
|
|
60409
|
+
error: dispatchPayload?.error || dispatchResult?.error || "agent_command rejected the task"
|
|
60410
|
+
});
|
|
60411
|
+
}
|
|
60412
|
+
try {
|
|
60413
|
+
appendLedgerEntry(ctx.mesh.id, {
|
|
60414
|
+
kind: "task_dispatched",
|
|
60415
|
+
nodeId: args.node_id,
|
|
60416
|
+
sessionId: args.session_id,
|
|
60417
|
+
providerType: cached2?.providerType,
|
|
60418
|
+
payload: { message: args.message, via: "local_direct" }
|
|
60419
|
+
});
|
|
60420
|
+
} catch {
|
|
60421
|
+
}
|
|
60422
|
+
return JSON.stringify({ success: true, dispatched: true, nodeId: args.node_id, sessionId: args.session_id });
|
|
60423
|
+
}
|
|
60424
|
+
const task = enqueueTask(ctx.mesh.id, args.message, {
|
|
60425
|
+
targetNodeId: args.node_id,
|
|
60426
|
+
targetSessionId: args.session_id
|
|
60427
|
+
});
|
|
60428
|
+
if (isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport) {
|
|
57762
60429
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
57763
60430
|
});
|
|
57764
60431
|
}
|
|
57765
60432
|
return JSON.stringify({ success: true, nodeId: args.node_id, taskId: task.id, status: task.status });
|
|
57766
60433
|
} catch (e) {
|
|
57767
|
-
|
|
60434
|
+
const failure2 = buildCoordinatorP2pRelayFailure(e, {
|
|
60435
|
+
command: "mesh_send_task",
|
|
60436
|
+
targetDaemonId: node.daemonId,
|
|
60437
|
+
nodeId: args.node_id,
|
|
60438
|
+
sessionId: args.session_id
|
|
60439
|
+
});
|
|
60440
|
+
return JSON.stringify(failure2);
|
|
57768
60441
|
}
|
|
57769
60442
|
}
|
|
57770
60443
|
async function meshReadChat(ctx, args) {
|
|
57771
|
-
const node = await
|
|
60444
|
+
const node = await findOptionalNodeWithRefresh(ctx, args.node_id);
|
|
60445
|
+
if (!node) {
|
|
60446
|
+
return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
|
|
60447
|
+
}
|
|
57772
60448
|
if (isLocalTransport(ctx.transport)) {
|
|
57773
60449
|
const cached2 = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
57774
60450
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached2?.providerSessionId;
|
|
@@ -57871,19 +60547,28 @@ async function meshLaunchSession(ctx, args) {
|
|
|
57871
60547
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
57872
60548
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
57873
60549
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
57874
|
-
|
|
57875
|
-
|
|
57876
|
-
|
|
57877
|
-
|
|
57878
|
-
|
|
57879
|
-
|
|
57880
|
-
|
|
57881
|
-
|
|
57882
|
-
|
|
57883
|
-
|
|
57884
|
-
|
|
57885
|
-
|
|
60550
|
+
let result;
|
|
60551
|
+
try {
|
|
60552
|
+
result = await commandForNode(ctx, node, "launch_cli", {
|
|
60553
|
+
cliType: resolvedProviderType,
|
|
60554
|
+
dir: node.workspace,
|
|
60555
|
+
settings: {
|
|
60556
|
+
meshNodeFor: ctx.mesh.id,
|
|
60557
|
+
meshNodeId: args.node_id,
|
|
60558
|
+
spawnedSessionVisibility,
|
|
60559
|
+
...coordinatorDaemonId ? { meshCoordinatorDaemonId: coordinatorDaemonId } : {},
|
|
60560
|
+
...coordinatorNode?.id ? { meshCoordinatorNodeId: coordinatorNode.id } : {},
|
|
60561
|
+
launchedByCoordinator: true
|
|
60562
|
+
}
|
|
60563
|
+
});
|
|
60564
|
+
} catch (e) {
|
|
60565
|
+
return JSON.stringify(recordRecoverableLaunchFailure(ctx, node, resolvedProviderType, e), null, 2);
|
|
60566
|
+
}
|
|
57886
60567
|
const launchPayload = extractLaunchPayload(result);
|
|
60568
|
+
if (launchPayload?.success === false || result?.success === false) {
|
|
60569
|
+
const launchError = new Error(launchPayload?.error || result?.error || "launch_cli rejected the session launch");
|
|
60570
|
+
return JSON.stringify(recordRecoverableLaunchFailure(ctx, node, resolvedProviderType, launchError), null, 2);
|
|
60571
|
+
}
|
|
57887
60572
|
const runtimeSessionId = typeof launchPayload?.sessionId === "string" ? launchPayload.sessionId : typeof launchPayload?.id === "string" ? launchPayload.id : typeof launchPayload?.runtimeSessionId === "string" ? launchPayload.runtimeSessionId : "";
|
|
57888
60573
|
const providerSessionId = typeof launchPayload?.providerSessionId === "string" && launchPayload.providerSessionId.trim() ? launchPayload.providerSessionId.trim() : void 0;
|
|
57889
60574
|
if (runtimeSessionId) {
|
|
@@ -57902,7 +60587,8 @@ async function meshLaunchSession(ctx, args) {
|
|
|
57902
60587
|
});
|
|
57903
60588
|
} catch {
|
|
57904
60589
|
}
|
|
57905
|
-
|
|
60590
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
60591
|
+
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
57906
60592
|
ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
57907
60593
|
});
|
|
57908
60594
|
} else if (isLocalTransport(ctx.transport)) {
|
|
@@ -57952,7 +60638,7 @@ async function meshLaunchSession(ctx, args) {
|
|
|
57952
60638
|
}
|
|
57953
60639
|
return JSON.stringify({ ...res, resolvedProviderType }, null, 2);
|
|
57954
60640
|
} catch (e) {
|
|
57955
|
-
return JSON.stringify(
|
|
60641
|
+
return JSON.stringify(recordRecoverableLaunchFailure(ctx, node, resolvedProviderType, e), null, 2);
|
|
57956
60642
|
}
|
|
57957
60643
|
} else {
|
|
57958
60644
|
return JSON.stringify({ error: "Cloud mesh launch_session requires node daemonId" });
|
|
@@ -57960,31 +60646,43 @@ async function meshLaunchSession(ctx, args) {
|
|
|
57960
60646
|
}
|
|
57961
60647
|
async function meshGitStatus(ctx, args) {
|
|
57962
60648
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
57963
|
-
|
|
57964
|
-
|
|
57965
|
-
|
|
57966
|
-
|
|
57967
|
-
|
|
57968
|
-
|
|
57969
|
-
|
|
57970
|
-
|
|
57971
|
-
|
|
57972
|
-
|
|
57973
|
-
|
|
57974
|
-
|
|
57975
|
-
|
|
57976
|
-
|
|
57977
|
-
|
|
60649
|
+
try {
|
|
60650
|
+
if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
60651
|
+
const result = await ctx.transport.gitStatus(node.daemonId, node.workspace, true);
|
|
60652
|
+
return JSON.stringify({
|
|
60653
|
+
nodeId: args.node_id,
|
|
60654
|
+
workspace: node.workspace,
|
|
60655
|
+
status: extractGitStatus(result),
|
|
60656
|
+
diff: extractGitDiff(result),
|
|
60657
|
+
relatedRepos: await collectRelatedRepoStatuses(ctx, node)
|
|
60658
|
+
}, null, 2);
|
|
60659
|
+
} else if (isLocalTransport(ctx.transport)) {
|
|
60660
|
+
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
60661
|
+
workspace: node.workspace
|
|
60662
|
+
});
|
|
60663
|
+
const diffResult = await commandForNode(ctx, node, "git_diff_summary", {
|
|
60664
|
+
workspace: node.workspace
|
|
60665
|
+
});
|
|
60666
|
+
return JSON.stringify({
|
|
60667
|
+
nodeId: args.node_id,
|
|
60668
|
+
workspace: node.workspace,
|
|
60669
|
+
status: extractGitStatus(statusResult),
|
|
60670
|
+
diff: extractGitDiff(diffResult),
|
|
60671
|
+
relatedRepos: await collectRelatedRepoStatuses(ctx, node)
|
|
60672
|
+
}, null, 2);
|
|
60673
|
+
} else {
|
|
60674
|
+
return JSON.stringify({ error: "No daemonId available for cloud git_status probe" });
|
|
60675
|
+
}
|
|
60676
|
+
} catch (e) {
|
|
60677
|
+
const failure2 = buildCoordinatorP2pRelayFailure(e, {
|
|
60678
|
+
command: "git_status",
|
|
60679
|
+
targetDaemonId: node.daemonId,
|
|
60680
|
+
nodeId: args.node_id
|
|
57978
60681
|
});
|
|
57979
60682
|
return JSON.stringify({
|
|
57980
|
-
|
|
57981
|
-
workspace: node.workspace
|
|
57982
|
-
status: extractGitStatus(statusResult),
|
|
57983
|
-
diff: extractGitDiff(diffResult),
|
|
57984
|
-
relatedRepos: await collectRelatedRepoStatuses(ctx, node)
|
|
60683
|
+
...failure2,
|
|
60684
|
+
workspace: node.workspace
|
|
57985
60685
|
}, null, 2);
|
|
57986
|
-
} else {
|
|
57987
|
-
return JSON.stringify({ error: "No daemonId available for cloud git_status probe" });
|
|
57988
60686
|
}
|
|
57989
60687
|
}
|
|
57990
60688
|
async function meshCheckpoint(ctx, args) {
|
|
@@ -58131,12 +60829,28 @@ async function meshCleanupSessions(ctx, args) {
|
|
|
58131
60829
|
async function meshRemoveNode(ctx, args) {
|
|
58132
60830
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
58133
60831
|
if (isLocalTransport(ctx.transport)) {
|
|
58134
|
-
const
|
|
58135
|
-
|
|
58136
|
-
|
|
58137
|
-
|
|
58138
|
-
|
|
58139
|
-
})
|
|
60832
|
+
const removeArgs = buildRemoveNodeArgs(ctx, args.node_id, args.session_cleanup_mode);
|
|
60833
|
+
let result;
|
|
60834
|
+
let transportFallback;
|
|
60835
|
+
try {
|
|
60836
|
+
result = await commandForNode(ctx, node, "remove_mesh_node", removeArgs);
|
|
60837
|
+
} catch (e) {
|
|
60838
|
+
if (ctx.transport instanceof IpcTransport && node.isLocalWorktree && isP2pTransportUnavailableError(e)) {
|
|
60839
|
+
result = await ctx.transport.command("remove_mesh_node", removeArgs);
|
|
60840
|
+
transportFallback = {
|
|
60841
|
+
from: "p2p_mesh_relay",
|
|
60842
|
+
to: "local_control_plane",
|
|
60843
|
+
reason: e?.message || String(e)
|
|
60844
|
+
};
|
|
60845
|
+
} else {
|
|
60846
|
+
return JSON.stringify({
|
|
60847
|
+
success: false,
|
|
60848
|
+
code: isP2pTransportUnavailableError(e) ? "p2p_unavailable" : "mesh_remove_node_failed",
|
|
60849
|
+
error: e?.message || String(e),
|
|
60850
|
+
recoveryHint: isP2pTransportUnavailableError(e) ? "If this is an ADHDev-managed local worktree, retry from a coordinator connected to the daemon that owns the worktree; dashboard command/data-plane traffic still requires P2P." : "Inspect mesh_status and retry after resolving the reported failure."
|
|
60851
|
+
}, null, 2);
|
|
60852
|
+
}
|
|
60853
|
+
}
|
|
58140
60854
|
if (result?.success && result.removed !== false) {
|
|
58141
60855
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
58142
60856
|
if (idx >= 0) {
|
|
@@ -58144,7 +60858,7 @@ async function meshRemoveNode(ctx, args) {
|
|
|
58144
60858
|
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
58145
60859
|
}
|
|
58146
60860
|
}
|
|
58147
|
-
return JSON.stringify(result, null, 2);
|
|
60861
|
+
return JSON.stringify({ ...result || {}, ...transportFallback ? { transportFallback } : {} }, null, 2);
|
|
58148
60862
|
} else if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
58149
60863
|
try {
|
|
58150
60864
|
const res = await ctx.transport.meshRemoveNode(node.daemonId, {
|
|
@@ -58338,15 +61052,6 @@ var CloudTransport = class {
|
|
|
58338
61052
|
});
|
|
58339
61053
|
if (!res.ok) throw new Error(`Delete remote mesh failed: ${res.status}`);
|
|
58340
61054
|
}
|
|
58341
|
-
async syncMeshLedger(meshId, data) {
|
|
58342
|
-
const res = await fetch(`${this.baseUrl}/api/v1/repo-meshes/${encodeURIComponent(meshId)}/ledger/sync`, {
|
|
58343
|
-
method: "POST",
|
|
58344
|
-
headers: this.headers(),
|
|
58345
|
-
body: JSON.stringify(data)
|
|
58346
|
-
});
|
|
58347
|
-
if (!res.ok) throw new Error(`Sync mesh ledger failed: ${res.status}`);
|
|
58348
|
-
return res.json();
|
|
58349
|
-
}
|
|
58350
61055
|
async listDaemons() {
|
|
58351
61056
|
const res = await fetch(`${this.baseUrl}/api/v1/daemons`, { headers: this.headers() });
|
|
58352
61057
|
if (!res.ok) throw new Error(`List daemons failed: ${res.status}`);
|
|
@@ -59840,6 +62545,15 @@ async function startMcpServer(opts) {
|
|
|
59840
62545
|
process.exit(1);
|
|
59841
62546
|
}
|
|
59842
62547
|
let localDaemonId;
|
|
62548
|
+
let localMachineId;
|
|
62549
|
+
if (transport instanceof LocalTransport || transport instanceof IpcTransport) {
|
|
62550
|
+
try {
|
|
62551
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
|
|
62552
|
+
const cfg = loadConfig2();
|
|
62553
|
+
if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
|
|
62554
|
+
} catch {
|
|
62555
|
+
}
|
|
62556
|
+
}
|
|
59843
62557
|
if (transport instanceof IpcTransport) {
|
|
59844
62558
|
try {
|
|
59845
62559
|
const statusResult = await transport.getStatus();
|
|
@@ -59848,7 +62562,7 @@ async function startMcpServer(opts) {
|
|
|
59848
62562
|
} catch {
|
|
59849
62563
|
}
|
|
59850
62564
|
}
|
|
59851
|
-
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {} };
|
|
62565
|
+
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {} };
|
|
59852
62566
|
const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
|
|
59853
62567
|
const server2 = new import_server.Server(
|
|
59854
62568
|
{ name: "adhdev-mcp-server", version: "0.9.76" },
|
|
@@ -59888,6 +62602,12 @@ async function startMcpServer(opts) {
|
|
|
59888
62602
|
case "mesh_view_queue":
|
|
59889
62603
|
text = await meshViewQueue(meshCtx, a);
|
|
59890
62604
|
break;
|
|
62605
|
+
case "mesh_queue_cancel":
|
|
62606
|
+
text = await meshQueueCancel(meshCtx, a);
|
|
62607
|
+
break;
|
|
62608
|
+
case "mesh_queue_requeue":
|
|
62609
|
+
text = await meshQueueRequeue(meshCtx, a);
|
|
62610
|
+
break;
|
|
59891
62611
|
case "mesh_send_task":
|
|
59892
62612
|
text = await meshSendTask(meshCtx, a);
|
|
59893
62613
|
break;
|
|
@@ -59924,6 +62644,9 @@ async function startMcpServer(opts) {
|
|
|
59924
62644
|
case "mesh_task_history":
|
|
59925
62645
|
text = await meshTaskHistory(meshCtx, a);
|
|
59926
62646
|
break;
|
|
62647
|
+
case "mesh_reconcile_ledger":
|
|
62648
|
+
text = await meshReconcileLedger(meshCtx, a);
|
|
62649
|
+
break;
|
|
59927
62650
|
default:
|
|
59928
62651
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
59929
62652
|
}
|