@adhdev/daemon-core 0.9.67 → 0.9.68
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 +506 -328
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +506 -327
- package/dist/index.mjs.map +1 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/commands/router.ts +156 -0
package/dist/index.mjs
CHANGED
|
@@ -25,6 +25,22 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
25
25
|
};
|
|
26
26
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
27
|
|
|
28
|
+
// src/repo-mesh-types.ts
|
|
29
|
+
var DEFAULT_MESH_POLICY;
|
|
30
|
+
var init_repo_mesh_types = __esm({
|
|
31
|
+
"src/repo-mesh-types.ts"() {
|
|
32
|
+
"use strict";
|
|
33
|
+
DEFAULT_MESH_POLICY = {
|
|
34
|
+
requirePreTaskCheckpoint: false,
|
|
35
|
+
requirePostTaskCheckpoint: true,
|
|
36
|
+
requireApprovalForPush: true,
|
|
37
|
+
requireApprovalForDestructiveGit: true,
|
|
38
|
+
dirtyWorkspaceBehavior: "warn",
|
|
39
|
+
maxParallelTasks: 2
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
28
44
|
// src/config/config.ts
|
|
29
45
|
var config_exports = {};
|
|
30
46
|
__export(config_exports, {
|
|
@@ -263,6 +279,275 @@ var init_config = __esm({
|
|
|
263
279
|
}
|
|
264
280
|
});
|
|
265
281
|
|
|
282
|
+
// src/config/mesh-config.ts
|
|
283
|
+
var mesh_config_exports = {};
|
|
284
|
+
__export(mesh_config_exports, {
|
|
285
|
+
addNode: () => addNode,
|
|
286
|
+
createMesh: () => createMesh,
|
|
287
|
+
deleteMesh: () => deleteMesh,
|
|
288
|
+
getMesh: () => getMesh,
|
|
289
|
+
getMeshByRepo: () => getMeshByRepo,
|
|
290
|
+
listMeshes: () => listMeshes,
|
|
291
|
+
normalizeRepoIdentity: () => normalizeRepoIdentity,
|
|
292
|
+
removeNode: () => removeNode,
|
|
293
|
+
updateMesh: () => updateMesh,
|
|
294
|
+
updateNode: () => updateNode
|
|
295
|
+
});
|
|
296
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
297
|
+
import { join as join3 } from "path";
|
|
298
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
299
|
+
function getMeshConfigPath() {
|
|
300
|
+
return join3(getConfigDir(), "meshes.json");
|
|
301
|
+
}
|
|
302
|
+
function loadMeshConfig() {
|
|
303
|
+
const path26 = getMeshConfigPath();
|
|
304
|
+
if (!existsSync3(path26)) return { meshes: [] };
|
|
305
|
+
try {
|
|
306
|
+
const raw = JSON.parse(readFileSync2(path26, "utf-8"));
|
|
307
|
+
if (!raw || !Array.isArray(raw.meshes)) return { meshes: [] };
|
|
308
|
+
return raw;
|
|
309
|
+
} catch {
|
|
310
|
+
return { meshes: [] };
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function saveMeshConfig(config) {
|
|
314
|
+
const path26 = getMeshConfigPath();
|
|
315
|
+
writeFileSync2(path26, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
316
|
+
}
|
|
317
|
+
function normalizeRepoIdentity(remoteUrl) {
|
|
318
|
+
let identity = remoteUrl.trim();
|
|
319
|
+
if (identity.startsWith("http://") || identity.startsWith("https://")) {
|
|
320
|
+
try {
|
|
321
|
+
const url = new URL(identity);
|
|
322
|
+
const path26 = url.pathname.replace(/^\//, "").replace(/\.git$/, "");
|
|
323
|
+
return `${url.hostname}/${path26}`;
|
|
324
|
+
} catch {
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const sshMatch = identity.match(/^(?:ssh:\/\/)?[\w.-]+@([\w.-]+)[:/]([\w.\-/]+?)(?:\.git)?$/);
|
|
328
|
+
if (sshMatch) return `${sshMatch[1]}/${sshMatch[2]}`;
|
|
329
|
+
return identity;
|
|
330
|
+
}
|
|
331
|
+
function listMeshes() {
|
|
332
|
+
return loadMeshConfig().meshes;
|
|
333
|
+
}
|
|
334
|
+
function getMesh(meshId) {
|
|
335
|
+
return loadMeshConfig().meshes.find((m) => m.id === meshId);
|
|
336
|
+
}
|
|
337
|
+
function getMeshByRepo(repoIdentity) {
|
|
338
|
+
return loadMeshConfig().meshes.find((m) => m.repoIdentity === repoIdentity);
|
|
339
|
+
}
|
|
340
|
+
function createMesh(opts) {
|
|
341
|
+
const config = loadMeshConfig();
|
|
342
|
+
if (config.meshes.length >= 20) {
|
|
343
|
+
throw new Error("Maximum 20 meshes allowed");
|
|
344
|
+
}
|
|
345
|
+
const repoIdentity = opts.repoIdentity || (opts.repoRemoteUrl ? normalizeRepoIdentity(opts.repoRemoteUrl) : "");
|
|
346
|
+
if (!repoIdentity) throw new Error("Either repoRemoteUrl or repoIdentity is required");
|
|
347
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
348
|
+
const mesh = {
|
|
349
|
+
id: `mesh_${randomUUID3().replace(/-/g, "")}`,
|
|
350
|
+
name: opts.name.trim().slice(0, 100),
|
|
351
|
+
repoIdentity,
|
|
352
|
+
repoRemoteUrl: opts.repoRemoteUrl,
|
|
353
|
+
defaultBranch: opts.defaultBranch,
|
|
354
|
+
policy: { ...DEFAULT_MESH_POLICY, ...opts.policy },
|
|
355
|
+
coordinator: opts.coordinator || {},
|
|
356
|
+
nodes: [],
|
|
357
|
+
createdAt: now,
|
|
358
|
+
updatedAt: now
|
|
359
|
+
};
|
|
360
|
+
config.meshes.push(mesh);
|
|
361
|
+
saveMeshConfig(config);
|
|
362
|
+
return mesh;
|
|
363
|
+
}
|
|
364
|
+
function updateMesh(meshId, opts) {
|
|
365
|
+
const config = loadMeshConfig();
|
|
366
|
+
const mesh = config.meshes.find((m) => m.id === meshId);
|
|
367
|
+
if (!mesh) return void 0;
|
|
368
|
+
if (opts.name !== void 0) mesh.name = opts.name.trim().slice(0, 100);
|
|
369
|
+
if (opts.defaultBranch !== void 0) mesh.defaultBranch = opts.defaultBranch;
|
|
370
|
+
if (opts.policy) mesh.policy = { ...mesh.policy, ...opts.policy };
|
|
371
|
+
if (opts.coordinator) mesh.coordinator = opts.coordinator;
|
|
372
|
+
mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
373
|
+
saveMeshConfig(config);
|
|
374
|
+
return mesh;
|
|
375
|
+
}
|
|
376
|
+
function deleteMesh(meshId) {
|
|
377
|
+
const config = loadMeshConfig();
|
|
378
|
+
const idx = config.meshes.findIndex((m) => m.id === meshId);
|
|
379
|
+
if (idx === -1) return false;
|
|
380
|
+
config.meshes.splice(idx, 1);
|
|
381
|
+
saveMeshConfig(config);
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
function addNode(meshId, opts) {
|
|
385
|
+
const config = loadMeshConfig();
|
|
386
|
+
const mesh = config.meshes.find((m) => m.id === meshId);
|
|
387
|
+
if (!mesh) return void 0;
|
|
388
|
+
if (mesh.nodes.length >= 10) {
|
|
389
|
+
throw new Error("Maximum 10 nodes per mesh");
|
|
390
|
+
}
|
|
391
|
+
if (mesh.nodes.some((n) => n.workspace === opts.workspace)) {
|
|
392
|
+
throw new Error("This workspace is already in the mesh");
|
|
393
|
+
}
|
|
394
|
+
const node = {
|
|
395
|
+
id: `node_${randomUUID3().replace(/-/g, "")}`,
|
|
396
|
+
workspace: opts.workspace.trim(),
|
|
397
|
+
repoRoot: opts.repoRoot,
|
|
398
|
+
userOverrides: opts.userOverrides || {},
|
|
399
|
+
policy: opts.policy || {},
|
|
400
|
+
isLocalWorktree: opts.isLocalWorktree
|
|
401
|
+
};
|
|
402
|
+
mesh.nodes.push(node);
|
|
403
|
+
mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
404
|
+
saveMeshConfig(config);
|
|
405
|
+
return node;
|
|
406
|
+
}
|
|
407
|
+
function removeNode(meshId, nodeId) {
|
|
408
|
+
const config = loadMeshConfig();
|
|
409
|
+
const mesh = config.meshes.find((m) => m.id === meshId);
|
|
410
|
+
if (!mesh) return false;
|
|
411
|
+
const idx = mesh.nodes.findIndex((n) => n.id === nodeId);
|
|
412
|
+
if (idx === -1) return false;
|
|
413
|
+
mesh.nodes.splice(idx, 1);
|
|
414
|
+
mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
415
|
+
saveMeshConfig(config);
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
function updateNode(meshId, nodeId, opts) {
|
|
419
|
+
const config = loadMeshConfig();
|
|
420
|
+
const mesh = config.meshes.find((m) => m.id === meshId);
|
|
421
|
+
if (!mesh) return void 0;
|
|
422
|
+
const node = mesh.nodes.find((n) => n.id === nodeId);
|
|
423
|
+
if (!node) return void 0;
|
|
424
|
+
if (opts.userOverrides) node.userOverrides = { ...node.userOverrides, ...opts.userOverrides };
|
|
425
|
+
if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
|
|
426
|
+
mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
427
|
+
saveMeshConfig(config);
|
|
428
|
+
return node;
|
|
429
|
+
}
|
|
430
|
+
var init_mesh_config = __esm({
|
|
431
|
+
"src/config/mesh-config.ts"() {
|
|
432
|
+
"use strict";
|
|
433
|
+
init_config();
|
|
434
|
+
init_repo_mesh_types();
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// src/mesh/coordinator-prompt.ts
|
|
439
|
+
var coordinator_prompt_exports = {};
|
|
440
|
+
__export(coordinator_prompt_exports, {
|
|
441
|
+
buildCoordinatorSystemPrompt: () => buildCoordinatorSystemPrompt
|
|
442
|
+
});
|
|
443
|
+
function buildCoordinatorSystemPrompt(ctx) {
|
|
444
|
+
const { mesh, status, userInstruction } = ctx;
|
|
445
|
+
const sections = [];
|
|
446
|
+
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.
|
|
447
|
+
|
|
448
|
+
Your mesh: **${mesh.name}**
|
|
449
|
+
Repository: \`${mesh.repoIdentity}\`${mesh.defaultBranch ? `
|
|
450
|
+
Default branch: \`${mesh.defaultBranch}\`` : ""}`);
|
|
451
|
+
if (status?.nodes?.length) {
|
|
452
|
+
sections.push(buildNodeStatusSection(status.nodes));
|
|
453
|
+
} else if (mesh.nodes.length) {
|
|
454
|
+
sections.push(buildNodeConfigSection(mesh));
|
|
455
|
+
} else {
|
|
456
|
+
sections.push("## Nodes\nNo nodes configured yet. Ask the user to add nodes with `adhdev mesh add-node`.");
|
|
457
|
+
}
|
|
458
|
+
sections.push(buildPolicySection(mesh.policy));
|
|
459
|
+
sections.push(TOOLS_SECTION);
|
|
460
|
+
sections.push(WORKFLOW_SECTION);
|
|
461
|
+
sections.push(RULES_SECTION);
|
|
462
|
+
if (userInstruction) {
|
|
463
|
+
sections.push(`## Additional Context
|
|
464
|
+
${userInstruction}`);
|
|
465
|
+
}
|
|
466
|
+
if (mesh.coordinator.systemPromptSuffix) {
|
|
467
|
+
sections.push(mesh.coordinator.systemPromptSuffix);
|
|
468
|
+
}
|
|
469
|
+
return sections.join("\n\n");
|
|
470
|
+
}
|
|
471
|
+
function buildNodeStatusSection(nodes) {
|
|
472
|
+
const lines = ["## Current Node Status", ""];
|
|
473
|
+
for (const n of nodes) {
|
|
474
|
+
const healthIcon = n.health === "online" ? "\u{1F7E2}" : n.health === "dirty" ? "\u{1F7E1}" : n.health === "offline" ? "\u26AB" : "\u{1F534}";
|
|
475
|
+
const sessions = n.activeSessions.length > 0 ? `sessions: ${n.activeSessions.join(", ")}` : "no active sessions";
|
|
476
|
+
const branch = n.git?.branch ? `branch: \`${n.git.branch}\`` : "";
|
|
477
|
+
lines.push(`- ${healthIcon} **${n.machineLabel}** (${n.nodeId})`);
|
|
478
|
+
lines.push(` workspace: \`${n.workspace}\` | ${branch} | ${sessions}`);
|
|
479
|
+
if (n.error) lines.push(` \u26A0\uFE0F ${n.error}`);
|
|
480
|
+
}
|
|
481
|
+
return lines.join("\n");
|
|
482
|
+
}
|
|
483
|
+
function buildNodeConfigSection(mesh) {
|
|
484
|
+
const lines = ["## Configured Nodes", ""];
|
|
485
|
+
for (const n of mesh.nodes) {
|
|
486
|
+
const labels = [];
|
|
487
|
+
if (n.isLocalWorktree) labels.push("worktree");
|
|
488
|
+
if (n.policy.readOnly) labels.push("read-only");
|
|
489
|
+
const suffix = labels.length ? ` [${labels.join(", ")}]` : "";
|
|
490
|
+
lines.push(`- **${n.workspace}** (${n.id})${suffix}`);
|
|
491
|
+
}
|
|
492
|
+
lines.push("", "_Use `mesh_status` to probe live health before delegating work._");
|
|
493
|
+
return lines.join("\n");
|
|
494
|
+
}
|
|
495
|
+
function buildPolicySection(policy) {
|
|
496
|
+
const rules = [];
|
|
497
|
+
if (policy.requirePreTaskCheckpoint) rules.push("- Create a git checkpoint **before** starting each task");
|
|
498
|
+
if (policy.requirePostTaskCheckpoint) rules.push("- Create a git checkpoint **after** each task completes");
|
|
499
|
+
if (policy.requireApprovalForPush) rules.push("- **Ask for user approval** before pushing to remote");
|
|
500
|
+
if (policy.requireApprovalForDestructiveGit) rules.push("- **Ask for user approval** before destructive git operations (force push, reset, etc.)");
|
|
501
|
+
const dirtyBehavior = {
|
|
502
|
+
block: "- **Do not** send tasks to nodes with dirty workspaces",
|
|
503
|
+
warn: "- Warn the user if a node has uncommitted changes before sending a task",
|
|
504
|
+
checkpoint_then_continue: "- Auto-checkpoint dirty nodes before sending tasks"
|
|
505
|
+
}[policy.dirtyWorkspaceBehavior] || "";
|
|
506
|
+
if (dirtyBehavior) rules.push(dirtyBehavior);
|
|
507
|
+
rules.push(`- Maximum **${policy.maxParallelTasks}** tasks running in parallel`);
|
|
508
|
+
return `## Policy
|
|
509
|
+
${rules.join("\n")}`;
|
|
510
|
+
}
|
|
511
|
+
var TOOLS_SECTION, WORKFLOW_SECTION, RULES_SECTION;
|
|
512
|
+
var init_coordinator_prompt = __esm({
|
|
513
|
+
"src/mesh/coordinator-prompt.ts"() {
|
|
514
|
+
"use strict";
|
|
515
|
+
TOOLS_SECTION = `## Available Tools
|
|
516
|
+
|
|
517
|
+
| Tool | Purpose |
|
|
518
|
+
|------|---------|
|
|
519
|
+
| \`mesh_status\` | Check all nodes' health, git state, and active sessions |
|
|
520
|
+
| \`mesh_list_nodes\` | List nodes with workspace paths |
|
|
521
|
+
| \`mesh_launch_session\` | Start a new agent session on a node |
|
|
522
|
+
| \`mesh_send_task\` | Send a task (natural language) to a running agent |
|
|
523
|
+
| \`mesh_read_chat\` | Read an agent's recent messages to check progress |
|
|
524
|
+
| \`mesh_git_status\` | Check git status on a specific node |
|
|
525
|
+
| \`mesh_checkpoint\` | Create a git checkpoint on a node |
|
|
526
|
+
| \`mesh_approve\` | Approve/reject a pending agent action |`;
|
|
527
|
+
WORKFLOW_SECTION = `## Orchestration Workflow
|
|
528
|
+
|
|
529
|
+
1. **Assess** \u2014 Call \`mesh_status\` to see which nodes are healthy and available.
|
|
530
|
+
2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist.
|
|
531
|
+
3. **Delegate** \u2014 For each task:
|
|
532
|
+
a. Pick the best node (consider: health, dirty state, current workload).
|
|
533
|
+
b. If no session exists, call \`mesh_launch_session\` to start one.
|
|
534
|
+
c. Call \`mesh_send_task\` with a clear, self-contained natural-language instruction.
|
|
535
|
+
4. **Monitor** \u2014 Periodically call \`mesh_read_chat\` to check progress. Handle approvals via \`mesh_approve\`.
|
|
536
|
+
5. **Verify** \u2014 When a task reports completion, call \`mesh_git_status\` to verify changes were made.
|
|
537
|
+
6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
|
|
538
|
+
7. **Report** \u2014 Summarize what was done, what changed, and any issues.`;
|
|
539
|
+
RULES_SECTION = `## Rules
|
|
540
|
+
|
|
541
|
+
- **Be conversational.** Delegate work the way a tech lead would \u2014 clear, specific instructions in natural language.
|
|
542
|
+
- **Don't inspect code.** Trust the agent's output. Verify via git diff/status, not by reading source files.
|
|
543
|
+
- **Don't over-parallelize.** Start with 1-2 concurrent tasks. Scale up if they succeed.
|
|
544
|
+
- **Handle failures gracefully.** If a task fails, read the chat to understand why, then retry or reassign.
|
|
545
|
+
- **Keep the user informed.** Report progress after each delegation round.
|
|
546
|
+
- **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
|
|
547
|
+
- **Never fabricate tool results.** Always call the actual tool; never pretend you did.`;
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
|
|
266
551
|
// src/logging/logger.ts
|
|
267
552
|
import * as fs2 from "fs";
|
|
268
553
|
import * as path9 from "path";
|
|
@@ -3320,15 +3605,8 @@ var init_provider_cli_adapter = __esm({
|
|
|
3320
3605
|
}
|
|
3321
3606
|
});
|
|
3322
3607
|
|
|
3323
|
-
// src/
|
|
3324
|
-
|
|
3325
|
-
requirePreTaskCheckpoint: false,
|
|
3326
|
-
requirePostTaskCheckpoint: true,
|
|
3327
|
-
requireApprovalForPush: true,
|
|
3328
|
-
requireApprovalForDestructiveGit: true,
|
|
3329
|
-
dirtyWorkspaceBehavior: "warn",
|
|
3330
|
-
maxParallelTasks: 2
|
|
3331
|
-
};
|
|
3608
|
+
// src/index.ts
|
|
3609
|
+
init_repo_mesh_types();
|
|
3332
3610
|
|
|
3333
3611
|
// src/git/git-executor.ts
|
|
3334
3612
|
import { execFile } from "child_process";
|
|
@@ -5037,337 +5315,102 @@ function applySessionNotificationOverlay(session, overlay) {
|
|
|
5037
5315
|
inboxBucket: forcedInboxBucket
|
|
5038
5316
|
};
|
|
5039
5317
|
}
|
|
5040
|
-
if (!currentNotificationId || !dismissedNotificationId || currentNotificationId !== dismissedNotificationId) {
|
|
5041
|
-
return {
|
|
5042
|
-
unread: !!session.unread,
|
|
5043
|
-
inboxBucket: session.inboxBucket || "idle"
|
|
5044
|
-
};
|
|
5045
|
-
}
|
|
5046
|
-
return {
|
|
5047
|
-
unread: false,
|
|
5048
|
-
inboxBucket: "idle"
|
|
5049
|
-
};
|
|
5050
|
-
}
|
|
5051
|
-
function markSessionSeen(state, sessionId, seenAt = Date.now(), completionMarker, providerSessionId) {
|
|
5052
|
-
const prev = state.sessionReads || {};
|
|
5053
|
-
const prevMarkers = state.sessionReadMarkers || {};
|
|
5054
|
-
const nextMarker = typeof completionMarker === "string" ? completionMarker : "";
|
|
5055
|
-
const readKeys = Array.from(new Set([
|
|
5056
|
-
sessionId,
|
|
5057
|
-
buildSessionReadStateKey(sessionId, providerSessionId)
|
|
5058
|
-
].filter(Boolean)));
|
|
5059
|
-
const nextSessionReads = { ...prev };
|
|
5060
|
-
const nextSessionReadMarkers = { ...prevMarkers };
|
|
5061
|
-
const nextSessionNotificationDismissals = { ...state.sessionNotificationDismissals || {} };
|
|
5062
|
-
const nextSessionNotificationUnreadOverrides = { ...state.sessionNotificationUnreadOverrides || {} };
|
|
5063
|
-
for (const key of readKeys) {
|
|
5064
|
-
nextSessionReads[key] = Math.max(prev[key] || 0, seenAt);
|
|
5065
|
-
if (nextMarker) nextSessionReadMarkers[key] = nextMarker;
|
|
5066
|
-
delete nextSessionNotificationDismissals[key];
|
|
5067
|
-
delete nextSessionNotificationUnreadOverrides[key];
|
|
5068
|
-
}
|
|
5069
|
-
return {
|
|
5070
|
-
...state,
|
|
5071
|
-
sessionReads: nextSessionReads,
|
|
5072
|
-
sessionReadMarkers: nextMarker ? nextSessionReadMarkers : prevMarkers,
|
|
5073
|
-
sessionNotificationDismissals: nextSessionNotificationDismissals,
|
|
5074
|
-
sessionNotificationUnreadOverrides: nextSessionNotificationUnreadOverrides
|
|
5075
|
-
};
|
|
5076
|
-
}
|
|
5077
|
-
|
|
5078
|
-
// src/config/saved-sessions.ts
|
|
5079
|
-
import * as path6 from "path";
|
|
5080
|
-
var MAX_SAVED_SESSIONS = 500;
|
|
5081
|
-
function normalizeWorkspace2(workspace) {
|
|
5082
|
-
if (!workspace) return "";
|
|
5083
|
-
try {
|
|
5084
|
-
return path6.resolve(expandPath(workspace));
|
|
5085
|
-
} catch {
|
|
5086
|
-
return path6.resolve(workspace);
|
|
5087
|
-
}
|
|
5088
|
-
}
|
|
5089
|
-
function buildSavedProviderSessionKey(providerSessionId) {
|
|
5090
|
-
return `saved:${providerSessionId.trim()}`;
|
|
5091
|
-
}
|
|
5092
|
-
function upsertSavedProviderSession(state, entry) {
|
|
5093
|
-
const providerSessionId = typeof entry.providerSessionId === "string" ? entry.providerSessionId.trim() : "";
|
|
5094
|
-
if (!providerSessionId) return state;
|
|
5095
|
-
const id = buildSavedProviderSessionKey(providerSessionId);
|
|
5096
|
-
const existing = (state.savedProviderSessions || []).find((item) => item.id === id);
|
|
5097
|
-
const nextEntry = {
|
|
5098
|
-
id,
|
|
5099
|
-
kind: entry.kind,
|
|
5100
|
-
providerType: entry.providerType,
|
|
5101
|
-
providerName: entry.providerName,
|
|
5102
|
-
providerSessionId,
|
|
5103
|
-
workspace: entry.workspace ? normalizeWorkspace2(entry.workspace) : void 0,
|
|
5104
|
-
summaryMetadata: normalizePersistedSummaryMetadata({
|
|
5105
|
-
summaryMetadata: entry.summaryMetadata
|
|
5106
|
-
}),
|
|
5107
|
-
title: entry.title,
|
|
5108
|
-
createdAt: existing?.createdAt || entry.createdAt || Date.now(),
|
|
5109
|
-
lastUsedAt: entry.lastUsedAt || Date.now()
|
|
5110
|
-
};
|
|
5111
|
-
const filtered = (state.savedProviderSessions || []).filter((item) => item.id !== id);
|
|
5112
|
-
return {
|
|
5113
|
-
...state,
|
|
5114
|
-
savedProviderSessions: [nextEntry, ...filtered].slice(0, MAX_SAVED_SESSIONS)
|
|
5115
|
-
};
|
|
5116
|
-
}
|
|
5117
|
-
function getSavedProviderSessions(state, filters) {
|
|
5118
|
-
return [...state.savedProviderSessions || []].filter((entry) => {
|
|
5119
|
-
if (filters?.providerType && entry.providerType !== filters.providerType) return false;
|
|
5120
|
-
if (filters?.kind && entry.kind !== filters.kind) return false;
|
|
5121
|
-
return true;
|
|
5122
|
-
}).map((entry) => ({
|
|
5123
|
-
...entry,
|
|
5124
|
-
summaryMetadata: normalizePersistedSummaryMetadata({
|
|
5125
|
-
summaryMetadata: entry.summaryMetadata
|
|
5126
|
-
})
|
|
5127
|
-
})).sort((a, b) => b.lastUsedAt - a.lastUsedAt);
|
|
5128
|
-
}
|
|
5129
|
-
|
|
5130
|
-
// src/config/mesh-config.ts
|
|
5131
|
-
init_config();
|
|
5132
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
5133
|
-
import { join as join3 } from "path";
|
|
5134
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
5135
|
-
function getMeshConfigPath() {
|
|
5136
|
-
return join3(getConfigDir(), "meshes.json");
|
|
5137
|
-
}
|
|
5138
|
-
function loadMeshConfig() {
|
|
5139
|
-
const path26 = getMeshConfigPath();
|
|
5140
|
-
if (!existsSync3(path26)) return { meshes: [] };
|
|
5141
|
-
try {
|
|
5142
|
-
const raw = JSON.parse(readFileSync2(path26, "utf-8"));
|
|
5143
|
-
if (!raw || !Array.isArray(raw.meshes)) return { meshes: [] };
|
|
5144
|
-
return raw;
|
|
5145
|
-
} catch {
|
|
5146
|
-
return { meshes: [] };
|
|
5147
|
-
}
|
|
5148
|
-
}
|
|
5149
|
-
function saveMeshConfig(config) {
|
|
5150
|
-
const path26 = getMeshConfigPath();
|
|
5151
|
-
writeFileSync2(path26, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
5152
|
-
}
|
|
5153
|
-
function normalizeRepoIdentity(remoteUrl) {
|
|
5154
|
-
let identity = remoteUrl.trim();
|
|
5155
|
-
if (identity.startsWith("http://") || identity.startsWith("https://")) {
|
|
5156
|
-
try {
|
|
5157
|
-
const url = new URL(identity);
|
|
5158
|
-
const path26 = url.pathname.replace(/^\//, "").replace(/\.git$/, "");
|
|
5159
|
-
return `${url.hostname}/${path26}`;
|
|
5160
|
-
} catch {
|
|
5161
|
-
}
|
|
5162
|
-
}
|
|
5163
|
-
const sshMatch = identity.match(/^(?:ssh:\/\/)?[\w.-]+@([\w.-]+)[:/]([\w.\-/]+?)(?:\.git)?$/);
|
|
5164
|
-
if (sshMatch) return `${sshMatch[1]}/${sshMatch[2]}`;
|
|
5165
|
-
return identity;
|
|
5166
|
-
}
|
|
5167
|
-
function listMeshes() {
|
|
5168
|
-
return loadMeshConfig().meshes;
|
|
5169
|
-
}
|
|
5170
|
-
function getMesh(meshId) {
|
|
5171
|
-
return loadMeshConfig().meshes.find((m) => m.id === meshId);
|
|
5172
|
-
}
|
|
5173
|
-
function getMeshByRepo(repoIdentity) {
|
|
5174
|
-
return loadMeshConfig().meshes.find((m) => m.repoIdentity === repoIdentity);
|
|
5175
|
-
}
|
|
5176
|
-
function createMesh(opts) {
|
|
5177
|
-
const config = loadMeshConfig();
|
|
5178
|
-
if (config.meshes.length >= 20) {
|
|
5179
|
-
throw new Error("Maximum 20 meshes allowed");
|
|
5180
|
-
}
|
|
5181
|
-
const repoIdentity = opts.repoIdentity || (opts.repoRemoteUrl ? normalizeRepoIdentity(opts.repoRemoteUrl) : "");
|
|
5182
|
-
if (!repoIdentity) throw new Error("Either repoRemoteUrl or repoIdentity is required");
|
|
5183
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5184
|
-
const mesh = {
|
|
5185
|
-
id: `mesh_${randomUUID3().replace(/-/g, "")}`,
|
|
5186
|
-
name: opts.name.trim().slice(0, 100),
|
|
5187
|
-
repoIdentity,
|
|
5188
|
-
repoRemoteUrl: opts.repoRemoteUrl,
|
|
5189
|
-
defaultBranch: opts.defaultBranch,
|
|
5190
|
-
policy: { ...DEFAULT_MESH_POLICY, ...opts.policy },
|
|
5191
|
-
coordinator: opts.coordinator || {},
|
|
5192
|
-
nodes: [],
|
|
5193
|
-
createdAt: now,
|
|
5194
|
-
updatedAt: now
|
|
5195
|
-
};
|
|
5196
|
-
config.meshes.push(mesh);
|
|
5197
|
-
saveMeshConfig(config);
|
|
5198
|
-
return mesh;
|
|
5199
|
-
}
|
|
5200
|
-
function updateMesh(meshId, opts) {
|
|
5201
|
-
const config = loadMeshConfig();
|
|
5202
|
-
const mesh = config.meshes.find((m) => m.id === meshId);
|
|
5203
|
-
if (!mesh) return void 0;
|
|
5204
|
-
if (opts.name !== void 0) mesh.name = opts.name.trim().slice(0, 100);
|
|
5205
|
-
if (opts.defaultBranch !== void 0) mesh.defaultBranch = opts.defaultBranch;
|
|
5206
|
-
if (opts.policy) mesh.policy = { ...mesh.policy, ...opts.policy };
|
|
5207
|
-
if (opts.coordinator) mesh.coordinator = opts.coordinator;
|
|
5208
|
-
mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5209
|
-
saveMeshConfig(config);
|
|
5210
|
-
return mesh;
|
|
5211
|
-
}
|
|
5212
|
-
function deleteMesh(meshId) {
|
|
5213
|
-
const config = loadMeshConfig();
|
|
5214
|
-
const idx = config.meshes.findIndex((m) => m.id === meshId);
|
|
5215
|
-
if (idx === -1) return false;
|
|
5216
|
-
config.meshes.splice(idx, 1);
|
|
5217
|
-
saveMeshConfig(config);
|
|
5218
|
-
return true;
|
|
5219
|
-
}
|
|
5220
|
-
function addNode(meshId, opts) {
|
|
5221
|
-
const config = loadMeshConfig();
|
|
5222
|
-
const mesh = config.meshes.find((m) => m.id === meshId);
|
|
5223
|
-
if (!mesh) return void 0;
|
|
5224
|
-
if (mesh.nodes.length >= 10) {
|
|
5225
|
-
throw new Error("Maximum 10 nodes per mesh");
|
|
5226
|
-
}
|
|
5227
|
-
if (mesh.nodes.some((n) => n.workspace === opts.workspace)) {
|
|
5228
|
-
throw new Error("This workspace is already in the mesh");
|
|
5318
|
+
if (!currentNotificationId || !dismissedNotificationId || currentNotificationId !== dismissedNotificationId) {
|
|
5319
|
+
return {
|
|
5320
|
+
unread: !!session.unread,
|
|
5321
|
+
inboxBucket: session.inboxBucket || "idle"
|
|
5322
|
+
};
|
|
5229
5323
|
}
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
repoRoot: opts.repoRoot,
|
|
5234
|
-
userOverrides: opts.userOverrides || {},
|
|
5235
|
-
policy: opts.policy || {},
|
|
5236
|
-
isLocalWorktree: opts.isLocalWorktree
|
|
5324
|
+
return {
|
|
5325
|
+
unread: false,
|
|
5326
|
+
inboxBucket: "idle"
|
|
5237
5327
|
};
|
|
5238
|
-
mesh.nodes.push(node);
|
|
5239
|
-
mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5240
|
-
saveMeshConfig(config);
|
|
5241
|
-
return node;
|
|
5242
|
-
}
|
|
5243
|
-
function removeNode(meshId, nodeId) {
|
|
5244
|
-
const config = loadMeshConfig();
|
|
5245
|
-
const mesh = config.meshes.find((m) => m.id === meshId);
|
|
5246
|
-
if (!mesh) return false;
|
|
5247
|
-
const idx = mesh.nodes.findIndex((n) => n.id === nodeId);
|
|
5248
|
-
if (idx === -1) return false;
|
|
5249
|
-
mesh.nodes.splice(idx, 1);
|
|
5250
|
-
mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5251
|
-
saveMeshConfig(config);
|
|
5252
|
-
return true;
|
|
5253
5328
|
}
|
|
5254
|
-
function
|
|
5255
|
-
const
|
|
5256
|
-
const
|
|
5257
|
-
|
|
5258
|
-
const
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5329
|
+
function markSessionSeen(state, sessionId, seenAt = Date.now(), completionMarker, providerSessionId) {
|
|
5330
|
+
const prev = state.sessionReads || {};
|
|
5331
|
+
const prevMarkers = state.sessionReadMarkers || {};
|
|
5332
|
+
const nextMarker = typeof completionMarker === "string" ? completionMarker : "";
|
|
5333
|
+
const readKeys = Array.from(new Set([
|
|
5334
|
+
sessionId,
|
|
5335
|
+
buildSessionReadStateKey(sessionId, providerSessionId)
|
|
5336
|
+
].filter(Boolean)));
|
|
5337
|
+
const nextSessionReads = { ...prev };
|
|
5338
|
+
const nextSessionReadMarkers = { ...prevMarkers };
|
|
5339
|
+
const nextSessionNotificationDismissals = { ...state.sessionNotificationDismissals || {} };
|
|
5340
|
+
const nextSessionNotificationUnreadOverrides = { ...state.sessionNotificationUnreadOverrides || {} };
|
|
5341
|
+
for (const key of readKeys) {
|
|
5342
|
+
nextSessionReads[key] = Math.max(prev[key] || 0, seenAt);
|
|
5343
|
+
if (nextMarker) nextSessionReadMarkers[key] = nextMarker;
|
|
5344
|
+
delete nextSessionNotificationDismissals[key];
|
|
5345
|
+
delete nextSessionNotificationUnreadOverrides[key];
|
|
5346
|
+
}
|
|
5347
|
+
return {
|
|
5348
|
+
...state,
|
|
5349
|
+
sessionReads: nextSessionReads,
|
|
5350
|
+
sessionReadMarkers: nextMarker ? nextSessionReadMarkers : prevMarkers,
|
|
5351
|
+
sessionNotificationDismissals: nextSessionNotificationDismissals,
|
|
5352
|
+
sessionNotificationUnreadOverrides: nextSessionNotificationUnreadOverrides
|
|
5353
|
+
};
|
|
5265
5354
|
}
|
|
5266
5355
|
|
|
5267
|
-
// src/
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
if (status?.nodes?.length) {
|
|
5277
|
-
sections.push(buildNodeStatusSection(status.nodes));
|
|
5278
|
-
} else if (mesh.nodes.length) {
|
|
5279
|
-
sections.push(buildNodeConfigSection(mesh));
|
|
5280
|
-
} else {
|
|
5281
|
-
sections.push("## Nodes\nNo nodes configured yet. Ask the user to add nodes with `adhdev mesh add-node`.");
|
|
5282
|
-
}
|
|
5283
|
-
sections.push(buildPolicySection(mesh.policy));
|
|
5284
|
-
sections.push(TOOLS_SECTION);
|
|
5285
|
-
sections.push(WORKFLOW_SECTION);
|
|
5286
|
-
sections.push(RULES_SECTION);
|
|
5287
|
-
if (userInstruction) {
|
|
5288
|
-
sections.push(`## Additional Context
|
|
5289
|
-
${userInstruction}`);
|
|
5290
|
-
}
|
|
5291
|
-
if (mesh.coordinator.systemPromptSuffix) {
|
|
5292
|
-
sections.push(mesh.coordinator.systemPromptSuffix);
|
|
5356
|
+
// src/config/saved-sessions.ts
|
|
5357
|
+
import * as path6 from "path";
|
|
5358
|
+
var MAX_SAVED_SESSIONS = 500;
|
|
5359
|
+
function normalizeWorkspace2(workspace) {
|
|
5360
|
+
if (!workspace) return "";
|
|
5361
|
+
try {
|
|
5362
|
+
return path6.resolve(expandPath(workspace));
|
|
5363
|
+
} catch {
|
|
5364
|
+
return path6.resolve(workspace);
|
|
5293
5365
|
}
|
|
5294
|
-
return sections.join("\n\n");
|
|
5295
5366
|
}
|
|
5296
|
-
function
|
|
5297
|
-
|
|
5298
|
-
for (const n of nodes) {
|
|
5299
|
-
const healthIcon = n.health === "online" ? "\u{1F7E2}" : n.health === "dirty" ? "\u{1F7E1}" : n.health === "offline" ? "\u26AB" : "\u{1F534}";
|
|
5300
|
-
const sessions = n.activeSessions.length > 0 ? `sessions: ${n.activeSessions.join(", ")}` : "no active sessions";
|
|
5301
|
-
const branch = n.git?.branch ? `branch: \`${n.git.branch}\`` : "";
|
|
5302
|
-
lines.push(`- ${healthIcon} **${n.machineLabel}** (${n.nodeId})`);
|
|
5303
|
-
lines.push(` workspace: \`${n.workspace}\` | ${branch} | ${sessions}`);
|
|
5304
|
-
if (n.error) lines.push(` \u26A0\uFE0F ${n.error}`);
|
|
5305
|
-
}
|
|
5306
|
-
return lines.join("\n");
|
|
5367
|
+
function buildSavedProviderSessionKey(providerSessionId) {
|
|
5368
|
+
return `saved:${providerSessionId.trim()}`;
|
|
5307
5369
|
}
|
|
5308
|
-
function
|
|
5309
|
-
const
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5370
|
+
function upsertSavedProviderSession(state, entry) {
|
|
5371
|
+
const providerSessionId = typeof entry.providerSessionId === "string" ? entry.providerSessionId.trim() : "";
|
|
5372
|
+
if (!providerSessionId) return state;
|
|
5373
|
+
const id = buildSavedProviderSessionKey(providerSessionId);
|
|
5374
|
+
const existing = (state.savedProviderSessions || []).find((item) => item.id === id);
|
|
5375
|
+
const nextEntry = {
|
|
5376
|
+
id,
|
|
5377
|
+
kind: entry.kind,
|
|
5378
|
+
providerType: entry.providerType,
|
|
5379
|
+
providerName: entry.providerName,
|
|
5380
|
+
providerSessionId,
|
|
5381
|
+
workspace: entry.workspace ? normalizeWorkspace2(entry.workspace) : void 0,
|
|
5382
|
+
summaryMetadata: normalizePersistedSummaryMetadata({
|
|
5383
|
+
summaryMetadata: entry.summaryMetadata
|
|
5384
|
+
}),
|
|
5385
|
+
title: entry.title,
|
|
5386
|
+
createdAt: existing?.createdAt || entry.createdAt || Date.now(),
|
|
5387
|
+
lastUsedAt: entry.lastUsedAt || Date.now()
|
|
5388
|
+
};
|
|
5389
|
+
const filtered = (state.savedProviderSessions || []).filter((item) => item.id !== id);
|
|
5390
|
+
return {
|
|
5391
|
+
...state,
|
|
5392
|
+
savedProviderSessions: [nextEntry, ...filtered].slice(0, MAX_SAVED_SESSIONS)
|
|
5393
|
+
};
|
|
5319
5394
|
}
|
|
5320
|
-
function
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
}
|
|
5331
|
-
if (dirtyBehavior) rules.push(dirtyBehavior);
|
|
5332
|
-
rules.push(`- Maximum **${policy.maxParallelTasks}** tasks running in parallel`);
|
|
5333
|
-
return `## Policy
|
|
5334
|
-
${rules.join("\n")}`;
|
|
5395
|
+
function getSavedProviderSessions(state, filters) {
|
|
5396
|
+
return [...state.savedProviderSessions || []].filter((entry) => {
|
|
5397
|
+
if (filters?.providerType && entry.providerType !== filters.providerType) return false;
|
|
5398
|
+
if (filters?.kind && entry.kind !== filters.kind) return false;
|
|
5399
|
+
return true;
|
|
5400
|
+
}).map((entry) => ({
|
|
5401
|
+
...entry,
|
|
5402
|
+
summaryMetadata: normalizePersistedSummaryMetadata({
|
|
5403
|
+
summaryMetadata: entry.summaryMetadata
|
|
5404
|
+
})
|
|
5405
|
+
})).sort((a, b) => b.lastUsedAt - a.lastUsedAt);
|
|
5335
5406
|
}
|
|
5336
|
-
var TOOLS_SECTION = `## Available Tools
|
|
5337
|
-
|
|
5338
|
-
| Tool | Purpose |
|
|
5339
|
-
|------|---------|
|
|
5340
|
-
| \`mesh_status\` | Check all nodes' health, git state, and active sessions |
|
|
5341
|
-
| \`mesh_list_nodes\` | List nodes with workspace paths |
|
|
5342
|
-
| \`mesh_launch_session\` | Start a new agent session on a node |
|
|
5343
|
-
| \`mesh_send_task\` | Send a task (natural language) to a running agent |
|
|
5344
|
-
| \`mesh_read_chat\` | Read an agent's recent messages to check progress |
|
|
5345
|
-
| \`mesh_git_status\` | Check git status on a specific node |
|
|
5346
|
-
| \`mesh_checkpoint\` | Create a git checkpoint on a node |
|
|
5347
|
-
| \`mesh_approve\` | Approve/reject a pending agent action |`;
|
|
5348
|
-
var WORKFLOW_SECTION = `## Orchestration Workflow
|
|
5349
|
-
|
|
5350
|
-
1. **Assess** \u2014 Call \`mesh_status\` to see which nodes are healthy and available.
|
|
5351
|
-
2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist.
|
|
5352
|
-
3. **Delegate** \u2014 For each task:
|
|
5353
|
-
a. Pick the best node (consider: health, dirty state, current workload).
|
|
5354
|
-
b. If no session exists, call \`mesh_launch_session\` to start one.
|
|
5355
|
-
c. Call \`mesh_send_task\` with a clear, self-contained natural-language instruction.
|
|
5356
|
-
4. **Monitor** \u2014 Periodically call \`mesh_read_chat\` to check progress. Handle approvals via \`mesh_approve\`.
|
|
5357
|
-
5. **Verify** \u2014 When a task reports completion, call \`mesh_git_status\` to verify changes were made.
|
|
5358
|
-
6. **Checkpoint** \u2014 Call \`mesh_checkpoint\` to save the work.
|
|
5359
|
-
7. **Report** \u2014 Summarize what was done, what changed, and any issues.`;
|
|
5360
|
-
var RULES_SECTION = `## Rules
|
|
5361
5407
|
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
- **Handle failures gracefully.** If a task fails, read the chat to understand why, then retry or reassign.
|
|
5366
|
-
- **Keep the user informed.** Report progress after each delegation round.
|
|
5367
|
-
- **Respect node capabilities.** Don't send build tasks to read-only nodes. Don't push from nodes that aren't allowed to.
|
|
5368
|
-
- **Never fabricate tool results.** Always call the actual tool; never pretend you did.`;
|
|
5408
|
+
// src/index.ts
|
|
5409
|
+
init_mesh_config();
|
|
5410
|
+
init_coordinator_prompt();
|
|
5369
5411
|
|
|
5370
5412
|
// src/mesh/mesh-sync.ts
|
|
5413
|
+
init_mesh_config();
|
|
5371
5414
|
async function syncMeshes(transport) {
|
|
5372
5415
|
const result = { pushed: 0, pulled: 0, deleted: 0, errors: [] };
|
|
5373
5416
|
let remoteMeshes;
|
|
@@ -21023,6 +21066,142 @@ var DaemonCommandRouter = class {
|
|
|
21023
21066
|
updateConfig({ machineNickname: nickname || null });
|
|
21024
21067
|
return { success: true };
|
|
21025
21068
|
}
|
|
21069
|
+
// ─── Mesh CRUD (local meshes.json) ───
|
|
21070
|
+
case "list_meshes": {
|
|
21071
|
+
try {
|
|
21072
|
+
const { listMeshes: listMeshes2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
21073
|
+
return { success: true, meshes: listMeshes2() };
|
|
21074
|
+
} catch (e) {
|
|
21075
|
+
return { success: false, error: e.message };
|
|
21076
|
+
}
|
|
21077
|
+
}
|
|
21078
|
+
case "get_mesh": {
|
|
21079
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
21080
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
21081
|
+
try {
|
|
21082
|
+
const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
21083
|
+
const mesh = getMesh3(meshId);
|
|
21084
|
+
if (!mesh) return { success: false, error: "Mesh not found" };
|
|
21085
|
+
return { success: true, mesh };
|
|
21086
|
+
} catch (e) {
|
|
21087
|
+
return { success: false, error: e.message };
|
|
21088
|
+
}
|
|
21089
|
+
}
|
|
21090
|
+
case "create_mesh": {
|
|
21091
|
+
const name = typeof args?.name === "string" ? args.name.trim() : "";
|
|
21092
|
+
const repoIdentity = typeof args?.repoIdentity === "string" ? args.repoIdentity.trim() : "";
|
|
21093
|
+
const repoRemoteUrl = typeof args?.repoRemoteUrl === "string" ? args.repoRemoteUrl.trim() : void 0;
|
|
21094
|
+
const defaultBranch = typeof args?.defaultBranch === "string" ? args.defaultBranch.trim() : void 0;
|
|
21095
|
+
if (!name) return { success: false, error: "name required" };
|
|
21096
|
+
try {
|
|
21097
|
+
const { createMesh: createMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
21098
|
+
const mesh = createMesh2({ name, repoIdentity, repoRemoteUrl, defaultBranch });
|
|
21099
|
+
return { success: true, mesh };
|
|
21100
|
+
} catch (e) {
|
|
21101
|
+
return { success: false, error: e.message };
|
|
21102
|
+
}
|
|
21103
|
+
}
|
|
21104
|
+
case "delete_mesh": {
|
|
21105
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
21106
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
21107
|
+
try {
|
|
21108
|
+
const { deleteMesh: deleteMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
21109
|
+
const deleted = deleteMesh3(meshId);
|
|
21110
|
+
return { success: true, deleted };
|
|
21111
|
+
} catch (e) {
|
|
21112
|
+
return { success: false, error: e.message };
|
|
21113
|
+
}
|
|
21114
|
+
}
|
|
21115
|
+
case "add_mesh_node": {
|
|
21116
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
21117
|
+
const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
|
|
21118
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
21119
|
+
if (!workspace) return { success: false, error: "workspace required" };
|
|
21120
|
+
try {
|
|
21121
|
+
const { addNode: addNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
21122
|
+
const node = addNode3(meshId, { workspace });
|
|
21123
|
+
if (!node) return { success: false, error: "Mesh not found" };
|
|
21124
|
+
return { success: true, node };
|
|
21125
|
+
} catch (e) {
|
|
21126
|
+
return { success: false, error: e.message };
|
|
21127
|
+
}
|
|
21128
|
+
}
|
|
21129
|
+
case "remove_mesh_node": {
|
|
21130
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
21131
|
+
const nodeId = typeof args?.nodeId === "string" ? args.nodeId.trim() : "";
|
|
21132
|
+
if (!meshId || !nodeId) return { success: false, error: "meshId and nodeId required" };
|
|
21133
|
+
try {
|
|
21134
|
+
const { removeNode: removeNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
21135
|
+
const removed = removeNode3(meshId, nodeId);
|
|
21136
|
+
return { success: true, removed };
|
|
21137
|
+
} catch (e) {
|
|
21138
|
+
return { success: false, error: e.message };
|
|
21139
|
+
}
|
|
21140
|
+
}
|
|
21141
|
+
// ─── Mesh Coordinator Launch ───
|
|
21142
|
+
case "launch_mesh_coordinator": {
|
|
21143
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
21144
|
+
const cliType = typeof args?.cliType === "string" ? args.cliType.trim() : "claude-cli";
|
|
21145
|
+
if (!meshId) return { success: false, error: "meshId required" };
|
|
21146
|
+
try {
|
|
21147
|
+
const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
21148
|
+
const { buildCoordinatorSystemPrompt: buildCoordinatorSystemPrompt2 } = await Promise.resolve().then(() => (init_coordinator_prompt(), coordinator_prompt_exports));
|
|
21149
|
+
const mesh = getMesh3(meshId);
|
|
21150
|
+
if (!mesh) return { success: false, error: "Mesh not found" };
|
|
21151
|
+
if (mesh.nodes.length === 0) return { success: false, error: "No nodes in mesh" };
|
|
21152
|
+
const workspace = mesh.nodes[0].workspace;
|
|
21153
|
+
const { existsSync: existsSync21, readFileSync: readFileSync15, writeFileSync: writeFileSync12, copyFileSync: copyFileSync3 } = await import("fs");
|
|
21154
|
+
const { join: join23 } = await import("path");
|
|
21155
|
+
const mcpConfigPath = join23(workspace, ".mcp.json");
|
|
21156
|
+
const hadExistingMcpConfig = existsSync21(mcpConfigPath);
|
|
21157
|
+
let existingMcpConfig = {};
|
|
21158
|
+
if (hadExistingMcpConfig) {
|
|
21159
|
+
try {
|
|
21160
|
+
existingMcpConfig = JSON.parse(readFileSync15(mcpConfigPath, "utf-8"));
|
|
21161
|
+
copyFileSync3(mcpConfigPath, mcpConfigPath + ".backup");
|
|
21162
|
+
} catch {
|
|
21163
|
+
}
|
|
21164
|
+
}
|
|
21165
|
+
const mcpConfig = {
|
|
21166
|
+
...existingMcpConfig,
|
|
21167
|
+
mcpServers: {
|
|
21168
|
+
...existingMcpConfig.mcpServers || {},
|
|
21169
|
+
"adhdev-mesh": {
|
|
21170
|
+
command: "adhdev-mcp",
|
|
21171
|
+
args: ["--repo-mesh", meshId]
|
|
21172
|
+
}
|
|
21173
|
+
}
|
|
21174
|
+
};
|
|
21175
|
+
writeFileSync12(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
21176
|
+
LOG.info("MeshCoordinator", `Wrote .mcp.json to ${workspace} with adhdev-mesh server`);
|
|
21177
|
+
let systemPrompt = "";
|
|
21178
|
+
try {
|
|
21179
|
+
systemPrompt = buildCoordinatorSystemPrompt2({ mesh });
|
|
21180
|
+
} catch {
|
|
21181
|
+
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).`;
|
|
21182
|
+
}
|
|
21183
|
+
const launchResult = await this.deps.cliManager.handleCliCommand("launch_cli", {
|
|
21184
|
+
cliType,
|
|
21185
|
+
dir: workspace,
|
|
21186
|
+
initialPrompt: systemPrompt
|
|
21187
|
+
});
|
|
21188
|
+
if (!launchResult?.success) {
|
|
21189
|
+
return { success: false, error: launchResult?.error || "Failed to launch CLI session" };
|
|
21190
|
+
}
|
|
21191
|
+
LOG.info("MeshCoordinator", `Launched ${cliType} coordinator for mesh ${meshId} in ${workspace}`);
|
|
21192
|
+
return {
|
|
21193
|
+
success: true,
|
|
21194
|
+
meshId,
|
|
21195
|
+
cliType,
|
|
21196
|
+
workspace,
|
|
21197
|
+
sessionId: launchResult.sessionId || launchResult.id,
|
|
21198
|
+
mcpConfigWritten: true
|
|
21199
|
+
};
|
|
21200
|
+
} catch (e) {
|
|
21201
|
+
LOG.error("MeshCoordinator", `Failed: ${e.message}`);
|
|
21202
|
+
return { success: false, error: e.message };
|
|
21203
|
+
}
|
|
21204
|
+
}
|
|
21026
21205
|
default:
|
|
21027
21206
|
break;
|
|
21028
21207
|
}
|