@adhdev/daemon-standalone 1.0.28-rc.26 → 1.0.28-rc.28

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,7 +7,7 @@
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-C3DSJEo9.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-CCwmPawg.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendor-cL4V2vaO.js">
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-8TNNoifN.css">
13
13
  </head>
@@ -51,6 +51,9 @@ var IPC_COMMAND_TIMEOUTS_MS = {
51
51
  // 120s leaves headroom and matches the relay-wrapped remote path.
52
52
  clone_mesh_node: 12e4,
53
53
  remove_mesh_node: 6e4,
54
+ // Retention plan/execute runs the same git probes as a remove dry-run plus
55
+ // queue/session/ledger reads; 60s matches the remove budget.
56
+ cleanup_worktree_nodes: 6e4,
54
57
  // A5: plan_mesh_refine_node is the SYNCHRONOUS refine dry-run — it runs several git
55
58
  // probes (status/merge-tree/submodule) inline before replying, which can approach the
56
59
  // 15s default on a slow (Windows) host. 45s defensively, matching git_status/diff.
@@ -424,10 +427,12 @@ var CANONICAL_MESH_TOOL_NAMES = [
424
427
  "mesh_approve",
425
428
  "mesh_answer_question",
426
429
  "mesh_list_pending_approvals",
430
+ "mesh_plan_onboarding",
427
431
  "mesh_create",
428
432
  "mesh_add_node",
429
433
  "mesh_clone_node",
430
434
  "mesh_remove_node",
435
+ "mesh_cleanup_worktree_nodes",
431
436
  "mesh_refine_node",
432
437
  "mesh_refine_batch",
433
438
  "mesh_refine_config",
@@ -1158,23 +1163,41 @@ var MESH_LIST_PENDING_APPROVALS_TOOL = {
1158
1163
  };
1159
1164
  var MESH_CREATE_TOOL = {
1160
1165
  name: "mesh_create",
1161
- description: "Bootstrap a brand-new mesh for a Git repository \u2014 the first step for an MCP-only agent that has no mesh yet. Mirrors `adhdev mesh create <name>`. A mesh groups one repo's workspaces/nodes so the coordinator can delegate work across them. You MUST supply the repo identity: pass repo_remote_url (the git remote URL, e.g. git@github.com:user/repo.git or https://github.com/user/repo.git \u2014 the identity is derived from it) OR repo_identity (an explicit normalized identity like github.com/user/repo). At least one is required; there is no auto-detection here (the CLI auto-detects from the current dir, but the MCP server may run elsewhere). Set add_current:true to also register a node in the same call (uses workspace if given, else the daemon's current working directory) \u2014 handy when the daemon runs in the repo you want as the base node. BOOT-GATE / WHEN CALLABLE: this tool is reachable in STANDARD mode (adhdev mcp, no --repo-mesh) \u2014 the bootstrap context where no mesh exists \u2014 and also in mesh mode (where it creates a SEPARATE additional mesh). It is NOT reachable before any mesh exists via mesh mode, because `adhdev mcp --repo-mesh <id>` refuses to start without an existing meshId. So the intended flow is: run standard-mode MCP \u2192 mesh_create \u2192 mesh_add_node \u2192 then relaunch as `adhdev mcp --repo-mesh <returned mesh_id>`. Returns mesh_id (and node_id when add_current is used); use mesh_id for the follow-up mesh_add_node call and to launch mesh mode.",
1166
+ description: "Bootstrap a brand-new mesh for a Git repository \u2014 the first step for an MCP-only agent that has no mesh yet. Mirrors `adhdev mesh create <name>`. A mesh groups one repo's workspaces/nodes so the coordinator can delegate work across them. Pass workspace to auto-detect Git identity/branch/worktree metadata through the read-only planner, or explicitly pass repo_remote_url / repo_identity for backward compatibility. This is a persistent write: call mesh_plan_onboarding first and obtain explicit user approval before invoking it. Set add_current:true to also register a node in the same call (uses workspace if given, else the daemon's current working directory). BOOT-GATE / WHEN CALLABLE: this tool is reachable in STANDARD mode (adhdev mcp, no --repo-mesh) \u2014 the bootstrap context where no mesh exists \u2014 and also in mesh mode (where it creates a SEPARATE additional mesh). It is NOT reachable before any mesh exists via mesh mode, because `adhdev mcp --repo-mesh <id>` refuses to start without an existing meshId. So the intended flow is: run standard-mode MCP \u2192 mesh_create \u2192 mesh_add_node \u2192 then relaunch as `adhdev mcp --repo-mesh <returned mesh_id>`. Returns mesh_id (and node_id when add_current is used); use mesh_id for the follow-up mesh_add_node call and to launch mesh mode.",
1162
1167
  inputSchema: {
1163
1168
  type: "object",
1164
1169
  properties: {
1165
1170
  name: { type: "string", description: 'Human-readable mesh name (e.g. "adhdev-main"). Trimmed, max 100 chars.' },
1166
- repo_remote_url: { type: "string", description: "Git remote URL of the repo (e.g. git@github.com:user/repo.git). The repo identity is normalized from this. Provide this OR repo_identity." },
1167
- repo_identity: { type: "string", description: "Explicit normalized repo identity (e.g. github.com/user/repo). Provide this OR repo_remote_url. Wins over repo_remote_url when both are given." },
1171
+ repo_remote_url: { type: "string", description: "Optional explicit Git remote URL. When omitted with repo_identity, identity is read-only auto-detected from workspace." },
1172
+ repo_identity: { type: "string", description: "Optional explicit normalized repo identity. Wins over repo_remote_url; when both are omitted, workspace is auto-detected." },
1168
1173
  default_branch: { type: "string", description: 'Default branch for the repo (e.g. "main"). Optional; used as the merge/convergence target.' },
1169
1174
  add_current: { type: "boolean", description: "Also register a node in this same call (parity with CLI --add-current). Uses `workspace` if provided, otherwise the daemon's current working directory." },
1170
- workspace: { type: "string", description: "Absolute workspace path to register when add_current:true. Ignored unless add_current is true. Defaults to the daemon's cwd." }
1175
+ workspace: { type: "string", description: "Absolute workspace path used for Git auto-detection and, when add_current:true, node registration. Defaults to the daemon cwd." }
1171
1176
  },
1172
1177
  required: ["name"]
1173
1178
  }
1174
1179
  };
1180
+ var MESH_PLAN_ONBOARDING_TOOL = {
1181
+ name: "mesh_plan_onboarding",
1182
+ description: "Read-only Git-aware Repo Mesh discovery and dry-run planning for a workspace path. Detects the Git root, normalized remotes/repo identity, current/default branch, main checkout vs linked worktree/common-dir metadata, dirty/conflict state, and existing mesh/node membership. Returns a typed create+onboarding, add-existing-workspace, or clone-new-worktree plan with suggested .adhdev configs. It never fetches, writes config, creates a mesh/node/branch/worktree, or otherwise mutates state. Use this before mesh_create, mesh_add_node, or mesh_clone_node; execute write steps only after explicit user approval.",
1183
+ inputSchema: {
1184
+ type: "object",
1185
+ properties: {
1186
+ workspace: { type: "string", description: "Absolute path on the daemon that owns the Git checkout." },
1187
+ mesh_id: { type: "string", description: "Optional existing mesh to validate against. In mesh mode defaults to the active mesh." },
1188
+ operation: {
1189
+ type: "string",
1190
+ enum: ["auto", "add_existing", "clone_worktree", "create_mesh"],
1191
+ description: "Planning intent. auto chooses create+onboard when no compatible mesh exists, otherwise add existing. clone_worktree requires branch and a clean source."
1192
+ },
1193
+ branch: { type: "string", description: "New branch name when operation=clone_worktree." }
1194
+ },
1195
+ required: ["workspace"]
1196
+ }
1197
+ };
1175
1198
  var MESH_ADD_NODE_TOOL = {
1176
1199
  name: "mesh_add_node",
1177
- description: "Register a workspace as a node in an EXISTING mesh \u2014 the second bootstrap step after mesh_create (or to add more nodes later). Mirrors `adhdev mesh add-node <mesh_id>` with --workspace / --read-only / --provider-priority. A node is a repo checkout on a daemon that the coordinator can launch agents on and delegate tasks to. mesh_id is REQUIRED in standard mode (pass the id returned by mesh_create); in mesh mode it defaults to the active mesh. workspace is the absolute path to the repo checkout ON THE DAEMON that owns it \u2014 the local base node is added by the daemon that created the mesh. NOTE: this registers an EXISTING directory as a node (including marking one as a worktree via is_worktree). To CREATE a fresh git worktree + branch for isolated parallel work, use mesh_clone_node instead \u2014 that runs the actual `git worktree add`. Returns node_id + workspace so you can immediately target the node with mesh_launch_session / mesh_send_task / mesh_enqueue_task.",
1200
+ description: "Register a workspace as a node in an EXISTING mesh \u2014 the second bootstrap step after mesh_create (or to add more nodes later). Mirrors `adhdev mesh add-node <mesh_id>` with --workspace / --read-only / --provider-priority. A node is a repo checkout on a daemon that the coordinator can launch agents on and delegate tasks to. mesh_id is REQUIRED in standard mode (pass the id returned by mesh_create); in mesh mode it defaults to the active mesh. workspace is the absolute path to the repo checkout ON THE DAEMON that owns it \u2014 the local base node is added by the daemon that created the mesh. This is a persistent mesh write: call mesh_plan_onboarding first and obtain explicit user approval. The implementation re-runs that preflight before writing. NOTE: this registers an EXISTING directory as a node (including auto-detected linked worktrees). To CREATE a fresh git worktree + branch for isolated parallel work, use mesh_clone_node instead \u2014 that runs the actual `git worktree add`. Returns node_id + workspace so you can immediately target the node with mesh_launch_session / mesh_send_task / mesh_enqueue_task.",
1178
1201
  inputSchema: {
1179
1202
  type: "object",
1180
1203
  properties: {
@@ -1193,7 +1216,7 @@ var MESH_ADD_NODE_TOOL = {
1193
1216
  };
1194
1217
  var MESH_CLONE_NODE_TOOL = {
1195
1218
  name: "mesh_clone_node",
1196
- description: "Create a new worktree-based node from an existing node for isolated parallel work. Creates a git worktree on a new branch so multiple tasks can run on separate branches simultaneously.",
1219
+ description: "Create a new worktree-based node from an existing node for isolated parallel work. Creates a git worktree on a new branch so multiple tasks can run on separate branches simultaneously. This writes a branch, worktree and mesh node: call mesh_plan_onboarding with operation=clone_worktree and obtain explicit user approval first; the implementation re-runs the clean/source preflight.",
1197
1220
  inputSchema: {
1198
1221
  type: "object",
1199
1222
  properties: {
@@ -1221,6 +1244,18 @@ var MESH_REMOVE_NODE_TOOL = {
1221
1244
  required: ["node_id"]
1222
1245
  }
1223
1246
  };
1247
+ var MESH_CLEANUP_WORKTREE_NODES_TOOL = {
1248
+ name: "mesh_cleanup_worktree_nodes",
1249
+ description: "Plan (dry-run, default) or execute safe removal of CONVERGED local worktree nodes (lifecycle retention). A node is eligible only when its feature branch is proven merged/pushed/converged AND every safety exclusion passes: no dirty/conflicted/stashed/submodule-drift state, no live session, no queue/direct-dispatch reference, no in-flight or blocked_review Refinery job, not the coordinator/base/cwd/evidence node. The automatic reconcile pass additionally requires two consecutive eligible ticks spanning a grace window (default 48h). Per-node reason codes are always returned; removal never uses force and branch refs are deleted only when proven fully merged.",
1250
+ inputSchema: {
1251
+ type: "object",
1252
+ properties: {
1253
+ node_id: { type: "string", description: "Optional: restrict the plan/execute to a single node. When omitted, every node in the mesh is evaluated." },
1254
+ dry_run: { type: "boolean", description: "Default true. true = read-only reason-coded plan (identical shape to the automatic pass); false = execute removal for every currently-eligible node (never forces; the non-destructive precheck re-runs immediately before each removal)." }
1255
+ },
1256
+ required: []
1257
+ }
1258
+ };
1224
1259
  var MESH_CLEANUP_SESSIONS_TOOL = {
1225
1260
  name: "mesh_cleanup_sessions",
1226
1261
  description: "Manually clean up delegated session records for a mesh node without removing the node. Defaults should preserve reviewable history unless the caller chooses a mode explicitly.",
@@ -1617,10 +1652,12 @@ var ALL_MESH_TOOLS = [
1617
1652
  MESH_APPROVE_TOOL,
1618
1653
  MESH_ANSWER_QUESTION_TOOL,
1619
1654
  MESH_LIST_PENDING_APPROVALS_TOOL,
1655
+ MESH_PLAN_ONBOARDING_TOOL,
1620
1656
  MESH_CREATE_TOOL,
1621
1657
  MESH_ADD_NODE_TOOL,
1622
1658
  MESH_CLONE_NODE_TOOL,
1623
1659
  MESH_REMOVE_NODE_TOOL,
1660
+ MESH_CLEANUP_WORKTREE_NODES_TOOL,
1624
1661
  MESH_REFINE_NODE_TOOL,
1625
1662
  MESH_REFINE_BATCH_TOOL,
1626
1663
  MESH_REFINE_CONFIG_TOOL,
@@ -7459,6 +7496,23 @@ async function meshCheckpoint(ctx, args) {
7459
7496
  }
7460
7497
  async function meshCloneNode(ctx, args) {
7461
7498
  const sourceNode = await findNodeWithRefresh(ctx, args.source_node_id);
7499
+ const planned = unwrapCommandPayload(await commandForNode(ctx, sourceNode, "plan_mesh_onboarding", {
7500
+ workspace: sourceNode.workspace,
7501
+ meshId: ctx.mesh.id,
7502
+ inlineMesh: ctx.mesh,
7503
+ operation: "clone_worktree",
7504
+ branch: args.branch
7505
+ }));
7506
+ if (!planned?.success) {
7507
+ return JSON.stringify({
7508
+ success: false,
7509
+ dry_run: true,
7510
+ code: planned?.code || "onboarding_blocked",
7511
+ error: planned?.error || "Worktree clone preflight failed",
7512
+ action: planned?.action,
7513
+ raw: planned
7514
+ }, null, 2);
7515
+ }
7462
7516
  const result = await commandForNode(ctx, sourceNode, "clone_mesh_node", {
7463
7517
  meshId: ctx.mesh.id,
7464
7518
  sourceNodeId: args.source_node_id,
@@ -7509,21 +7563,105 @@ async function meshRemoveNode(ctx, args) {
7509
7563
  }
7510
7564
  return JSON.stringify({ ...result || {}, ...transportFallback ? { transportFallback } : {} }, null, 2);
7511
7565
  }
7566
+ async function meshCleanupWorktreeNodes(ctx, args) {
7567
+ const dryRun = args?.dry_run !== false;
7568
+ const wireArgs = {
7569
+ meshId: ctx.mesh.id,
7570
+ dryRun,
7571
+ ...args?.node_id ? { nodeId: args.node_id } : {}
7572
+ };
7573
+ let result;
7574
+ try {
7575
+ if (args?.node_id) {
7576
+ const node = await findNodeWithRefresh(ctx, args.node_id);
7577
+ result = await commandForNode(ctx, node, "cleanup_worktree_nodes", wireArgs);
7578
+ } else {
7579
+ result = await ctx.transport.command("cleanup_worktree_nodes", wireArgs);
7580
+ }
7581
+ } catch (e) {
7582
+ return JSON.stringify({
7583
+ success: false,
7584
+ code: isP2pTransportUnavailableError(e) ? "p2p_unavailable" : "mesh_cleanup_worktree_nodes_failed",
7585
+ error: e?.message || String(e),
7586
+ recoveryHint: "Inspect mesh_status and retry after resolving the reported failure; per-node reasons are never hidden in a successful plan."
7587
+ }, null, 2);
7588
+ }
7589
+ if (!dryRun && result?.success && Array.isArray(result.entries)) {
7590
+ let changed = false;
7591
+ for (const entry of result.entries) {
7592
+ if (entry?.execution?.success && entry.execution.removed) {
7593
+ const idx = ctx.mesh.nodes.findIndex((n) => n.id === entry.nodeId);
7594
+ if (idx >= 0) {
7595
+ ctx.mesh.nodes.splice(idx, 1);
7596
+ changed = true;
7597
+ }
7598
+ }
7599
+ }
7600
+ if (changed) ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
7601
+ }
7602
+ return JSON.stringify(result ?? { success: false, error: "no response from daemon" }, null, 2);
7603
+ }
7512
7604
 
7513
7605
  // src/tools/mesh-tools-crud.ts
7606
+ async function meshPlanOnboarding(transport, args, defaultMeshId) {
7607
+ const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
7608
+ if (!workspace) {
7609
+ return JSON.stringify({
7610
+ success: false,
7611
+ dryRun: true,
7612
+ code: "workspace_required",
7613
+ error: "workspace required",
7614
+ action: "Pass an absolute path on the daemon that owns the checkout."
7615
+ }, null, 2);
7616
+ }
7617
+ const meshId = typeof args?.mesh_id === "string" && args.mesh_id.trim() ? args.mesh_id.trim() : typeof defaultMeshId === "string" ? defaultMeshId.trim() : "";
7618
+ const result = await transport.command("plan_mesh_onboarding", {
7619
+ workspace,
7620
+ ...meshId ? { meshId } : {},
7621
+ ...args?.operation ? { operation: args.operation } : {},
7622
+ ...typeof args?.branch === "string" && args.branch.trim() ? { branch: args.branch.trim() } : {}
7623
+ });
7624
+ return JSON.stringify(unwrapCommandPayload(result), null, 2);
7625
+ }
7514
7626
  async function meshCreate(transport, args) {
7515
7627
  const name = typeof args?.name === "string" ? args.name.trim() : "";
7516
7628
  if (!name) {
7517
7629
  return JSON.stringify({ success: false, error: "name required" }, null, 2);
7518
7630
  }
7519
- const repoRemoteUrl = typeof args?.repo_remote_url === "string" ? args.repo_remote_url.trim() : "";
7520
- const repoIdentity = typeof args?.repo_identity === "string" ? args.repo_identity.trim() : "";
7521
- const defaultBranch = typeof args?.default_branch === "string" ? args.default_branch.trim() : "";
7631
+ let repoRemoteUrl = typeof args?.repo_remote_url === "string" ? args.repo_remote_url.trim() : "";
7632
+ let repoIdentity = typeof args?.repo_identity === "string" ? args.repo_identity.trim() : "";
7633
+ let defaultBranch = typeof args?.default_branch === "string" ? args.default_branch.trim() : "";
7634
+ let discovery;
7522
7635
  if (!repoRemoteUrl && !repoIdentity) {
7523
- return JSON.stringify({
7524
- success: false,
7525
- error: "Either repo_remote_url or repo_identity is required. Pass the repo's git remote URL (e.g. git@github.com:user/repo.git) or an explicit identity (e.g. github.com/user/repo)."
7526
- }, null, 2);
7636
+ const workspace = typeof args?.workspace === "string" && args.workspace.trim() ? args.workspace.trim() : void 0;
7637
+ const planned = unwrapCommandPayload(await transport.command("plan_mesh_onboarding", {
7638
+ ...workspace ? { workspace } : {},
7639
+ operation: "auto"
7640
+ }));
7641
+ if (!planned?.success) {
7642
+ return JSON.stringify({
7643
+ success: false,
7644
+ dry_run: true,
7645
+ code: planned?.code || "onboarding_blocked",
7646
+ error: planned?.error || "Could not infer repository identity from the workspace.",
7647
+ action: planned?.action,
7648
+ raw: planned
7649
+ }, null, 2);
7650
+ }
7651
+ if (planned?.plan?.kind !== "create_mesh_and_onboard") {
7652
+ return JSON.stringify({
7653
+ success: false,
7654
+ dry_run: true,
7655
+ code: "compatible_mesh_exists",
7656
+ error: planned?.plan?.summary || "A compatible mesh already exists.",
7657
+ action: "Use mesh_add_node with the compatible mesh instead of creating a duplicate mesh.",
7658
+ raw: planned
7659
+ }, null, 2);
7660
+ }
7661
+ discovery = planned.discovery;
7662
+ repoRemoteUrl = discovery?.origin?.urls?.[0] || discovery?.upstream?.urls?.[0] || "";
7663
+ repoIdentity = discovery?.repoIdentity || "";
7664
+ defaultBranch ||= discovery?.defaultBranch || "";
7527
7665
  }
7528
7666
  const createResult = await transport.command("create_mesh", {
7529
7667
  name,
@@ -7553,7 +7691,9 @@ async function meshCreate(transport, args) {
7553
7691
  const workspace = typeof args?.workspace === "string" && args.workspace.trim() ? args.workspace.trim() : "";
7554
7692
  const addResult = await transport.command("add_mesh_node", {
7555
7693
  meshId: mesh.id,
7556
- ...workspace ? { workspace } : {},
7694
+ ...workspace || discovery?.repoRoot ? { workspace: discovery?.repoRoot || workspace } : {},
7695
+ ...discovery?.repoRoot ? { repoRoot: discovery.repoRoot } : {},
7696
+ ...discovery?.isLinkedWorktree === true ? { isLocalWorktree: true } : {},
7557
7697
  inlineMesh: mesh
7558
7698
  });
7559
7699
  const addPayload = unwrapCommandPayload(addResult);
@@ -7576,12 +7716,31 @@ async function meshAddNode(transport, args, defaultMeshId) {
7576
7716
  return JSON.stringify({ success: false, error: "workspace required (absolute path to the repo checkout on the target daemon)." }, null, 2);
7577
7717
  }
7578
7718
  const providerPriority = Array.isArray(args?.provider_priority) ? args.provider_priority.map((v) => typeof v === "string" ? v.trim() : "").filter(Boolean) : typeof args?.provider_priority === "string" ? args.provider_priority.split(",").map((v) => v.trim()).filter(Boolean) : [];
7579
- const addResult = await transport.command("add_mesh_node", {
7719
+ const planResult = await transport.command("plan_mesh_onboarding", {
7580
7720
  meshId,
7581
7721
  workspace,
7722
+ operation: "add_existing",
7723
+ ...args?.inline_mesh ? { inlineMesh: args.inline_mesh } : {}
7724
+ });
7725
+ const planPayload = unwrapCommandPayload(planResult);
7726
+ if (!planPayload?.success) {
7727
+ return JSON.stringify({
7728
+ success: false,
7729
+ dry_run: true,
7730
+ code: planPayload?.code || "onboarding_blocked",
7731
+ error: planPayload?.error || "Repo Mesh onboarding preflight failed",
7732
+ action: planPayload?.action,
7733
+ raw: planPayload ?? planResult
7734
+ }, null, 2);
7735
+ }
7736
+ const discoveredWorkspace = typeof planPayload?.discovery?.repoRoot === "string" ? planPayload.discovery.repoRoot : workspace;
7737
+ const addResult = await transport.command("add_mesh_node", {
7738
+ meshId,
7739
+ workspace: discoveredWorkspace,
7740
+ repoRoot: discoveredWorkspace,
7582
7741
  ...args?.read_only === true ? { readOnly: true } : {},
7583
7742
  ...providerPriority.length ? { providerPriority } : {},
7584
- ...args?.is_worktree === true ? { isLocalWorktree: true } : {},
7743
+ ...args?.is_worktree === true || planPayload?.discovery?.isLinkedWorktree === true ? { isLocalWorktree: true } : {},
7585
7744
  ...args?.inline_mesh ? { inlineMesh: args.inline_mesh } : {}
7586
7745
  });
7587
7746
  const addPayload = unwrapCommandPayload(addResult);
@@ -9017,11 +9176,14 @@ async function startMcpServer(opts) {
9017
9176
  case "mesh_list_pending_approvals":
9018
9177
  text = await meshListPendingApprovals(meshCtx, a);
9019
9178
  break;
9179
+ case "mesh_plan_onboarding":
9180
+ text = await meshPlanOnboarding(meshCtx.transport, a, meshCtx.mesh.id);
9181
+ break;
9020
9182
  case "mesh_create":
9021
9183
  text = await meshCreate(meshCtx.transport, a);
9022
9184
  break;
9023
9185
  case "mesh_add_node":
9024
- text = await meshAddNode(meshCtx.transport, a, meshCtx.mesh.id);
9186
+ text = await meshAddNode(meshCtx.transport, { ...a, inline_mesh: meshCtx.mesh }, meshCtx.mesh.id);
9025
9187
  break;
9026
9188
  case "mesh_clone_node":
9027
9189
  text = await meshCloneNode(meshCtx, a);
@@ -9029,6 +9191,9 @@ async function startMcpServer(opts) {
9029
9191
  case "mesh_remove_node":
9030
9192
  text = await meshRemoveNode(meshCtx, a);
9031
9193
  break;
9194
+ case "mesh_cleanup_worktree_nodes":
9195
+ text = await meshCleanupWorktreeNodes(meshCtx, a);
9196
+ break;
9032
9197
  case "mesh_refine_node":
9033
9198
  text = await meshRefineNode(meshCtx, a);
9034
9199
  break;
@@ -9163,6 +9328,7 @@ async function startMcpServer(opts) {
9163
9328
  // Mesh bootstrap: create a mesh + register its first node from an MCP-only agent.
9164
9329
  // Exposed in standard mode precisely because this is the no-mesh-yet context —
9165
9330
  // mesh mode refuses to boot without an existing meshId (see the mesh-mode block above).
9331
+ MESH_PLAN_ONBOARDING_TOOL,
9166
9332
  MESH_CREATE_TOOL,
9167
9333
  MESH_ADD_NODE_TOOL,
9168
9334
  ...isLocal ? [SCREENSHOT_TOOL] : []
@@ -9258,6 +9424,10 @@ async function startMcpServer(opts) {
9258
9424
  const text = await meshCreate(transport, a);
9259
9425
  return { content: [{ type: "text", text }] };
9260
9426
  }
9427
+ case "mesh_plan_onboarding": {
9428
+ const text = await meshPlanOnboarding(transport, a);
9429
+ return { content: [{ type: "text", text }] };
9430
+ }
9261
9431
  case "mesh_add_node": {
9262
9432
  const text = await meshAddNode(transport, a);
9263
9433
  return { content: [{ type: "text", text }] };