@adhdev/daemon-standalone 0.9.82-rc.209 → 0.9.82-rc.210

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-CtWWaEgh.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-fge7gXie.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendor-BwuWgaJI.js">
12
- <link rel="stylesheet" crossorigin href="/assets/index-BxT7AHR9.css">
12
+ <link rel="stylesheet" crossorigin href="/assets/index-DBlsL5MS.css">
13
13
  </head>
14
14
  <body>
15
15
  <!-- Apply theme immediately to prevent FOIT (Flash of Incorrect Theme) -->
@@ -248,6 +248,30 @@ function isCoordinatorVisibleMessage(message) {
248
248
  if (meta?.internal === true || meta?.debug === true || meta?.control === true || meta?.userVisible === false || meta?.user_visible === false) return false;
249
249
  return role === "user" || role === "assistant" || role === "agent";
250
250
  }
251
+ function summarizeToolMessage(message) {
252
+ if (!message || typeof message !== "object") return null;
253
+ const kind = String(message.kind ?? message.type ?? message.messageKind ?? "").toLowerCase();
254
+ const role = String(message.role ?? "").toLowerCase();
255
+ if (kind === "terminal" || kind === "bash") {
256
+ const cmd = message.command ?? message.cmd ?? message.input ?? messageContent(message);
257
+ const exit = message.exitCode ?? message.exit_code ?? message.code;
258
+ const cmdShort = typeof cmd === "string" ? cmd.split("\n")[0].slice(0, 120) : null;
259
+ if (!cmdShort) return null;
260
+ return exit !== void 0 && exit !== null ? `[Bash] ${cmdShort} \u2192 exit ${exit}` : `[Bash] ${cmdShort}`;
261
+ }
262
+ if (kind === "tool_call" || kind === "tool" || role === "tool") {
263
+ const name = message.name ?? message.toolName ?? message.tool_name ?? message.function?.name;
264
+ if (typeof name === "string" && name.trim()) return `[Tool] ${name.trim()}`;
265
+ return null;
266
+ }
267
+ if (kind === "tool_result") {
268
+ const exit = message.exitCode ?? message.exit_code ?? message.code;
269
+ const name = message.name ?? message.toolName ?? message.tool_name;
270
+ const label = typeof name === "string" && name.trim() ? name.trim() : "tool";
271
+ return exit !== void 0 && exit !== null ? `[Tool result: ${label}] exit ${exit}` : null;
272
+ }
273
+ return null;
274
+ }
251
275
  function buildCompactMessageTail(visibleMessages, opts) {
252
276
  return visibleMessages.slice(-opts.limit);
253
277
  }
@@ -261,6 +285,9 @@ function compactChatPayload(payload, opts = {}) {
261
285
  });
262
286
  const summary = typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : messageContent(finalAssistant).trim();
263
287
  const messages = buildCompactMessageTail(visible, { summary, finalAssistant, limit });
288
+ const toolSummaries = rawMessages.filter((m) => !isCoordinatorVisibleMessage(m)).map(summarizeToolMessage).filter((s) => s !== null);
289
+ const omittedMessages = Math.max(0, rawMessages.length - messages.length);
290
+ const filteredMessages = Math.max(0, rawMessages.length - visible.length);
264
291
  return {
265
292
  success: payload?.success !== false,
266
293
  compact: true,
@@ -270,8 +297,9 @@ function compactChatPayload(payload, opts = {}) {
270
297
  providerSessionId: payload?.providerSessionId ?? null,
271
298
  totalMessages: rawMessages.length,
272
299
  visibleMessages: visible.length,
273
- filteredMessages: visible.length,
274
- omittedMessages: Math.max(0, rawMessages.length - visible.length),
300
+ filteredMessages,
301
+ omittedMessages,
302
+ ...toolSummaries.length > 0 ? { toolSummaries } : {},
275
303
  summary,
276
304
  ...payload?.changedFiles !== void 0 ? { changedFiles: payload.changedFiles } : {},
277
305
  ...payload?.testsRun !== void 0 ? { testsRun: payload.testsRun } : {},
@@ -1608,9 +1636,21 @@ function getNodeLaunchReadiness(node) {
1608
1636
  launchBlockedMessage: missingProviderPriorityMessage(node.id)
1609
1637
  };
1610
1638
  }
1611
- function getWorktreeBootstrapLaunchBlock(node) {
1639
+ function getWorktreeBootstrapLaunchBlock(node, meshPolicy) {
1640
+ if (!node.isLocalWorktree) return void 0;
1612
1641
  const bootstrap = node.worktreeBootstrap;
1613
- if (!node.isLocalWorktree || bootstrap?.status !== "failed" || bootstrap?.required === false) return void 0;
1642
+ const requireReady = !!(meshPolicy && typeof meshPolicy === "object" && meshPolicy.requireBootstrapBeforeLaunch === true);
1643
+ if (requireReady && bootstrap?.status !== "ready") {
1644
+ return {
1645
+ success: false,
1646
+ code: "bootstrap_not_ready",
1647
+ error: `Node '${node.id}' bootstrap state is '${bootstrap?.status ?? "unknown"}' and mesh policy requireBootstrapBeforeLaunch is enabled.`,
1648
+ nodeId: node.id,
1649
+ worktreeBootstrap: bootstrap ?? null,
1650
+ recoveryHint: "Run the worktree bootstrap (clone runOnClone or a refine with bootstrap inherit) until the node reports ready, or disable requireBootstrapBeforeLaunch."
1651
+ };
1652
+ }
1653
+ if (bootstrap?.status !== "failed" || bootstrap?.required === false) return void 0;
1614
1654
  return {
1615
1655
  success: false,
1616
1656
  code: "worktree_bootstrap_failed",
@@ -1898,7 +1938,11 @@ var MESH_ENQUEUE_TASK_TOOL = {
1898
1938
  task_mode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "Optional task-mode contract. live_debug_readonly rejects obvious write/commit/push/deploy/destructive instructions before dispatch." },
1899
1939
  taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
1900
1940
  requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
1901
- required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." }
1941
+ required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." },
1942
+ depends_on: { type: "array", items: { type: "string" }, description: "Task ids that must complete before this task becomes claimable. Cycles are rejected at enqueue." },
1943
+ dependsOn: { type: "array", items: { type: "string" }, description: "CamelCase alias for depends_on." },
1944
+ mission_id: { type: "string", description: "Mission this task belongs to (mesh_mission record id)." },
1945
+ missionId: { type: "string", description: "CamelCase alias for mission_id." }
1902
1946
  },
1903
1947
  required: ["message"]
1904
1948
  }
@@ -1936,7 +1980,7 @@ var MESH_QUEUE_CANCEL_TOOL = {
1936
1980
  };
1937
1981
  var MESH_QUEUE_REQUEUE_TOOL = {
1938
1982
  name: "mesh_queue_requeue",
1939
- 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.",
1983
+ 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. When the task has exceeded its retry cap it is auto-failed instead; use force=true to override.",
1940
1984
  inputSchema: {
1941
1985
  type: "object",
1942
1986
  properties: {
@@ -1945,7 +1989,8 @@ var MESH_QUEUE_REQUEUE_TOOL = {
1945
1989
  target_node_id: { type: "string", description: "Optional replacement target node ID." },
1946
1990
  target_session_id: { type: "string", description: "Optional replacement target runtime session ID." },
1947
1991
  clear_target_node: { type: "boolean", description: "When true, remove any existing target node constraint." },
1948
- 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." }
1992
+ 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." },
1993
+ force: { type: "boolean", description: "When true, bypass the retry cap and requeue even if maxRetries has been exceeded. Use only for explicit operator recovery." }
1949
1994
  },
1950
1995
  required: ["task_id"]
1951
1996
  }
@@ -2045,6 +2090,20 @@ var MESH_CHECKPOINT_TOOL = {
2045
2090
  required: ["node_id", "message"]
2046
2091
  }
2047
2092
  };
2093
+ var MESH_MISSION_UPSERT_TOOL = {
2094
+ name: "mesh_mission_upsert",
2095
+ description: "Create or update a persistent mission record so the plan survives coordinator restarts. Create a mission before enqueueing a multi-task batch, attach tasks via mesh_enqueue_task mission_id, and update status to completed/abandoned when the outcome is decided. Progress is derived from task statuses \u2014 there is no separate progress field.",
2096
+ inputSchema: {
2097
+ type: "object",
2098
+ properties: {
2099
+ mission_id: { type: "string", description: "Mission id to update. Omit to create a new mission." },
2100
+ title: { type: "string", description: "Short mission title." },
2101
+ goal: { type: "string", description: "Free-text mission goal/definition of done." },
2102
+ status: { type: "string", enum: ["active", "paused", "completed", "abandoned"], description: "Mission lifecycle status. Defaults to active on create." }
2103
+ },
2104
+ required: ["title"]
2105
+ }
2106
+ };
2048
2107
  var MESH_APPROVE_TOOL = {
2049
2108
  name: "mesh_approve",
2050
2109
  description: "Approve or reject a pending action on a delegated agent session.",
@@ -2182,6 +2241,17 @@ var MESH_REFINE_PLAN_TOOL = {
2182
2241
  required: ["node_id"]
2183
2242
  }
2184
2243
  };
2244
+ var MESH_REVIEW_INBOX_TOOL = {
2245
+ name: "mesh_review_inbox",
2246
+ description: "List local worktree nodes that need human review: merge candidates (pushed feature branches ready to merge) and Refinery-blocked review results. Returns evidence summaries, diff stats vs. the default branch, and suggested actions (Refine / Requeue / Dismiss). Remote nodes are excluded in M4.0.",
2247
+ inputSchema: {
2248
+ type: "object",
2249
+ properties: {
2250
+ mesh_id: { type: "string", description: "Mesh ID (optional \u2014 inferred from active mesh if omitted)." }
2251
+ },
2252
+ required: []
2253
+ }
2254
+ };
2185
2255
  var ALL_MESH_TOOLS = [
2186
2256
  MESH_STATUS_TOOL,
2187
2257
  MESH_LIST_NODES_TOOL,
@@ -2206,9 +2276,12 @@ var ALL_MESH_TOOLS = [
2206
2276
  MESH_REFINE_PLAN_TOOL,
2207
2277
  MESH_CLEANUP_SESSIONS_TOOL,
2208
2278
  MESH_TASK_HISTORY_TOOL,
2209
- MESH_RECONCILE_LEDGER_TOOL
2279
+ MESH_RECONCILE_LEDGER_TOOL,
2280
+ MESH_MISSION_UPSERT_TOOL,
2281
+ MESH_REVIEW_INBOX_TOOL
2210
2282
  ];
2211
2283
  async function meshStatus(ctx, args = {}) {
2284
+ const rateResult = (0, import_daemon_core.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
2212
2285
  await refreshMeshFromDaemon(ctx);
2213
2286
  const { mesh, transport } = ctx;
2214
2287
  let ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
@@ -2396,6 +2469,7 @@ async function meshStatus(ctx, args = {}) {
2396
2469
  ...args.includeTerminalDirectWork === true ? { terminalDirectWork: activeWorkEvidence.terminalDirectWork } : {},
2397
2470
  activeWorkSummary: activeWorkEvidence.summary,
2398
2471
  ...pollingGuidance ? { pollingGuidance } : {},
2472
+ ...rateResult.rateLimitExceeded ? { pollingRateAdvisory: { type: "rate_limit_exceeded", tool: "mesh_status", callsInWindow: rateResult.callsInWindow, message: rateResult.advisory } } : {},
2399
2473
  branchConvergenceSummary: summarizeBranchConvergence(results),
2400
2474
  ...coordinatorSessions.length > 0 ? {
2401
2475
  coordinatorSessions,
@@ -2410,6 +2484,19 @@ async function meshStatus(ctx, args = {}) {
2410
2484
  response.ledgerSummary = ledgerSummary;
2411
2485
  } catch {
2412
2486
  }
2487
+ try {
2488
+ const missions = (0, import_daemon_core.getActiveMeshMissionSummaries)(mesh.id);
2489
+ if (missions.length > 0) {
2490
+ response.missions = missions.map((mission) => {
2491
+ try {
2492
+ return { ...mission, stats: (0, import_daemon_core.computeMeshMissionStats)(mesh.id, mission.id) };
2493
+ } catch {
2494
+ return mission;
2495
+ }
2496
+ });
2497
+ }
2498
+ } catch {
2499
+ }
2413
2500
  try {
2414
2501
  const pendingEvents = await drainCoordinatorPendingEvents(ctx);
2415
2502
  const asyncRefineJobs = (0, import_daemon_core.buildMeshAsyncRefineJobs)({
@@ -2438,10 +2525,20 @@ async function meshTaskHistory(ctx, args) {
2438
2525
  payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
2439
2526
  }));
2440
2527
  const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
2528
+ let taskStats;
2529
+ try {
2530
+ const taskIds = [...new Set(rawEntries.map((e) => typeof e.payload?.taskId === "string" ? e.payload.taskId : "").filter(Boolean))];
2531
+ if (taskIds.length > 0) {
2532
+ const stats = (0, import_daemon_core.computeMeshTaskStats)(mesh.id, { taskIds });
2533
+ if (stats.length > 0) taskStats = stats;
2534
+ }
2535
+ } catch {
2536
+ }
2441
2537
  return JSON.stringify({
2442
2538
  meshId: mesh.id,
2443
2539
  entries,
2444
2540
  summary,
2541
+ ...taskStats ? { taskStats } : {},
2445
2542
  ...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
2446
2543
  }, null, 2);
2447
2544
  }
@@ -2460,7 +2557,7 @@ async function meshReconcileLedger(ctx, args) {
2460
2557
  for (const node of nodes) {
2461
2558
  try {
2462
2559
  if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
2463
- const slice2 = (0, import_daemon_core.readLedgerSlice)(ctx.mesh.id, queryArgs);
2560
+ const slice2 = (0, import_daemon_core.readLedgerSliceFromStore)(ctx.mesh.id, queryArgs);
2464
2561
  replicas.push((0, import_daemon_core.buildMeshLedgerReplicaEvidence)({
2465
2562
  nodeId: node.id,
2466
2563
  daemonId: node.daemonId,
@@ -2544,11 +2641,32 @@ async function meshListNodes(ctx) {
2544
2641
  }))
2545
2642
  }, null, 2);
2546
2643
  }
2644
+ async function meshMissionUpsert(ctx, args) {
2645
+ try {
2646
+ const mission = (0, import_daemon_core.upsertMeshMission)(ctx.mesh.id, {
2647
+ id: readString(args.mission_id) || readString(args.missionId) || void 0,
2648
+ title: args.title,
2649
+ goal: typeof args.goal === "string" ? args.goal : void 0,
2650
+ status: readString(args.status) || void 0
2651
+ });
2652
+ return JSON.stringify({
2653
+ success: true,
2654
+ mission,
2655
+ nextAction: "Attach tasks with mesh_enqueue_task mission_id and depends_on. mesh_status shows live task aggregates for this mission."
2656
+ });
2657
+ } catch (e) {
2658
+ const message = e?.message || String(e);
2659
+ const code = message.includes("mission_title_required") ? "mission_title_required" : message.includes("invalid_mission_status") ? "invalid_mission_status" : void 0;
2660
+ return JSON.stringify({ success: false, ...code ? { code } : {}, error: message });
2661
+ }
2662
+ }
2547
2663
  async function meshEnqueueTask(ctx, args) {
2548
2664
  const taskMode = readString(args.task_mode) || readString(args.taskMode);
2549
2665
  const requiredTags = (0, import_daemon_core.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
2666
+ const dependsOn = Array.isArray(args.dependsOn) ? args.dependsOn : Array.isArray(args.depends_on) ? args.depends_on : void 0;
2667
+ const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
2550
2668
  try {
2551
- const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags });
2669
+ const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags, dependsOn, missionId });
2552
2670
  if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
2553
2671
  const queueTrigger = await triggerMeshQueueAndReport(ctx);
2554
2672
  return JSON.stringify({
@@ -2632,15 +2750,26 @@ async function meshEnqueueTask(ctx, args) {
2632
2750
  if (message.includes("live_debug_readonly_guardrail_violation")) {
2633
2751
  return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
2634
2752
  }
2753
+ if (message.includes("dependency_cycle_detected")) {
2754
+ return JSON.stringify({ success: false, code: "dependency_cycle_detected", dependsOn, error: message });
2755
+ }
2635
2756
  return JSON.stringify({ success: false, error: message });
2636
2757
  }
2637
2758
  }
2638
2759
  async function meshViewQueue(ctx, args) {
2760
+ const rateResult = (0, import_daemon_core.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
2639
2761
  try {
2640
2762
  await refreshMeshFromDaemon(ctx);
2641
2763
  const statusFilter = sanitizeQueueStatusFilter(args.status);
2642
2764
  const view = normalizeQueueViewMode(args.view);
2643
- const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh));
2765
+ const rawQueue = (0, import_daemon_core.getQueue)(ctx.mesh.id);
2766
+ const statusById = new Map(rawQueue.map((task) => [task.id, task.status]));
2767
+ const withDependencies = rawQueue.map((task) => {
2768
+ if (!Array.isArray(task.dependsOn) || task.dependsOn.length === 0) return task;
2769
+ const depState = (0, import_daemon_core.describeTaskDependencyState)(task, statusById);
2770
+ return { ...task, ...depState };
2771
+ });
2772
+ const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness(withDependencies, ctx.mesh));
2644
2773
  const queue = filterQueueForView(fullQueue, view, statusFilter);
2645
2774
  const summary = buildQueueStatusSummary(fullQueue);
2646
2775
  const visibleSummary = buildQueueStatusSummary(queue);
@@ -2692,6 +2821,7 @@ async function meshViewQueue(ctx, args) {
2692
2821
  staleDirectWork: activeWorkEvidence.staleDirectWork,
2693
2822
  activeWorkSummary: activeWorkEvidence.summary,
2694
2823
  ...pollingGuidance ? { pollingGuidance } : {},
2824
+ ...rateResult.rateLimitExceeded ? { pollingRateAdvisory: { type: "rate_limit_exceeded", tool: "mesh_view_queue", callsInWindow: rateResult.callsInWindow, message: rateResult.advisory } } : {},
2695
2825
  summary,
2696
2826
  visibleSummary,
2697
2827
  activeCounts: summary.activeCounts,
@@ -2751,9 +2881,19 @@ async function meshQueueRequeue(ctx, args) {
2751
2881
  targetNodeId,
2752
2882
  targetSessionId,
2753
2883
  clearTargetNode: args.clear_target_node === true || args.clearTargetNode === true,
2754
- clearTargetSession: targetSessionId ? false : !keepTargetSession
2884
+ clearTargetSession: targetSessionId ? false : !keepTargetSession,
2885
+ force: args.force === true
2755
2886
  });
2756
2887
  if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
2888
+ if (task.status === "failed" && task.cancelReason?.startsWith("max_retries_exceeded")) {
2889
+ return JSON.stringify({
2890
+ success: false,
2891
+ code: "max_retries_exceeded",
2892
+ error: task.cancelReason,
2893
+ task,
2894
+ hint: "Use force=true to bypass the retry cap for explicit operator recovery."
2895
+ }, null, 2);
2896
+ }
2757
2897
  if (isLocalTransport(ctx.transport)) {
2758
2898
  ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
2759
2899
  });
@@ -3211,7 +3351,7 @@ async function meshReadDebug(ctx, args) {
3211
3351
  }
3212
3352
  async function meshLaunchSession(ctx, args) {
3213
3353
  const node = await findNodeWithRefresh(ctx, args.node_id);
3214
- const bootstrapBlock = getWorktreeBootstrapLaunchBlock(node);
3354
+ const bootstrapBlock = getWorktreeBootstrapLaunchBlock(node, ctx.mesh.policy);
3215
3355
  if (bootstrapBlock) return JSON.stringify(bootstrapBlock, null, 2);
3216
3356
  if (isLocalTransport(ctx.transport)) {
3217
3357
  let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
@@ -3247,6 +3387,9 @@ async function meshLaunchSession(ctx, args) {
3247
3387
  cliType: resolvedProviderType,
3248
3388
  dir: node.workspace,
3249
3389
  settings: {
3390
+ // Worker launch envelope (A5): structured metadata so worker sessions
3391
+ // know their role and can route completion events back correctly.
3392
+ role: "worker",
3250
3393
  meshNodeFor: ctx.mesh.id,
3251
3394
  meshNodeId: args.node_id,
3252
3395
  spawnedSessionVisibility,
@@ -3716,6 +3859,18 @@ async function meshRefineNode(ctx, args) {
3716
3859
  return JSON.stringify({ error: "Cloud mesh refine_node requires node daemonId" });
3717
3860
  }
3718
3861
  }
3862
+ async function meshReviewInbox(ctx, args = {}) {
3863
+ if (!isLocalTransport(ctx.transport)) {
3864
+ return JSON.stringify({ error: "mesh_review_inbox requires a local daemon transport (M4.0 scope: local nodes only)" });
3865
+ }
3866
+ await refreshMeshFromDaemon(ctx);
3867
+ const meshId = (args.mesh_id ?? ctx.mesh.id).trim();
3868
+ const result = await commandForNode(ctx, ctx.mesh.nodes[0], "get_mesh_review_inbox", {
3869
+ meshId,
3870
+ inlineMesh: ctx.mesh
3871
+ });
3872
+ return JSON.stringify(result, null, 2);
3873
+ }
3719
3874
 
3720
3875
  // src/help.ts
3721
3876
  var STANDARD_TOOLS = [
@@ -5570,6 +5725,12 @@ async function startMcpServer(opts) {
5570
5725
  case "mesh_reconcile_ledger":
5571
5726
  text = await meshReconcileLedger(meshCtx, a);
5572
5727
  break;
5728
+ case "mesh_mission_upsert":
5729
+ text = await meshMissionUpsert(meshCtx, a);
5730
+ break;
5731
+ case "mesh_review_inbox":
5732
+ text = await meshReviewInbox(meshCtx, a);
5733
+ break;
5573
5734
  default:
5574
5735
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
5575
5736
  }