@adhdev/daemon-standalone 0.9.82-rc.261 → 0.9.82-rc.263
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 +963 -69
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/vendor/mcp-server/index.js +230 -16
- package/vendor/mcp-server/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1119,6 +1119,28 @@ function buildCompactGitSnapshot(status) {
|
|
|
1119
1119
|
}
|
|
1120
1120
|
return slim;
|
|
1121
1121
|
}
|
|
1122
|
+
function summarizeNodeSessions(sessions) {
|
|
1123
|
+
const list = Array.isArray(sessions) ? sessions : [];
|
|
1124
|
+
const byStatus = {};
|
|
1125
|
+
const providerCounts = {};
|
|
1126
|
+
const selfCoordinatorSessionIds = [];
|
|
1127
|
+
for (const s of list) {
|
|
1128
|
+
const status = typeof s?.status === "string" && s.status ? s.status : "unknown";
|
|
1129
|
+
byStatus[status] = (byStatus[status] ?? 0) + 1;
|
|
1130
|
+
const provider = typeof s?.providerType === "string" && s.providerType ? s.providerType : "unknown";
|
|
1131
|
+
providerCounts[provider] = (providerCounts[provider] ?? 0) + 1;
|
|
1132
|
+
if (s?.isSelfCoordinator === true && s.id) selfCoordinatorSessionIds.push(String(s.id));
|
|
1133
|
+
}
|
|
1134
|
+
const summary = {
|
|
1135
|
+
total: list.length,
|
|
1136
|
+
byStatus,
|
|
1137
|
+
providerCounts
|
|
1138
|
+
};
|
|
1139
|
+
if (selfCoordinatorSessionIds.length > 0) {
|
|
1140
|
+
summary.selfCoordinatorSessionIds = selfCoordinatorSessionIds;
|
|
1141
|
+
}
|
|
1142
|
+
return summary;
|
|
1143
|
+
}
|
|
1122
1144
|
function extractLaunchPayload(value) {
|
|
1123
1145
|
return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
|
|
1124
1146
|
}
|
|
@@ -1705,6 +1727,30 @@ async function collectLiveStatusSessions(ctx, node) {
|
|
|
1705
1727
|
return [];
|
|
1706
1728
|
}
|
|
1707
1729
|
}
|
|
1730
|
+
async function collectLiveStatusProbe(ctx, node) {
|
|
1731
|
+
try {
|
|
1732
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
1733
|
+
return {
|
|
1734
|
+
sessions: extractStatusMetadataSessions(statusResult),
|
|
1735
|
+
daemonBuild: extractDaemonBuildInfo(statusResult)
|
|
1736
|
+
};
|
|
1737
|
+
} catch {
|
|
1738
|
+
return { sessions: [] };
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
function extractDaemonBuildInfo(value) {
|
|
1742
|
+
const payload = unwrapCommandPayload(value);
|
|
1743
|
+
const build = payload?.daemonBuild && typeof payload.daemonBuild === "object" ? payload.daemonBuild : value?.daemonBuild && typeof value.daemonBuild === "object" ? value.daemonBuild : void 0;
|
|
1744
|
+
if (!build) return void 0;
|
|
1745
|
+
const commit = readString(build.commit);
|
|
1746
|
+
if (!commit) return void 0;
|
|
1747
|
+
return {
|
|
1748
|
+
commit,
|
|
1749
|
+
commitShort: readString(build.commitShort) || commit.slice(0, 7),
|
|
1750
|
+
version: readString(build.version) || "unknown",
|
|
1751
|
+
...readString(build.builtAt) ? { builtAt: readString(build.builtAt) } : {}
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1708
1754
|
async function collectMeshViewQueueNodesWithLiveSessions(ctx) {
|
|
1709
1755
|
const nodes = await Promise.all(ctx.mesh.nodes.map(async (node) => {
|
|
1710
1756
|
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
@@ -1957,13 +2003,14 @@ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
|
|
|
1957
2003
|
}
|
|
1958
2004
|
var MESH_STATUS_TOOL = {
|
|
1959
2005
|
name: "mesh_status",
|
|
1960
|
-
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. Do not repeatedly call this to wait for generating delegated work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
|
|
2006
|
+
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. Also reports the running daemon build per daemonId under top-level daemonBuilds ({commit, commitShort, version}); when a live daemon was built from a commit BEHIND its workspace HEAD it adds staleDaemonBuilds[] + staleDaemonBuildWarning \u2014 meaning a just-merged refinery/mesh-tool fix is NOT yet live on that daemon (awaiting deploy/restart; a local dist rebuild does not update a cloud daemon). Do not repeatedly call this to wait for generating delegated work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
|
|
1961
2007
|
inputSchema: {
|
|
1962
2008
|
type: "object",
|
|
1963
2009
|
properties: {
|
|
1964
2010
|
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
|
|
1965
2011
|
includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." },
|
|
1966
|
-
|
|
2012
|
+
includeSessions: { type: "boolean", description: "Opt in to per-node live session arrays. Default false: compact mode returns a per-node sessionSummary (counts) and de-duplicated full session lists under top-level daemonSessions keyed by daemonId (sessions are not repeated for every node that shares a daemon). Set true to also include the full session array on each node." },
|
|
2013
|
+
compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Folds per-node session arrays to sessionSummary and de-duplicates daemon-shared sessions into daemonSessions. Set false (or verbose=true) for the full dashboard-grade payload." },
|
|
1967
2014
|
verbose: { type: "boolean", description: "Force the full payload; overrides compact." }
|
|
1968
2015
|
}
|
|
1969
2016
|
}
|
|
@@ -2119,15 +2166,17 @@ var MESH_GIT_STATUS_TOOL = {
|
|
|
2119
2166
|
};
|
|
2120
2167
|
var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
2121
2168
|
name: "mesh_fast_forward_node",
|
|
2122
|
-
description:
|
|
2169
|
+
description: 'Safely dry-run or execute an obvious direct fast-forward for a mesh node without launching an agent session. mode="merge" (default) absorbs upstream commits into the local branch via git merge --ff-only (ahead=0, behind>0). mode="push" publishes local commits to origin via a strict ff-only push (HEAD must be a descendant of origin/<branch>). Defaults to dry-run; execution requires execute=true. Never force-pushes, rebases, resets, cleans, or checks out arbitrary revisions. When the merge path finds the branch ahead with nothing to merge, it returns code "ahead_needs_push" pointing at mode="push".',
|
|
2123
2170
|
inputSchema: {
|
|
2124
2171
|
type: "object",
|
|
2125
2172
|
properties: {
|
|
2126
2173
|
node_id: { type: "string", description: "Target node ID." },
|
|
2174
|
+
mode: { type: "string", enum: ["merge", "push"], description: "merge (default): git merge --ff-only to absorb upstream. push: strict ff-only push of local commits to origin/<branch>; refuses any non-fast-forward." },
|
|
2127
2175
|
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
2128
|
-
execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
|
|
2176
|
+
execute: { type: "boolean", description: "When true, apply the fast-forward/push if all safety gates pass. Defaults false/dry-run." },
|
|
2129
2177
|
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
2130
|
-
update_submodules: { type: "boolean", description: "
|
|
2178
|
+
update_submodules: { type: "boolean", description: 'mode="merge" only: when true, if the root fast-forward changes gitlinks, run only git submodule update --init --recursive and verify submodules clean.' },
|
|
2179
|
+
push_submodules: { type: "boolean", description: 'mode="push" only: also ff-only push submodule HEADs to their origin main. Gated by mesh policy allowAutoPublishSubmoduleMainCommits \u2014 skipped unless that policy is enabled. Defaults false (root push only).' }
|
|
2131
2180
|
},
|
|
2132
2181
|
required: ["node_id"]
|
|
2133
2182
|
}
|
|
@@ -2228,8 +2277,10 @@ var MESH_TASK_HISTORY_TOOL = {
|
|
|
2228
2277
|
inputSchema: {
|
|
2229
2278
|
type: "object",
|
|
2230
2279
|
properties: {
|
|
2231
|
-
tail: { type: "number", description: "Number of recent entries to return (default: 20)." },
|
|
2232
|
-
kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed, direct_fast_forward." }
|
|
2280
|
+
tail: { type: "number", description: "Number of recent entries to return (default: 20; clamped to 40 in compact mode, 200 in verbose)." },
|
|
2281
|
+
kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed, direct_fast_forward." },
|
|
2282
|
+
compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Truncates long payload strings (message/taskSummary \u2264200, finalSummary \u2264300) and drops large nested evidence blobs (accessible via mesh_reconcile_ledger). Set false (or verbose=true) for full untruncated payloads." },
|
|
2283
|
+
verbose: { type: "boolean", description: "Force the full untruncated payload; overrides compact." }
|
|
2233
2284
|
}
|
|
2234
2285
|
}
|
|
2235
2286
|
};
|
|
@@ -2247,6 +2298,18 @@ var MESH_RECONCILE_LEDGER_TOOL = {
|
|
|
2247
2298
|
}
|
|
2248
2299
|
}
|
|
2249
2300
|
};
|
|
2301
|
+
var MESH_PRUNE_STALE_DIRECT_TOOL = {
|
|
2302
|
+
name: "mesh_prune_stale_direct",
|
|
2303
|
+
description: "Prune orphaned staleDirect dispatch records \u2014 direct task dispatches whose original node/session is no longer present in the live mesh. dry_run (default) reports exactly which records would be pruned without mutating anything; pass execute=true to delete them. Active/pending/assigned/generating work and fresh unacknowledged dispatch failures (node/session still live) are always preserved. The append-only mesh ledger audit history is left intact.",
|
|
2304
|
+
inputSchema: {
|
|
2305
|
+
type: "object",
|
|
2306
|
+
properties: {
|
|
2307
|
+
execute: { type: "boolean", description: "When true, actually delete the orphaned records. Defaults false (dry run). Ignored when dry_run=true." },
|
|
2308
|
+
dry_run: { type: "boolean", description: "Force a preview without mutation even if execute=true. Defaults to dry-run behavior when execute is not set." },
|
|
2309
|
+
include_terminal: { type: "boolean", description: "Also prune terminal (completed/failed) direct dispatch store rows in addition to orphans. Defaults false." }
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
};
|
|
2250
2313
|
var MESH_REFINE_NODE_TOOL = {
|
|
2251
2314
|
name: "mesh_refine_node",
|
|
2252
2315
|
description: "The Refinery: Accept an async validation/merge/cleanup job for a completed worktree node. The immediate response includes async:true, status:'accepted', jobId, interactionId, target node, and startedAt; completion/failure evidence is delivered through pending mesh events and the mesh task ledger.",
|
|
@@ -2260,7 +2323,7 @@ var MESH_REFINE_NODE_TOOL = {
|
|
|
2260
2323
|
};
|
|
2261
2324
|
var MESH_REFINE_BATCH_TOOL = {
|
|
2262
2325
|
name: "mesh_refine_batch",
|
|
2263
|
-
description: "Batch Refinery: converge multiple sibling worktree nodes onto the base branch in one conflict-aware sequential pipeline. Orders nodes by change-area (non-submodule nodes first, submodule-touching nodes serialized last) so each merged sibling advances the base and the next node auto-rebases + re-checks patch-equivalence before its own merge. Each node runs the same validation/patch-equivalence/submodule-reachability/merge/cleanup gates as mesh_refine_node. Conflicting or blocked nodes are isolated as blocked_review while the rest of the batch proceeds. Defaults to dry-run (plan only); set execute=true to converge. Never force-pushes or resets.",
|
|
2326
|
+
description: "Batch Refinery: converge multiple sibling worktree nodes onto the base branch in one conflict-aware sequential pipeline. Orders nodes by change-area (non-submodule nodes first, submodule-touching nodes serialized last) so each merged sibling advances the base and the next node auto-rebases + re-checks patch-equivalence before its own merge. Each node runs the same validation/patch-equivalence/submodule-reachability/merge/cleanup gates as mesh_refine_node. Conflicting or blocked nodes are isolated as blocked_review while the rest of the batch proceeds. Defaults to dry-run (plan only); set execute=true to converge. Never force-pushes or resets. execute=true is async: the immediate response is async:true / status:'accepted' with the batch jobId and ordered target node list; per-node convergence runs in the background and the aggregate completion/failure (with per-node merged / blocked_review / not_mergeable results) is delivered as a terminal refine event via pending mesh events and the ledger \u2014 do not re-invoke while a batch is in flight. dry_run returns the plan synchronously.",
|
|
2264
2327
|
inputSchema: {
|
|
2265
2328
|
type: "object",
|
|
2266
2329
|
properties: {
|
|
@@ -2347,6 +2410,7 @@ var ALL_MESH_TOOLS = [
|
|
|
2347
2410
|
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
2348
2411
|
MESH_REFINE_PLAN_TOOL,
|
|
2349
2412
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
2413
|
+
MESH_PRUNE_STALE_DIRECT_TOOL,
|
|
2350
2414
|
MESH_TASK_HISTORY_TOOL,
|
|
2351
2415
|
MESH_RECONCILE_LEDGER_TOOL,
|
|
2352
2416
|
MESH_MISSION_UPSERT_TOOL,
|
|
@@ -2384,6 +2448,9 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2384
2448
|
entry.isDirty = dirty;
|
|
2385
2449
|
entry.uncommittedChanges = uncommittedChanges;
|
|
2386
2450
|
entry.branchConvergence = buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges);
|
|
2451
|
+
if (status?.daemonBuildBehind && typeof status.daemonBuildBehind === "object") {
|
|
2452
|
+
entry.staleDaemonBuild = status.daemonBuildBehind;
|
|
2453
|
+
}
|
|
2387
2454
|
const submodules = extractSubmodules(statusResult, node.policy?.submoduleIgnorePaths || []);
|
|
2388
2455
|
if (submodules && submodules.some((s) => s?.outOfSync)) {
|
|
2389
2456
|
entry.submoduleWarning = "One or more submodules are out of sync with the parent repo. Run `git submodule update` or check deployment readiness.";
|
|
@@ -2451,7 +2518,9 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2451
2518
|
}
|
|
2452
2519
|
const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
|
|
2453
2520
|
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
2454
|
-
const
|
|
2521
|
+
const statusProbe = await collectLiveStatusProbe(ctx, node);
|
|
2522
|
+
const liveSessions = statusProbe.sessions;
|
|
2523
|
+
if (statusProbe.daemonBuild) entry.daemonBuild = statusProbe.daemonBuild;
|
|
2455
2524
|
if (liveSessions.length > 0) {
|
|
2456
2525
|
entry.sessions = liveSessions.map((s) => {
|
|
2457
2526
|
const coordinatorMeshId = typeof s.coordinator?.meshId === "string" ? s.coordinator.meshId : void 0;
|
|
@@ -2501,10 +2570,58 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2501
2570
|
}
|
|
2502
2571
|
}
|
|
2503
2572
|
}
|
|
2573
|
+
const includeSessions = args.includeSessions === true;
|
|
2574
|
+
const daemonSessions = {};
|
|
2575
|
+
if (compact) {
|
|
2576
|
+
const seenDaemons = /* @__PURE__ */ new Set();
|
|
2577
|
+
for (const entry of results) {
|
|
2578
|
+
const daemonId = typeof entry?.daemonId === "string" && entry.daemonId ? entry.daemonId : "";
|
|
2579
|
+
const sessions = Array.isArray(entry?.sessions) ? entry.sessions : [];
|
|
2580
|
+
if (daemonId && sessions.length > 0 && !seenDaemons.has(daemonId)) {
|
|
2581
|
+
seenDaemons.add(daemonId);
|
|
2582
|
+
daemonSessions[daemonId] = includeSessions ? sessions : summarizeNodeSessions(sessions);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
const daemonBuilds = {};
|
|
2587
|
+
for (const entry of results) {
|
|
2588
|
+
const daemonId = typeof entry?.daemonId === "string" && entry.daemonId ? entry.daemonId : "";
|
|
2589
|
+
if (daemonId && entry?.daemonBuild && !(daemonId in daemonBuilds)) {
|
|
2590
|
+
daemonBuilds[daemonId] = entry.daemonBuild;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
const staleDaemonBuilds = [];
|
|
2594
|
+
const seenStale = /* @__PURE__ */ new Set();
|
|
2595
|
+
for (const entry of results) {
|
|
2596
|
+
const behind = entry?.staleDaemonBuild;
|
|
2597
|
+
if (!behind || typeof behind !== "object") continue;
|
|
2598
|
+
const daemonId = typeof entry?.daemonId === "string" ? entry.daemonId : "";
|
|
2599
|
+
const key = `${daemonId}::${behind.scope ?? ""}::${behind.buildCommit ?? ""}::${behind.head ?? ""}`;
|
|
2600
|
+
if (seenStale.has(key)) continue;
|
|
2601
|
+
seenStale.add(key);
|
|
2602
|
+
staleDaemonBuilds.push({
|
|
2603
|
+
daemonId,
|
|
2604
|
+
nodeId: entry.nodeId,
|
|
2605
|
+
scope: behind.scope,
|
|
2606
|
+
liveBuildCommit: behind.buildCommit,
|
|
2607
|
+
liveBuildCommitShort: behind.buildCommitShort,
|
|
2608
|
+
head: behind.head,
|
|
2609
|
+
warning: behind.warning
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2504
2612
|
const nodesForResponse = compact ? results.map((entry) => {
|
|
2505
|
-
if (!entry || typeof entry !== "object"
|
|
2506
|
-
const
|
|
2507
|
-
|
|
2613
|
+
if (!entry || typeof entry !== "object") return entry;
|
|
2614
|
+
const next = { ...entry };
|
|
2615
|
+
if (next.git !== void 0) {
|
|
2616
|
+
const slimGit = buildCompactGitSnapshot(next.git);
|
|
2617
|
+
if (slimGit) next.git = slimGit;
|
|
2618
|
+
}
|
|
2619
|
+
if (Array.isArray(next.sessions)) {
|
|
2620
|
+
next.sessionSummary = summarizeNodeSessions(next.sessions);
|
|
2621
|
+
if (!includeSessions) delete next.sessions;
|
|
2622
|
+
}
|
|
2623
|
+
if (next.daemonBuild !== void 0) delete next.daemonBuild;
|
|
2624
|
+
return next;
|
|
2508
2625
|
}) : results;
|
|
2509
2626
|
const response = {
|
|
2510
2627
|
meshId: mesh.id,
|
|
@@ -2520,6 +2637,12 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2520
2637
|
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
2521
2638
|
},
|
|
2522
2639
|
nodes: nodesForResponse,
|
|
2640
|
+
...compact && Object.keys(daemonSessions).length > 0 ? { daemonSessions } : {},
|
|
2641
|
+
...Object.keys(daemonBuilds).length > 0 ? { daemonBuilds } : {},
|
|
2642
|
+
...staleDaemonBuilds.length > 0 ? {
|
|
2643
|
+
staleDaemonBuilds,
|
|
2644
|
+
staleDaemonBuildWarning: "One or more live daemons were built from a commit behind the workspace HEAD. Merged refinery/mesh-tool fixes are NOT live on those daemons until they are rebuilt/redeployed and restarted \u2014 a local daemon-core dist rebuild does not update a cloud daemon. Do not assume a just-merged fix is active."
|
|
2645
|
+
} : {},
|
|
2523
2646
|
activeWork: activeWorkEvidence.activeWork,
|
|
2524
2647
|
staleDirectWorkSummary,
|
|
2525
2648
|
...args.includeStaleDirectWorkDetails === true ? { staleDirectWork: activeWorkEvidence.staleDirectWork } : {},
|
|
@@ -2584,14 +2707,16 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2584
2707
|
}
|
|
2585
2708
|
async function meshTaskHistory(ctx, args) {
|
|
2586
2709
|
const { mesh } = ctx;
|
|
2710
|
+
const compact = args.verbose === true ? false : args.compact ?? true;
|
|
2587
2711
|
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
2588
|
-
const
|
|
2712
|
+
const requestedTail = typeof args.tail === "number" && args.tail > 0 ? Math.floor(args.tail) : 20;
|
|
2713
|
+
const tail = compact ? Math.min(requestedTail, 40) : Math.min(requestedTail, 200);
|
|
2589
2714
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
2590
2715
|
const rawEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
2591
|
-
const entries = rawEntries.map((e) => ({
|
|
2716
|
+
const entries = compact ? rawEntries.map((e) => ({
|
|
2592
2717
|
...e,
|
|
2593
2718
|
payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
|
|
2594
|
-
}));
|
|
2719
|
+
})) : rawEntries;
|
|
2595
2720
|
const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
2596
2721
|
let taskStats;
|
|
2597
2722
|
try {
|
|
@@ -2604,6 +2729,7 @@ async function meshTaskHistory(ctx, args) {
|
|
|
2604
2729
|
}
|
|
2605
2730
|
return JSON.stringify({
|
|
2606
2731
|
meshId: mesh.id,
|
|
2732
|
+
payloadMode: compact ? "compact" : "full",
|
|
2607
2733
|
entries,
|
|
2608
2734
|
summary,
|
|
2609
2735
|
...taskStats ? { taskStats } : {},
|
|
@@ -2688,6 +2814,89 @@ async function meshReconcileLedger(ctx, args) {
|
|
|
2688
2814
|
});
|
|
2689
2815
|
return JSON.stringify({ success: true, evidence }, null, 2);
|
|
2690
2816
|
}
|
|
2817
|
+
async function meshPruneStaleDirect(ctx, args = {}) {
|
|
2818
|
+
await refreshMeshFromDaemon(ctx);
|
|
2819
|
+
const execute = args.execute === true && args.dry_run !== true;
|
|
2820
|
+
const includeTerminal = args.include_terminal === true;
|
|
2821
|
+
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
2822
|
+
const ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 500 });
|
|
2823
|
+
const directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
|
|
2824
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2825
|
+
meshId: ctx.mesh.id,
|
|
2826
|
+
queue: (0, import_daemon_core.getQueue)(ctx.mesh.id),
|
|
2827
|
+
ledgerEntries,
|
|
2828
|
+
directDispatches,
|
|
2829
|
+
nodes: liveNodes,
|
|
2830
|
+
includeTerminalDirect: includeTerminal
|
|
2831
|
+
});
|
|
2832
|
+
const candidates = [
|
|
2833
|
+
...activeWorkEvidence.staleDirectWork,
|
|
2834
|
+
...includeTerminal ? activeWorkEvidence.terminalDirectWork : []
|
|
2835
|
+
];
|
|
2836
|
+
const storeTaskIds = new Set(directDispatches.map((d) => d.taskId));
|
|
2837
|
+
const prunable = [];
|
|
2838
|
+
const preservedUnacknowledged = [];
|
|
2839
|
+
const preservedLedgerOnly = [];
|
|
2840
|
+
const preservedNotOrphan = [];
|
|
2841
|
+
for (const record of candidates) {
|
|
2842
|
+
const classification = (0, import_daemon_core.classifyStaleDirectForPrune)(record, { includeTerminal });
|
|
2843
|
+
if (classification === "preserve_unacknowledged") {
|
|
2844
|
+
preservedUnacknowledged.push(record);
|
|
2845
|
+
continue;
|
|
2846
|
+
}
|
|
2847
|
+
if (classification === "preserve_active") {
|
|
2848
|
+
preservedNotOrphan.push(record);
|
|
2849
|
+
continue;
|
|
2850
|
+
}
|
|
2851
|
+
if (!storeTaskIds.has(record.taskId)) {
|
|
2852
|
+
preservedLedgerOnly.push(record);
|
|
2853
|
+
continue;
|
|
2854
|
+
}
|
|
2855
|
+
prunable.push(record);
|
|
2856
|
+
}
|
|
2857
|
+
const summarize = (records) => records.map((r) => ({
|
|
2858
|
+
taskId: r.taskId,
|
|
2859
|
+
nodeId: r.nodeId,
|
|
2860
|
+
sessionId: r.sessionId,
|
|
2861
|
+
status: r.status,
|
|
2862
|
+
terminal: r.terminal === true,
|
|
2863
|
+
staleReason: r.staleReason,
|
|
2864
|
+
taskTitle: r.taskTitle,
|
|
2865
|
+
createdAt: r.createdAt
|
|
2866
|
+
}));
|
|
2867
|
+
let prunedCount = 0;
|
|
2868
|
+
if (execute && prunable.length) {
|
|
2869
|
+
prunedCount = (0, import_daemon_core.deleteDirectDispatchesByTaskId)(ctx.mesh.id, prunable.map((r) => r.taskId));
|
|
2870
|
+
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2871
|
+
kind: "direct_dispatch_pruned",
|
|
2872
|
+
payload: {
|
|
2873
|
+
source: "mesh_prune_stale_direct",
|
|
2874
|
+
prunedCount,
|
|
2875
|
+
taskIds: prunable.map((r) => r.taskId),
|
|
2876
|
+
reasons: Array.from(new Set(prunable.map((r) => r.staleReason || (r.terminal ? "terminal" : "unknown"))))
|
|
2877
|
+
}
|
|
2878
|
+
});
|
|
2879
|
+
}
|
|
2880
|
+
return JSON.stringify({
|
|
2881
|
+
success: true,
|
|
2882
|
+
mode: execute ? "execute" : "dry_run",
|
|
2883
|
+
meshId: ctx.mesh.id,
|
|
2884
|
+
includeTerminal,
|
|
2885
|
+
candidateCount: candidates.length,
|
|
2886
|
+
prunableCount: prunable.length,
|
|
2887
|
+
prunedCount,
|
|
2888
|
+
prunable: summarize(prunable),
|
|
2889
|
+
preserved: {
|
|
2890
|
+
unacknowledgedCount: preservedUnacknowledged.length,
|
|
2891
|
+
ledgerOnlyCount: preservedLedgerOnly.length,
|
|
2892
|
+
notOrphanCount: preservedNotOrphan.length,
|
|
2893
|
+
unacknowledged: summarize(preservedUnacknowledged),
|
|
2894
|
+
ledgerOnly: summarize(preservedLedgerOnly),
|
|
2895
|
+
notOrphan: summarize(preservedNotOrphan)
|
|
2896
|
+
},
|
|
2897
|
+
note: execute ? `Pruned ${prunedCount} orphaned direct dispatch record(s) from the active staleDirect surface. The append-only mesh ledger audit history is preserved; a direct_dispatch_pruned entry records this prune.` : "Dry run \u2014 nothing was deleted. Re-run with execute=true to prune the listed orphaned records. Fresh unacknowledged dispatch failures (node/session still live) and ledger-only audit entries are always preserved."
|
|
2898
|
+
}, null, 2);
|
|
2899
|
+
}
|
|
2691
2900
|
async function meshListNodes(ctx) {
|
|
2692
2901
|
await refreshMeshFromDaemon(ctx);
|
|
2693
2902
|
const { mesh } = ctx;
|
|
@@ -3554,10 +3763,12 @@ async function meshFastForwardNode(ctx, args) {
|
|
|
3554
3763
|
meshId: ctx.mesh.id,
|
|
3555
3764
|
nodeId: node.id,
|
|
3556
3765
|
workspace: node.workspace,
|
|
3766
|
+
mode: args.mode === "push" ? "push" : "merge",
|
|
3557
3767
|
branch: typeof args.branch === "string" ? args.branch : void 0,
|
|
3558
3768
|
execute: args.execute === true && args.dry_run !== true,
|
|
3559
3769
|
dryRun,
|
|
3560
3770
|
updateSubmodules: args.update_submodules === true,
|
|
3771
|
+
pushSubmodules: args.push_submodules === true,
|
|
3561
3772
|
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
3562
3773
|
});
|
|
3563
3774
|
return JSON.stringify(unwrapCommandPayload(result), null, 2);
|
|
@@ -3745,7 +3956,7 @@ async function meshRefineBatch(ctx, args = {}) {
|
|
|
3745
3956
|
inlineMesh: ctx.mesh
|
|
3746
3957
|
});
|
|
3747
3958
|
const payload = unwrapCommandPayload(result) ?? result;
|
|
3748
|
-
if (payload?.batch && payload?.dryRun === false && Array.isArray(payload?.results)) {
|
|
3959
|
+
if (payload?.batch && payload?.dryRun === false && payload?.async !== true && Array.isArray(payload?.results)) {
|
|
3749
3960
|
for (const outcome of payload.results) {
|
|
3750
3961
|
if (outcome?.convergence === "merged_to_main" || outcome?.convergence === "skipped_patch_equivalent") {
|
|
3751
3962
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === outcome.nodeId);
|
|
@@ -4963,6 +5174,9 @@ async function startMcpServer(opts) {
|
|
|
4963
5174
|
case "mesh_cleanup_sessions":
|
|
4964
5175
|
text = await meshCleanupSessions(meshCtx, a);
|
|
4965
5176
|
break;
|
|
5177
|
+
case "mesh_prune_stale_direct":
|
|
5178
|
+
text = await meshPruneStaleDirect(meshCtx, a);
|
|
5179
|
+
break;
|
|
4966
5180
|
case "mesh_task_history":
|
|
4967
5181
|
text = await meshTaskHistory(meshCtx, a);
|
|
4968
5182
|
break;
|