@adhdev/daemon-standalone 1.0.27 → 1.0.28-rc.10

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-Bfkec2FE.js"></script>
11
- <link rel="modulepreload" crossorigin href="/assets/vendor-cL4V2vaO.js">
12
- <link rel="stylesheet" crossorigin href="/assets/index-B51nIfEq.css">
10
+ <script type="module" crossorigin src="/assets/index-BD5OPONs.js"></script>
11
+ <link rel="modulepreload" crossorigin href="/assets/vendor-DyCWA2YZ.js">
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",
@@ -1116,6 +1118,41 @@ var MESH_LIST_PENDING_APPROVALS_TOOL = {
1116
1118
  properties: {}
1117
1119
  }
1118
1120
  };
1121
+ var MESH_CREATE_TOOL = {
1122
+ name: "mesh_create",
1123
+ 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.",
1124
+ inputSchema: {
1125
+ type: "object",
1126
+ properties: {
1127
+ name: { type: "string", description: 'Human-readable mesh name (e.g. "adhdev-main"). Trimmed, max 100 chars.' },
1128
+ 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." },
1129
+ 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." },
1130
+ default_branch: { type: "string", description: 'Default branch for the repo (e.g. "main"). Optional; used as the merge/convergence target.' },
1131
+ 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." },
1132
+ 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." }
1133
+ },
1134
+ required: ["name"]
1135
+ }
1136
+ };
1137
+ var MESH_ADD_NODE_TOOL = {
1138
+ name: "mesh_add_node",
1139
+ 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.",
1140
+ inputSchema: {
1141
+ type: "object",
1142
+ properties: {
1143
+ 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." },
1144
+ 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." },
1145
+ read_only: { type: "boolean", description: "Mark the node read-only (no launches/mutations targeted here). Parity with CLI --read-only." },
1146
+ provider_priority: {
1147
+ type: "array",
1148
+ items: { type: "string" },
1149
+ 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.'
1150
+ },
1151
+ 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." }
1152
+ },
1153
+ required: ["workspace"]
1154
+ }
1155
+ };
1119
1156
  var MESH_CLONE_NODE_TOOL = {
1120
1157
  name: "mesh_clone_node",
1121
1158
  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 +1443,6 @@ var MESH_MAGI_REVIEW_TOOL = {
1406
1443
  n: { type: "number", description: "Global replica override per slot (clamped by the total-replica guard cap, default 12)." },
1407
1444
  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
1445
  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
1446
  require_independent_evidence: { type: "boolean", description: "Default true \u2014 high-impact claims with no file:line/source evidence are routed to needs_verification." },
1411
1447
  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
1448
  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 +1579,8 @@ var ALL_MESH_TOOLS = [
1543
1579
  MESH_APPROVE_TOOL,
1544
1580
  MESH_ANSWER_QUESTION_TOOL,
1545
1581
  MESH_LIST_PENDING_APPROVALS_TOOL,
1582
+ MESH_CREATE_TOOL,
1583
+ MESH_ADD_NODE_TOOL,
1546
1584
  MESH_CLONE_NODE_TOOL,
1547
1585
  MESH_REMOVE_NODE_TOOL,
1548
1586
  MESH_REFINE_NODE_TOOL,
@@ -2044,7 +2082,19 @@ function buildDirectTaskPayload(message, via, opts) {
2044
2082
  ...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {},
2045
2083
  ...opts.dispatchedToIdleSession !== void 0 ? { dispatchedToIdleSession: opts.dispatchedToIdleSession } : {},
2046
2084
  ...opts.coordinatorSessionId ? { coordinatorSessionId: opts.coordinatorSessionId } : {},
2047
- ...opts.coordinatorDaemonId ? { coordinatorDaemonId: opts.coordinatorDaemonId } : {}
2085
+ ...opts.coordinatorDaemonId ? { coordinatorDaemonId: opts.coordinatorDaemonId } : {},
2086
+ // Uniform routing rationale (mirrors the queue-claim task_dispatched shape) so both
2087
+ // paths render identically in mesh_task_history / the dashboard. The legacy top-level
2088
+ // `source`/`via`/`providerType` fields above are preserved verbatim for existing
2089
+ // consumers (mesh-active-work / mesh-events-stale key on payload.source === 'direct').
2090
+ routingDecision: {
2091
+ source: "direct",
2092
+ via,
2093
+ ...opts.selectedNodeId ? { selectedNodeId: opts.selectedNodeId } : {},
2094
+ ...opts.providerType ? { resolvedProviderType: opts.providerType } : {},
2095
+ ...opts.resolvedModel ? { resolvedModel: opts.resolvedModel } : {},
2096
+ ...opts.resolvedThinkingLevel ? { resolvedThinkingLevel: opts.resolvedThinkingLevel } : {}
2097
+ }
2048
2098
  };
2049
2099
  }
2050
2100
  function findNode(mesh, nodeId) {
@@ -2809,11 +2859,27 @@ function isGitStatusDirty(status) {
2809
2859
  if (Array.isArray(status?.submodules) && status.submodules.some((submodule) => submodule?.dirty || submodule?.outOfSync || submodule?.error)) return true;
2810
2860
  return countUncommittedChanges(status) > 0;
2811
2861
  }
2862
+ var ROUTING_SKIPPED_COMPACT_MAX = 5;
2863
+ function compactRoutingDecision(routing) {
2864
+ const out = {};
2865
+ for (const [k, v] of Object.entries(routing)) {
2866
+ if (k === "skippedCandidates" && Array.isArray(v)) {
2867
+ const kept = v.slice(0, ROUTING_SKIPPED_COMPACT_MAX);
2868
+ out[k] = kept;
2869
+ if (v.length > kept.length) out.skippedCandidatesDropped = v.length - kept.length;
2870
+ } else {
2871
+ out[k] = v;
2872
+ }
2873
+ }
2874
+ return out;
2875
+ }
2812
2876
  function slimLedgerPayload(payload) {
2813
2877
  const slim = {};
2814
2878
  for (const [k, v] of Object.entries(payload)) {
2815
2879
  if (k === "message" || k === "taskSummary") {
2816
2880
  slim[k] = typeof v === "string" && v.length > 200 ? v.slice(0, 200) + "\u2026" : v;
2881
+ } else if (k === "routingDecision" && v && typeof v === "object" && !Array.isArray(v)) {
2882
+ slim[k] = compactRoutingDecision(v);
2817
2883
  } else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
2818
2884
  } else if (k === "finalSummary") {
2819
2885
  slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
@@ -5532,8 +5598,6 @@ async function meshMagiReview(ctx, args) {
5532
5598
  }, null, 2);
5533
5599
  }
5534
5600
  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
5601
  await refreshMeshFromDaemon(ctx);
5538
5602
  const referenceCommit = resolveMagiReferenceCommit(ctx);
5539
5603
  const referenceSubmoduleKey = resolveMagiReferenceSubmoduleKey(ctx);
@@ -5639,7 +5703,6 @@ Target: ${args.target}` : ""}`,
5639
5703
  panel: panelName,
5640
5704
  taskKind,
5641
5705
  ...questionSchemaWarning ? { questionSchemaWarning } : {},
5642
- ...judgeWarning ? { judgeWarning } : {},
5643
5706
  question,
5644
5707
  replicaCount: replicaRecords.length,
5645
5708
  replicas: replicaRecords.map((r) => ({ taskId: r.taskId, provider: r.provider, targetNodeId: r.targetNodeId })),
@@ -6488,6 +6551,7 @@ async function meshSendTask(ctx, args) {
6488
6551
  taskMode,
6489
6552
  providerType,
6490
6553
  targetSessionId: dispatchedSessionId,
6554
+ ...args.node_id ? { selectedNodeId: args.node_id } : {},
6491
6555
  ...ctx.coordinatorSessionId ? { coordinatorSessionId: ctx.coordinatorSessionId } : {},
6492
6556
  // COORD-EVENT-MISROUTE: persist the dispatching coordinator daemon anchor
6493
6557
  // (same value stamped into meshContext above) so a transcript-reconcile
@@ -6650,6 +6714,7 @@ async function meshSendTask(ctx, args) {
6650
6714
  providerType: resolvedProviderType,
6651
6715
  targetSessionId: args.session_id,
6652
6716
  dispatchedToIdleSession: sessionWasIdle,
6717
+ ...args.node_id ? { selectedNodeId: args.node_id } : {},
6653
6718
  ...ctx.coordinatorSessionId ? { coordinatorSessionId: ctx.coordinatorSessionId } : {},
6654
6719
  // COORD-EVENT-MISROUTE: persist the dispatching coordinator daemon anchor so a
6655
6720
  // transcript-reconcile synth recovers it from the ledger rather than stamping
@@ -7391,6 +7456,100 @@ async function meshRemoveNode(ctx, args) {
7391
7456
  return JSON.stringify({ ...result || {}, ...transportFallback ? { transportFallback } : {} }, null, 2);
7392
7457
  }
7393
7458
 
7459
+ // src/tools/mesh-tools-crud.ts
7460
+ async function meshCreate(transport, args) {
7461
+ const name = typeof args?.name === "string" ? args.name.trim() : "";
7462
+ if (!name) {
7463
+ return JSON.stringify({ success: false, error: "name required" }, null, 2);
7464
+ }
7465
+ const repoRemoteUrl = typeof args?.repo_remote_url === "string" ? args.repo_remote_url.trim() : "";
7466
+ const repoIdentity = typeof args?.repo_identity === "string" ? args.repo_identity.trim() : "";
7467
+ const defaultBranch = typeof args?.default_branch === "string" ? args.default_branch.trim() : "";
7468
+ if (!repoRemoteUrl && !repoIdentity) {
7469
+ return JSON.stringify({
7470
+ success: false,
7471
+ 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)."
7472
+ }, null, 2);
7473
+ }
7474
+ const createResult = await transport.command("create_mesh", {
7475
+ name,
7476
+ ...repoRemoteUrl ? { repoRemoteUrl } : {},
7477
+ ...repoIdentity ? { repoIdentity } : {},
7478
+ ...defaultBranch ? { defaultBranch } : {}
7479
+ });
7480
+ const createPayload = unwrapCommandPayload(createResult);
7481
+ const mesh = createPayload?.mesh;
7482
+ if (!createPayload?.success || !mesh?.id) {
7483
+ return JSON.stringify({
7484
+ success: false,
7485
+ error: createPayload?.error || "create_mesh failed",
7486
+ raw: createPayload ?? createResult
7487
+ }, null, 2);
7488
+ }
7489
+ const out = {
7490
+ success: true,
7491
+ mesh_id: mesh.id,
7492
+ name: mesh.name,
7493
+ repo_identity: mesh.repoIdentity,
7494
+ default_branch: mesh.defaultBranch,
7495
+ node_count: Array.isArray(mesh.nodes) ? mesh.nodes.length : 0,
7496
+ 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}`
7497
+ };
7498
+ if (args?.add_current === true) {
7499
+ const workspace = typeof args?.workspace === "string" && args.workspace.trim() ? args.workspace.trim() : "";
7500
+ const addResult = await transport.command("add_mesh_node", {
7501
+ meshId: mesh.id,
7502
+ ...workspace ? { workspace } : {},
7503
+ inlineMesh: mesh
7504
+ });
7505
+ const addPayload = unwrapCommandPayload(addResult);
7506
+ if (addPayload?.success && addPayload?.node?.id) {
7507
+ out.node_id = addPayload.node.id;
7508
+ out.node_workspace = addPayload.node.workspace;
7509
+ } else {
7510
+ out.add_current_error = addPayload?.error || "add_mesh_node failed (mesh was still created)";
7511
+ }
7512
+ }
7513
+ return JSON.stringify(out, null, 2);
7514
+ }
7515
+ async function meshAddNode(transport, args, defaultMeshId) {
7516
+ const meshId = typeof args?.mesh_id === "string" && args.mesh_id.trim() ? args.mesh_id.trim() : typeof defaultMeshId === "string" ? defaultMeshId.trim() : "";
7517
+ const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
7518
+ if (!meshId) {
7519
+ return JSON.stringify({ success: false, error: "mesh_id required (create one first with mesh_create)." }, null, 2);
7520
+ }
7521
+ if (!workspace) {
7522
+ return JSON.stringify({ success: false, error: "workspace required (absolute path to the repo checkout on the target daemon)." }, null, 2);
7523
+ }
7524
+ 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) : [];
7525
+ const addResult = await transport.command("add_mesh_node", {
7526
+ meshId,
7527
+ workspace,
7528
+ ...args?.read_only === true ? { readOnly: true } : {},
7529
+ ...providerPriority.length ? { providerPriority } : {},
7530
+ ...args?.is_worktree === true ? { isLocalWorktree: true } : {},
7531
+ ...args?.inline_mesh ? { inlineMesh: args.inline_mesh } : {}
7532
+ });
7533
+ const addPayload = unwrapCommandPayload(addResult);
7534
+ if (!addPayload?.success || !addPayload?.node?.id) {
7535
+ return JSON.stringify({
7536
+ success: false,
7537
+ error: addPayload?.error || "add_mesh_node failed",
7538
+ code: addPayload?.code,
7539
+ raw: addPayload ?? addResult
7540
+ }, null, 2);
7541
+ }
7542
+ return JSON.stringify({
7543
+ success: true,
7544
+ mesh_id: meshId,
7545
+ node_id: addPayload.node.id,
7546
+ workspace: addPayload.node.workspace,
7547
+ read_only: addPayload.node.policy?.readOnly === true,
7548
+ provider_priority: addPayload.node.policy?.providerPriority,
7549
+ 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.`
7550
+ }, null, 2);
7551
+ }
7552
+
7394
7553
  // src/tools/mesh-tools-refine.ts
7395
7554
  async function meshRefineConfigSchema(ctx) {
7396
7555
  const node = resolveRefineConfigNode(ctx);
@@ -8770,6 +8929,12 @@ async function startMcpServer(opts) {
8770
8929
  case "mesh_list_pending_approvals":
8771
8930
  text = await meshListPendingApprovals(meshCtx, a);
8772
8931
  break;
8932
+ case "mesh_create":
8933
+ text = await meshCreate(meshCtx.transport, a);
8934
+ break;
8935
+ case "mesh_add_node":
8936
+ text = await meshAddNode(meshCtx.transport, a, meshCtx.mesh.id);
8937
+ break;
8773
8938
  case "mesh_clone_node":
8774
8939
  text = await meshCloneNode(meshCtx, a);
8775
8940
  break;
@@ -8907,6 +9072,11 @@ async function startMcpServer(opts) {
8907
9072
  GIT_DIFF_TOOL,
8908
9073
  GIT_CHECKPOINT_TOOL,
8909
9074
  GIT_PUSH_TOOL,
9075
+ // Mesh bootstrap: create a mesh + register its first node from an MCP-only agent.
9076
+ // Exposed in standard mode precisely because this is the no-mesh-yet context —
9077
+ // mesh mode refuses to boot without an existing meshId (see the mesh-mode block above).
9078
+ MESH_CREATE_TOOL,
9079
+ MESH_ADD_NODE_TOOL,
8910
9080
  ...isLocal ? [SCREENSHOT_TOOL] : []
8911
9081
  ];
8912
9082
  const server = new import_server.Server(
@@ -8996,6 +9166,14 @@ async function startMcpServer(opts) {
8996
9166
  const text = await checkPending(transport, { format: a.format });
8997
9167
  return { content: [{ type: "text", text }] };
8998
9168
  }
9169
+ case "mesh_create": {
9170
+ const text = await meshCreate(transport, a);
9171
+ return { content: [{ type: "text", text }] };
9172
+ }
9173
+ case "mesh_add_node": {
9174
+ const text = await meshAddNode(transport, a);
9175
+ return { content: [{ type: "text", text }] };
9176
+ }
8999
9177
  default:
9000
9178
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
9001
9179
  }