@adhdev/daemon-standalone 1.0.28-rc.1 → 1.0.28-rc.11
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 +8022 -7127
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/public/assets/{index-B51nIfEq.css → index-8TNNoifN.css} +1 -1
- package/public/assets/{index-CX3wQvni.js → index-DJ5eteX0.js} +51 -51
- package/public/index.html +2 -2
- package/vendor/mcp-server/index.js +201 -9
- package/vendor/mcp-server/index.js.map +1 -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-DJ5eteX0.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/vendor-DyCWA2YZ.js">
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-8TNNoifN.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<!-- Apply theme immediately to prevent FOIT (Flash of Incorrect Theme) -->
|
|
@@ -393,6 +393,8 @@ var CANONICAL_MESH_TOOL_NAMES = [
|
|
|
393
393
|
"mesh_approve",
|
|
394
394
|
"mesh_answer_question",
|
|
395
395
|
"mesh_list_pending_approvals",
|
|
396
|
+
"mesh_create",
|
|
397
|
+
"mesh_add_node",
|
|
396
398
|
"mesh_clone_node",
|
|
397
399
|
"mesh_remove_node",
|
|
398
400
|
"mesh_refine_node",
|
|
@@ -998,12 +1000,19 @@ var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
|
998
1000
|
};
|
|
999
1001
|
var MESH_RESTART_DAEMON_TOOL = {
|
|
1000
1002
|
name: "mesh_restart_daemon",
|
|
1001
|
-
description: `
|
|
1003
|
+
description: `Restart a mesh node's daemon, optionally updating it first \u2014 the same path as the dashboard "preview update" button, exposed as a mesh command so a coordinator can roll a worker daemon without a manual restart round-trip. No agent session is launched. mode="upgrade" (default): update to the latest published version on the release channel, then restart; already-latest is a no-op (no restart, returns alreadyLatest:true). mode="restart": pure re-spawn with no reinstall \u2014 restarts even when already latest, with much shorter downtime; use it to reset wedged daemon state (memory leaks, zombie sessions). Idle-gated: a node whose daemon has an active session (generating / waiting_approval / starting) is refused with code "blocking_sessions" so an in-flight turn is never interrupted. self_only=true waives ONLY this mesh's own coordinator session (the structural self-deadlock case \u2014 the coordinator is always generating while it calls). Other sessions still refuse. force=true bypasses the gate entirely: in-flight turns die and the unpersisted pendingOutboundQueue is lost. when_idle=true schedules the restart to run automatically once the daemon goes idle (the safest path \u2014 no queue loss); cancel_when_idle=true cancels it and every response reports the schedule under deferredRestart. kill_session_host=true additionally stops the session-host process, destroying ALL hosted CLI sessions (hard refresh; this is what Windows already does on every upgrade). Default off. Note: on Windows any daemon restart/upgrade terminates all hosted sessions regardless of options; on POSIX hosted sessions survive a plain restart and rebind on next boot. Passing channel switches the daemon's release channel (and server URL) before restarting; omit it to keep the daemon on its configured channel.`,
|
|
1002
1004
|
inputSchema: {
|
|
1003
1005
|
type: "object",
|
|
1004
1006
|
properties: {
|
|
1005
|
-
node_id: { type: "string", description: "Target node ID \u2014 the daemon that owns this node is
|
|
1006
|
-
channel: { type: "string", enum: ["stable", "preview"], description: "Optional release channel to update from. Defaults to the daemon's configured updateChannel. Setting it also repoints the daemon's server URL to that channel." }
|
|
1007
|
+
node_id: { type: "string", description: "Target node ID \u2014 the daemon that owns this node is restarted (and updated, in upgrade mode)." },
|
|
1008
|
+
channel: { type: "string", enum: ["stable", "preview"], description: "Optional release channel to update from (upgrade mode only). Defaults to the daemon's configured updateChannel. Setting it also repoints the daemon's server URL to that channel." },
|
|
1009
|
+
mode: { type: "string", enum: ["upgrade", "restart"], description: "upgrade (default): update to latest on channel, then restart (already-latest is a no-op). restart: pure re-spawn, no reinstall \u2014 restarts even when already latest." },
|
|
1010
|
+
force: { type: "boolean", description: "Bypass the idle-gate entirely. Destructive: in-flight turns are killed and the in-memory pendingOutboundQueue is permanently lost. Default false." },
|
|
1011
|
+
self_only: { type: "boolean", description: "Waive only this mesh's own coordinator session when it blocks the restart (the coordinator self-deadlock). Other nodes' active sessions still refuse. Default false." },
|
|
1012
|
+
when_idle: { type: "boolean", description: "If blocked, schedule the restart to execute automatically once the daemon goes idle (safest \u2014 no pendingOutboundQueue loss). The schedule expires after timeout_ms (default 30 min). Default false." },
|
|
1013
|
+
cancel_when_idle: { type: "boolean", description: "Cancel a previously scheduled when_idle restart on the owning daemon." },
|
|
1014
|
+
timeout_ms: { type: "number", description: "Expiry for a when_idle schedule in milliseconds (default 1800000 = 30 min, max 6 h)." },
|
|
1015
|
+
kill_session_host: { type: "boolean", description: "Hard refresh: also stop the session-host process, destroying ALL hosted CLI sessions on the machine. Default false." }
|
|
1007
1016
|
},
|
|
1008
1017
|
required: ["node_id"]
|
|
1009
1018
|
}
|
|
@@ -1116,6 +1125,41 @@ var MESH_LIST_PENDING_APPROVALS_TOOL = {
|
|
|
1116
1125
|
properties: {}
|
|
1117
1126
|
}
|
|
1118
1127
|
};
|
|
1128
|
+
var MESH_CREATE_TOOL = {
|
|
1129
|
+
name: "mesh_create",
|
|
1130
|
+
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.",
|
|
1131
|
+
inputSchema: {
|
|
1132
|
+
type: "object",
|
|
1133
|
+
properties: {
|
|
1134
|
+
name: { type: "string", description: 'Human-readable mesh name (e.g. "adhdev-main"). Trimmed, max 100 chars.' },
|
|
1135
|
+
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." },
|
|
1136
|
+
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." },
|
|
1137
|
+
default_branch: { type: "string", description: 'Default branch for the repo (e.g. "main"). Optional; used as the merge/convergence target.' },
|
|
1138
|
+
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." },
|
|
1139
|
+
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." }
|
|
1140
|
+
},
|
|
1141
|
+
required: ["name"]
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
var MESH_ADD_NODE_TOOL = {
|
|
1145
|
+
name: "mesh_add_node",
|
|
1146
|
+
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.",
|
|
1147
|
+
inputSchema: {
|
|
1148
|
+
type: "object",
|
|
1149
|
+
properties: {
|
|
1150
|
+
mesh_id: { type: "string", description: "Target mesh id (from mesh_create / mesh_status). Required in standard mode; defaults to the active mesh in mesh mode." },
|
|
1151
|
+
workspace: { type: "string", description: "Absolute path to the repo checkout on the owning daemon (e.g. /Users/me/work/repo). Must be unique within the mesh." },
|
|
1152
|
+
read_only: { type: "boolean", description: "Mark the node read-only (no launches/mutations targeted here). Parity with CLI --read-only." },
|
|
1153
|
+
provider_priority: {
|
|
1154
|
+
type: "array",
|
|
1155
|
+
items: { type: "string" },
|
|
1156
|
+
description: 'Ordered provider types this node prefers when mesh_launch_session omits an explicit type (e.g. ["claude-cli","codex"]). Parity with CLI --provider-priority. A comma-separated string is also accepted.'
|
|
1157
|
+
},
|
|
1158
|
+
is_worktree: { type: "boolean", description: "Mark this workspace as an existing local git worktree (parity with CLI --worktree). This only tags an already-present worktree dir; it does NOT create one \u2014 use mesh_clone_node to create a worktree+branch." }
|
|
1159
|
+
},
|
|
1160
|
+
required: ["workspace"]
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1119
1163
|
var MESH_CLONE_NODE_TOOL = {
|
|
1120
1164
|
name: "mesh_clone_node",
|
|
1121
1165
|
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.",
|
|
@@ -1406,7 +1450,6 @@ var MESH_MAGI_REVIEW_TOOL = {
|
|
|
1406
1450
|
n: { type: "number", description: "Global replica override per slot (clamped by the total-replica guard cap, default 12)." },
|
|
1407
1451
|
task_kind: { type: "string", enum: ["claim_audit", "rca", "design", "freeform"], description: "REQUIRED. Selects (1) the SINGLE output schema injected into each replica prompt and the strict parser used at collection (no schema-on-schema conflict), AND (2) the user-configured kind-panel binding that supplies the fan-out slots (mesh settings \u2192 magiKindPanels; errors magi_kind_not_configured if that kind has no configured slots \u2014 no named-panel/inline/preset fallback). claim_audit: {claims[],top_findings[],open_questions[]}. rca: {rootCause,failsAt,mechanism,evidence[],fixDirection,confidence}. design: {recommendation,rationale,alternatives[],tradeoffs[],risks[],evidence[],confidence}. freeform: no schema \u2014 natural-language answer, parsing/evidence checks waived, cross-verification is weak. Every kind except freeform requires non-empty evidence[]; an empty-evidence or schema-invalid answer triggers ONE delta re-request before being dropped as unparseable. Do NOT also embed an output-format schema in the question \u2014 it collides with this contract (a warning is surfaced if detected)." },
|
|
1408
1452
|
mode: { type: "string", enum: ["rca", "investigation", "claim_audit", "design_review", "code_audit"], description: "Synthesis emphasis hint \u2014 affects labels only, never the agent count or schema. Distinct from task_kind (which selects the output schema)." },
|
|
1409
|
-
use_judge: { type: "boolean", description: "Default false (clustering synthesis). STUB: judge synthesis is not yet implemented \u2014 passing true currently falls back to clustering with a warning. Reserved interface only." },
|
|
1410
1453
|
require_independent_evidence: { type: "boolean", description: "Default true \u2014 high-impact claims with no file:line/source evidence are routed to needs_verification." },
|
|
1411
1454
|
include_stale: { type: "boolean", description: "Default false. By default, panel slots whose node HEAD commit differs from the coordinator reference commit are EXCLUDED (they would investigate different code). Set true to fan out to them anyway \u2014 results will be git-skewed and a warning is surfaced. If exclusion drops the panel below 2 independent targets the call errors rather than degrading to N=1; include_stale=true is one way to recover." },
|
|
1412
1455
|
wait: { type: "boolean", description: "Default true \u2014 collect replica outputs and return the synthesis. Set false to dispatch async and return a consensusGroupId handle; collect later with mesh_magi_collect." },
|
|
@@ -1543,6 +1586,8 @@ var ALL_MESH_TOOLS = [
|
|
|
1543
1586
|
MESH_APPROVE_TOOL,
|
|
1544
1587
|
MESH_ANSWER_QUESTION_TOOL,
|
|
1545
1588
|
MESH_LIST_PENDING_APPROVALS_TOOL,
|
|
1589
|
+
MESH_CREATE_TOOL,
|
|
1590
|
+
MESH_ADD_NODE_TOOL,
|
|
1546
1591
|
MESH_CLONE_NODE_TOOL,
|
|
1547
1592
|
MESH_REMOVE_NODE_TOOL,
|
|
1548
1593
|
MESH_REFINE_NODE_TOOL,
|
|
@@ -2044,7 +2089,19 @@ function buildDirectTaskPayload(message, via, opts) {
|
|
|
2044
2089
|
...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {},
|
|
2045
2090
|
...opts.dispatchedToIdleSession !== void 0 ? { dispatchedToIdleSession: opts.dispatchedToIdleSession } : {},
|
|
2046
2091
|
...opts.coordinatorSessionId ? { coordinatorSessionId: opts.coordinatorSessionId } : {},
|
|
2047
|
-
...opts.coordinatorDaemonId ? { coordinatorDaemonId: opts.coordinatorDaemonId } : {}
|
|
2092
|
+
...opts.coordinatorDaemonId ? { coordinatorDaemonId: opts.coordinatorDaemonId } : {},
|
|
2093
|
+
// Uniform routing rationale (mirrors the queue-claim task_dispatched shape) so both
|
|
2094
|
+
// paths render identically in mesh_task_history / the dashboard. The legacy top-level
|
|
2095
|
+
// `source`/`via`/`providerType` fields above are preserved verbatim for existing
|
|
2096
|
+
// consumers (mesh-active-work / mesh-events-stale key on payload.source === 'direct').
|
|
2097
|
+
routingDecision: {
|
|
2098
|
+
source: "direct",
|
|
2099
|
+
via,
|
|
2100
|
+
...opts.selectedNodeId ? { selectedNodeId: opts.selectedNodeId } : {},
|
|
2101
|
+
...opts.providerType ? { resolvedProviderType: opts.providerType } : {},
|
|
2102
|
+
...opts.resolvedModel ? { resolvedModel: opts.resolvedModel } : {},
|
|
2103
|
+
...opts.resolvedThinkingLevel ? { resolvedThinkingLevel: opts.resolvedThinkingLevel } : {}
|
|
2104
|
+
}
|
|
2048
2105
|
};
|
|
2049
2106
|
}
|
|
2050
2107
|
function findNode(mesh, nodeId) {
|
|
@@ -2809,11 +2866,27 @@ function isGitStatusDirty(status) {
|
|
|
2809
2866
|
if (Array.isArray(status?.submodules) && status.submodules.some((submodule) => submodule?.dirty || submodule?.outOfSync || submodule?.error)) return true;
|
|
2810
2867
|
return countUncommittedChanges(status) > 0;
|
|
2811
2868
|
}
|
|
2869
|
+
var ROUTING_SKIPPED_COMPACT_MAX = 5;
|
|
2870
|
+
function compactRoutingDecision(routing) {
|
|
2871
|
+
const out = {};
|
|
2872
|
+
for (const [k, v] of Object.entries(routing)) {
|
|
2873
|
+
if (k === "skippedCandidates" && Array.isArray(v)) {
|
|
2874
|
+
const kept = v.slice(0, ROUTING_SKIPPED_COMPACT_MAX);
|
|
2875
|
+
out[k] = kept;
|
|
2876
|
+
if (v.length > kept.length) out.skippedCandidatesDropped = v.length - kept.length;
|
|
2877
|
+
} else {
|
|
2878
|
+
out[k] = v;
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
return out;
|
|
2882
|
+
}
|
|
2812
2883
|
function slimLedgerPayload(payload) {
|
|
2813
2884
|
const slim = {};
|
|
2814
2885
|
for (const [k, v] of Object.entries(payload)) {
|
|
2815
2886
|
if (k === "message" || k === "taskSummary") {
|
|
2816
2887
|
slim[k] = typeof v === "string" && v.length > 200 ? v.slice(0, 200) + "\u2026" : v;
|
|
2888
|
+
} else if (k === "routingDecision" && v && typeof v === "object" && !Array.isArray(v)) {
|
|
2889
|
+
slim[k] = compactRoutingDecision(v);
|
|
2817
2890
|
} else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
|
|
2818
2891
|
} else if (k === "finalSummary") {
|
|
2819
2892
|
slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
|
|
@@ -5532,8 +5605,6 @@ async function meshMagiReview(ctx, args) {
|
|
|
5532
5605
|
}, null, 2);
|
|
5533
5606
|
}
|
|
5534
5607
|
const questionSchemaWarning = detectQuestionOutputSchemaConflict(question);
|
|
5535
|
-
const useJudge = (args.use_judge ?? args.useJudge) === true;
|
|
5536
|
-
const judgeWarning = useJudge ? "use_judge=true requested, but judge synthesis is not yet implemented \u2014 falling back to clustering synthesis." : null;
|
|
5537
5608
|
await refreshMeshFromDaemon(ctx);
|
|
5538
5609
|
const referenceCommit = resolveMagiReferenceCommit(ctx);
|
|
5539
5610
|
const referenceSubmoduleKey = resolveMagiReferenceSubmoduleKey(ctx);
|
|
@@ -5639,7 +5710,6 @@ Target: ${args.target}` : ""}`,
|
|
|
5639
5710
|
panel: panelName,
|
|
5640
5711
|
taskKind,
|
|
5641
5712
|
...questionSchemaWarning ? { questionSchemaWarning } : {},
|
|
5642
|
-
...judgeWarning ? { judgeWarning } : {},
|
|
5643
5713
|
question,
|
|
5644
5714
|
replicaCount: replicaRecords.length,
|
|
5645
5715
|
replicas: replicaRecords.map((r) => ({ taskId: r.taskId, provider: r.provider, targetNodeId: r.targetNodeId })),
|
|
@@ -6488,6 +6558,7 @@ async function meshSendTask(ctx, args) {
|
|
|
6488
6558
|
taskMode,
|
|
6489
6559
|
providerType,
|
|
6490
6560
|
targetSessionId: dispatchedSessionId,
|
|
6561
|
+
...args.node_id ? { selectedNodeId: args.node_id } : {},
|
|
6491
6562
|
...ctx.coordinatorSessionId ? { coordinatorSessionId: ctx.coordinatorSessionId } : {},
|
|
6492
6563
|
// COORD-EVENT-MISROUTE: persist the dispatching coordinator daemon anchor
|
|
6493
6564
|
// (same value stamped into meshContext above) so a transcript-reconcile
|
|
@@ -6650,6 +6721,7 @@ async function meshSendTask(ctx, args) {
|
|
|
6650
6721
|
providerType: resolvedProviderType,
|
|
6651
6722
|
targetSessionId: args.session_id,
|
|
6652
6723
|
dispatchedToIdleSession: sessionWasIdle,
|
|
6724
|
+
...args.node_id ? { selectedNodeId: args.node_id } : {},
|
|
6653
6725
|
...ctx.coordinatorSessionId ? { coordinatorSessionId: ctx.coordinatorSessionId } : {},
|
|
6654
6726
|
// COORD-EVENT-MISROUTE: persist the dispatching coordinator daemon anchor so a
|
|
6655
6727
|
// transcript-reconcile synth recovers it from the ledger rather than stamping
|
|
@@ -7300,7 +7372,14 @@ async function meshRestartDaemon(ctx, args) {
|
|
|
7300
7372
|
meshId: ctx.mesh.id,
|
|
7301
7373
|
nodeId: node.id,
|
|
7302
7374
|
inlineMesh: ctx.mesh,
|
|
7303
|
-
...args.channel ? { channel: args.channel } : {}
|
|
7375
|
+
...args.channel ? { channel: args.channel } : {},
|
|
7376
|
+
...args.mode ? { mode: args.mode } : {},
|
|
7377
|
+
...args.force === true ? { force: true } : {},
|
|
7378
|
+
...args.self_only === true ? { selfOnly: true } : {},
|
|
7379
|
+
...args.when_idle === true ? { whenIdle: true } : {},
|
|
7380
|
+
...args.cancel_when_idle === true ? { cancelWhenIdle: true } : {},
|
|
7381
|
+
...typeof args.timeout_ms === "number" ? { timeoutMs: args.timeout_ms } : {},
|
|
7382
|
+
...args.kill_session_host === true ? { killSessionHost: true } : {}
|
|
7304
7383
|
});
|
|
7305
7384
|
return JSON.stringify(unwrapCommandPayload(result), null, 2);
|
|
7306
7385
|
} catch (e) {
|
|
@@ -7391,6 +7470,100 @@ async function meshRemoveNode(ctx, args) {
|
|
|
7391
7470
|
return JSON.stringify({ ...result || {}, ...transportFallback ? { transportFallback } : {} }, null, 2);
|
|
7392
7471
|
}
|
|
7393
7472
|
|
|
7473
|
+
// src/tools/mesh-tools-crud.ts
|
|
7474
|
+
async function meshCreate(transport, args) {
|
|
7475
|
+
const name = typeof args?.name === "string" ? args.name.trim() : "";
|
|
7476
|
+
if (!name) {
|
|
7477
|
+
return JSON.stringify({ success: false, error: "name required" }, null, 2);
|
|
7478
|
+
}
|
|
7479
|
+
const repoRemoteUrl = typeof args?.repo_remote_url === "string" ? args.repo_remote_url.trim() : "";
|
|
7480
|
+
const repoIdentity = typeof args?.repo_identity === "string" ? args.repo_identity.trim() : "";
|
|
7481
|
+
const defaultBranch = typeof args?.default_branch === "string" ? args.default_branch.trim() : "";
|
|
7482
|
+
if (!repoRemoteUrl && !repoIdentity) {
|
|
7483
|
+
return JSON.stringify({
|
|
7484
|
+
success: false,
|
|
7485
|
+
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)."
|
|
7486
|
+
}, null, 2);
|
|
7487
|
+
}
|
|
7488
|
+
const createResult = await transport.command("create_mesh", {
|
|
7489
|
+
name,
|
|
7490
|
+
...repoRemoteUrl ? { repoRemoteUrl } : {},
|
|
7491
|
+
...repoIdentity ? { repoIdentity } : {},
|
|
7492
|
+
...defaultBranch ? { defaultBranch } : {}
|
|
7493
|
+
});
|
|
7494
|
+
const createPayload = unwrapCommandPayload(createResult);
|
|
7495
|
+
const mesh = createPayload?.mesh;
|
|
7496
|
+
if (!createPayload?.success || !mesh?.id) {
|
|
7497
|
+
return JSON.stringify({
|
|
7498
|
+
success: false,
|
|
7499
|
+
error: createPayload?.error || "create_mesh failed",
|
|
7500
|
+
raw: createPayload ?? createResult
|
|
7501
|
+
}, null, 2);
|
|
7502
|
+
}
|
|
7503
|
+
const out = {
|
|
7504
|
+
success: true,
|
|
7505
|
+
mesh_id: mesh.id,
|
|
7506
|
+
name: mesh.name,
|
|
7507
|
+
repo_identity: mesh.repoIdentity,
|
|
7508
|
+
default_branch: mesh.defaultBranch,
|
|
7509
|
+
node_count: Array.isArray(mesh.nodes) ? mesh.nodes.length : 0,
|
|
7510
|
+
next_step: `Add the base node with mesh_add_node (mesh_id: "${mesh.id}", workspace: <repo path>), then start mesh mode with: adhdev mcp --repo-mesh ${mesh.id}`
|
|
7511
|
+
};
|
|
7512
|
+
if (args?.add_current === true) {
|
|
7513
|
+
const workspace = typeof args?.workspace === "string" && args.workspace.trim() ? args.workspace.trim() : "";
|
|
7514
|
+
const addResult = await transport.command("add_mesh_node", {
|
|
7515
|
+
meshId: mesh.id,
|
|
7516
|
+
...workspace ? { workspace } : {},
|
|
7517
|
+
inlineMesh: mesh
|
|
7518
|
+
});
|
|
7519
|
+
const addPayload = unwrapCommandPayload(addResult);
|
|
7520
|
+
if (addPayload?.success && addPayload?.node?.id) {
|
|
7521
|
+
out.node_id = addPayload.node.id;
|
|
7522
|
+
out.node_workspace = addPayload.node.workspace;
|
|
7523
|
+
} else {
|
|
7524
|
+
out.add_current_error = addPayload?.error || "add_mesh_node failed (mesh was still created)";
|
|
7525
|
+
}
|
|
7526
|
+
}
|
|
7527
|
+
return JSON.stringify(out, null, 2);
|
|
7528
|
+
}
|
|
7529
|
+
async function meshAddNode(transport, args, defaultMeshId) {
|
|
7530
|
+
const meshId = typeof args?.mesh_id === "string" && args.mesh_id.trim() ? args.mesh_id.trim() : typeof defaultMeshId === "string" ? defaultMeshId.trim() : "";
|
|
7531
|
+
const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
|
|
7532
|
+
if (!meshId) {
|
|
7533
|
+
return JSON.stringify({ success: false, error: "mesh_id required (create one first with mesh_create)." }, null, 2);
|
|
7534
|
+
}
|
|
7535
|
+
if (!workspace) {
|
|
7536
|
+
return JSON.stringify({ success: false, error: "workspace required (absolute path to the repo checkout on the target daemon)." }, null, 2);
|
|
7537
|
+
}
|
|
7538
|
+
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) : [];
|
|
7539
|
+
const addResult = await transport.command("add_mesh_node", {
|
|
7540
|
+
meshId,
|
|
7541
|
+
workspace,
|
|
7542
|
+
...args?.read_only === true ? { readOnly: true } : {},
|
|
7543
|
+
...providerPriority.length ? { providerPriority } : {},
|
|
7544
|
+
...args?.is_worktree === true ? { isLocalWorktree: true } : {},
|
|
7545
|
+
...args?.inline_mesh ? { inlineMesh: args.inline_mesh } : {}
|
|
7546
|
+
});
|
|
7547
|
+
const addPayload = unwrapCommandPayload(addResult);
|
|
7548
|
+
if (!addPayload?.success || !addPayload?.node?.id) {
|
|
7549
|
+
return JSON.stringify({
|
|
7550
|
+
success: false,
|
|
7551
|
+
error: addPayload?.error || "add_mesh_node failed",
|
|
7552
|
+
code: addPayload?.code,
|
|
7553
|
+
raw: addPayload ?? addResult
|
|
7554
|
+
}, null, 2);
|
|
7555
|
+
}
|
|
7556
|
+
return JSON.stringify({
|
|
7557
|
+
success: true,
|
|
7558
|
+
mesh_id: meshId,
|
|
7559
|
+
node_id: addPayload.node.id,
|
|
7560
|
+
workspace: addPayload.node.workspace,
|
|
7561
|
+
read_only: addPayload.node.policy?.readOnly === true,
|
|
7562
|
+
provider_priority: addPayload.node.policy?.providerPriority,
|
|
7563
|
+
next_step: `Node registered. Launch an agent on it with mesh_launch_session (node_id: "${addPayload.node.id}") or delegate work with mesh_send_task / mesh_enqueue_task.`
|
|
7564
|
+
}, null, 2);
|
|
7565
|
+
}
|
|
7566
|
+
|
|
7394
7567
|
// src/tools/mesh-tools-refine.ts
|
|
7395
7568
|
async function meshRefineConfigSchema(ctx) {
|
|
7396
7569
|
const node = resolveRefineConfigNode(ctx);
|
|
@@ -8770,6 +8943,12 @@ async function startMcpServer(opts) {
|
|
|
8770
8943
|
case "mesh_list_pending_approvals":
|
|
8771
8944
|
text = await meshListPendingApprovals(meshCtx, a);
|
|
8772
8945
|
break;
|
|
8946
|
+
case "mesh_create":
|
|
8947
|
+
text = await meshCreate(meshCtx.transport, a);
|
|
8948
|
+
break;
|
|
8949
|
+
case "mesh_add_node":
|
|
8950
|
+
text = await meshAddNode(meshCtx.transport, a, meshCtx.mesh.id);
|
|
8951
|
+
break;
|
|
8773
8952
|
case "mesh_clone_node":
|
|
8774
8953
|
text = await meshCloneNode(meshCtx, a);
|
|
8775
8954
|
break;
|
|
@@ -8907,6 +9086,11 @@ async function startMcpServer(opts) {
|
|
|
8907
9086
|
GIT_DIFF_TOOL,
|
|
8908
9087
|
GIT_CHECKPOINT_TOOL,
|
|
8909
9088
|
GIT_PUSH_TOOL,
|
|
9089
|
+
// Mesh bootstrap: create a mesh + register its first node from an MCP-only agent.
|
|
9090
|
+
// Exposed in standard mode precisely because this is the no-mesh-yet context —
|
|
9091
|
+
// mesh mode refuses to boot without an existing meshId (see the mesh-mode block above).
|
|
9092
|
+
MESH_CREATE_TOOL,
|
|
9093
|
+
MESH_ADD_NODE_TOOL,
|
|
8910
9094
|
...isLocal ? [SCREENSHOT_TOOL] : []
|
|
8911
9095
|
];
|
|
8912
9096
|
const server = new import_server.Server(
|
|
@@ -8996,6 +9180,14 @@ async function startMcpServer(opts) {
|
|
|
8996
9180
|
const text = await checkPending(transport, { format: a.format });
|
|
8997
9181
|
return { content: [{ type: "text", text }] };
|
|
8998
9182
|
}
|
|
9183
|
+
case "mesh_create": {
|
|
9184
|
+
const text = await meshCreate(transport, a);
|
|
9185
|
+
return { content: [{ type: "text", text }] };
|
|
9186
|
+
}
|
|
9187
|
+
case "mesh_add_node": {
|
|
9188
|
+
const text = await meshAddNode(transport, a);
|
|
9189
|
+
return { content: [{ type: "text", text }] };
|
|
9190
|
+
}
|
|
8999
9191
|
default:
|
|
9000
9192
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
9001
9193
|
}
|