@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/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-C5W61nZC.js"></script>
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-DW-XJSIP.css">
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
- compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Set false (or verbose=true) for the full dashboard-grade payload." },
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: "Safely dry-run or execute an obvious direct fast-forward for a mesh node without launching an agent session. Defaults to dry-run; execution requires execute=true. Never pushes, rebases, resets, cleans, or checks out arbitrary revisions.",
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: "When true, if the root fast-forward changes gitlinks, run only git submodule update --init --recursive and verify submodules clean." }
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: 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.",
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 liveSessions = await collectLiveStatusSessions(ctx, node);
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" || entry.git === void 0) return entry;
2506
- const slimGit = buildCompactGitSnapshot(entry.git);
2507
- return slimGit ? { ...entry, git: slimGit } : entry;
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 tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
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;