@adhdev/daemon-core 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
@@ -30,6 +30,22 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  ));
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
32
 
33
+ // src/repo-mesh-types.ts
34
+ var DEFAULT_MESH_POLICY;
35
+ var init_repo_mesh_types = __esm({
36
+ "src/repo-mesh-types.ts"() {
37
+ "use strict";
38
+ DEFAULT_MESH_POLICY = {
39
+ requirePreTaskCheckpoint: false,
40
+ requirePostTaskCheckpoint: true,
41
+ requireApprovalForPush: true,
42
+ requireApprovalForDestructiveGit: true,
43
+ dirtyWorkspaceBehavior: "warn",
44
+ maxParallelTasks: 2
45
+ };
46
+ }
47
+ });
48
+
33
49
  // src/config/config.ts
34
50
  var config_exports = {};
35
51
  __export(config_exports, {
@@ -268,6 +284,276 @@ var init_config = __esm({
268
284
  }
269
285
  });
270
286
 
287
+ // src/config/mesh-config.ts
288
+ var mesh_config_exports = {};
289
+ __export(mesh_config_exports, {
290
+ addNode: () => addNode,
291
+ createMesh: () => createMesh,
292
+ deleteMesh: () => deleteMesh,
293
+ getMesh: () => getMesh,
294
+ getMeshByRepo: () => getMeshByRepo,
295
+ listMeshes: () => listMeshes,
296
+ normalizeRepoIdentity: () => normalizeRepoIdentity,
297
+ removeNode: () => removeNode,
298
+ updateMesh: () => updateMesh,
299
+ updateNode: () => updateNode
300
+ });
301
+ function getMeshConfigPath() {
302
+ return (0, import_path2.join)(getConfigDir(), "meshes.json");
303
+ }
304
+ function loadMeshConfig() {
305
+ const path26 = getMeshConfigPath();
306
+ if (!(0, import_fs2.existsSync)(path26)) return { meshes: [] };
307
+ try {
308
+ const raw = JSON.parse((0, import_fs2.readFileSync)(path26, "utf-8"));
309
+ if (!raw || !Array.isArray(raw.meshes)) return { meshes: [] };
310
+ return raw;
311
+ } catch {
312
+ return { meshes: [] };
313
+ }
314
+ }
315
+ function saveMeshConfig(config) {
316
+ const path26 = getMeshConfigPath();
317
+ (0, import_fs2.writeFileSync)(path26, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
318
+ }
319
+ function normalizeRepoIdentity(remoteUrl) {
320
+ let identity = remoteUrl.trim();
321
+ if (identity.startsWith("http://") || identity.startsWith("https://")) {
322
+ try {
323
+ const url = new URL(identity);
324
+ const path26 = url.pathname.replace(/^\//, "").replace(/\.git$/, "");
325
+ return `${url.hostname}/${path26}`;
326
+ } catch {
327
+ }
328
+ }
329
+ const sshMatch = identity.match(/^(?:ssh:\/\/)?[\w.-]+@([\w.-]+)[:/]([\w.\-/]+?)(?:\.git)?$/);
330
+ if (sshMatch) return `${sshMatch[1]}/${sshMatch[2]}`;
331
+ return identity;
332
+ }
333
+ function listMeshes() {
334
+ return loadMeshConfig().meshes;
335
+ }
336
+ function getMesh(meshId) {
337
+ return loadMeshConfig().meshes.find((m) => m.id === meshId);
338
+ }
339
+ function getMeshByRepo(repoIdentity) {
340
+ return loadMeshConfig().meshes.find((m) => m.repoIdentity === repoIdentity);
341
+ }
342
+ function createMesh(opts) {
343
+ const config = loadMeshConfig();
344
+ if (config.meshes.length >= 20) {
345
+ throw new Error("Maximum 20 meshes allowed");
346
+ }
347
+ const repoIdentity = opts.repoIdentity || (opts.repoRemoteUrl ? normalizeRepoIdentity(opts.repoRemoteUrl) : "");
348
+ if (!repoIdentity) throw new Error("Either repoRemoteUrl or repoIdentity is required");
349
+ const now = (/* @__PURE__ */ new Date()).toISOString();
350
+ const mesh = {
351
+ id: `mesh_${(0, import_crypto3.randomUUID)().replace(/-/g, "")}`,
352
+ name: opts.name.trim().slice(0, 100),
353
+ repoIdentity,
354
+ repoRemoteUrl: opts.repoRemoteUrl,
355
+ defaultBranch: opts.defaultBranch,
356
+ policy: { ...DEFAULT_MESH_POLICY, ...opts.policy },
357
+ coordinator: opts.coordinator || {},
358
+ nodes: [],
359
+ createdAt: now,
360
+ updatedAt: now
361
+ };
362
+ config.meshes.push(mesh);
363
+ saveMeshConfig(config);
364
+ return mesh;
365
+ }
366
+ function updateMesh(meshId, opts) {
367
+ const config = loadMeshConfig();
368
+ const mesh = config.meshes.find((m) => m.id === meshId);
369
+ if (!mesh) return void 0;
370
+ if (opts.name !== void 0) mesh.name = opts.name.trim().slice(0, 100);
371
+ if (opts.defaultBranch !== void 0) mesh.defaultBranch = opts.defaultBranch;
372
+ if (opts.policy) mesh.policy = { ...mesh.policy, ...opts.policy };
373
+ if (opts.coordinator) mesh.coordinator = opts.coordinator;
374
+ mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
375
+ saveMeshConfig(config);
376
+ return mesh;
377
+ }
378
+ function deleteMesh(meshId) {
379
+ const config = loadMeshConfig();
380
+ const idx = config.meshes.findIndex((m) => m.id === meshId);
381
+ if (idx === -1) return false;
382
+ config.meshes.splice(idx, 1);
383
+ saveMeshConfig(config);
384
+ return true;
385
+ }
386
+ function addNode(meshId, opts) {
387
+ const config = loadMeshConfig();
388
+ const mesh = config.meshes.find((m) => m.id === meshId);
389
+ if (!mesh) return void 0;
390
+ if (mesh.nodes.length >= 10) {
391
+ throw new Error("Maximum 10 nodes per mesh");
392
+ }
393
+ if (mesh.nodes.some((n) => n.workspace === opts.workspace)) {
394
+ throw new Error("This workspace is already in the mesh");
395
+ }
396
+ const node = {
397
+ id: `node_${(0, import_crypto3.randomUUID)().replace(/-/g, "")}`,
398
+ workspace: opts.workspace.trim(),
399
+ repoRoot: opts.repoRoot,
400
+ userOverrides: opts.userOverrides || {},
401
+ policy: opts.policy || {},
402
+ isLocalWorktree: opts.isLocalWorktree
403
+ };
404
+ mesh.nodes.push(node);
405
+ mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
406
+ saveMeshConfig(config);
407
+ return node;
408
+ }
409
+ function removeNode(meshId, nodeId) {
410
+ const config = loadMeshConfig();
411
+ const mesh = config.meshes.find((m) => m.id === meshId);
412
+ if (!mesh) return false;
413
+ const idx = mesh.nodes.findIndex((n) => n.id === nodeId);
414
+ if (idx === -1) return false;
415
+ mesh.nodes.splice(idx, 1);
416
+ mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
417
+ saveMeshConfig(config);
418
+ return true;
419
+ }
420
+ function updateNode(meshId, nodeId, opts) {
421
+ const config = loadMeshConfig();
422
+ const mesh = config.meshes.find((m) => m.id === meshId);
423
+ if (!mesh) return void 0;
424
+ const node = mesh.nodes.find((n) => n.id === nodeId);
425
+ if (!node) return void 0;
426
+ if (opts.userOverrides) node.userOverrides = { ...node.userOverrides, ...opts.userOverrides };
427
+ if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
428
+ mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
429
+ saveMeshConfig(config);
430
+ return node;
431
+ }
432
+ var import_fs2, import_path2, import_crypto3;
433
+ var init_mesh_config = __esm({
434
+ "src/config/mesh-config.ts"() {
435
+ "use strict";
436
+ import_fs2 = require("fs");
437
+ import_path2 = require("path");
438
+ import_crypto3 = require("crypto");
439
+ init_config();
440
+ init_repo_mesh_types();
441
+ }
442
+ });
443
+
444
+ // src/mesh/coordinator-prompt.ts
445
+ var coordinator_prompt_exports = {};
446
+ __export(coordinator_prompt_exports, {
447
+ buildCoordinatorSystemPrompt: () => buildCoordinatorSystemPrompt
448
+ });
449
+ function buildCoordinatorSystemPrompt(ctx) {
450
+ const { mesh, status, userInstruction } = ctx;
451
+ const sections = [];
452
+ 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.
453
+
454
+ Your mesh: **${mesh.name}**
455
+ Repository: \`${mesh.repoIdentity}\`${mesh.defaultBranch ? `
456
+ Default branch: \`${mesh.defaultBranch}\`` : ""}`);
457
+ if (status?.nodes?.length) {
458
+ sections.push(buildNodeStatusSection(status.nodes));
459
+ } else if (mesh.nodes.length) {
460
+ sections.push(buildNodeConfigSection(mesh));
461
+ } else {
462
+ sections.push("## Nodes\nNo nodes configured yet. Ask the user to add nodes with `adhdev mesh add-node`.");
463
+ }
464
+ sections.push(buildPolicySection(mesh.policy));
465
+ sections.push(TOOLS_SECTION);
466
+ sections.push(WORKFLOW_SECTION);
467
+ sections.push(RULES_SECTION);
468
+ if (userInstruction) {
469
+ sections.push(`## Additional Context
470
+ ${userInstruction}`);
471
+ }
472
+ if (mesh.coordinator.systemPromptSuffix) {
473
+ sections.push(mesh.coordinator.systemPromptSuffix);
474
+ }
475
+ return sections.join("\n\n");
476
+ }
477
+ function buildNodeStatusSection(nodes) {
478
+ const lines = ["## Current Node Status", ""];
479
+ for (const n of nodes) {
480
+ const healthIcon = n.health === "online" ? "\u{1F7E2}" : n.health === "dirty" ? "\u{1F7E1}" : n.health === "offline" ? "\u26AB" : "\u{1F534}";
481
+ const sessions = n.activeSessions.length > 0 ? `sessions: ${n.activeSessions.join(", ")}` : "no active sessions";
482
+ const branch = n.git?.branch ? `branch: \`${n.git.branch}\`` : "";
483
+ lines.push(`- ${healthIcon} **${n.machineLabel}** (${n.nodeId})`);
484
+ lines.push(` workspace: \`${n.workspace}\` | ${branch} | ${sessions}`);
485
+ if (n.error) lines.push(` \u26A0\uFE0F ${n.error}`);
486
+ }
487
+ return lines.join("\n");
488
+ }
489
+ function buildNodeConfigSection(mesh) {
490
+ const lines = ["## Configured Nodes", ""];
491
+ for (const n of mesh.nodes) {
492
+ const labels = [];
493
+ if (n.isLocalWorktree) labels.push("worktree");
494
+ if (n.policy.readOnly) labels.push("read-only");
495
+ const suffix = labels.length ? ` [${labels.join(", ")}]` : "";
496
+ lines.push(`- **${n.workspace}** (${n.id})${suffix}`);
497
+ }
498
+ lines.push("", "_Use `mesh_status` to probe live health before delegating work._");
499
+ return lines.join("\n");
500
+ }
501
+ function buildPolicySection(policy) {
502
+ const rules = [];
503
+ if (policy.requirePreTaskCheckpoint) rules.push("- Create a git checkpoint **before** starting each task");
504
+ if (policy.requirePostTaskCheckpoint) rules.push("- Create a git checkpoint **after** each task completes");
505
+ if (policy.requireApprovalForPush) rules.push("- **Ask for user approval** before pushing to remote");
506
+ if (policy.requireApprovalForDestructiveGit) rules.push("- **Ask for user approval** before destructive git operations (force push, reset, etc.)");
507
+ const dirtyBehavior = {
508
+ block: "- **Do not** send tasks to nodes with dirty workspaces",
509
+ warn: "- Warn the user if a node has uncommitted changes before sending a task",
510
+ checkpoint_then_continue: "- Auto-checkpoint dirty nodes before sending tasks"
511
+ }[policy.dirtyWorkspaceBehavior] || "";
512
+ if (dirtyBehavior) rules.push(dirtyBehavior);
513
+ rules.push(`- Maximum **${policy.maxParallelTasks}** tasks running in parallel`);
514
+ return `## Policy
515
+ ${rules.join("\n")}`;
516
+ }
517
+ var TOOLS_SECTION, WORKFLOW_SECTION, RULES_SECTION;
518
+ var init_coordinator_prompt = __esm({
519
+ "src/mesh/coordinator-prompt.ts"() {
520
+ "use strict";
521
+ TOOLS_SECTION = `## Available Tools
522
+
523
+ | Tool | Purpose |
524
+ |------|---------|
525
+ | \`mesh_status\` | Check all nodes' health, git state, and active sessions |
526
+ | \`mesh_list_nodes\` | List nodes with workspace paths |
527
+ | \`mesh_launch_session\` | Start a new agent session on a node |
528
+ | \`mesh_send_task\` | Send a task (natural language) to a running agent |
529
+ | \`mesh_read_chat\` | Read an agent's recent messages to check progress |
530
+ | \`mesh_git_status\` | Check git status on a specific node |
531
+ | \`mesh_checkpoint\` | Create a git checkpoint on a node |
532
+ | \`mesh_approve\` | Approve/reject a pending agent action |`;
533
+ WORKFLOW_SECTION = `## Orchestration Workflow
534
+
535
+ 1. **Assess** \u2014 Call \`mesh_status\` to see which nodes are healthy and available.
536
+ 2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist.
537
+ 3. **Delegate** \u2014 For each task:
538
+ a. Pick the best node (consider: health, dirty state, current workload).
539
+ b. If no session exists, call \`mesh_launch_session\` to start one.
540
+ c. Call \`mesh_send_task\` with a clear, self-contained natural-language instruction.
541
+ 4. **Monitor** \u2014 Periodically call \`mesh_read_chat\` to check progress. Handle approvals via \`mesh_approve\`.
542
+ 5. **Verify** \u2014 When a task reports completion, call \`mesh_git_status\` to verify changes were made.
543
+ 6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
544
+ 7. **Report** \u2014 Summarize what was done, what changed, and any issues.`;
545
+ RULES_SECTION = `## Rules
546
+
547
+ - **Be conversational.** Delegate work the way a tech lead would \u2014 clear, specific instructions in natural language.
548
+ - **Don't inspect code.** Trust the agent's output. Verify via git diff/status, not by reading source files.
549
+ - **Don't over-parallelize.** Start with 1-2 concurrent tasks. Scale up if they succeed.
550
+ - **Handle failures gracefully.** If a task fails, read the chat to understand why, then retry or reassign.
551
+ - **Keep the user informed.** Report progress after each delegation round.
552
+ - **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
553
+ - **Never fabricate tool results.** Always call the actual tool; never pretend you did.`;
554
+ }
555
+ });
556
+
271
557
  // src/logging/logger.ts
272
558
  function setLogLevel(level) {
273
559
  currentLevel = level;
@@ -3512,16 +3798,7 @@ __export(index_exports, {
3512
3798
  upsertSavedProviderSession: () => upsertSavedProviderSession
3513
3799
  });
3514
3800
  module.exports = __toCommonJS(index_exports);
3515
-
3516
- // src/repo-mesh-types.ts
3517
- var DEFAULT_MESH_POLICY = {
3518
- requirePreTaskCheckpoint: false,
3519
- requirePostTaskCheckpoint: true,
3520
- requireApprovalForPush: true,
3521
- requireApprovalForDestructiveGit: true,
3522
- dirtyWorkspaceBehavior: "warn",
3523
- maxParallelTasks: 2
3524
- };
3801
+ init_repo_mesh_types();
3525
3802
 
3526
3803
  // src/git/git-executor.ts
3527
3804
  var import_node_child_process = require("child_process");
@@ -5224,343 +5501,108 @@ function applySessionNotificationOverlay(session, overlay) {
5224
5501
  const dismissedNotificationId = typeof overlay.dismissedNotificationId === "string" ? overlay.dismissedNotificationId.trim() : "";
5225
5502
  const unreadNotificationId = typeof overlay.unreadNotificationId === "string" ? overlay.unreadNotificationId.trim() : "";
5226
5503
  if (unreadNotificationId && (currentNotificationId === unreadNotificationId || taskCompleteNotificationId === unreadNotificationId)) {
5227
- const forcedInboxBucket = session.inboxBucket === "needs_attention" || session.status === "waiting_approval" ? "needs_attention" : "task_complete";
5228
- return {
5229
- unread: true,
5230
- inboxBucket: forcedInboxBucket
5231
- };
5232
- }
5233
- if (!currentNotificationId || !dismissedNotificationId || currentNotificationId !== dismissedNotificationId) {
5234
- return {
5235
- unread: !!session.unread,
5236
- inboxBucket: session.inboxBucket || "idle"
5237
- };
5238
- }
5239
- return {
5240
- unread: false,
5241
- inboxBucket: "idle"
5242
- };
5243
- }
5244
- function markSessionSeen(state, sessionId, seenAt = Date.now(), completionMarker, providerSessionId) {
5245
- const prev = state.sessionReads || {};
5246
- const prevMarkers = state.sessionReadMarkers || {};
5247
- const nextMarker = typeof completionMarker === "string" ? completionMarker : "";
5248
- const readKeys = Array.from(new Set([
5249
- sessionId,
5250
- buildSessionReadStateKey(sessionId, providerSessionId)
5251
- ].filter(Boolean)));
5252
- const nextSessionReads = { ...prev };
5253
- const nextSessionReadMarkers = { ...prevMarkers };
5254
- const nextSessionNotificationDismissals = { ...state.sessionNotificationDismissals || {} };
5255
- const nextSessionNotificationUnreadOverrides = { ...state.sessionNotificationUnreadOverrides || {} };
5256
- for (const key of readKeys) {
5257
- nextSessionReads[key] = Math.max(prev[key] || 0, seenAt);
5258
- if (nextMarker) nextSessionReadMarkers[key] = nextMarker;
5259
- delete nextSessionNotificationDismissals[key];
5260
- delete nextSessionNotificationUnreadOverrides[key];
5261
- }
5262
- return {
5263
- ...state,
5264
- sessionReads: nextSessionReads,
5265
- sessionReadMarkers: nextMarker ? nextSessionReadMarkers : prevMarkers,
5266
- sessionNotificationDismissals: nextSessionNotificationDismissals,
5267
- sessionNotificationUnreadOverrides: nextSessionNotificationUnreadOverrides
5268
- };
5269
- }
5270
-
5271
- // src/config/saved-sessions.ts
5272
- var path6 = __toESM(require("path"));
5273
- var MAX_SAVED_SESSIONS = 500;
5274
- function normalizeWorkspace2(workspace) {
5275
- if (!workspace) return "";
5276
- try {
5277
- return path6.resolve(expandPath(workspace));
5278
- } catch {
5279
- return path6.resolve(workspace);
5280
- }
5281
- }
5282
- function buildSavedProviderSessionKey(providerSessionId) {
5283
- return `saved:${providerSessionId.trim()}`;
5284
- }
5285
- function upsertSavedProviderSession(state, entry) {
5286
- const providerSessionId = typeof entry.providerSessionId === "string" ? entry.providerSessionId.trim() : "";
5287
- if (!providerSessionId) return state;
5288
- const id = buildSavedProviderSessionKey(providerSessionId);
5289
- const existing = (state.savedProviderSessions || []).find((item) => item.id === id);
5290
- const nextEntry = {
5291
- id,
5292
- kind: entry.kind,
5293
- providerType: entry.providerType,
5294
- providerName: entry.providerName,
5295
- providerSessionId,
5296
- workspace: entry.workspace ? normalizeWorkspace2(entry.workspace) : void 0,
5297
- summaryMetadata: normalizePersistedSummaryMetadata({
5298
- summaryMetadata: entry.summaryMetadata
5299
- }),
5300
- title: entry.title,
5301
- createdAt: existing?.createdAt || entry.createdAt || Date.now(),
5302
- lastUsedAt: entry.lastUsedAt || Date.now()
5303
- };
5304
- const filtered = (state.savedProviderSessions || []).filter((item) => item.id !== id);
5305
- return {
5306
- ...state,
5307
- savedProviderSessions: [nextEntry, ...filtered].slice(0, MAX_SAVED_SESSIONS)
5308
- };
5309
- }
5310
- function getSavedProviderSessions(state, filters) {
5311
- return [...state.savedProviderSessions || []].filter((entry) => {
5312
- if (filters?.providerType && entry.providerType !== filters.providerType) return false;
5313
- if (filters?.kind && entry.kind !== filters.kind) return false;
5314
- return true;
5315
- }).map((entry) => ({
5316
- ...entry,
5317
- summaryMetadata: normalizePersistedSummaryMetadata({
5318
- summaryMetadata: entry.summaryMetadata
5319
- })
5320
- })).sort((a, b) => b.lastUsedAt - a.lastUsedAt);
5321
- }
5322
-
5323
- // src/config/mesh-config.ts
5324
- var import_fs2 = require("fs");
5325
- var import_path2 = require("path");
5326
- var import_crypto3 = require("crypto");
5327
- init_config();
5328
- function getMeshConfigPath() {
5329
- return (0, import_path2.join)(getConfigDir(), "meshes.json");
5330
- }
5331
- function loadMeshConfig() {
5332
- const path26 = getMeshConfigPath();
5333
- if (!(0, import_fs2.existsSync)(path26)) return { meshes: [] };
5334
- try {
5335
- const raw = JSON.parse((0, import_fs2.readFileSync)(path26, "utf-8"));
5336
- if (!raw || !Array.isArray(raw.meshes)) return { meshes: [] };
5337
- return raw;
5338
- } catch {
5339
- return { meshes: [] };
5340
- }
5341
- }
5342
- function saveMeshConfig(config) {
5343
- const path26 = getMeshConfigPath();
5344
- (0, import_fs2.writeFileSync)(path26, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
5345
- }
5346
- function normalizeRepoIdentity(remoteUrl) {
5347
- let identity = remoteUrl.trim();
5348
- if (identity.startsWith("http://") || identity.startsWith("https://")) {
5349
- try {
5350
- const url = new URL(identity);
5351
- const path26 = url.pathname.replace(/^\//, "").replace(/\.git$/, "");
5352
- return `${url.hostname}/${path26}`;
5353
- } catch {
5354
- }
5355
- }
5356
- const sshMatch = identity.match(/^(?:ssh:\/\/)?[\w.-]+@([\w.-]+)[:/]([\w.\-/]+?)(?:\.git)?$/);
5357
- if (sshMatch) return `${sshMatch[1]}/${sshMatch[2]}`;
5358
- return identity;
5359
- }
5360
- function listMeshes() {
5361
- return loadMeshConfig().meshes;
5362
- }
5363
- function getMesh(meshId) {
5364
- return loadMeshConfig().meshes.find((m) => m.id === meshId);
5365
- }
5366
- function getMeshByRepo(repoIdentity) {
5367
- return loadMeshConfig().meshes.find((m) => m.repoIdentity === repoIdentity);
5368
- }
5369
- function createMesh(opts) {
5370
- const config = loadMeshConfig();
5371
- if (config.meshes.length >= 20) {
5372
- throw new Error("Maximum 20 meshes allowed");
5373
- }
5374
- const repoIdentity = opts.repoIdentity || (opts.repoRemoteUrl ? normalizeRepoIdentity(opts.repoRemoteUrl) : "");
5375
- if (!repoIdentity) throw new Error("Either repoRemoteUrl or repoIdentity is required");
5376
- const now = (/* @__PURE__ */ new Date()).toISOString();
5377
- const mesh = {
5378
- id: `mesh_${(0, import_crypto3.randomUUID)().replace(/-/g, "")}`,
5379
- name: opts.name.trim().slice(0, 100),
5380
- repoIdentity,
5381
- repoRemoteUrl: opts.repoRemoteUrl,
5382
- defaultBranch: opts.defaultBranch,
5383
- policy: { ...DEFAULT_MESH_POLICY, ...opts.policy },
5384
- coordinator: opts.coordinator || {},
5385
- nodes: [],
5386
- createdAt: now,
5387
- updatedAt: now
5388
- };
5389
- config.meshes.push(mesh);
5390
- saveMeshConfig(config);
5391
- return mesh;
5392
- }
5393
- function updateMesh(meshId, opts) {
5394
- const config = loadMeshConfig();
5395
- const mesh = config.meshes.find((m) => m.id === meshId);
5396
- if (!mesh) return void 0;
5397
- if (opts.name !== void 0) mesh.name = opts.name.trim().slice(0, 100);
5398
- if (opts.defaultBranch !== void 0) mesh.defaultBranch = opts.defaultBranch;
5399
- if (opts.policy) mesh.policy = { ...mesh.policy, ...opts.policy };
5400
- if (opts.coordinator) mesh.coordinator = opts.coordinator;
5401
- mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5402
- saveMeshConfig(config);
5403
- return mesh;
5404
- }
5405
- function deleteMesh(meshId) {
5406
- const config = loadMeshConfig();
5407
- const idx = config.meshes.findIndex((m) => m.id === meshId);
5408
- if (idx === -1) return false;
5409
- config.meshes.splice(idx, 1);
5410
- saveMeshConfig(config);
5411
- return true;
5412
- }
5413
- function addNode(meshId, opts) {
5414
- const config = loadMeshConfig();
5415
- const mesh = config.meshes.find((m) => m.id === meshId);
5416
- if (!mesh) return void 0;
5417
- if (mesh.nodes.length >= 10) {
5418
- throw new Error("Maximum 10 nodes per mesh");
5419
- }
5420
- if (mesh.nodes.some((n) => n.workspace === opts.workspace)) {
5421
- throw new Error("This workspace is already in the mesh");
5422
- }
5423
- const node = {
5424
- id: `node_${(0, import_crypto3.randomUUID)().replace(/-/g, "")}`,
5425
- workspace: opts.workspace.trim(),
5426
- repoRoot: opts.repoRoot,
5427
- userOverrides: opts.userOverrides || {},
5428
- policy: opts.policy || {},
5429
- isLocalWorktree: opts.isLocalWorktree
5430
- };
5431
- mesh.nodes.push(node);
5432
- mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5433
- saveMeshConfig(config);
5434
- return node;
5435
- }
5436
- function removeNode(meshId, nodeId) {
5437
- const config = loadMeshConfig();
5438
- const mesh = config.meshes.find((m) => m.id === meshId);
5439
- if (!mesh) return false;
5440
- const idx = mesh.nodes.findIndex((n) => n.id === nodeId);
5441
- if (idx === -1) return false;
5442
- mesh.nodes.splice(idx, 1);
5443
- mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5444
- saveMeshConfig(config);
5445
- return true;
5446
- }
5447
- function updateNode(meshId, nodeId, opts) {
5448
- const config = loadMeshConfig();
5449
- const mesh = config.meshes.find((m) => m.id === meshId);
5450
- if (!mesh) return void 0;
5451
- const node = mesh.nodes.find((n) => n.id === nodeId);
5452
- if (!node) return void 0;
5453
- if (opts.userOverrides) node.userOverrides = { ...node.userOverrides, ...opts.userOverrides };
5454
- if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
5455
- mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5456
- saveMeshConfig(config);
5457
- return node;
5458
- }
5459
-
5460
- // src/mesh/coordinator-prompt.ts
5461
- function buildCoordinatorSystemPrompt(ctx) {
5462
- const { mesh, status, userInstruction } = ctx;
5463
- const sections = [];
5464
- 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.
5465
-
5466
- Your mesh: **${mesh.name}**
5467
- Repository: \`${mesh.repoIdentity}\`${mesh.defaultBranch ? `
5468
- Default branch: \`${mesh.defaultBranch}\`` : ""}`);
5469
- if (status?.nodes?.length) {
5470
- sections.push(buildNodeStatusSection(status.nodes));
5471
- } else if (mesh.nodes.length) {
5472
- sections.push(buildNodeConfigSection(mesh));
5473
- } else {
5474
- sections.push("## Nodes\nNo nodes configured yet. Ask the user to add nodes with `adhdev mesh add-node`.");
5475
- }
5476
- sections.push(buildPolicySection(mesh.policy));
5477
- sections.push(TOOLS_SECTION);
5478
- sections.push(WORKFLOW_SECTION);
5479
- sections.push(RULES_SECTION);
5480
- if (userInstruction) {
5481
- sections.push(`## Additional Context
5482
- ${userInstruction}`);
5504
+ const forcedInboxBucket = session.inboxBucket === "needs_attention" || session.status === "waiting_approval" ? "needs_attention" : "task_complete";
5505
+ return {
5506
+ unread: true,
5507
+ inboxBucket: forcedInboxBucket
5508
+ };
5483
5509
  }
5484
- if (mesh.coordinator.systemPromptSuffix) {
5485
- sections.push(mesh.coordinator.systemPromptSuffix);
5510
+ if (!currentNotificationId || !dismissedNotificationId || currentNotificationId !== dismissedNotificationId) {
5511
+ return {
5512
+ unread: !!session.unread,
5513
+ inboxBucket: session.inboxBucket || "idle"
5514
+ };
5486
5515
  }
5487
- return sections.join("\n\n");
5516
+ return {
5517
+ unread: false,
5518
+ inboxBucket: "idle"
5519
+ };
5488
5520
  }
5489
- function buildNodeStatusSection(nodes) {
5490
- const lines = ["## Current Node Status", ""];
5491
- for (const n of nodes) {
5492
- const healthIcon = n.health === "online" ? "\u{1F7E2}" : n.health === "dirty" ? "\u{1F7E1}" : n.health === "offline" ? "\u26AB" : "\u{1F534}";
5493
- const sessions = n.activeSessions.length > 0 ? `sessions: ${n.activeSessions.join(", ")}` : "no active sessions";
5494
- const branch = n.git?.branch ? `branch: \`${n.git.branch}\`` : "";
5495
- lines.push(`- ${healthIcon} **${n.machineLabel}** (${n.nodeId})`);
5496
- lines.push(` workspace: \`${n.workspace}\` | ${branch} | ${sessions}`);
5497
- if (n.error) lines.push(` \u26A0\uFE0F ${n.error}`);
5521
+ function markSessionSeen(state, sessionId, seenAt = Date.now(), completionMarker, providerSessionId) {
5522
+ const prev = state.sessionReads || {};
5523
+ const prevMarkers = state.sessionReadMarkers || {};
5524
+ const nextMarker = typeof completionMarker === "string" ? completionMarker : "";
5525
+ const readKeys = Array.from(new Set([
5526
+ sessionId,
5527
+ buildSessionReadStateKey(sessionId, providerSessionId)
5528
+ ].filter(Boolean)));
5529
+ const nextSessionReads = { ...prev };
5530
+ const nextSessionReadMarkers = { ...prevMarkers };
5531
+ const nextSessionNotificationDismissals = { ...state.sessionNotificationDismissals || {} };
5532
+ const nextSessionNotificationUnreadOverrides = { ...state.sessionNotificationUnreadOverrides || {} };
5533
+ for (const key of readKeys) {
5534
+ nextSessionReads[key] = Math.max(prev[key] || 0, seenAt);
5535
+ if (nextMarker) nextSessionReadMarkers[key] = nextMarker;
5536
+ delete nextSessionNotificationDismissals[key];
5537
+ delete nextSessionNotificationUnreadOverrides[key];
5498
5538
  }
5499
- return lines.join("\n");
5539
+ return {
5540
+ ...state,
5541
+ sessionReads: nextSessionReads,
5542
+ sessionReadMarkers: nextMarker ? nextSessionReadMarkers : prevMarkers,
5543
+ sessionNotificationDismissals: nextSessionNotificationDismissals,
5544
+ sessionNotificationUnreadOverrides: nextSessionNotificationUnreadOverrides
5545
+ };
5500
5546
  }
5501
- function buildNodeConfigSection(mesh) {
5502
- const lines = ["## Configured Nodes", ""];
5503
- for (const n of mesh.nodes) {
5504
- const labels = [];
5505
- if (n.isLocalWorktree) labels.push("worktree");
5506
- if (n.policy.readOnly) labels.push("read-only");
5507
- const suffix = labels.length ? ` [${labels.join(", ")}]` : "";
5508
- lines.push(`- **${n.workspace}** (${n.id})${suffix}`);
5547
+
5548
+ // src/config/saved-sessions.ts
5549
+ var path6 = __toESM(require("path"));
5550
+ var MAX_SAVED_SESSIONS = 500;
5551
+ function normalizeWorkspace2(workspace) {
5552
+ if (!workspace) return "";
5553
+ try {
5554
+ return path6.resolve(expandPath(workspace));
5555
+ } catch {
5556
+ return path6.resolve(workspace);
5509
5557
  }
5510
- lines.push("", "_Use `mesh_status` to probe live health before delegating work._");
5511
- return lines.join("\n");
5512
5558
  }
5513
- function buildPolicySection(policy) {
5514
- const rules = [];
5515
- if (policy.requirePreTaskCheckpoint) rules.push("- Create a git checkpoint **before** starting each task");
5516
- if (policy.requirePostTaskCheckpoint) rules.push("- Create a git checkpoint **after** each task completes");
5517
- if (policy.requireApprovalForPush) rules.push("- **Ask for user approval** before pushing to remote");
5518
- if (policy.requireApprovalForDestructiveGit) rules.push("- **Ask for user approval** before destructive git operations (force push, reset, etc.)");
5519
- const dirtyBehavior = {
5520
- block: "- **Do not** send tasks to nodes with dirty workspaces",
5521
- warn: "- Warn the user if a node has uncommitted changes before sending a task",
5522
- checkpoint_then_continue: "- Auto-checkpoint dirty nodes before sending tasks"
5523
- }[policy.dirtyWorkspaceBehavior] || "";
5524
- if (dirtyBehavior) rules.push(dirtyBehavior);
5525
- rules.push(`- Maximum **${policy.maxParallelTasks}** tasks running in parallel`);
5526
- return `## Policy
5527
- ${rules.join("\n")}`;
5559
+ function buildSavedProviderSessionKey(providerSessionId) {
5560
+ return `saved:${providerSessionId.trim()}`;
5561
+ }
5562
+ function upsertSavedProviderSession(state, entry) {
5563
+ const providerSessionId = typeof entry.providerSessionId === "string" ? entry.providerSessionId.trim() : "";
5564
+ if (!providerSessionId) return state;
5565
+ const id = buildSavedProviderSessionKey(providerSessionId);
5566
+ const existing = (state.savedProviderSessions || []).find((item) => item.id === id);
5567
+ const nextEntry = {
5568
+ id,
5569
+ kind: entry.kind,
5570
+ providerType: entry.providerType,
5571
+ providerName: entry.providerName,
5572
+ providerSessionId,
5573
+ workspace: entry.workspace ? normalizeWorkspace2(entry.workspace) : void 0,
5574
+ summaryMetadata: normalizePersistedSummaryMetadata({
5575
+ summaryMetadata: entry.summaryMetadata
5576
+ }),
5577
+ title: entry.title,
5578
+ createdAt: existing?.createdAt || entry.createdAt || Date.now(),
5579
+ lastUsedAt: entry.lastUsedAt || Date.now()
5580
+ };
5581
+ const filtered = (state.savedProviderSessions || []).filter((item) => item.id !== id);
5582
+ return {
5583
+ ...state,
5584
+ savedProviderSessions: [nextEntry, ...filtered].slice(0, MAX_SAVED_SESSIONS)
5585
+ };
5586
+ }
5587
+ function getSavedProviderSessions(state, filters) {
5588
+ return [...state.savedProviderSessions || []].filter((entry) => {
5589
+ if (filters?.providerType && entry.providerType !== filters.providerType) return false;
5590
+ if (filters?.kind && entry.kind !== filters.kind) return false;
5591
+ return true;
5592
+ }).map((entry) => ({
5593
+ ...entry,
5594
+ summaryMetadata: normalizePersistedSummaryMetadata({
5595
+ summaryMetadata: entry.summaryMetadata
5596
+ })
5597
+ })).sort((a, b) => b.lastUsedAt - a.lastUsedAt);
5528
5598
  }
5529
- var TOOLS_SECTION = `## Available Tools
5530
-
5531
- | Tool | Purpose |
5532
- |------|---------|
5533
- | \`mesh_status\` | Check all nodes' health, git state, and active sessions |
5534
- | \`mesh_list_nodes\` | List nodes with workspace paths |
5535
- | \`mesh_launch_session\` | Start a new agent session on a node |
5536
- | \`mesh_send_task\` | Send a task (natural language) to a running agent |
5537
- | \`mesh_read_chat\` | Read an agent's recent messages to check progress |
5538
- | \`mesh_git_status\` | Check git status on a specific node |
5539
- | \`mesh_checkpoint\` | Create a git checkpoint on a node |
5540
- | \`mesh_approve\` | Approve/reject a pending agent action |`;
5541
- var WORKFLOW_SECTION = `## Orchestration Workflow
5542
-
5543
- 1. **Assess** \u2014 Call \`mesh_status\` to see which nodes are healthy and available.
5544
- 2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist.
5545
- 3. **Delegate** \u2014 For each task:
5546
- a. Pick the best node (consider: health, dirty state, current workload).
5547
- b. If no session exists, call \`mesh_launch_session\` to start one.
5548
- c. Call \`mesh_send_task\` with a clear, self-contained natural-language instruction.
5549
- 4. **Monitor** \u2014 Periodically call \`mesh_read_chat\` to check progress. Handle approvals via \`mesh_approve\`.
5550
- 5. **Verify** \u2014 When a task reports completion, call \`mesh_git_status\` to verify changes were made.
5551
- 6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
5552
- 7. **Report** \u2014 Summarize what was done, what changed, and any issues.`;
5553
- var RULES_SECTION = `## Rules
5554
5599
 
5555
- - **Be conversational.** Delegate work the way a tech lead would \u2014 clear, specific instructions in natural language.
5556
- - **Don't inspect code.** Trust the agent's output. Verify via git diff/status, not by reading source files.
5557
- - **Don't over-parallelize.** Start with 1-2 concurrent tasks. Scale up if they succeed.
5558
- - **Handle failures gracefully.** If a task fails, read the chat to understand why, then retry or reassign.
5559
- - **Keep the user informed.** Report progress after each delegation round.
5560
- - **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
5561
- - **Never fabricate tool results.** Always call the actual tool; never pretend you did.`;
5600
+ // src/index.ts
5601
+ init_mesh_config();
5602
+ init_coordinator_prompt();
5562
5603
 
5563
5604
  // src/mesh/mesh-sync.ts
5605
+ init_mesh_config();
5564
5606
  async function syncMeshes(transport) {
5565
5607
  const result = { pushed: 0, pulled: 0, deleted: 0, errors: [] };
5566
5608
  let remoteMeshes;
@@ -17489,6 +17531,8 @@ var KNOWN_PROVIDER_FIELDS = /* @__PURE__ */ new Set([
17489
17531
  "type",
17490
17532
  "name",
17491
17533
  "category",
17534
+ "transcriptAuthority",
17535
+ "transcriptContext",
17492
17536
  "aliases",
17493
17537
  "cdpPorts",
17494
17538
  "targetFilter",
@@ -17533,6 +17577,7 @@ var KNOWN_PROVIDER_FIELDS = /* @__PURE__ */ new Set([
17533
17577
  "staticConfigOptions",
17534
17578
  "spawnArgBuilder",
17535
17579
  "auth",
17580
+ "meshCoordinator",
17536
17581
  "contractVersion",
17537
17582
  "capabilities",
17538
17583
  "providerVersion",
@@ -17590,6 +17635,7 @@ function validateProviderDefinition(raw) {
17590
17635
  }
17591
17636
  validateCapabilities(provider, controls, errors);
17592
17637
  validateCanonicalHistory(provider.canonicalHistory, errors);
17638
+ validateMeshCoordinator(provider.meshCoordinator, errors);
17593
17639
  for (const control of controls) {
17594
17640
  validateControl(control, errors);
17595
17641
  }
@@ -17680,6 +17726,60 @@ function validateCanonicalHistory(raw, errors) {
17680
17726
  }
17681
17727
  }
17682
17728
  }
17729
+ function validateMeshCoordinator(raw, errors) {
17730
+ if (raw === void 0) return;
17731
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
17732
+ errors.push("meshCoordinator must be an object");
17733
+ return;
17734
+ }
17735
+ const meshCoordinator = raw;
17736
+ if (typeof meshCoordinator.supported !== "boolean") {
17737
+ errors.push("meshCoordinator.supported must be boolean");
17738
+ }
17739
+ if (meshCoordinator.reason !== void 0 && (typeof meshCoordinator.reason !== "string" || !meshCoordinator.reason.trim())) {
17740
+ errors.push("meshCoordinator.reason must be a non-empty string when provided");
17741
+ }
17742
+ const mcpConfig = meshCoordinator.mcpConfig;
17743
+ if (mcpConfig === void 0) return;
17744
+ if (!mcpConfig || typeof mcpConfig !== "object" || Array.isArray(mcpConfig)) {
17745
+ errors.push("meshCoordinator.mcpConfig must be an object");
17746
+ return;
17747
+ }
17748
+ const config = mcpConfig;
17749
+ const mode = config.mode;
17750
+ if (!["auto_import", "manual", "none"].includes(String(mode))) {
17751
+ errors.push("meshCoordinator.mcpConfig.mode must be one of: auto_import, manual, none");
17752
+ }
17753
+ const format = config.format;
17754
+ if (format !== void 0 && !["claude_mcp_json", "hermes_config_yaml"].includes(String(format))) {
17755
+ errors.push("meshCoordinator.mcpConfig.format must be one of: claude_mcp_json, hermes_config_yaml");
17756
+ }
17757
+ for (const key of ["path", "serverName", "configPathCommand", "instructions", "template"]) {
17758
+ const value = config[key];
17759
+ if (value !== void 0 && (typeof value !== "string" || !value.trim())) {
17760
+ errors.push(`meshCoordinator.mcpConfig.${key} must be a non-empty string when provided`);
17761
+ }
17762
+ }
17763
+ if (config.requiresRestart !== void 0 && typeof config.requiresRestart !== "boolean") {
17764
+ errors.push("meshCoordinator.mcpConfig.requiresRestart must be boolean when provided");
17765
+ }
17766
+ if (mode === "auto_import") {
17767
+ if (format === void 0) {
17768
+ errors.push("meshCoordinator.mcpConfig.format is required for auto_import MCP setup");
17769
+ }
17770
+ if (typeof config.path !== "string" || !config.path.trim()) {
17771
+ errors.push("meshCoordinator.mcpConfig.path is required for auto_import MCP setup");
17772
+ }
17773
+ }
17774
+ if (mode === "manual") {
17775
+ if (typeof config.instructions !== "string" || !config.instructions.trim()) {
17776
+ errors.push("meshCoordinator.mcpConfig.instructions is required for manual MCP setup");
17777
+ }
17778
+ if (typeof config.template !== "string" || !config.template.trim()) {
17779
+ errors.push("meshCoordinator.mcpConfig.template is required for manual MCP setup");
17780
+ }
17781
+ }
17782
+ }
17683
17783
  function validateControl(control, errors) {
17684
17784
  if (!control || typeof control !== "object") {
17685
17785
  errors.push("controls: each control must be an object");
@@ -19835,6 +19935,69 @@ cleanOldFiles();
19835
19935
  // src/commands/router.ts
19836
19936
  init_logger();
19837
19937
 
19938
+ // src/commands/mesh-coordinator.ts
19939
+ var import_path4 = require("path");
19940
+ var DEFAULT_SERVER_NAME = "adhdev-mesh";
19941
+ var DEFAULT_ADHDEV_MCP_COMMAND = "adhdev-mcp";
19942
+ function resolveMeshCoordinatorSetup(options) {
19943
+ const { provider, meshId, workspace } = options;
19944
+ const config = provider?.meshCoordinator;
19945
+ if (!config?.supported) {
19946
+ return {
19947
+ kind: "unsupported",
19948
+ reason: config?.reason || "Provider does not declare Repo Mesh coordinator support"
19949
+ };
19950
+ }
19951
+ const mcpConfig = config.mcpConfig;
19952
+ if (!mcpConfig || mcpConfig.mode === "none") {
19953
+ return {
19954
+ kind: "unsupported",
19955
+ reason: config.reason || "Provider does not declare a usable Repo Mesh MCP configuration mode"
19956
+ };
19957
+ }
19958
+ const serverName = mcpConfig.serverName?.trim() || DEFAULT_SERVER_NAME;
19959
+ if (mcpConfig.mode === "auto_import") {
19960
+ const path26 = mcpConfig.path?.trim();
19961
+ if (!path26) {
19962
+ return { kind: "unsupported", reason: "Provider auto-import MCP config is missing a config path" };
19963
+ }
19964
+ return {
19965
+ kind: "auto_import",
19966
+ serverName,
19967
+ configPath: (0, import_path4.join)(workspace, path26),
19968
+ configFormat: mcpConfig.format
19969
+ };
19970
+ }
19971
+ if (mcpConfig.mode === "manual") {
19972
+ const instructions = mcpConfig.instructions?.trim();
19973
+ const template = mcpConfig.template;
19974
+ if (!instructions || !template?.trim()) {
19975
+ return { kind: "unsupported", reason: "Provider manual MCP setup is missing instructions or template" };
19976
+ }
19977
+ return {
19978
+ kind: "manual",
19979
+ serverName,
19980
+ configFormat: mcpConfig.format,
19981
+ configPathCommand: mcpConfig.configPathCommand,
19982
+ requiresRestart: mcpConfig.requiresRestart === true,
19983
+ instructions,
19984
+ template: renderMeshCoordinatorTemplate(template, {
19985
+ meshId,
19986
+ workspace,
19987
+ serverName,
19988
+ adhdevMcpCommand: options.adhdevMcpCommand || DEFAULT_ADHDEV_MCP_COMMAND
19989
+ })
19990
+ };
19991
+ }
19992
+ return {
19993
+ kind: "unsupported",
19994
+ reason: `Unsupported Repo Mesh MCP configuration mode: ${String(mcpConfig.mode)}`
19995
+ };
19996
+ }
19997
+ function renderMeshCoordinatorTemplate(template, values) {
19998
+ return template.replace(/\{\{\s*(meshId|workspace|serverName|adhdevMcpCommand)\s*\}\}/g, (_, key) => values[key] || "");
19999
+ }
20000
+
19838
20001
  // src/status/snapshot.ts
19839
20002
  var os17 = __toESM(require("os"));
19840
20003
  init_config();
@@ -19886,7 +20049,8 @@ function buildAvailableProviders(providerLoader) {
19886
20049
  ...provider.enabled !== void 0 ? { enabled: provider.enabled } : {},
19887
20050
  ...provider.machineStatus !== void 0 ? { machineStatus: provider.machineStatus } : {},
19888
20051
  ...provider.lastDetection !== void 0 ? { lastDetection: provider.lastDetection } : {},
19889
- ...provider.lastVerification !== void 0 ? { lastVerification: provider.lastVerification } : {}
20052
+ ...provider.lastVerification !== void 0 ? { lastVerification: provider.lastVerification } : {},
20053
+ ...provider.meshCoordinator !== void 0 ? { meshCoordinator: provider.meshCoordinator } : {}
19890
20054
  }));
19891
20055
  }
19892
20056
  function buildMachineInfo(profile = "full") {
@@ -21211,6 +21375,178 @@ var DaemonCommandRouter = class {
21211
21375
  updateConfig({ machineNickname: nickname || null });
21212
21376
  return { success: true };
21213
21377
  }
21378
+ // ─── Mesh CRUD (local meshes.json) ───
21379
+ case "list_meshes": {
21380
+ try {
21381
+ const { listMeshes: listMeshes2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
21382
+ return { success: true, meshes: listMeshes2() };
21383
+ } catch (e) {
21384
+ return { success: false, error: e.message };
21385
+ }
21386
+ }
21387
+ case "get_mesh": {
21388
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
21389
+ if (!meshId) return { success: false, error: "meshId required" };
21390
+ try {
21391
+ const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
21392
+ const mesh = getMesh3(meshId);
21393
+ if (!mesh) return { success: false, error: "Mesh not found" };
21394
+ return { success: true, mesh };
21395
+ } catch (e) {
21396
+ return { success: false, error: e.message };
21397
+ }
21398
+ }
21399
+ case "create_mesh": {
21400
+ const name = typeof args?.name === "string" ? args.name.trim() : "";
21401
+ const repoIdentity = typeof args?.repoIdentity === "string" ? args.repoIdentity.trim() : "";
21402
+ const repoRemoteUrl = typeof args?.repoRemoteUrl === "string" ? args.repoRemoteUrl.trim() : void 0;
21403
+ const defaultBranch = typeof args?.defaultBranch === "string" ? args.defaultBranch.trim() : void 0;
21404
+ if (!name) return { success: false, error: "name required" };
21405
+ try {
21406
+ const { createMesh: createMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
21407
+ const mesh = createMesh2({ name, repoIdentity, repoRemoteUrl, defaultBranch });
21408
+ return { success: true, mesh };
21409
+ } catch (e) {
21410
+ return { success: false, error: e.message };
21411
+ }
21412
+ }
21413
+ case "delete_mesh": {
21414
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
21415
+ if (!meshId) return { success: false, error: "meshId required" };
21416
+ try {
21417
+ const { deleteMesh: deleteMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
21418
+ const deleted = deleteMesh3(meshId);
21419
+ return { success: true, deleted };
21420
+ } catch (e) {
21421
+ return { success: false, error: e.message };
21422
+ }
21423
+ }
21424
+ case "add_mesh_node": {
21425
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
21426
+ const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
21427
+ if (!meshId) return { success: false, error: "meshId required" };
21428
+ if (!workspace) return { success: false, error: "workspace required" };
21429
+ try {
21430
+ const { addNode: addNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
21431
+ const node = addNode3(meshId, { workspace });
21432
+ if (!node) return { success: false, error: "Mesh not found" };
21433
+ return { success: true, node };
21434
+ } catch (e) {
21435
+ return { success: false, error: e.message };
21436
+ }
21437
+ }
21438
+ case "remove_mesh_node": {
21439
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
21440
+ const nodeId = typeof args?.nodeId === "string" ? args.nodeId.trim() : "";
21441
+ if (!meshId || !nodeId) return { success: false, error: "meshId and nodeId required" };
21442
+ try {
21443
+ const { removeNode: removeNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
21444
+ const removed = removeNode3(meshId, nodeId);
21445
+ return { success: true, removed };
21446
+ } catch (e) {
21447
+ return { success: false, error: e.message };
21448
+ }
21449
+ }
21450
+ // ─── Mesh Coordinator Launch ───
21451
+ case "launch_mesh_coordinator": {
21452
+ const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
21453
+ const cliType = typeof args?.cliType === "string" ? args.cliType.trim() : "claude-cli";
21454
+ if (!meshId) return { success: false, error: "meshId required" };
21455
+ try {
21456
+ const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
21457
+ const { buildCoordinatorSystemPrompt: buildCoordinatorSystemPrompt2 } = await Promise.resolve().then(() => (init_coordinator_prompt(), coordinator_prompt_exports));
21458
+ const mesh = getMesh3(meshId);
21459
+ if (!mesh) return { success: false, error: "Mesh not found" };
21460
+ if (mesh.nodes.length === 0) return { success: false, error: "No nodes in mesh" };
21461
+ const workspace = mesh.nodes[0].workspace;
21462
+ const providerMeta = this.deps.providerLoader.resolve?.(cliType) || this.deps.providerLoader.getMeta(cliType);
21463
+ const coordinatorSetup = resolveMeshCoordinatorSetup({
21464
+ provider: providerMeta,
21465
+ meshId,
21466
+ workspace
21467
+ });
21468
+ if (coordinatorSetup.kind === "unsupported") {
21469
+ return {
21470
+ success: false,
21471
+ code: "mesh_coordinator_unsupported",
21472
+ error: coordinatorSetup.reason,
21473
+ meshId,
21474
+ cliType,
21475
+ workspace
21476
+ };
21477
+ }
21478
+ if (coordinatorSetup.kind === "manual") {
21479
+ return {
21480
+ success: false,
21481
+ code: "mesh_coordinator_manual_mcp_setup_required",
21482
+ error: coordinatorSetup.instructions,
21483
+ meshId,
21484
+ cliType,
21485
+ workspace,
21486
+ meshCoordinatorSetup: coordinatorSetup
21487
+ };
21488
+ }
21489
+ if (coordinatorSetup.configFormat !== "claude_mcp_json") {
21490
+ return {
21491
+ success: false,
21492
+ code: "mesh_coordinator_unsupported",
21493
+ error: `Unsupported auto-import MCP config format: ${String(coordinatorSetup.configFormat)}`,
21494
+ meshId,
21495
+ cliType,
21496
+ workspace
21497
+ };
21498
+ }
21499
+ const { existsSync: existsSync21, readFileSync: readFileSync15, writeFileSync: writeFileSync12, copyFileSync: copyFileSync3 } = await import("fs");
21500
+ const mcpConfigPath = coordinatorSetup.configPath;
21501
+ const hadExistingMcpConfig = existsSync21(mcpConfigPath);
21502
+ let existingMcpConfig = {};
21503
+ if (hadExistingMcpConfig) {
21504
+ try {
21505
+ existingMcpConfig = JSON.parse(readFileSync15(mcpConfigPath, "utf-8"));
21506
+ copyFileSync3(mcpConfigPath, mcpConfigPath + ".backup");
21507
+ } catch {
21508
+ }
21509
+ }
21510
+ const mcpConfig = {
21511
+ ...existingMcpConfig,
21512
+ mcpServers: {
21513
+ ...existingMcpConfig.mcpServers || {},
21514
+ [coordinatorSetup.serverName]: {
21515
+ command: "adhdev-mcp",
21516
+ args: ["--repo-mesh", meshId]
21517
+ }
21518
+ }
21519
+ };
21520
+ writeFileSync12(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
21521
+ LOG.info("MeshCoordinator", `Wrote ${mcpConfigPath} with ${coordinatorSetup.serverName} server`);
21522
+ let systemPrompt = "";
21523
+ try {
21524
+ systemPrompt = buildCoordinatorSystemPrompt2({ mesh });
21525
+ } catch {
21526
+ 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).`;
21527
+ }
21528
+ const launchResult = await this.deps.cliManager.handleCliCommand("launch_cli", {
21529
+ cliType,
21530
+ dir: workspace,
21531
+ initialPrompt: systemPrompt
21532
+ });
21533
+ if (!launchResult?.success) {
21534
+ return { success: false, error: launchResult?.error || "Failed to launch CLI session" };
21535
+ }
21536
+ LOG.info("MeshCoordinator", `Launched ${cliType} coordinator for mesh ${meshId} in ${workspace}`);
21537
+ return {
21538
+ success: true,
21539
+ meshId,
21540
+ cliType,
21541
+ workspace,
21542
+ sessionId: launchResult.sessionId || launchResult.id,
21543
+ mcpConfigWritten: true
21544
+ };
21545
+ } catch (e) {
21546
+ LOG.error("MeshCoordinator", `Failed: ${e.message}`);
21547
+ return { success: false, error: e.message };
21548
+ }
21549
+ }
21214
21550
  default:
21215
21551
  break;
21216
21552
  }