@adhdev/daemon-standalone 0.9.82-rc.262 → 0.9.82-rc.264
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 +582 -66
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/index-BF7lyi-_.js +113 -0
- package/public/assets/index-CbR_x7-3.css +1 -0
- package/public/index.html +2 -2
- package/vendor/mcp-server/index.js +267 -16
- package/vendor/mcp-server/index.js.map +1 -1
- package/public/assets/index-C5W61nZC.js +0 -113
- package/public/assets/index-DW-XJSIP.css +0 -1
package/public/index.html
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
<meta name="description" content="ADHDev self-hosted dashboard for controlling AI agents" />
|
|
8
8
|
<link rel="icon" href="/otter-logo.png" />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-BF7lyi-_.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/vendor-BHUMCOj6.js">
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CbR_x7-3.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<!-- Apply theme immediately to prevent FOIT (Flash of Incorrect Theme) -->
|
|
@@ -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
|
}
|
|
@@ -1577,6 +1599,28 @@ function isGitStatusDirty(status) {
|
|
|
1577
1599
|
if (Array.isArray(status?.submodules) && status.submodules.some((submodule) => submodule?.dirty || submodule?.outOfSync || submodule?.error)) return true;
|
|
1578
1600
|
return countUncommittedChanges(status) > 0;
|
|
1579
1601
|
}
|
|
1602
|
+
var LARGE_LEDGER_FIELD_KEYS = /* @__PURE__ */ new Set(["plan", "validationPlan", "suggestedConfig", "payload"]);
|
|
1603
|
+
var LARGE_LEDGER_OBJECT_THRESHOLD = 800;
|
|
1604
|
+
function summarizeLargeLedgerField(key, value) {
|
|
1605
|
+
if (typeof value === "string") {
|
|
1606
|
+
return value.length > 500 ? value.slice(0, 500) + "\u2026" : value;
|
|
1607
|
+
}
|
|
1608
|
+
if (Array.isArray(value)) {
|
|
1609
|
+
const serialized = JSON.stringify(value);
|
|
1610
|
+
if (serialized && serialized.length > LARGE_LEDGER_OBJECT_THRESHOLD) {
|
|
1611
|
+
return `[${key} summarized: ${value.length} items \u2014 use verbose=true or mesh_reconcile_ledger]`;
|
|
1612
|
+
}
|
|
1613
|
+
return value;
|
|
1614
|
+
}
|
|
1615
|
+
if (value && typeof value === "object") {
|
|
1616
|
+
const serialized = JSON.stringify(value);
|
|
1617
|
+
if (serialized && serialized.length > LARGE_LEDGER_OBJECT_THRESHOLD) {
|
|
1618
|
+
return `[${key} summarized: ${Object.keys(value).length} keys \u2014 use verbose=true or mesh_reconcile_ledger]`;
|
|
1619
|
+
}
|
|
1620
|
+
return value;
|
|
1621
|
+
}
|
|
1622
|
+
return value;
|
|
1623
|
+
}
|
|
1580
1624
|
function slimLedgerPayload(payload) {
|
|
1581
1625
|
const slim = {};
|
|
1582
1626
|
for (const [k, v] of Object.entries(payload)) {
|
|
@@ -1585,6 +1629,8 @@ function slimLedgerPayload(payload) {
|
|
|
1585
1629
|
} else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
|
|
1586
1630
|
} else if (k === "finalSummary") {
|
|
1587
1631
|
slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
|
|
1632
|
+
} else if (LARGE_LEDGER_FIELD_KEYS.has(k)) {
|
|
1633
|
+
slim[k] = summarizeLargeLedgerField(k, v);
|
|
1588
1634
|
} else {
|
|
1589
1635
|
slim[k] = v;
|
|
1590
1636
|
}
|
|
@@ -1705,6 +1751,30 @@ async function collectLiveStatusSessions(ctx, node) {
|
|
|
1705
1751
|
return [];
|
|
1706
1752
|
}
|
|
1707
1753
|
}
|
|
1754
|
+
async function collectLiveStatusProbe(ctx, node) {
|
|
1755
|
+
try {
|
|
1756
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
1757
|
+
return {
|
|
1758
|
+
sessions: extractStatusMetadataSessions(statusResult),
|
|
1759
|
+
daemonBuild: extractDaemonBuildInfo(statusResult)
|
|
1760
|
+
};
|
|
1761
|
+
} catch {
|
|
1762
|
+
return { sessions: [] };
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
function extractDaemonBuildInfo(value) {
|
|
1766
|
+
const payload = unwrapCommandPayload(value);
|
|
1767
|
+
const build = payload?.daemonBuild && typeof payload.daemonBuild === "object" ? payload.daemonBuild : value?.daemonBuild && typeof value.daemonBuild === "object" ? value.daemonBuild : void 0;
|
|
1768
|
+
if (!build) return void 0;
|
|
1769
|
+
const commit = readString(build.commit);
|
|
1770
|
+
if (!commit) return void 0;
|
|
1771
|
+
return {
|
|
1772
|
+
commit,
|
|
1773
|
+
commitShort: readString(build.commitShort) || commit.slice(0, 7),
|
|
1774
|
+
version: readString(build.version) || "unknown",
|
|
1775
|
+
...readString(build.builtAt) ? { builtAt: readString(build.builtAt) } : {}
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1708
1778
|
async function collectMeshViewQueueNodesWithLiveSessions(ctx) {
|
|
1709
1779
|
const nodes = await Promise.all(ctx.mesh.nodes.map(async (node) => {
|
|
1710
1780
|
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
@@ -1957,13 +2027,14 @@ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
|
|
|
1957
2027
|
}
|
|
1958
2028
|
var MESH_STATUS_TOOL = {
|
|
1959
2029
|
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.",
|
|
2030
|
+
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
2031
|
inputSchema: {
|
|
1962
2032
|
type: "object",
|
|
1963
2033
|
properties: {
|
|
1964
2034
|
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
|
|
1965
2035
|
includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." },
|
|
1966
|
-
|
|
2036
|
+
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." },
|
|
2037
|
+
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
2038
|
verbose: { type: "boolean", description: "Force the full payload; overrides compact." }
|
|
1968
2039
|
}
|
|
1969
2040
|
}
|
|
@@ -2119,15 +2190,17 @@ var MESH_GIT_STATUS_TOOL = {
|
|
|
2119
2190
|
};
|
|
2120
2191
|
var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
2121
2192
|
name: "mesh_fast_forward_node",
|
|
2122
|
-
description:
|
|
2193
|
+
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
2194
|
inputSchema: {
|
|
2124
2195
|
type: "object",
|
|
2125
2196
|
properties: {
|
|
2126
2197
|
node_id: { type: "string", description: "Target node ID." },
|
|
2198
|
+
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
2199
|
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." },
|
|
2200
|
+
execute: { type: "boolean", description: "When true, apply the fast-forward/push if all safety gates pass. Defaults false/dry-run." },
|
|
2129
2201
|
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
2130
|
-
update_submodules: { type: "boolean", description: "
|
|
2202
|
+
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.' },
|
|
2203
|
+
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
2204
|
},
|
|
2132
2205
|
required: ["node_id"]
|
|
2133
2206
|
}
|
|
@@ -2228,8 +2301,10 @@ var MESH_TASK_HISTORY_TOOL = {
|
|
|
2228
2301
|
inputSchema: {
|
|
2229
2302
|
type: "object",
|
|
2230
2303
|
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." }
|
|
2304
|
+
tail: { type: "number", description: "Number of recent entries to return (default: 20; clamped to 40 in compact mode, 200 in verbose)." },
|
|
2305
|
+
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." },
|
|
2306
|
+
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." },
|
|
2307
|
+
verbose: { type: "boolean", description: "Force the full untruncated payload; overrides compact." }
|
|
2233
2308
|
}
|
|
2234
2309
|
}
|
|
2235
2310
|
};
|
|
@@ -2247,13 +2322,27 @@ var MESH_RECONCILE_LEDGER_TOOL = {
|
|
|
2247
2322
|
}
|
|
2248
2323
|
}
|
|
2249
2324
|
};
|
|
2325
|
+
var MESH_PRUNE_STALE_DIRECT_TOOL = {
|
|
2326
|
+
name: "mesh_prune_stale_direct",
|
|
2327
|
+
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.",
|
|
2328
|
+
inputSchema: {
|
|
2329
|
+
type: "object",
|
|
2330
|
+
properties: {
|
|
2331
|
+
execute: { type: "boolean", description: "When true, actually delete the orphaned records. Defaults false (dry run). Ignored when dry_run=true." },
|
|
2332
|
+
dry_run: { type: "boolean", description: "Force a preview without mutation even if execute=true. Defaults to dry-run behavior when execute is not set." },
|
|
2333
|
+
include_terminal: { type: "boolean", description: "Also prune terminal (completed/failed) direct dispatch store rows in addition to orphans. Defaults false." }
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
};
|
|
2250
2337
|
var MESH_REFINE_NODE_TOOL = {
|
|
2251
2338
|
name: "mesh_refine_node",
|
|
2252
|
-
description: "The Refinery:
|
|
2339
|
+
description: "The Refinery: validate \u2192 merge \u2192 push \u2192 clean up a completed worktree node onto the base branch. Defaults to dry-run (plan only): returns the validation plan with mergeWillRun:false/cleanupWillRun:false and performs NO merge/push/cleanup. Pass execute=true to actually converge the node. execute=true is async: 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. dry_run=true overrides execute. Matches the mesh_refine_batch / mesh_fast_forward_node dry_run/execute contract.",
|
|
2253
2340
|
inputSchema: {
|
|
2254
2341
|
type: "object",
|
|
2255
2342
|
properties: {
|
|
2256
|
-
node_id: { type: "string", description: "Node ID of the completed worktree node to refine and merge." }
|
|
2343
|
+
node_id: { type: "string", description: "Node ID of the completed worktree node to refine and merge." },
|
|
2344
|
+
execute: { type: "boolean", description: "When true, run validation/merge/push/cleanup for this node. Defaults false/dry-run." },
|
|
2345
|
+
dry_run: { type: "boolean", description: "Preview the validation plan without merging. Defaults true unless execute=true; dry_run=true overrides execute." }
|
|
2257
2346
|
},
|
|
2258
2347
|
required: ["node_id"]
|
|
2259
2348
|
}
|
|
@@ -2347,6 +2436,7 @@ var ALL_MESH_TOOLS = [
|
|
|
2347
2436
|
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
2348
2437
|
MESH_REFINE_PLAN_TOOL,
|
|
2349
2438
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
2439
|
+
MESH_PRUNE_STALE_DIRECT_TOOL,
|
|
2350
2440
|
MESH_TASK_HISTORY_TOOL,
|
|
2351
2441
|
MESH_RECONCILE_LEDGER_TOOL,
|
|
2352
2442
|
MESH_MISSION_UPSERT_TOOL,
|
|
@@ -2384,6 +2474,9 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2384
2474
|
entry.isDirty = dirty;
|
|
2385
2475
|
entry.uncommittedChanges = uncommittedChanges;
|
|
2386
2476
|
entry.branchConvergence = buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges);
|
|
2477
|
+
if (status?.daemonBuildBehind && typeof status.daemonBuildBehind === "object") {
|
|
2478
|
+
entry.staleDaemonBuild = status.daemonBuildBehind;
|
|
2479
|
+
}
|
|
2387
2480
|
const submodules = extractSubmodules(statusResult, node.policy?.submoduleIgnorePaths || []);
|
|
2388
2481
|
if (submodules && submodules.some((s) => s?.outOfSync)) {
|
|
2389
2482
|
entry.submoduleWarning = "One or more submodules are out of sync with the parent repo. Run `git submodule update` or check deployment readiness.";
|
|
@@ -2451,7 +2544,9 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2451
2544
|
}
|
|
2452
2545
|
const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
|
|
2453
2546
|
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
2454
|
-
const
|
|
2547
|
+
const statusProbe = await collectLiveStatusProbe(ctx, node);
|
|
2548
|
+
const liveSessions = statusProbe.sessions;
|
|
2549
|
+
if (statusProbe.daemonBuild) entry.daemonBuild = statusProbe.daemonBuild;
|
|
2455
2550
|
if (liveSessions.length > 0) {
|
|
2456
2551
|
entry.sessions = liveSessions.map((s) => {
|
|
2457
2552
|
const coordinatorMeshId = typeof s.coordinator?.meshId === "string" ? s.coordinator.meshId : void 0;
|
|
@@ -2501,10 +2596,63 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2501
2596
|
}
|
|
2502
2597
|
}
|
|
2503
2598
|
}
|
|
2599
|
+
const includeSessions = args.includeSessions === true;
|
|
2600
|
+
const daemonSessions = {};
|
|
2601
|
+
if (compact) {
|
|
2602
|
+
const seenDaemons = /* @__PURE__ */ new Set();
|
|
2603
|
+
for (const entry of results) {
|
|
2604
|
+
const daemonId = typeof entry?.daemonId === "string" && entry.daemonId ? entry.daemonId : "";
|
|
2605
|
+
const sessions = Array.isArray(entry?.sessions) ? entry.sessions : [];
|
|
2606
|
+
if (daemonId && sessions.length > 0 && !seenDaemons.has(daemonId)) {
|
|
2607
|
+
seenDaemons.add(daemonId);
|
|
2608
|
+
daemonSessions[daemonId] = includeSessions ? sessions : summarizeNodeSessions(sessions);
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
const daemonBuilds = {};
|
|
2613
|
+
for (const entry of results) {
|
|
2614
|
+
const daemonId = typeof entry?.daemonId === "string" && entry.daemonId ? entry.daemonId : "";
|
|
2615
|
+
if (daemonId && entry?.daemonBuild && !(daemonId in daemonBuilds)) {
|
|
2616
|
+
daemonBuilds[daemonId] = entry.daemonBuild;
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
const staleDaemonBuilds = [];
|
|
2620
|
+
const seenStale = /* @__PURE__ */ new Set();
|
|
2621
|
+
for (const entry of results) {
|
|
2622
|
+
const behind = entry?.staleDaemonBuild;
|
|
2623
|
+
if (!behind || typeof behind !== "object") continue;
|
|
2624
|
+
const daemonId = typeof entry?.daemonId === "string" ? entry.daemonId : "";
|
|
2625
|
+
const key = `${daemonId}::${behind.scope ?? ""}::${behind.buildCommit ?? ""}::${behind.head ?? ""}`;
|
|
2626
|
+
if (seenStale.has(key)) continue;
|
|
2627
|
+
seenStale.add(key);
|
|
2628
|
+
const isDaemonAffecting = behind.isDaemonAffecting !== false;
|
|
2629
|
+
staleDaemonBuilds.push({
|
|
2630
|
+
daemonId,
|
|
2631
|
+
nodeId: entry.nodeId,
|
|
2632
|
+
scope: behind.scope,
|
|
2633
|
+
liveBuildCommit: behind.buildCommit,
|
|
2634
|
+
liveBuildCommitShort: behind.buildCommitShort,
|
|
2635
|
+
head: behind.head,
|
|
2636
|
+
isDaemonAffecting,
|
|
2637
|
+
...Array.isArray(behind.affectedPackages) && behind.affectedPackages.length > 0 ? { affectedPackages: behind.affectedPackages } : {},
|
|
2638
|
+
warning: behind.warning
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2641
|
+
const daemonAffectingStaleBuilds = staleDaemonBuilds.filter((b) => b.isDaemonAffecting !== false);
|
|
2642
|
+
const webOnlyStaleBuilds = staleDaemonBuilds.filter((b) => b.isDaemonAffecting === false);
|
|
2504
2643
|
const nodesForResponse = compact ? results.map((entry) => {
|
|
2505
|
-
if (!entry || typeof entry !== "object"
|
|
2506
|
-
const
|
|
2507
|
-
|
|
2644
|
+
if (!entry || typeof entry !== "object") return entry;
|
|
2645
|
+
const next = { ...entry };
|
|
2646
|
+
if (next.git !== void 0) {
|
|
2647
|
+
const slimGit = buildCompactGitSnapshot(next.git);
|
|
2648
|
+
if (slimGit) next.git = slimGit;
|
|
2649
|
+
}
|
|
2650
|
+
if (Array.isArray(next.sessions)) {
|
|
2651
|
+
next.sessionSummary = summarizeNodeSessions(next.sessions);
|
|
2652
|
+
if (!includeSessions) delete next.sessions;
|
|
2653
|
+
}
|
|
2654
|
+
if (next.daemonBuild !== void 0) delete next.daemonBuild;
|
|
2655
|
+
return next;
|
|
2508
2656
|
}) : results;
|
|
2509
2657
|
const response = {
|
|
2510
2658
|
meshId: mesh.id,
|
|
@@ -2520,6 +2668,15 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2520
2668
|
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
2521
2669
|
},
|
|
2522
2670
|
nodes: nodesForResponse,
|
|
2671
|
+
...compact && Object.keys(daemonSessions).length > 0 ? { daemonSessions } : {},
|
|
2672
|
+
...Object.keys(daemonBuilds).length > 0 ? { daemonBuilds } : {},
|
|
2673
|
+
...staleDaemonBuilds.length > 0 ? { staleDaemonBuilds } : {},
|
|
2674
|
+
...daemonAffectingStaleBuilds.length > 0 ? {
|
|
2675
|
+
staleDaemonBuildWarning: "One or more live daemons were built from a commit behind the workspace HEAD with daemon-runtime package changes. 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."
|
|
2676
|
+
} : {},
|
|
2677
|
+
...webOnlyStaleBuilds.length > 0 ? {
|
|
2678
|
+
webOnlyStaleBuildNote: 'One or more live daemons are behind workspace HEAD, but only web packages changed in that range. The daemon does NOT need a rebuild/restart \u2014 redeploy the web app to reflect those changes. This is informational, not a "fix not live" condition.'
|
|
2679
|
+
} : {},
|
|
2523
2680
|
activeWork: activeWorkEvidence.activeWork,
|
|
2524
2681
|
staleDirectWorkSummary,
|
|
2525
2682
|
...args.includeStaleDirectWorkDetails === true ? { staleDirectWork: activeWorkEvidence.staleDirectWork } : {},
|
|
@@ -2584,14 +2741,17 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2584
2741
|
}
|
|
2585
2742
|
async function meshTaskHistory(ctx, args) {
|
|
2586
2743
|
const { mesh } = ctx;
|
|
2744
|
+
const compact = args.verbose === true ? false : args.compact ?? true;
|
|
2587
2745
|
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
2588
|
-
const
|
|
2746
|
+
const requestedTail = typeof args.tail === "number" && args.tail > 0 ? Math.floor(args.tail) : 20;
|
|
2747
|
+
const compactCap = requestedTail > 50 ? 20 : 30;
|
|
2748
|
+
const tail = compact ? Math.min(requestedTail, compactCap) : Math.min(requestedTail, 200);
|
|
2589
2749
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
2590
2750
|
const rawEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
2591
|
-
const entries = rawEntries.map((e) => ({
|
|
2751
|
+
const entries = compact ? rawEntries.map((e) => ({
|
|
2592
2752
|
...e,
|
|
2593
2753
|
payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
|
|
2594
|
-
}));
|
|
2754
|
+
})) : rawEntries;
|
|
2595
2755
|
const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
2596
2756
|
let taskStats;
|
|
2597
2757
|
try {
|
|
@@ -2604,6 +2764,7 @@ async function meshTaskHistory(ctx, args) {
|
|
|
2604
2764
|
}
|
|
2605
2765
|
return JSON.stringify({
|
|
2606
2766
|
meshId: mesh.id,
|
|
2767
|
+
payloadMode: compact ? "compact" : "full",
|
|
2607
2768
|
entries,
|
|
2608
2769
|
summary,
|
|
2609
2770
|
...taskStats ? { taskStats } : {},
|
|
@@ -2688,6 +2849,89 @@ async function meshReconcileLedger(ctx, args) {
|
|
|
2688
2849
|
});
|
|
2689
2850
|
return JSON.stringify({ success: true, evidence }, null, 2);
|
|
2690
2851
|
}
|
|
2852
|
+
async function meshPruneStaleDirect(ctx, args = {}) {
|
|
2853
|
+
await refreshMeshFromDaemon(ctx);
|
|
2854
|
+
const execute = args.execute === true && args.dry_run !== true;
|
|
2855
|
+
const includeTerminal = args.include_terminal === true;
|
|
2856
|
+
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
2857
|
+
const ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 500 });
|
|
2858
|
+
const directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
|
|
2859
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2860
|
+
meshId: ctx.mesh.id,
|
|
2861
|
+
queue: (0, import_daemon_core.getQueue)(ctx.mesh.id),
|
|
2862
|
+
ledgerEntries,
|
|
2863
|
+
directDispatches,
|
|
2864
|
+
nodes: liveNodes,
|
|
2865
|
+
includeTerminalDirect: includeTerminal
|
|
2866
|
+
});
|
|
2867
|
+
const candidates = [
|
|
2868
|
+
...activeWorkEvidence.staleDirectWork,
|
|
2869
|
+
...includeTerminal ? activeWorkEvidence.terminalDirectWork : []
|
|
2870
|
+
];
|
|
2871
|
+
const storeTaskIds = new Set(directDispatches.map((d) => d.taskId));
|
|
2872
|
+
const prunable = [];
|
|
2873
|
+
const preservedUnacknowledged = [];
|
|
2874
|
+
const preservedLedgerOnly = [];
|
|
2875
|
+
const preservedNotOrphan = [];
|
|
2876
|
+
for (const record of candidates) {
|
|
2877
|
+
const classification = (0, import_daemon_core.classifyStaleDirectForPrune)(record, { includeTerminal });
|
|
2878
|
+
if (classification === "preserve_unacknowledged") {
|
|
2879
|
+
preservedUnacknowledged.push(record);
|
|
2880
|
+
continue;
|
|
2881
|
+
}
|
|
2882
|
+
if (classification === "preserve_active") {
|
|
2883
|
+
preservedNotOrphan.push(record);
|
|
2884
|
+
continue;
|
|
2885
|
+
}
|
|
2886
|
+
if (!storeTaskIds.has(record.taskId)) {
|
|
2887
|
+
preservedLedgerOnly.push(record);
|
|
2888
|
+
continue;
|
|
2889
|
+
}
|
|
2890
|
+
prunable.push(record);
|
|
2891
|
+
}
|
|
2892
|
+
const summarize = (records) => records.map((r) => ({
|
|
2893
|
+
taskId: r.taskId,
|
|
2894
|
+
nodeId: r.nodeId,
|
|
2895
|
+
sessionId: r.sessionId,
|
|
2896
|
+
status: r.status,
|
|
2897
|
+
terminal: r.terminal === true,
|
|
2898
|
+
staleReason: r.staleReason,
|
|
2899
|
+
taskTitle: r.taskTitle,
|
|
2900
|
+
createdAt: r.createdAt
|
|
2901
|
+
}));
|
|
2902
|
+
let prunedCount = 0;
|
|
2903
|
+
if (execute && prunable.length) {
|
|
2904
|
+
prunedCount = (0, import_daemon_core.deleteDirectDispatchesByTaskId)(ctx.mesh.id, prunable.map((r) => r.taskId));
|
|
2905
|
+
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2906
|
+
kind: "direct_dispatch_pruned",
|
|
2907
|
+
payload: {
|
|
2908
|
+
source: "mesh_prune_stale_direct",
|
|
2909
|
+
prunedCount,
|
|
2910
|
+
taskIds: prunable.map((r) => r.taskId),
|
|
2911
|
+
reasons: Array.from(new Set(prunable.map((r) => r.staleReason || (r.terminal ? "terminal" : "unknown"))))
|
|
2912
|
+
}
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
return JSON.stringify({
|
|
2916
|
+
success: true,
|
|
2917
|
+
mode: execute ? "execute" : "dry_run",
|
|
2918
|
+
meshId: ctx.mesh.id,
|
|
2919
|
+
includeTerminal,
|
|
2920
|
+
candidateCount: candidates.length,
|
|
2921
|
+
prunableCount: prunable.length,
|
|
2922
|
+
prunedCount,
|
|
2923
|
+
prunable: summarize(prunable),
|
|
2924
|
+
preserved: {
|
|
2925
|
+
unacknowledgedCount: preservedUnacknowledged.length,
|
|
2926
|
+
ledgerOnlyCount: preservedLedgerOnly.length,
|
|
2927
|
+
notOrphanCount: preservedNotOrphan.length,
|
|
2928
|
+
unacknowledged: summarize(preservedUnacknowledged),
|
|
2929
|
+
ledgerOnly: summarize(preservedLedgerOnly),
|
|
2930
|
+
notOrphan: summarize(preservedNotOrphan)
|
|
2931
|
+
},
|
|
2932
|
+
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."
|
|
2933
|
+
}, null, 2);
|
|
2934
|
+
}
|
|
2691
2935
|
async function meshListNodes(ctx) {
|
|
2692
2936
|
await refreshMeshFromDaemon(ctx);
|
|
2693
2937
|
const { mesh } = ctx;
|
|
@@ -3554,10 +3798,12 @@ async function meshFastForwardNode(ctx, args) {
|
|
|
3554
3798
|
meshId: ctx.mesh.id,
|
|
3555
3799
|
nodeId: node.id,
|
|
3556
3800
|
workspace: node.workspace,
|
|
3801
|
+
mode: args.mode === "push" ? "push" : "merge",
|
|
3557
3802
|
branch: typeof args.branch === "string" ? args.branch : void 0,
|
|
3558
3803
|
execute: args.execute === true && args.dry_run !== true,
|
|
3559
3804
|
dryRun,
|
|
3560
3805
|
updateSubmodules: args.update_submodules === true,
|
|
3806
|
+
pushSubmodules: args.push_submodules === true,
|
|
3561
3807
|
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
3562
3808
|
});
|
|
3563
3809
|
return JSON.stringify(unwrapCommandPayload(result), null, 2);
|
|
@@ -3723,6 +3969,8 @@ async function meshRefineNode(ctx, args) {
|
|
|
3723
3969
|
const result = await commandForNode(ctx, node, "refine_mesh_node", {
|
|
3724
3970
|
meshId: ctx.mesh.id,
|
|
3725
3971
|
nodeId: args.node_id,
|
|
3972
|
+
...args.execute !== void 0 ? { execute: args.execute } : {},
|
|
3973
|
+
...args.dry_run !== void 0 ? { dryRun: args.dry_run } : {},
|
|
3726
3974
|
inlineMesh: ctx.mesh
|
|
3727
3975
|
});
|
|
3728
3976
|
if (result?.success && result.async !== true && result.removeResult?.removed !== false) {
|
|
@@ -4963,6 +5211,9 @@ async function startMcpServer(opts) {
|
|
|
4963
5211
|
case "mesh_cleanup_sessions":
|
|
4964
5212
|
text = await meshCleanupSessions(meshCtx, a);
|
|
4965
5213
|
break;
|
|
5214
|
+
case "mesh_prune_stale_direct":
|
|
5215
|
+
text = await meshPruneStaleDirect(meshCtx, a);
|
|
5216
|
+
break;
|
|
4966
5217
|
case "mesh_task_history":
|
|
4967
5218
|
text = await meshTaskHistory(meshCtx, a);
|
|
4968
5219
|
break;
|