@adhdev/daemon-standalone 0.9.67 → 0.9.69

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 CHANGED
@@ -27884,6 +27884,20 @@ var require_dist2 = __commonJS({
27884
27884
  mod
27885
27885
  ));
27886
27886
  var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
27887
+ var DEFAULT_MESH_POLICY;
27888
+ var init_repo_mesh_types = __esm2({
27889
+ "src/repo-mesh-types.ts"() {
27890
+ "use strict";
27891
+ DEFAULT_MESH_POLICY = {
27892
+ requirePreTaskCheckpoint: false,
27893
+ requirePostTaskCheckpoint: true,
27894
+ requireApprovalForPush: true,
27895
+ requireApprovalForDestructiveGit: true,
27896
+ dirtyWorkspaceBehavior: "warn",
27897
+ maxParallelTasks: 2
27898
+ };
27899
+ }
27900
+ });
27887
27901
  var config_exports = {};
27888
27902
  __export2(config_exports, {
27889
27903
  generateMachineId: () => generateMachineId,
@@ -28125,6 +28139,276 @@ var require_dist2 = __commonJS({
28125
28139
  MACHINE_ID_PREFIX = "mach_";
28126
28140
  }
28127
28141
  });
28142
+ var mesh_config_exports = {};
28143
+ __export2(mesh_config_exports, {
28144
+ addNode: () => addNode,
28145
+ createMesh: () => createMesh,
28146
+ deleteMesh: () => deleteMesh,
28147
+ getMesh: () => getMesh,
28148
+ getMeshByRepo: () => getMeshByRepo,
28149
+ listMeshes: () => listMeshes,
28150
+ normalizeRepoIdentity: () => normalizeRepoIdentity,
28151
+ removeNode: () => removeNode,
28152
+ updateMesh: () => updateMesh,
28153
+ updateNode: () => updateNode
28154
+ });
28155
+ function getMeshConfigPath() {
28156
+ return (0, import_path22.join)(getConfigDir(), "meshes.json");
28157
+ }
28158
+ function loadMeshConfig() {
28159
+ const path26 = getMeshConfigPath();
28160
+ if (!(0, import_fs2.existsSync)(path26)) return { meshes: [] };
28161
+ try {
28162
+ const raw = JSON.parse((0, import_fs2.readFileSync)(path26, "utf-8"));
28163
+ if (!raw || !Array.isArray(raw.meshes)) return { meshes: [] };
28164
+ return raw;
28165
+ } catch {
28166
+ return { meshes: [] };
28167
+ }
28168
+ }
28169
+ function saveMeshConfig(config2) {
28170
+ const path26 = getMeshConfigPath();
28171
+ (0, import_fs2.writeFileSync)(path26, JSON.stringify(config2, null, 2), { encoding: "utf-8", mode: 384 });
28172
+ }
28173
+ function normalizeRepoIdentity(remoteUrl) {
28174
+ let identity = remoteUrl.trim();
28175
+ if (identity.startsWith("http://") || identity.startsWith("https://")) {
28176
+ try {
28177
+ const url2 = new URL(identity);
28178
+ const path26 = url2.pathname.replace(/^\//, "").replace(/\.git$/, "");
28179
+ return `${url2.hostname}/${path26}`;
28180
+ } catch {
28181
+ }
28182
+ }
28183
+ const sshMatch = identity.match(/^(?:ssh:\/\/)?[\w.-]+@([\w.-]+)[:/]([\w.\-/]+?)(?:\.git)?$/);
28184
+ if (sshMatch) return `${sshMatch[1]}/${sshMatch[2]}`;
28185
+ return identity;
28186
+ }
28187
+ function listMeshes() {
28188
+ return loadMeshConfig().meshes;
28189
+ }
28190
+ function getMesh(meshId) {
28191
+ return loadMeshConfig().meshes.find((m) => m.id === meshId);
28192
+ }
28193
+ function getMeshByRepo(repoIdentity) {
28194
+ return loadMeshConfig().meshes.find((m) => m.repoIdentity === repoIdentity);
28195
+ }
28196
+ function createMesh(opts) {
28197
+ const config2 = loadMeshConfig();
28198
+ if (config2.meshes.length >= 20) {
28199
+ throw new Error("Maximum 20 meshes allowed");
28200
+ }
28201
+ const repoIdentity = opts.repoIdentity || (opts.repoRemoteUrl ? normalizeRepoIdentity(opts.repoRemoteUrl) : "");
28202
+ if (!repoIdentity) throw new Error("Either repoRemoteUrl or repoIdentity is required");
28203
+ const now = (/* @__PURE__ */ new Date()).toISOString();
28204
+ const mesh = {
28205
+ id: `mesh_${(0, import_crypto32.randomUUID)().replace(/-/g, "")}`,
28206
+ name: opts.name.trim().slice(0, 100),
28207
+ repoIdentity,
28208
+ repoRemoteUrl: opts.repoRemoteUrl,
28209
+ defaultBranch: opts.defaultBranch,
28210
+ policy: { ...DEFAULT_MESH_POLICY, ...opts.policy },
28211
+ coordinator: opts.coordinator || {},
28212
+ nodes: [],
28213
+ createdAt: now,
28214
+ updatedAt: now
28215
+ };
28216
+ config2.meshes.push(mesh);
28217
+ saveMeshConfig(config2);
28218
+ return mesh;
28219
+ }
28220
+ function updateMesh(meshId, opts) {
28221
+ const config2 = loadMeshConfig();
28222
+ const mesh = config2.meshes.find((m) => m.id === meshId);
28223
+ if (!mesh) return void 0;
28224
+ if (opts.name !== void 0) mesh.name = opts.name.trim().slice(0, 100);
28225
+ if (opts.defaultBranch !== void 0) mesh.defaultBranch = opts.defaultBranch;
28226
+ if (opts.policy) mesh.policy = { ...mesh.policy, ...opts.policy };
28227
+ if (opts.coordinator) mesh.coordinator = opts.coordinator;
28228
+ mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
28229
+ saveMeshConfig(config2);
28230
+ return mesh;
28231
+ }
28232
+ function deleteMesh(meshId) {
28233
+ const config2 = loadMeshConfig();
28234
+ const idx = config2.meshes.findIndex((m) => m.id === meshId);
28235
+ if (idx === -1) return false;
28236
+ config2.meshes.splice(idx, 1);
28237
+ saveMeshConfig(config2);
28238
+ return true;
28239
+ }
28240
+ function addNode(meshId, opts) {
28241
+ const config2 = loadMeshConfig();
28242
+ const mesh = config2.meshes.find((m) => m.id === meshId);
28243
+ if (!mesh) return void 0;
28244
+ if (mesh.nodes.length >= 10) {
28245
+ throw new Error("Maximum 10 nodes per mesh");
28246
+ }
28247
+ if (mesh.nodes.some((n) => n.workspace === opts.workspace)) {
28248
+ throw new Error("This workspace is already in the mesh");
28249
+ }
28250
+ const node = {
28251
+ id: `node_${(0, import_crypto32.randomUUID)().replace(/-/g, "")}`,
28252
+ workspace: opts.workspace.trim(),
28253
+ repoRoot: opts.repoRoot,
28254
+ userOverrides: opts.userOverrides || {},
28255
+ policy: opts.policy || {},
28256
+ isLocalWorktree: opts.isLocalWorktree
28257
+ };
28258
+ mesh.nodes.push(node);
28259
+ mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
28260
+ saveMeshConfig(config2);
28261
+ return node;
28262
+ }
28263
+ function removeNode(meshId, nodeId) {
28264
+ const config2 = loadMeshConfig();
28265
+ const mesh = config2.meshes.find((m) => m.id === meshId);
28266
+ if (!mesh) return false;
28267
+ const idx = mesh.nodes.findIndex((n) => n.id === nodeId);
28268
+ if (idx === -1) return false;
28269
+ mesh.nodes.splice(idx, 1);
28270
+ mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
28271
+ saveMeshConfig(config2);
28272
+ return true;
28273
+ }
28274
+ function updateNode(meshId, nodeId, opts) {
28275
+ const config2 = loadMeshConfig();
28276
+ const mesh = config2.meshes.find((m) => m.id === meshId);
28277
+ if (!mesh) return void 0;
28278
+ const node = mesh.nodes.find((n) => n.id === nodeId);
28279
+ if (!node) return void 0;
28280
+ if (opts.userOverrides) node.userOverrides = { ...node.userOverrides, ...opts.userOverrides };
28281
+ if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
28282
+ mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
28283
+ saveMeshConfig(config2);
28284
+ return node;
28285
+ }
28286
+ var import_fs2;
28287
+ var import_path22;
28288
+ var import_crypto32;
28289
+ var init_mesh_config = __esm2({
28290
+ "src/config/mesh-config.ts"() {
28291
+ "use strict";
28292
+ import_fs2 = require("fs");
28293
+ import_path22 = require("path");
28294
+ import_crypto32 = require("crypto");
28295
+ init_config();
28296
+ init_repo_mesh_types();
28297
+ }
28298
+ });
28299
+ var coordinator_prompt_exports = {};
28300
+ __export2(coordinator_prompt_exports, {
28301
+ buildCoordinatorSystemPrompt: () => buildCoordinatorSystemPrompt
28302
+ });
28303
+ function buildCoordinatorSystemPrompt(ctx) {
28304
+ const { mesh, status, userInstruction } = ctx;
28305
+ const sections = [];
28306
+ sections.push(`You are a **Repo Mesh Coordinator** \u2014 a technical team lead who orchestrates work across multiple agent sessions on a shared Git repository.
28307
+
28308
+ Your mesh: **${mesh.name}**
28309
+ Repository: \`${mesh.repoIdentity}\`${mesh.defaultBranch ? `
28310
+ Default branch: \`${mesh.defaultBranch}\`` : ""}`);
28311
+ if (status?.nodes?.length) {
28312
+ sections.push(buildNodeStatusSection(status.nodes));
28313
+ } else if (mesh.nodes.length) {
28314
+ sections.push(buildNodeConfigSection(mesh));
28315
+ } else {
28316
+ sections.push("## Nodes\nNo nodes configured yet. Ask the user to add nodes with `adhdev mesh add-node`.");
28317
+ }
28318
+ sections.push(buildPolicySection(mesh.policy));
28319
+ sections.push(TOOLS_SECTION);
28320
+ sections.push(WORKFLOW_SECTION);
28321
+ sections.push(RULES_SECTION);
28322
+ if (userInstruction) {
28323
+ sections.push(`## Additional Context
28324
+ ${userInstruction}`);
28325
+ }
28326
+ if (mesh.coordinator.systemPromptSuffix) {
28327
+ sections.push(mesh.coordinator.systemPromptSuffix);
28328
+ }
28329
+ return sections.join("\n\n");
28330
+ }
28331
+ function buildNodeStatusSection(nodes) {
28332
+ const lines = ["## Current Node Status", ""];
28333
+ for (const n of nodes) {
28334
+ const healthIcon = n.health === "online" ? "\u{1F7E2}" : n.health === "dirty" ? "\u{1F7E1}" : n.health === "offline" ? "\u26AB" : "\u{1F534}";
28335
+ const sessions = n.activeSessions.length > 0 ? `sessions: ${n.activeSessions.join(", ")}` : "no active sessions";
28336
+ const branch = n.git?.branch ? `branch: \`${n.git.branch}\`` : "";
28337
+ lines.push(`- ${healthIcon} **${n.machineLabel}** (${n.nodeId})`);
28338
+ lines.push(` workspace: \`${n.workspace}\` | ${branch} | ${sessions}`);
28339
+ if (n.error) lines.push(` \u26A0\uFE0F ${n.error}`);
28340
+ }
28341
+ return lines.join("\n");
28342
+ }
28343
+ function buildNodeConfigSection(mesh) {
28344
+ const lines = ["## Configured Nodes", ""];
28345
+ for (const n of mesh.nodes) {
28346
+ const labels = [];
28347
+ if (n.isLocalWorktree) labels.push("worktree");
28348
+ if (n.policy.readOnly) labels.push("read-only");
28349
+ const suffix = labels.length ? ` [${labels.join(", ")}]` : "";
28350
+ lines.push(`- **${n.workspace}** (${n.id})${suffix}`);
28351
+ }
28352
+ lines.push("", "_Use `mesh_status` to probe live health before delegating work._");
28353
+ return lines.join("\n");
28354
+ }
28355
+ function buildPolicySection(policy) {
28356
+ const rules = [];
28357
+ if (policy.requirePreTaskCheckpoint) rules.push("- Create a git checkpoint **before** starting each task");
28358
+ if (policy.requirePostTaskCheckpoint) rules.push("- Create a git checkpoint **after** each task completes");
28359
+ if (policy.requireApprovalForPush) rules.push("- **Ask for user approval** before pushing to remote");
28360
+ if (policy.requireApprovalForDestructiveGit) rules.push("- **Ask for user approval** before destructive git operations (force push, reset, etc.)");
28361
+ const dirtyBehavior = {
28362
+ block: "- **Do not** send tasks to nodes with dirty workspaces",
28363
+ warn: "- Warn the user if a node has uncommitted changes before sending a task",
28364
+ checkpoint_then_continue: "- Auto-checkpoint dirty nodes before sending tasks"
28365
+ }[policy.dirtyWorkspaceBehavior] || "";
28366
+ if (dirtyBehavior) rules.push(dirtyBehavior);
28367
+ rules.push(`- Maximum **${policy.maxParallelTasks}** tasks running in parallel`);
28368
+ return `## Policy
28369
+ ${rules.join("\n")}`;
28370
+ }
28371
+ var TOOLS_SECTION;
28372
+ var WORKFLOW_SECTION;
28373
+ var RULES_SECTION;
28374
+ var init_coordinator_prompt = __esm2({
28375
+ "src/mesh/coordinator-prompt.ts"() {
28376
+ "use strict";
28377
+ TOOLS_SECTION = `## Available Tools
28378
+
28379
+ | Tool | Purpose |
28380
+ |------|---------|
28381
+ | \`mesh_status\` | Check all nodes' health, git state, and active sessions |
28382
+ | \`mesh_list_nodes\` | List nodes with workspace paths |
28383
+ | \`mesh_launch_session\` | Start a new agent session on a node |
28384
+ | \`mesh_send_task\` | Send a task (natural language) to a running agent |
28385
+ | \`mesh_read_chat\` | Read an agent's recent messages to check progress |
28386
+ | \`mesh_git_status\` | Check git status on a specific node |
28387
+ | \`mesh_checkpoint\` | Create a git checkpoint on a node |
28388
+ | \`mesh_approve\` | Approve/reject a pending agent action |`;
28389
+ WORKFLOW_SECTION = `## Orchestration Workflow
28390
+
28391
+ 1. **Assess** \u2014 Call \`mesh_status\` to see which nodes are healthy and available.
28392
+ 2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist.
28393
+ 3. **Delegate** \u2014 For each task:
28394
+ a. Pick the best node (consider: health, dirty state, current workload).
28395
+ b. If no session exists, call \`mesh_launch_session\` to start one.
28396
+ c. Call \`mesh_send_task\` with a clear, self-contained natural-language instruction.
28397
+ 4. **Monitor** \u2014 Periodically call \`mesh_read_chat\` to check progress. Handle approvals via \`mesh_approve\`.
28398
+ 5. **Verify** \u2014 When a task reports completion, call \`mesh_git_status\` to verify changes were made.
28399
+ 6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
28400
+ 7. **Report** \u2014 Summarize what was done, what changed, and any issues.`;
28401
+ RULES_SECTION = `## Rules
28402
+
28403
+ - **Be conversational.** Delegate work the way a tech lead would \u2014 clear, specific instructions in natural language.
28404
+ - **Don't inspect code.** Trust the agent's output. Verify via git diff/status, not by reading source files.
28405
+ - **Don't over-parallelize.** Start with 1-2 concurrent tasks. Scale up if they succeed.
28406
+ - **Handle failures gracefully.** If a task fails, read the chat to understand why, then retry or reassign.
28407
+ - **Keep the user informed.** Report progress after each delegation round.
28408
+ - **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
28409
+ - **Never fabricate tool results.** Always call the actual tool; never pretend you did.`;
28410
+ }
28411
+ });
28128
28412
  function setLogLevel(level) {
28129
28413
  currentLevel = level;
28130
28414
  daemonLog("Logger", `Log level set to: ${level}`, "info");
@@ -31381,14 +31665,7 @@ var require_dist2 = __commonJS({
31381
31665
  upsertSavedProviderSession: () => upsertSavedProviderSession
31382
31666
  });
31383
31667
  module2.exports = __toCommonJS2(index_exports);
31384
- var DEFAULT_MESH_POLICY = {
31385
- requirePreTaskCheckpoint: false,
31386
- requirePostTaskCheckpoint: true,
31387
- requireApprovalForPush: true,
31388
- requireApprovalForDestructiveGit: true,
31389
- dirtyWorkspaceBehavior: "warn",
31390
- maxParallelTasks: 2
31391
- };
31668
+ init_repo_mesh_types();
31392
31669
  var import_node_child_process = require("child_process");
31393
31670
  var import_node_fs3 = require("fs");
31394
31671
  var import_promises4 = require("fs/promises");
@@ -33063,337 +33340,104 @@ var require_dist2 = __commonJS({
33063
33340
  return ["task_complete", target, lastMessageHash, String(timestamp)].join("|");
33064
33341
  })();
33065
33342
  const dismissedNotificationId = typeof overlay.dismissedNotificationId === "string" ? overlay.dismissedNotificationId.trim() : "";
33066
- const unreadNotificationId = typeof overlay.unreadNotificationId === "string" ? overlay.unreadNotificationId.trim() : "";
33067
- if (unreadNotificationId && (currentNotificationId === unreadNotificationId || taskCompleteNotificationId === unreadNotificationId)) {
33068
- const forcedInboxBucket = session.inboxBucket === "needs_attention" || session.status === "waiting_approval" ? "needs_attention" : "task_complete";
33069
- return {
33070
- unread: true,
33071
- inboxBucket: forcedInboxBucket
33072
- };
33073
- }
33074
- if (!currentNotificationId || !dismissedNotificationId || currentNotificationId !== dismissedNotificationId) {
33075
- return {
33076
- unread: !!session.unread,
33077
- inboxBucket: session.inboxBucket || "idle"
33078
- };
33079
- }
33080
- return {
33081
- unread: false,
33082
- inboxBucket: "idle"
33083
- };
33084
- }
33085
- function markSessionSeen(state, sessionId, seenAt = Date.now(), completionMarker, providerSessionId) {
33086
- const prev = state.sessionReads || {};
33087
- const prevMarkers = state.sessionReadMarkers || {};
33088
- const nextMarker = typeof completionMarker === "string" ? completionMarker : "";
33089
- const readKeys = Array.from(new Set([
33090
- sessionId,
33091
- buildSessionReadStateKey(sessionId, providerSessionId)
33092
- ].filter(Boolean)));
33093
- const nextSessionReads = { ...prev };
33094
- const nextSessionReadMarkers = { ...prevMarkers };
33095
- const nextSessionNotificationDismissals = { ...state.sessionNotificationDismissals || {} };
33096
- const nextSessionNotificationUnreadOverrides = { ...state.sessionNotificationUnreadOverrides || {} };
33097
- for (const key of readKeys) {
33098
- nextSessionReads[key] = Math.max(prev[key] || 0, seenAt);
33099
- if (nextMarker) nextSessionReadMarkers[key] = nextMarker;
33100
- delete nextSessionNotificationDismissals[key];
33101
- delete nextSessionNotificationUnreadOverrides[key];
33102
- }
33103
- return {
33104
- ...state,
33105
- sessionReads: nextSessionReads,
33106
- sessionReadMarkers: nextMarker ? nextSessionReadMarkers : prevMarkers,
33107
- sessionNotificationDismissals: nextSessionNotificationDismissals,
33108
- sessionNotificationUnreadOverrides: nextSessionNotificationUnreadOverrides
33109
- };
33110
- }
33111
- var path6 = __toESM2(require("path"));
33112
- var MAX_SAVED_SESSIONS = 500;
33113
- function normalizeWorkspace2(workspace) {
33114
- if (!workspace) return "";
33115
- try {
33116
- return path6.resolve(expandPath(workspace));
33117
- } catch {
33118
- return path6.resolve(workspace);
33119
- }
33120
- }
33121
- function buildSavedProviderSessionKey(providerSessionId) {
33122
- return `saved:${providerSessionId.trim()}`;
33123
- }
33124
- function upsertSavedProviderSession(state, entry) {
33125
- const providerSessionId = typeof entry.providerSessionId === "string" ? entry.providerSessionId.trim() : "";
33126
- if (!providerSessionId) return state;
33127
- const id = buildSavedProviderSessionKey(providerSessionId);
33128
- const existing = (state.savedProviderSessions || []).find((item) => item.id === id);
33129
- const nextEntry = {
33130
- id,
33131
- kind: entry.kind,
33132
- providerType: entry.providerType,
33133
- providerName: entry.providerName,
33134
- providerSessionId,
33135
- workspace: entry.workspace ? normalizeWorkspace2(entry.workspace) : void 0,
33136
- summaryMetadata: normalizePersistedSummaryMetadata({
33137
- summaryMetadata: entry.summaryMetadata
33138
- }),
33139
- title: entry.title,
33140
- createdAt: existing?.createdAt || entry.createdAt || Date.now(),
33141
- lastUsedAt: entry.lastUsedAt || Date.now()
33142
- };
33143
- const filtered = (state.savedProviderSessions || []).filter((item) => item.id !== id);
33144
- return {
33145
- ...state,
33146
- savedProviderSessions: [nextEntry, ...filtered].slice(0, MAX_SAVED_SESSIONS)
33147
- };
33148
- }
33149
- function getSavedProviderSessions(state, filters) {
33150
- return [...state.savedProviderSessions || []].filter((entry) => {
33151
- if (filters?.providerType && entry.providerType !== filters.providerType) return false;
33152
- if (filters?.kind && entry.kind !== filters.kind) return false;
33153
- return true;
33154
- }).map((entry) => ({
33155
- ...entry,
33156
- summaryMetadata: normalizePersistedSummaryMetadata({
33157
- summaryMetadata: entry.summaryMetadata
33158
- })
33159
- })).sort((a, b2) => b2.lastUsedAt - a.lastUsedAt);
33160
- }
33161
- var import_fs2 = require("fs");
33162
- var import_path22 = require("path");
33163
- var import_crypto32 = require("crypto");
33164
- init_config();
33165
- function getMeshConfigPath() {
33166
- return (0, import_path22.join)(getConfigDir(), "meshes.json");
33167
- }
33168
- function loadMeshConfig() {
33169
- const path26 = getMeshConfigPath();
33170
- if (!(0, import_fs2.existsSync)(path26)) return { meshes: [] };
33171
- try {
33172
- const raw = JSON.parse((0, import_fs2.readFileSync)(path26, "utf-8"));
33173
- if (!raw || !Array.isArray(raw.meshes)) return { meshes: [] };
33174
- return raw;
33175
- } catch {
33176
- return { meshes: [] };
33177
- }
33178
- }
33179
- function saveMeshConfig(config2) {
33180
- const path26 = getMeshConfigPath();
33181
- (0, import_fs2.writeFileSync)(path26, JSON.stringify(config2, null, 2), { encoding: "utf-8", mode: 384 });
33182
- }
33183
- function normalizeRepoIdentity(remoteUrl) {
33184
- let identity = remoteUrl.trim();
33185
- if (identity.startsWith("http://") || identity.startsWith("https://")) {
33186
- try {
33187
- const url2 = new URL(identity);
33188
- const path26 = url2.pathname.replace(/^\//, "").replace(/\.git$/, "");
33189
- return `${url2.hostname}/${path26}`;
33190
- } catch {
33191
- }
33192
- }
33193
- const sshMatch = identity.match(/^(?:ssh:\/\/)?[\w.-]+@([\w.-]+)[:/]([\w.\-/]+?)(?:\.git)?$/);
33194
- if (sshMatch) return `${sshMatch[1]}/${sshMatch[2]}`;
33195
- return identity;
33196
- }
33197
- function listMeshes() {
33198
- return loadMeshConfig().meshes;
33199
- }
33200
- function getMesh(meshId) {
33201
- return loadMeshConfig().meshes.find((m) => m.id === meshId);
33202
- }
33203
- function getMeshByRepo(repoIdentity) {
33204
- return loadMeshConfig().meshes.find((m) => m.repoIdentity === repoIdentity);
33205
- }
33206
- function createMesh(opts) {
33207
- const config2 = loadMeshConfig();
33208
- if (config2.meshes.length >= 20) {
33209
- throw new Error("Maximum 20 meshes allowed");
33210
- }
33211
- const repoIdentity = opts.repoIdentity || (opts.repoRemoteUrl ? normalizeRepoIdentity(opts.repoRemoteUrl) : "");
33212
- if (!repoIdentity) throw new Error("Either repoRemoteUrl or repoIdentity is required");
33213
- const now = (/* @__PURE__ */ new Date()).toISOString();
33214
- const mesh = {
33215
- id: `mesh_${(0, import_crypto32.randomUUID)().replace(/-/g, "")}`,
33216
- name: opts.name.trim().slice(0, 100),
33217
- repoIdentity,
33218
- repoRemoteUrl: opts.repoRemoteUrl,
33219
- defaultBranch: opts.defaultBranch,
33220
- policy: { ...DEFAULT_MESH_POLICY, ...opts.policy },
33221
- coordinator: opts.coordinator || {},
33222
- nodes: [],
33223
- createdAt: now,
33224
- updatedAt: now
33225
- };
33226
- config2.meshes.push(mesh);
33227
- saveMeshConfig(config2);
33228
- return mesh;
33229
- }
33230
- function updateMesh(meshId, opts) {
33231
- const config2 = loadMeshConfig();
33232
- const mesh = config2.meshes.find((m) => m.id === meshId);
33233
- if (!mesh) return void 0;
33234
- if (opts.name !== void 0) mesh.name = opts.name.trim().slice(0, 100);
33235
- if (opts.defaultBranch !== void 0) mesh.defaultBranch = opts.defaultBranch;
33236
- if (opts.policy) mesh.policy = { ...mesh.policy, ...opts.policy };
33237
- if (opts.coordinator) mesh.coordinator = opts.coordinator;
33238
- mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
33239
- saveMeshConfig(config2);
33240
- return mesh;
33241
- }
33242
- function deleteMesh(meshId) {
33243
- const config2 = loadMeshConfig();
33244
- const idx = config2.meshes.findIndex((m) => m.id === meshId);
33245
- if (idx === -1) return false;
33246
- config2.meshes.splice(idx, 1);
33247
- saveMeshConfig(config2);
33248
- return true;
33249
- }
33250
- function addNode(meshId, opts) {
33251
- const config2 = loadMeshConfig();
33252
- const mesh = config2.meshes.find((m) => m.id === meshId);
33253
- if (!mesh) return void 0;
33254
- if (mesh.nodes.length >= 10) {
33255
- throw new Error("Maximum 10 nodes per mesh");
33256
- }
33257
- if (mesh.nodes.some((n) => n.workspace === opts.workspace)) {
33258
- throw new Error("This workspace is already in the mesh");
33259
- }
33260
- const node = {
33261
- id: `node_${(0, import_crypto32.randomUUID)().replace(/-/g, "")}`,
33262
- workspace: opts.workspace.trim(),
33263
- repoRoot: opts.repoRoot,
33264
- userOverrides: opts.userOverrides || {},
33265
- policy: opts.policy || {},
33266
- isLocalWorktree: opts.isLocalWorktree
33267
- };
33268
- mesh.nodes.push(node);
33269
- mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
33270
- saveMeshConfig(config2);
33271
- return node;
33272
- }
33273
- function removeNode(meshId, nodeId) {
33274
- const config2 = loadMeshConfig();
33275
- const mesh = config2.meshes.find((m) => m.id === meshId);
33276
- if (!mesh) return false;
33277
- const idx = mesh.nodes.findIndex((n) => n.id === nodeId);
33278
- if (idx === -1) return false;
33279
- mesh.nodes.splice(idx, 1);
33280
- mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
33281
- saveMeshConfig(config2);
33282
- return true;
33283
- }
33284
- function updateNode(meshId, nodeId, opts) {
33285
- const config2 = loadMeshConfig();
33286
- const mesh = config2.meshes.find((m) => m.id === meshId);
33287
- if (!mesh) return void 0;
33288
- const node = mesh.nodes.find((n) => n.id === nodeId);
33289
- if (!node) return void 0;
33290
- if (opts.userOverrides) node.userOverrides = { ...node.userOverrides, ...opts.userOverrides };
33291
- if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
33292
- mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
33293
- saveMeshConfig(config2);
33294
- return node;
33295
- }
33296
- function buildCoordinatorSystemPrompt(ctx) {
33297
- const { mesh, status, userInstruction } = ctx;
33298
- const sections = [];
33299
- sections.push(`You are a **Repo Mesh Coordinator** \u2014 a technical team lead who orchestrates work across multiple agent sessions on a shared Git repository.
33300
-
33301
- Your mesh: **${mesh.name}**
33302
- Repository: \`${mesh.repoIdentity}\`${mesh.defaultBranch ? `
33303
- Default branch: \`${mesh.defaultBranch}\`` : ""}`);
33304
- if (status?.nodes?.length) {
33305
- sections.push(buildNodeStatusSection(status.nodes));
33306
- } else if (mesh.nodes.length) {
33307
- sections.push(buildNodeConfigSection(mesh));
33308
- } else {
33309
- sections.push("## Nodes\nNo nodes configured yet. Ask the user to add nodes with `adhdev mesh add-node`.");
33310
- }
33311
- sections.push(buildPolicySection(mesh.policy));
33312
- sections.push(TOOLS_SECTION);
33313
- sections.push(WORKFLOW_SECTION);
33314
- sections.push(RULES_SECTION);
33315
- if (userInstruction) {
33316
- sections.push(`## Additional Context
33317
- ${userInstruction}`);
33343
+ const unreadNotificationId = typeof overlay.unreadNotificationId === "string" ? overlay.unreadNotificationId.trim() : "";
33344
+ if (unreadNotificationId && (currentNotificationId === unreadNotificationId || taskCompleteNotificationId === unreadNotificationId)) {
33345
+ const forcedInboxBucket = session.inboxBucket === "needs_attention" || session.status === "waiting_approval" ? "needs_attention" : "task_complete";
33346
+ return {
33347
+ unread: true,
33348
+ inboxBucket: forcedInboxBucket
33349
+ };
33318
33350
  }
33319
- if (mesh.coordinator.systemPromptSuffix) {
33320
- sections.push(mesh.coordinator.systemPromptSuffix);
33351
+ if (!currentNotificationId || !dismissedNotificationId || currentNotificationId !== dismissedNotificationId) {
33352
+ return {
33353
+ unread: !!session.unread,
33354
+ inboxBucket: session.inboxBucket || "idle"
33355
+ };
33321
33356
  }
33322
- return sections.join("\n\n");
33357
+ return {
33358
+ unread: false,
33359
+ inboxBucket: "idle"
33360
+ };
33323
33361
  }
33324
- function buildNodeStatusSection(nodes) {
33325
- const lines = ["## Current Node Status", ""];
33326
- for (const n of nodes) {
33327
- const healthIcon = n.health === "online" ? "\u{1F7E2}" : n.health === "dirty" ? "\u{1F7E1}" : n.health === "offline" ? "\u26AB" : "\u{1F534}";
33328
- const sessions = n.activeSessions.length > 0 ? `sessions: ${n.activeSessions.join(", ")}` : "no active sessions";
33329
- const branch = n.git?.branch ? `branch: \`${n.git.branch}\`` : "";
33330
- lines.push(`- ${healthIcon} **${n.machineLabel}** (${n.nodeId})`);
33331
- lines.push(` workspace: \`${n.workspace}\` | ${branch} | ${sessions}`);
33332
- if (n.error) lines.push(` \u26A0\uFE0F ${n.error}`);
33362
+ function markSessionSeen(state, sessionId, seenAt = Date.now(), completionMarker, providerSessionId) {
33363
+ const prev = state.sessionReads || {};
33364
+ const prevMarkers = state.sessionReadMarkers || {};
33365
+ const nextMarker = typeof completionMarker === "string" ? completionMarker : "";
33366
+ const readKeys = Array.from(new Set([
33367
+ sessionId,
33368
+ buildSessionReadStateKey(sessionId, providerSessionId)
33369
+ ].filter(Boolean)));
33370
+ const nextSessionReads = { ...prev };
33371
+ const nextSessionReadMarkers = { ...prevMarkers };
33372
+ const nextSessionNotificationDismissals = { ...state.sessionNotificationDismissals || {} };
33373
+ const nextSessionNotificationUnreadOverrides = { ...state.sessionNotificationUnreadOverrides || {} };
33374
+ for (const key of readKeys) {
33375
+ nextSessionReads[key] = Math.max(prev[key] || 0, seenAt);
33376
+ if (nextMarker) nextSessionReadMarkers[key] = nextMarker;
33377
+ delete nextSessionNotificationDismissals[key];
33378
+ delete nextSessionNotificationUnreadOverrides[key];
33333
33379
  }
33334
- return lines.join("\n");
33380
+ return {
33381
+ ...state,
33382
+ sessionReads: nextSessionReads,
33383
+ sessionReadMarkers: nextMarker ? nextSessionReadMarkers : prevMarkers,
33384
+ sessionNotificationDismissals: nextSessionNotificationDismissals,
33385
+ sessionNotificationUnreadOverrides: nextSessionNotificationUnreadOverrides
33386
+ };
33335
33387
  }
33336
- function buildNodeConfigSection(mesh) {
33337
- const lines = ["## Configured Nodes", ""];
33338
- for (const n of mesh.nodes) {
33339
- const labels = [];
33340
- if (n.isLocalWorktree) labels.push("worktree");
33341
- if (n.policy.readOnly) labels.push("read-only");
33342
- const suffix = labels.length ? ` [${labels.join(", ")}]` : "";
33343
- lines.push(`- **${n.workspace}** (${n.id})${suffix}`);
33388
+ var path6 = __toESM2(require("path"));
33389
+ var MAX_SAVED_SESSIONS = 500;
33390
+ function normalizeWorkspace2(workspace) {
33391
+ if (!workspace) return "";
33392
+ try {
33393
+ return path6.resolve(expandPath(workspace));
33394
+ } catch {
33395
+ return path6.resolve(workspace);
33344
33396
  }
33345
- lines.push("", "_Use `mesh_status` to probe live health before delegating work._");
33346
- return lines.join("\n");
33347
33397
  }
33348
- function buildPolicySection(policy) {
33349
- const rules = [];
33350
- if (policy.requirePreTaskCheckpoint) rules.push("- Create a git checkpoint **before** starting each task");
33351
- if (policy.requirePostTaskCheckpoint) rules.push("- Create a git checkpoint **after** each task completes");
33352
- if (policy.requireApprovalForPush) rules.push("- **Ask for user approval** before pushing to remote");
33353
- if (policy.requireApprovalForDestructiveGit) rules.push("- **Ask for user approval** before destructive git operations (force push, reset, etc.)");
33354
- const dirtyBehavior = {
33355
- block: "- **Do not** send tasks to nodes with dirty workspaces",
33356
- warn: "- Warn the user if a node has uncommitted changes before sending a task",
33357
- checkpoint_then_continue: "- Auto-checkpoint dirty nodes before sending tasks"
33358
- }[policy.dirtyWorkspaceBehavior] || "";
33359
- if (dirtyBehavior) rules.push(dirtyBehavior);
33360
- rules.push(`- Maximum **${policy.maxParallelTasks}** tasks running in parallel`);
33361
- return `## Policy
33362
- ${rules.join("\n")}`;
33398
+ function buildSavedProviderSessionKey(providerSessionId) {
33399
+ return `saved:${providerSessionId.trim()}`;
33363
33400
  }
33364
- var TOOLS_SECTION = `## Available Tools
33365
-
33366
- | Tool | Purpose |
33367
- |------|---------|
33368
- | \`mesh_status\` | Check all nodes' health, git state, and active sessions |
33369
- | \`mesh_list_nodes\` | List nodes with workspace paths |
33370
- | \`mesh_launch_session\` | Start a new agent session on a node |
33371
- | \`mesh_send_task\` | Send a task (natural language) to a running agent |
33372
- | \`mesh_read_chat\` | Read an agent's recent messages to check progress |
33373
- | \`mesh_git_status\` | Check git status on a specific node |
33374
- | \`mesh_checkpoint\` | Create a git checkpoint on a node |
33375
- | \`mesh_approve\` | Approve/reject a pending agent action |`;
33376
- var WORKFLOW_SECTION = `## Orchestration Workflow
33377
-
33378
- 1. **Assess** \u2014 Call \`mesh_status\` to see which nodes are healthy and available.
33379
- 2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist.
33380
- 3. **Delegate** \u2014 For each task:
33381
- a. Pick the best node (consider: health, dirty state, current workload).
33382
- b. If no session exists, call \`mesh_launch_session\` to start one.
33383
- c. Call \`mesh_send_task\` with a clear, self-contained natural-language instruction.
33384
- 4. **Monitor** \u2014 Periodically call \`mesh_read_chat\` to check progress. Handle approvals via \`mesh_approve\`.
33385
- 5. **Verify** \u2014 When a task reports completion, call \`mesh_git_status\` to verify changes were made.
33386
- 6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
33387
- 7. **Report** \u2014 Summarize what was done, what changed, and any issues.`;
33388
- var RULES_SECTION = `## Rules
33389
-
33390
- - **Be conversational.** Delegate work the way a tech lead would \u2014 clear, specific instructions in natural language.
33391
- - **Don't inspect code.** Trust the agent's output. Verify via git diff/status, not by reading source files.
33392
- - **Don't over-parallelize.** Start with 1-2 concurrent tasks. Scale up if they succeed.
33393
- - **Handle failures gracefully.** If a task fails, read the chat to understand why, then retry or reassign.
33394
- - **Keep the user informed.** Report progress after each delegation round.
33395
- - **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
33396
- - **Never fabricate tool results.** Always call the actual tool; never pretend you did.`;
33401
+ function upsertSavedProviderSession(state, entry) {
33402
+ const providerSessionId = typeof entry.providerSessionId === "string" ? entry.providerSessionId.trim() : "";
33403
+ if (!providerSessionId) return state;
33404
+ const id = buildSavedProviderSessionKey(providerSessionId);
33405
+ const existing = (state.savedProviderSessions || []).find((item) => item.id === id);
33406
+ const nextEntry = {
33407
+ id,
33408
+ kind: entry.kind,
33409
+ providerType: entry.providerType,
33410
+ providerName: entry.providerName,
33411
+ providerSessionId,
33412
+ workspace: entry.workspace ? normalizeWorkspace2(entry.workspace) : void 0,
33413
+ summaryMetadata: normalizePersistedSummaryMetadata({
33414
+ summaryMetadata: entry.summaryMetadata
33415
+ }),
33416
+ title: entry.title,
33417
+ createdAt: existing?.createdAt || entry.createdAt || Date.now(),
33418
+ lastUsedAt: entry.lastUsedAt || Date.now()
33419
+ };
33420
+ const filtered = (state.savedProviderSessions || []).filter((item) => item.id !== id);
33421
+ return {
33422
+ ...state,
33423
+ savedProviderSessions: [nextEntry, ...filtered].slice(0, MAX_SAVED_SESSIONS)
33424
+ };
33425
+ }
33426
+ function getSavedProviderSessions(state, filters) {
33427
+ return [...state.savedProviderSessions || []].filter((entry) => {
33428
+ if (filters?.providerType && entry.providerType !== filters.providerType) return false;
33429
+ if (filters?.kind && entry.kind !== filters.kind) return false;
33430
+ return true;
33431
+ }).map((entry) => ({
33432
+ ...entry,
33433
+ summaryMetadata: normalizePersistedSummaryMetadata({
33434
+ summaryMetadata: entry.summaryMetadata
33435
+ })
33436
+ })).sort((a, b2) => b2.lastUsedAt - a.lastUsedAt);
33437
+ }
33438
+ init_mesh_config();
33439
+ init_coordinator_prompt();
33440
+ init_mesh_config();
33397
33441
  async function syncMeshes(transport) {
33398
33442
  const result = { pushed: 0, pulled: 0, deleted: 0, errors: [] };
33399
33443
  let remoteMeshes;
@@ -45210,6 +45254,8 @@ Run 'adhdev doctor' for detailed diagnostics.`
45210
45254
  "type",
45211
45255
  "name",
45212
45256
  "category",
45257
+ "transcriptAuthority",
45258
+ "transcriptContext",
45213
45259
  "aliases",
45214
45260
  "cdpPorts",
45215
45261
  "targetFilter",
@@ -45254,6 +45300,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
45254
45300
  "staticConfigOptions",
45255
45301
  "spawnArgBuilder",
45256
45302
  "auth",
45303
+ "meshCoordinator",
45257
45304
  "contractVersion",
45258
45305
  "capabilities",
45259
45306
  "providerVersion",
@@ -45311,6 +45358,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
45311
45358
  }
45312
45359
  validateCapabilities(provider, controls, errors);
45313
45360
  validateCanonicalHistory(provider.canonicalHistory, errors);
45361
+ validateMeshCoordinator(provider.meshCoordinator, errors);
45314
45362
  for (const control of controls) {
45315
45363
  validateControl(control, errors);
45316
45364
  }
@@ -45401,6 +45449,60 @@ Run 'adhdev doctor' for detailed diagnostics.`
45401
45449
  }
45402
45450
  }
45403
45451
  }
45452
+ function validateMeshCoordinator(raw, errors) {
45453
+ if (raw === void 0) return;
45454
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
45455
+ errors.push("meshCoordinator must be an object");
45456
+ return;
45457
+ }
45458
+ const meshCoordinator = raw;
45459
+ if (typeof meshCoordinator.supported !== "boolean") {
45460
+ errors.push("meshCoordinator.supported must be boolean");
45461
+ }
45462
+ if (meshCoordinator.reason !== void 0 && (typeof meshCoordinator.reason !== "string" || !meshCoordinator.reason.trim())) {
45463
+ errors.push("meshCoordinator.reason must be a non-empty string when provided");
45464
+ }
45465
+ const mcpConfig = meshCoordinator.mcpConfig;
45466
+ if (mcpConfig === void 0) return;
45467
+ if (!mcpConfig || typeof mcpConfig !== "object" || Array.isArray(mcpConfig)) {
45468
+ errors.push("meshCoordinator.mcpConfig must be an object");
45469
+ return;
45470
+ }
45471
+ const config2 = mcpConfig;
45472
+ const mode = config2.mode;
45473
+ if (!["auto_import", "manual", "none"].includes(String(mode))) {
45474
+ errors.push("meshCoordinator.mcpConfig.mode must be one of: auto_import, manual, none");
45475
+ }
45476
+ const format = config2.format;
45477
+ if (format !== void 0 && !["claude_mcp_json", "hermes_config_yaml"].includes(String(format))) {
45478
+ errors.push("meshCoordinator.mcpConfig.format must be one of: claude_mcp_json, hermes_config_yaml");
45479
+ }
45480
+ for (const key of ["path", "serverName", "configPathCommand", "instructions", "template"]) {
45481
+ const value = config2[key];
45482
+ if (value !== void 0 && (typeof value !== "string" || !value.trim())) {
45483
+ errors.push(`meshCoordinator.mcpConfig.${key} must be a non-empty string when provided`);
45484
+ }
45485
+ }
45486
+ if (config2.requiresRestart !== void 0 && typeof config2.requiresRestart !== "boolean") {
45487
+ errors.push("meshCoordinator.mcpConfig.requiresRestart must be boolean when provided");
45488
+ }
45489
+ if (mode === "auto_import") {
45490
+ if (format === void 0) {
45491
+ errors.push("meshCoordinator.mcpConfig.format is required for auto_import MCP setup");
45492
+ }
45493
+ if (typeof config2.path !== "string" || !config2.path.trim()) {
45494
+ errors.push("meshCoordinator.mcpConfig.path is required for auto_import MCP setup");
45495
+ }
45496
+ }
45497
+ if (mode === "manual") {
45498
+ if (typeof config2.instructions !== "string" || !config2.instructions.trim()) {
45499
+ errors.push("meshCoordinator.mcpConfig.instructions is required for manual MCP setup");
45500
+ }
45501
+ if (typeof config2.template !== "string" || !config2.template.trim()) {
45502
+ errors.push("meshCoordinator.mcpConfig.template is required for manual MCP setup");
45503
+ }
45504
+ }
45505
+ }
45404
45506
  function validateControl(control, errors) {
45405
45507
  if (!control || typeof control !== "object") {
45406
45508
  errors.push("controls: each control must be an object");
@@ -47543,6 +47645,67 @@ Run 'adhdev doctor' for detailed diagnostics.`
47543
47645
  }
47544
47646
  cleanOldFiles();
47545
47647
  init_logger();
47648
+ var import_path4 = require("path");
47649
+ var DEFAULT_SERVER_NAME = "adhdev-mesh";
47650
+ var DEFAULT_ADHDEV_MCP_COMMAND = "adhdev-mcp";
47651
+ function resolveMeshCoordinatorSetup(options) {
47652
+ const { provider, meshId, workspace } = options;
47653
+ const config2 = provider?.meshCoordinator;
47654
+ if (!config2?.supported) {
47655
+ return {
47656
+ kind: "unsupported",
47657
+ reason: config2?.reason || "Provider does not declare Repo Mesh coordinator support"
47658
+ };
47659
+ }
47660
+ const mcpConfig = config2.mcpConfig;
47661
+ if (!mcpConfig || mcpConfig.mode === "none") {
47662
+ return {
47663
+ kind: "unsupported",
47664
+ reason: config2.reason || "Provider does not declare a usable Repo Mesh MCP configuration mode"
47665
+ };
47666
+ }
47667
+ const serverName = mcpConfig.serverName?.trim() || DEFAULT_SERVER_NAME;
47668
+ if (mcpConfig.mode === "auto_import") {
47669
+ const path26 = mcpConfig.path?.trim();
47670
+ if (!path26) {
47671
+ return { kind: "unsupported", reason: "Provider auto-import MCP config is missing a config path" };
47672
+ }
47673
+ return {
47674
+ kind: "auto_import",
47675
+ serverName,
47676
+ configPath: (0, import_path4.join)(workspace, path26),
47677
+ configFormat: mcpConfig.format
47678
+ };
47679
+ }
47680
+ if (mcpConfig.mode === "manual") {
47681
+ const instructions = mcpConfig.instructions?.trim();
47682
+ const template = mcpConfig.template;
47683
+ if (!instructions || !template?.trim()) {
47684
+ return { kind: "unsupported", reason: "Provider manual MCP setup is missing instructions or template" };
47685
+ }
47686
+ return {
47687
+ kind: "manual",
47688
+ serverName,
47689
+ configFormat: mcpConfig.format,
47690
+ configPathCommand: mcpConfig.configPathCommand,
47691
+ requiresRestart: mcpConfig.requiresRestart === true,
47692
+ instructions,
47693
+ template: renderMeshCoordinatorTemplate(template, {
47694
+ meshId,
47695
+ workspace,
47696
+ serverName,
47697
+ adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
47698
+ })
47699
+ };
47700
+ }
47701
+ return {
47702
+ kind: "unsupported",
47703
+ reason: `Unsupported Repo Mesh MCP configuration mode: ${String(mcpConfig.mode)}`
47704
+ };
47705
+ }
47706
+ function renderMeshCoordinatorTemplate(template, values) {
47707
+ return template.replace(/\{\{\s*(meshId|workspace|serverName|adhdevMcpCommand)\s*\}\}/g, (_, key) => values[key] || "");
47708
+ }
47546
47709
  var os17 = __toESM2(require("os"));
47547
47710
  init_config();
47548
47711
  init_terminal_screen();
@@ -47593,7 +47756,8 @@ Run 'adhdev doctor' for detailed diagnostics.`
47593
47756
  ...provider.enabled !== void 0 ? { enabled: provider.enabled } : {},
47594
47757
  ...provider.machineStatus !== void 0 ? { machineStatus: provider.machineStatus } : {},
47595
47758
  ...provider.lastDetection !== void 0 ? { lastDetection: provider.lastDetection } : {},
47596
- ...provider.lastVerification !== void 0 ? { lastVerification: provider.lastVerification } : {}
47759
+ ...provider.lastVerification !== void 0 ? { lastVerification: provider.lastVerification } : {},
47760
+ ...provider.meshCoordinator !== void 0 ? { meshCoordinator: provider.meshCoordinator } : {}
47597
47761
  }));
47598
47762
  }
47599
47763
  function buildMachineInfo2(profile = "full") {
@@ -48914,6 +49078,178 @@ Run 'adhdev doctor' for detailed diagnostics.`
48914
49078
  updateConfig({ machineNickname: nickname || null });
48915
49079
  return { success: true };
48916
49080
  }
49081
+ // ─── Mesh CRUD (local meshes.json) ───
49082
+ case "list_meshes": {
49083
+ try {
49084
+ const { listMeshes: listMeshes2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
49085
+ return { success: true, meshes: listMeshes2() };
49086
+ } catch (e) {
49087
+ return { success: false, error: e.message };
49088
+ }
49089
+ }
49090
+ case "get_mesh": {
49091
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
49092
+ if (!meshId) return { success: false, error: "meshId required" };
49093
+ try {
49094
+ const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
49095
+ const mesh = getMesh3(meshId);
49096
+ if (!mesh) return { success: false, error: "Mesh not found" };
49097
+ return { success: true, mesh };
49098
+ } catch (e) {
49099
+ return { success: false, error: e.message };
49100
+ }
49101
+ }
49102
+ case "create_mesh": {
49103
+ const name = typeof args?.name === "string" ? args.name.trim() : "";
49104
+ const repoIdentity = typeof args?.repoIdentity === "string" ? args.repoIdentity.trim() : "";
49105
+ const repoRemoteUrl = typeof args?.repoRemoteUrl === "string" ? args.repoRemoteUrl.trim() : void 0;
49106
+ const defaultBranch = typeof args?.defaultBranch === "string" ? args.defaultBranch.trim() : void 0;
49107
+ if (!name) return { success: false, error: "name required" };
49108
+ try {
49109
+ const { createMesh: createMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
49110
+ const mesh = createMesh2({ name, repoIdentity, repoRemoteUrl, defaultBranch });
49111
+ return { success: true, mesh };
49112
+ } catch (e) {
49113
+ return { success: false, error: e.message };
49114
+ }
49115
+ }
49116
+ case "delete_mesh": {
49117
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
49118
+ if (!meshId) return { success: false, error: "meshId required" };
49119
+ try {
49120
+ const { deleteMesh: deleteMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
49121
+ const deleted = deleteMesh3(meshId);
49122
+ return { success: true, deleted };
49123
+ } catch (e) {
49124
+ return { success: false, error: e.message };
49125
+ }
49126
+ }
49127
+ case "add_mesh_node": {
49128
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
49129
+ const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
49130
+ if (!meshId) return { success: false, error: "meshId required" };
49131
+ if (!workspace) return { success: false, error: "workspace required" };
49132
+ try {
49133
+ const { addNode: addNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
49134
+ const node = addNode3(meshId, { workspace });
49135
+ if (!node) return { success: false, error: "Mesh not found" };
49136
+ return { success: true, node };
49137
+ } catch (e) {
49138
+ return { success: false, error: e.message };
49139
+ }
49140
+ }
49141
+ case "remove_mesh_node": {
49142
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
49143
+ const nodeId = typeof args?.nodeId === "string" ? args.nodeId.trim() : "";
49144
+ if (!meshId || !nodeId) return { success: false, error: "meshId and nodeId required" };
49145
+ try {
49146
+ const { removeNode: removeNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
49147
+ const removed = removeNode3(meshId, nodeId);
49148
+ return { success: true, removed };
49149
+ } catch (e) {
49150
+ return { success: false, error: e.message };
49151
+ }
49152
+ }
49153
+ // ─── Mesh Coordinator Launch ───
49154
+ case "launch_mesh_coordinator": {
49155
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
49156
+ const cliType = typeof args?.cliType === "string" ? args.cliType.trim() : "claude-cli";
49157
+ if (!meshId) return { success: false, error: "meshId required" };
49158
+ try {
49159
+ const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
49160
+ const { buildCoordinatorSystemPrompt: buildCoordinatorSystemPrompt2 } = await Promise.resolve().then(() => (init_coordinator_prompt(), coordinator_prompt_exports));
49161
+ const mesh = getMesh3(meshId);
49162
+ if (!mesh) return { success: false, error: "Mesh not found" };
49163
+ if (mesh.nodes.length === 0) return { success: false, error: "No nodes in mesh" };
49164
+ const workspace = mesh.nodes[0].workspace;
49165
+ const providerMeta = this.deps.providerLoader.resolve?.(cliType) || this.deps.providerLoader.getMeta(cliType);
49166
+ const coordinatorSetup = resolveMeshCoordinatorSetup({
49167
+ provider: providerMeta,
49168
+ meshId,
49169
+ workspace
49170
+ });
49171
+ if (coordinatorSetup.kind === "unsupported") {
49172
+ return {
49173
+ success: false,
49174
+ code: "mesh_coordinator_unsupported",
49175
+ error: coordinatorSetup.reason,
49176
+ meshId,
49177
+ cliType,
49178
+ workspace
49179
+ };
49180
+ }
49181
+ if (coordinatorSetup.kind === "manual") {
49182
+ return {
49183
+ success: false,
49184
+ code: "mesh_coordinator_manual_mcp_setup_required",
49185
+ error: coordinatorSetup.instructions,
49186
+ meshId,
49187
+ cliType,
49188
+ workspace,
49189
+ meshCoordinatorSetup: coordinatorSetup
49190
+ };
49191
+ }
49192
+ if (coordinatorSetup.configFormat !== "claude_mcp_json") {
49193
+ return {
49194
+ success: false,
49195
+ code: "mesh_coordinator_unsupported",
49196
+ error: `Unsupported auto-import MCP config format: ${String(coordinatorSetup.configFormat)}`,
49197
+ meshId,
49198
+ cliType,
49199
+ workspace
49200
+ };
49201
+ }
49202
+ const { existsSync: existsSync21, readFileSync: readFileSync15, writeFileSync: writeFileSync12, copyFileSync: copyFileSync3 } = await import("fs");
49203
+ const mcpConfigPath = coordinatorSetup.configPath;
49204
+ const hadExistingMcpConfig = existsSync21(mcpConfigPath);
49205
+ let existingMcpConfig = {};
49206
+ if (hadExistingMcpConfig) {
49207
+ try {
49208
+ existingMcpConfig = JSON.parse(readFileSync15(mcpConfigPath, "utf-8"));
49209
+ copyFileSync3(mcpConfigPath, mcpConfigPath + ".backup");
49210
+ } catch {
49211
+ }
49212
+ }
49213
+ const mcpConfig = {
49214
+ ...existingMcpConfig,
49215
+ mcpServers: {
49216
+ ...existingMcpConfig.mcpServers || {},
49217
+ [coordinatorSetup.serverName]: {
49218
+ command: "adhdev-mcp",
49219
+ args: ["--repo-mesh", meshId]
49220
+ }
49221
+ }
49222
+ };
49223
+ writeFileSync12(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
49224
+ LOG2.info("MeshCoordinator", `Wrote ${mcpConfigPath} with ${coordinatorSetup.serverName} server`);
49225
+ let systemPrompt = "";
49226
+ try {
49227
+ systemPrompt = buildCoordinatorSystemPrompt2({ mesh });
49228
+ } catch {
49229
+ systemPrompt = `You are a Repo Mesh Coordinator for "${mesh.name}". Use the adhdev-mesh MCP tools (mesh_status, mesh_list_nodes, mesh_send_task, mesh_read_chat, mesh_launch_session, etc.) to orchestrate work across ${mesh.nodes.length} node(s).`;
49230
+ }
49231
+ const launchResult = await this.deps.cliManager.handleCliCommand("launch_cli", {
49232
+ cliType,
49233
+ dir: workspace,
49234
+ initialPrompt: systemPrompt
49235
+ });
49236
+ if (!launchResult?.success) {
49237
+ return { success: false, error: launchResult?.error || "Failed to launch CLI session" };
49238
+ }
49239
+ LOG2.info("MeshCoordinator", `Launched ${cliType} coordinator for mesh ${meshId} in ${workspace}`);
49240
+ return {
49241
+ success: true,
49242
+ meshId,
49243
+ cliType,
49244
+ workspace,
49245
+ sessionId: launchResult.sessionId || launchResult.id,
49246
+ mcpConfigWritten: true
49247
+ };
49248
+ } catch (e) {
49249
+ LOG2.error("MeshCoordinator", `Failed: ${e.message}`);
49250
+ return { success: false, error: e.message };
49251
+ }
49252
+ }
48917
49253
  default:
48918
49254
  break;
48919
49255
  }