@adhdev/daemon-standalone 0.9.82-rc.376 → 0.9.82-rc.378
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 +3117 -3070
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/index-Mk-rTdMe.js +113 -0
- package/public/index.html +1 -1
- package/vendor/mcp-server/index.js +1369 -1328
- package/vendor/mcp-server/index.js.map +1 -1
|
@@ -35,9 +35,6 @@ __export(index_exports, {
|
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
|
|
38
|
-
// src/tools/mesh-tools.ts
|
|
39
|
-
var import_node_crypto = require("crypto");
|
|
40
|
-
|
|
41
38
|
// src/transports/ipc.ts
|
|
42
39
|
var DEFAULT_IPC_PORT = 19222;
|
|
43
40
|
var DEFAULT_IPC_PATH = "/ipc";
|
|
@@ -341,47 +338,7 @@ function compactChatPayload(payload, opts = {}) {
|
|
|
341
338
|
};
|
|
342
339
|
}
|
|
343
340
|
|
|
344
|
-
// src/tools/
|
|
345
|
-
var RAPID_READ_CHAT_ADVISORY_WINDOW_MS = 5e3;
|
|
346
|
-
var ACTIVE_READ_STATUSES = /* @__PURE__ */ new Set([
|
|
347
|
-
"generating",
|
|
348
|
-
"running",
|
|
349
|
-
"streaming",
|
|
350
|
-
"starting",
|
|
351
|
-
"busy"
|
|
352
|
-
]);
|
|
353
|
-
var recentReads = /* @__PURE__ */ new Map();
|
|
354
|
-
function isActiveReadChatStatus(status) {
|
|
355
|
-
return typeof status === "string" && ACTIVE_READ_STATUSES.has(status.toLowerCase());
|
|
356
|
-
}
|
|
357
|
-
function annotateRapidReadChatAdvisory(payload, options) {
|
|
358
|
-
const now = options.now ?? Date.now();
|
|
359
|
-
const status = options.status ?? payload?.status ?? payload?.data?.status ?? payload?.result?.status;
|
|
360
|
-
const active = isActiveReadChatStatus(status);
|
|
361
|
-
const previous = recentReads.get(options.key);
|
|
362
|
-
if (!active) {
|
|
363
|
-
recentReads.set(options.key, { at: now, status: typeof status === "string" ? status : void 0 });
|
|
364
|
-
return payload;
|
|
365
|
-
}
|
|
366
|
-
recentReads.set(options.key, { at: now, status: typeof status === "string" ? status : void 0 });
|
|
367
|
-
if (!previous || !isActiveReadChatStatus(previous.status)) return payload;
|
|
368
|
-
const elapsedMs = now - previous.at;
|
|
369
|
-
if (elapsedMs < 0 || elapsedMs >= RAPID_READ_CHAT_ADVISORY_WINDOW_MS) return payload;
|
|
370
|
-
return {
|
|
371
|
-
...payload,
|
|
372
|
-
pollingAdvisory: {
|
|
373
|
-
type: "rapid_read_chat_polling",
|
|
374
|
-
toolName: options.toolName,
|
|
375
|
-
windowMs: RAPID_READ_CHAT_ADVISORY_WINDOW_MS,
|
|
376
|
-
elapsedMs,
|
|
377
|
-
nextSuggestedReadAt: previous.at + RAPID_READ_CHAT_ADVISORY_WINDOW_MS,
|
|
378
|
-
completionCallbackExpected: Boolean(options.completionCallbackExpected),
|
|
379
|
-
message: `This session is still ${String(status)}. Avoid repeated ${options.toolName} polling for the same generating session; wait for the completion callback/status event or retry after the suggested time if you are debugging a real stall.`
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// src/tools/mesh-tools.ts
|
|
341
|
+
// src/tools/mesh-tools-internal.ts
|
|
385
342
|
var import_daemon_core2 = require("@adhdev/daemon-core");
|
|
386
343
|
|
|
387
344
|
// src/tools/mesh-tool-shared.ts
|
|
@@ -525,606 +482,219 @@ function isIdleSessionRecord(session) {
|
|
|
525
482
|
return status === "idle" || chatStatus === "waiting_input";
|
|
526
483
|
}
|
|
527
484
|
|
|
528
|
-
// src/tools/mesh-
|
|
529
|
-
var
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const nodeSessionIds = /* @__PURE__ */ new Map();
|
|
536
|
-
for (const node of Array.isArray(mesh?.nodes) ? mesh.nodes : []) {
|
|
537
|
-
const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
|
|
538
|
-
if (!nodeId) continue;
|
|
539
|
-
nodeIds.add(nodeId);
|
|
540
|
-
const sessions = collectNodeSessionIds(node);
|
|
541
|
-
if (sessions.size > 0) nodeSessionIds.set(nodeId, sessions);
|
|
542
|
-
}
|
|
543
|
-
return { nodeIds, nodeSessionIds };
|
|
544
|
-
}
|
|
545
|
-
function queueAssignmentStaleReason(task, liveness) {
|
|
546
|
-
if (task?.status !== "assigned") return void 0;
|
|
547
|
-
const nodeId = readString(task.assignedNodeId) || readString(task.nodeId) || readString(task.node_id) || readString(task.targetNodeId);
|
|
548
|
-
const sessionId = readString(task.assignedSessionId) || readString(task.sessionId) || readString(task.session_id) || readString(task.targetSessionId);
|
|
549
|
-
if (nodeId && liveness.nodeIds.size > 0 && !liveness.nodeIds.has(nodeId)) {
|
|
550
|
-
return "assigned node is not present in the current mesh snapshot";
|
|
485
|
+
// src/tools/mesh-node-identity.ts
|
|
486
|
+
var import_daemon_core = require("@adhdev/daemon-core");
|
|
487
|
+
function resolveCoordinatorNode(ctx) {
|
|
488
|
+
const preferredNodeId = typeof ctx.mesh.coordinator?.preferredNodeId === "string" ? ctx.mesh.coordinator.preferredNodeId.trim() : "";
|
|
489
|
+
if (preferredNodeId) {
|
|
490
|
+
const preferred = ctx.mesh.nodes.find((n) => n.id === preferredNodeId && typeof n.daemonId === "string" && n.daemonId.trim());
|
|
491
|
+
if (preferred) return preferred;
|
|
551
492
|
}
|
|
552
|
-
if (
|
|
553
|
-
|
|
493
|
+
if (ctx.localMachineId) {
|
|
494
|
+
const byMachine = ctx.mesh.nodes.find((n) => readNodeMachineId(n) === ctx.localMachineId);
|
|
495
|
+
if (byMachine) return byMachine;
|
|
554
496
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (!nodeId && ageMs !== null && ageMs >= STALE_ASSIGNED_QUEUE_MS) {
|
|
558
|
-
return "assigned task has no assigned node metadata";
|
|
497
|
+
if (ctx.localDaemonId) {
|
|
498
|
+
return ctx.mesh.nodes.find((n) => readNodeDaemonId(n) === ctx.localDaemonId);
|
|
559
499
|
}
|
|
560
500
|
return void 0;
|
|
561
501
|
}
|
|
562
|
-
function
|
|
563
|
-
|
|
564
|
-
let staleAssigned = 0;
|
|
565
|
-
for (const task of queue) {
|
|
566
|
-
const status = typeof task?.status === "string" ? task.status : void 0;
|
|
567
|
-
if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
|
|
568
|
-
counts[status] += 1;
|
|
569
|
-
}
|
|
570
|
-
if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
|
|
571
|
-
}
|
|
572
|
-
const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
|
|
573
|
-
return {
|
|
574
|
-
totalCount: queue.length,
|
|
575
|
-
activeCount: counts.pending + liveAssigned,
|
|
576
|
-
historicalCount: counts.completed + counts.failed + counts.cancelled,
|
|
577
|
-
counts,
|
|
578
|
-
activeCounts: {
|
|
579
|
-
pending: counts.pending,
|
|
580
|
-
assigned: liveAssigned
|
|
581
|
-
},
|
|
582
|
-
staleAssignedCount: staleAssigned,
|
|
583
|
-
rawActiveCounts: {
|
|
584
|
-
pending: counts.pending,
|
|
585
|
-
assigned: counts.assigned
|
|
586
|
-
},
|
|
587
|
-
historicalCounts: {
|
|
588
|
-
completed: counts.completed,
|
|
589
|
-
failed: counts.failed,
|
|
590
|
-
cancelled: counts.cancelled
|
|
591
|
-
}
|
|
592
|
-
};
|
|
502
|
+
function resolveCoordinatorDaemonId(ctx) {
|
|
503
|
+
return readString(resolveCoordinatorNode(ctx)?.daemonId) || readString(ctx.localDaemonId) || readString(ctx.localMachineId);
|
|
593
504
|
}
|
|
594
|
-
function
|
|
595
|
-
return
|
|
505
|
+
function readNodeMachineId(node) {
|
|
506
|
+
return readString(node.machineId) || readString(node.machine_id) || readString(node.machine?.id) || readString(node.machine?.machineId) || readString(node.lastProbe?.machineId) || readString(node.last_probe?.machine_id) || readString(node.lastProbe?.machine?.id) || readString(node.lastProbe?.machine?.machineId) || readString(node.last_probe?.machine?.id) || readString(node.last_probe?.machine?.machine_id);
|
|
596
507
|
}
|
|
597
|
-
function
|
|
598
|
-
|
|
599
|
-
const statuses = value.map((item) => typeof item === "string" ? item.trim() : "").filter((status) => ACTIVE_QUEUE_STATUSES.has(status) || HISTORICAL_QUEUE_STATUSES.has(status));
|
|
600
|
-
return statuses.length ? Array.from(new Set(statuses)) : void 0;
|
|
508
|
+
function readNodeDaemonId(node) {
|
|
509
|
+
return readString(node.daemonId) || readString(node.daemon_id) || readString(node.machine?.daemonId) || readString(node.machine?.daemon_id) || readString(node.lastProbe?.daemonId) || readString(node.last_probe?.daemon_id) || readString(node.lastProbe?.machine?.daemonId) || readString(node.lastProbe?.machine?.daemon_id) || readString(node.last_probe?.machine?.daemonId) || readString(node.last_probe?.machine?.daemon_id);
|
|
601
510
|
}
|
|
602
|
-
function
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
}
|
|
607
|
-
if (view === "active") return queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
608
|
-
if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
609
|
-
return queue;
|
|
511
|
+
function normalizeHostname(value) {
|
|
512
|
+
const hostname = readString(value);
|
|
513
|
+
if (!hostname) return void 0;
|
|
514
|
+
return hostname.toLowerCase().replace(/\.$/, "");
|
|
610
515
|
}
|
|
611
|
-
function
|
|
612
|
-
|
|
613
|
-
const historical = [];
|
|
614
|
-
const other = [];
|
|
615
|
-
for (const task of queue) {
|
|
616
|
-
const status = String(task?.status || "");
|
|
617
|
-
if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
|
|
618
|
-
else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
|
|
619
|
-
else other.push(task);
|
|
620
|
-
}
|
|
621
|
-
return [...active, ...other, ...historical];
|
|
516
|
+
function readNodeHostname(node) {
|
|
517
|
+
return readString(node.hostname) || readString(node.host) || readString(node.machineHostname) || readString(node.machine_hostname) || readString(node.machine?.hostname) || readString(node.machine?.host) || readString(node.lastProbe?.hostname) || readString(node.last_probe?.hostname) || readString(node.lastProbe?.machine?.hostname) || readString(node.last_probe?.machine?.hostname);
|
|
622
518
|
}
|
|
623
|
-
function
|
|
624
|
-
return
|
|
625
|
-
id: task?.id,
|
|
626
|
-
status: task?.status,
|
|
627
|
-
assignedNodeId: task?.assignedNodeId,
|
|
628
|
-
assignedSessionId: task?.assignedSessionId,
|
|
629
|
-
targetNodeId: task?.targetNodeId,
|
|
630
|
-
targetSessionId: task?.targetSessionId,
|
|
631
|
-
updatedAt: task?.updatedAt,
|
|
632
|
-
staleAssigned: task?.staleAssigned === true,
|
|
633
|
-
staleReason: task?.staleReason
|
|
634
|
-
};
|
|
519
|
+
function readNodeDisplayMachineName(node) {
|
|
520
|
+
return readString(node.machineName) || readString(node.machine_name) || readString(node.machineLabel) || readString(node.machine_label) || readString(node.machineNickname) || readString(node.machine_nickname) || readString(node.alias) || readString(node.machine?.name) || readString(node.machine?.displayName) || readString(node.machine?.display_name) || readString(node.lastProbe?.machineName) || readString(node.last_probe?.machine_name) || readString(node.lastProbe?.machine?.name) || readString(node.last_probe?.machine?.name) || readNodeHostname(node);
|
|
635
521
|
}
|
|
636
|
-
function
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
522
|
+
function compactIdentityEvidence(value) {
|
|
523
|
+
if (!value) return void 0;
|
|
524
|
+
return value.length > 24 ? `${value.slice(0, 12)}\u2026${value.slice(-8)}` : value;
|
|
525
|
+
}
|
|
526
|
+
function pushIdentityEvidence(evidence, label, value) {
|
|
527
|
+
const compact = compactIdentityEvidence(value);
|
|
528
|
+
if (compact) evidence.push(`${label}:${compact}`);
|
|
529
|
+
}
|
|
530
|
+
function buildNodeMachineIdentity(ctx, node) {
|
|
531
|
+
const machineId = readNodeMachineId(node);
|
|
532
|
+
const daemonId = readNodeDaemonId(node);
|
|
533
|
+
const hostname = readNodeHostname(node);
|
|
534
|
+
const machineName = readNodeDisplayMachineName(node);
|
|
535
|
+
const coordinatorHostname = readString(ctx.coordinatorHostname);
|
|
536
|
+
const localControlPlaneReason = getLocalControlPlaneMatchReason(ctx, node);
|
|
537
|
+
const directLocal = !!localControlPlaneReason;
|
|
538
|
+
const hostnameMatches = Boolean(
|
|
539
|
+
normalizeHostname(hostname) && normalizeHostname(coordinatorHostname) && normalizeHostname(hostname) === normalizeHostname(coordinatorHostname)
|
|
540
|
+
);
|
|
541
|
+
const sameMachine = directLocal || hostnameMatches;
|
|
542
|
+
const evidence = [];
|
|
543
|
+
pushIdentityEvidence(evidence, "machineName", machineName);
|
|
544
|
+
pushIdentityEvidence(evidence, "hostname", hostname);
|
|
545
|
+
pushIdentityEvidence(evidence, "machineId", machineId);
|
|
546
|
+
pushIdentityEvidence(evidence, "daemonId", daemonId);
|
|
547
|
+
if (localControlPlaneReason) {
|
|
548
|
+
pushIdentityEvidence(evidence, "localMatch", localControlPlaneReason);
|
|
549
|
+
pushIdentityEvidence(evidence, "localMachineId", ctx.localMachineId);
|
|
550
|
+
pushIdentityEvidence(evidence, "localDaemonId", ctx.localDaemonId);
|
|
551
|
+
}
|
|
552
|
+
const locality = sameMachine ? "same_machine" : evidence.length > 0 ? "remote_known" : "remote_or_unknown";
|
|
553
|
+
const localityReason = sameMachine ? localControlPlaneReason || "matched coordinator hostname" : evidence.length > 0 ? `known remote/other machine identity; no local coordinator match (${evidence.join(", ")})` : "no useful machine identity evidence available";
|
|
660
554
|
return {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
cleanupCandidateCount: cleanupCandidates.length
|
|
555
|
+
daemonId,
|
|
556
|
+
machineId,
|
|
557
|
+
hostname,
|
|
558
|
+
machineName,
|
|
559
|
+
displayName: machineName || hostname || daemonId || machineId,
|
|
560
|
+
coordinatorHostname,
|
|
561
|
+
sameMachine,
|
|
562
|
+
locality,
|
|
563
|
+
localityReason,
|
|
564
|
+
identityEvidence: evidence
|
|
672
565
|
};
|
|
673
566
|
}
|
|
674
|
-
function
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
sourceOfTruth: "mesh_work_queue_file",
|
|
681
|
-
payloadMode: "compact",
|
|
682
|
-
staleAssignedDefinition: maintenance.staleAssignedDefinition,
|
|
683
|
-
historicalDefinition: maintenance.historicalDefinition,
|
|
684
|
-
// staleAssignedTasks are active assigned rows (not historical) — retain a
|
|
685
|
-
// bounded sample so coordinators can still see drift without the full array.
|
|
686
|
-
staleAssignedTasks: staleAssignedTasks.slice(0, 5),
|
|
687
|
-
staleAssignedSampleLimit: 5,
|
|
688
|
-
staleAssignedCount: maintenance.staleAssignedCount ?? staleAssignedTasks.length,
|
|
689
|
-
historicalRecordCount: maintenance.historicalRecordCount ?? 0,
|
|
690
|
-
oldHistoricalRecordCount: maintenance.oldHistoricalRecordCount ?? 0,
|
|
691
|
-
cleanupCandidateCount,
|
|
692
|
-
cleanupCandidatesOmitted: true,
|
|
693
|
-
cleanupCandidatesHint: "Per-row cleanup candidates are omitted in compact mode; call mesh_view_queue with verbose=true for the full maintenance/cleanupDryRun rows."
|
|
567
|
+
function nodeHasLocalDaemonEvidence(ctx, node) {
|
|
568
|
+
const isLocal = (session) => {
|
|
569
|
+
if (!session || typeof session !== "object") return false;
|
|
570
|
+
if (ctx.localDaemonId && (0, import_daemon_core.daemonIdsEquivalent)(session.runtime?.owner, ctx.localDaemonId)) return true;
|
|
571
|
+
if (ctx.localDaemonId && (0, import_daemon_core.daemonIdsEquivalent)(session.daemonClient?.daemonId, ctx.localDaemonId)) return true;
|
|
572
|
+
return false;
|
|
694
573
|
};
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const slim = {};
|
|
707
|
-
for (const [k, v] of Object.entries(task)) {
|
|
708
|
-
if (k === "message") slim[k] = truncateForCompact(v, COMPACT_QUEUE_MESSAGE_CAP);
|
|
709
|
-
else slim[k] = elideLargeNestedValue(k, v);
|
|
574
|
+
const sessionArrays = [
|
|
575
|
+
node?.sessions,
|
|
576
|
+
node?.activeSessions,
|
|
577
|
+
node?.active_sessions,
|
|
578
|
+
node?.lastProbe?.sessions,
|
|
579
|
+
node?.last_probe?.sessions,
|
|
580
|
+
node?.lastProbe?.status?.sessions,
|
|
581
|
+
node?.last_probe?.status?.sessions
|
|
582
|
+
];
|
|
583
|
+
for (const arr of sessionArrays) {
|
|
584
|
+
if (Array.isArray(arr) && arr.some(isLocal)) return true;
|
|
710
585
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
586
|
+
const sessionRecords = [
|
|
587
|
+
node?.activeSession,
|
|
588
|
+
node?.active_session,
|
|
589
|
+
node?.currentSession,
|
|
590
|
+
node?.current_session,
|
|
591
|
+
node?.runtimeSession,
|
|
592
|
+
node?.runtime_session,
|
|
593
|
+
node?.session,
|
|
594
|
+
node?.lastProbe?.activeSession,
|
|
595
|
+
node?.last_probe?.active_session,
|
|
596
|
+
node?.lastProbe?.currentSession,
|
|
597
|
+
node?.last_probe?.current_session,
|
|
598
|
+
node?.lastProbe?.session,
|
|
599
|
+
node?.last_probe?.session
|
|
600
|
+
];
|
|
601
|
+
for (const session of sessionRecords) {
|
|
602
|
+
if (isLocal(session)) return true;
|
|
724
603
|
}
|
|
725
|
-
return
|
|
604
|
+
return false;
|
|
726
605
|
}
|
|
727
|
-
function
|
|
728
|
-
|
|
729
|
-
const
|
|
730
|
-
return
|
|
606
|
+
function isDirectLocalNode(ctx, node) {
|
|
607
|
+
const machineId = readNodeMachineId(node);
|
|
608
|
+
const daemonId = readNodeDaemonId(node);
|
|
609
|
+
return Boolean(
|
|
610
|
+
ctx.localMachineId && (0, import_daemon_core.daemonIdsEquivalent)(machineId, ctx.localMachineId) || ctx.localDaemonId && (0, import_daemon_core.daemonIdsEquivalent)(daemonId, ctx.localDaemonId) || nodeHasLocalDaemonEvidence(ctx, node)
|
|
611
|
+
);
|
|
731
612
|
}
|
|
732
|
-
function
|
|
733
|
-
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
completedAt: task.updatedAt
|
|
746
|
-
} : {}
|
|
747
|
-
};
|
|
748
|
-
if (taskStatus !== "assigned") return annotated;
|
|
749
|
-
const updatedAt = new Date(task.updatedAt).getTime();
|
|
750
|
-
const ageMs = Number.isFinite(updatedAt) ? now - updatedAt : null;
|
|
751
|
-
const staleReason = queueAssignmentStaleReason(task, liveness);
|
|
752
|
-
if (!staleReason) return annotated;
|
|
753
|
-
return {
|
|
754
|
-
...annotated,
|
|
755
|
-
stale: true,
|
|
756
|
-
staleAssigned: true,
|
|
757
|
-
staleReason,
|
|
758
|
-
...ageMs !== null ? { assignedAgeMs: ageMs } : {}
|
|
759
|
-
};
|
|
760
|
-
});
|
|
613
|
+
function isConfiguredCoordinatorNode(ctx, node) {
|
|
614
|
+
if (!ctx.localMachineId && !ctx.localDaemonId) return false;
|
|
615
|
+
const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
|
|
616
|
+
if (!nodeId) return false;
|
|
617
|
+
const nodeDaemonId = readNodeDaemonId(node);
|
|
618
|
+
const nodeMachineId = readNodeMachineId(node);
|
|
619
|
+
if (nodeDaemonId && ctx.localDaemonId && !(0, import_daemon_core.daemonIdsEquivalent)(nodeDaemonId, ctx.localDaemonId)) return false;
|
|
620
|
+
if (nodeMachineId && ctx.localMachineId && !(0, import_daemon_core.daemonIdsEquivalent)(nodeMachineId, ctx.localMachineId)) return false;
|
|
621
|
+
const preferredNodeId = readString(ctx.mesh.coordinator?.preferredNodeId) || readString(ctx.mesh.coordinator?.preferred_node_id);
|
|
622
|
+
if (preferredNodeId) return nodeId === preferredNodeId;
|
|
623
|
+
const first = ctx.mesh.nodes?.[0];
|
|
624
|
+
const firstNodeId = readString(first?.id) || readString(first?.nodeId) || readString(first?.node_id);
|
|
625
|
+
return !!firstNodeId && nodeId === firstNodeId;
|
|
761
626
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
if (
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
"
|
|
769
|
-
"branch",
|
|
770
|
-
"headCommit",
|
|
771
|
-
"upstream",
|
|
772
|
-
"upstreamStatus",
|
|
773
|
-
"ahead",
|
|
774
|
-
"behind",
|
|
775
|
-
"dirty",
|
|
776
|
-
"detached",
|
|
777
|
-
"submodules"
|
|
778
|
-
];
|
|
779
|
-
for (const key of carry) {
|
|
780
|
-
if (status[key] !== void 0) slim[key] = status[key];
|
|
627
|
+
function getLocalControlPlaneMatchReason(ctx, node) {
|
|
628
|
+
if (isDirectLocalNode(ctx, node)) return "matched coordinator daemon or machine id";
|
|
629
|
+
if (isConfiguredCoordinatorNode(ctx, node)) return "matched configured coordinator node";
|
|
630
|
+
if (node.isLocalWorktree === true) {
|
|
631
|
+
const sourceNode = findClonedFromNode(ctx, node);
|
|
632
|
+
if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return "matched local cloned-from node";
|
|
633
|
+
if (sourceNode && isConfiguredCoordinatorNode(ctx, sourceNode)) return "matched configured coordinator source node";
|
|
781
634
|
}
|
|
782
|
-
return
|
|
635
|
+
return void 0;
|
|
783
636
|
}
|
|
784
|
-
function
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
return
|
|
788
|
-
count: submodules.length,
|
|
789
|
-
...outOfSync.length > 0 ? { outOfSyncPaths: outOfSync } : {}
|
|
790
|
-
};
|
|
637
|
+
function findClonedFromNode(ctx, node) {
|
|
638
|
+
const clonedFromNodeId = readString(node.clonedFromNodeId) || readString(node.cloned_from_node_id);
|
|
639
|
+
if (!clonedFromNodeId) return void 0;
|
|
640
|
+
return ctx.mesh.nodes.find((n) => (0, import_daemon_core.meshNodeIdMatches)(n, clonedFromNodeId));
|
|
791
641
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
if (
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
642
|
+
function resolvePreferredWorktreeNodeId(ctx) {
|
|
643
|
+
const worktreeNodes = (ctx.mesh.nodes || []).filter((n) => n.isLocalWorktree === true);
|
|
644
|
+
if (worktreeNodes.length === 0) return void 0;
|
|
645
|
+
const chosen = worktreeNodes[worktreeNodes.length - 1];
|
|
646
|
+
return readString(chosen?.id) || readString(chosen?.nodeId) || readString(chosen?.node_id);
|
|
647
|
+
}
|
|
648
|
+
function isLocalControlPlaneNode(ctx, node) {
|
|
649
|
+
return !!getLocalControlPlaneMatchReason(ctx, node);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/tools/mesh-tool-schemas.ts
|
|
653
|
+
var MESH_STATUS_TOOL = {
|
|
654
|
+
name: "mesh_status",
|
|
655
|
+
description: "Get the current status of all nodes in the repo mesh \u2014 health, git state, active sessions, recovery hints, and recommended next steps. Use this to decide which node to send work to or how to recover from failures. Also reports the running daemon build per daemonId under top-level daemonBuilds ({commit, commitShort, version}); when a live daemon was built from a commit BEHIND its workspace HEAD it adds staleDaemonBuilds[] + staleDaemonBuildWarning \u2014 meaning a just-merged refinery/mesh-tool fix is NOT yet live on that daemon (awaiting deploy/restart; a local dist rebuild does not update a cloud daemon). Do not repeatedly call this to wait for generating delegated work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
|
|
656
|
+
inputSchema: {
|
|
657
|
+
type: "object",
|
|
658
|
+
properties: {
|
|
659
|
+
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
|
|
660
|
+
includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." },
|
|
661
|
+
includeSessions: { type: "boolean", description: "Opt in to per-node live session arrays. Default false: compact mode returns a per-node sessionSummary (counts) and de-duplicated full session lists under top-level daemonSessions keyed by daemonId (sessions are not repeated for every node that shares a daemon). Set true to also include the full session array on each node." },
|
|
662
|
+
compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Folds per-node session arrays to sessionSummary and de-duplicates daemon-shared sessions into daemonSessions. Set false (or verbose=true) for the full dashboard-grade payload." },
|
|
663
|
+
verbose: { type: "boolean", description: "Force the full payload; overrides compact." }
|
|
805
664
|
}
|
|
806
665
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
if (typeof next.submoduleWarning === "string") {
|
|
819
|
-
next.submodulesOutOfSync = true;
|
|
820
|
-
delete next.submoduleWarning;
|
|
821
|
-
}
|
|
822
|
-
if (next.staleDaemonBuild && typeof next.staleDaemonBuild === "object") {
|
|
823
|
-
const b = next.staleDaemonBuild;
|
|
824
|
-
next.staleDaemonBuild = {
|
|
825
|
-
scope: b.scope,
|
|
826
|
-
isDaemonAffecting: b.isDaemonAffecting !== false,
|
|
827
|
-
seeStaleDaemonBuilds: true
|
|
828
|
-
};
|
|
666
|
+
};
|
|
667
|
+
var MESH_LIST_NODES_TOOL = {
|
|
668
|
+
name: "mesh_list_nodes",
|
|
669
|
+
description: "List all nodes in the mesh with their capabilities, platform, and workspace paths.",
|
|
670
|
+
inputSchema: {
|
|
671
|
+
type: "object",
|
|
672
|
+
properties: {
|
|
673
|
+
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
|
|
674
|
+
}
|
|
829
675
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
if (entry.launchReady === false) return true;
|
|
853
|
-
if (entry.staleDaemonBuild) return true;
|
|
854
|
-
if (entry.submoduleWarning || entry.submodulesOutOfSync) return true;
|
|
855
|
-
if (entry.recoveryHints) return true;
|
|
856
|
-
if (Array.isArray(entry.nextStepHints) && entry.nextStepHints.length > 0) return true;
|
|
857
|
-
if (entry.branchConvergence?.needsConvergence === true) return true;
|
|
858
|
-
const sessionCount = Array.isArray(entry.sessions) ? entry.sessions.length : entry.sessionSummary?.total ?? 0;
|
|
859
|
-
if (sessionCount > 0) return true;
|
|
860
|
-
return false;
|
|
861
|
-
}
|
|
862
|
-
function minimalCompactNode(entry) {
|
|
863
|
-
if (!entry || typeof entry !== "object") return entry;
|
|
864
|
-
const bc = entry.branchConvergence && typeof entry.branchConvergence === "object" ? {
|
|
865
|
-
status: entry.branchConvergence.status,
|
|
866
|
-
needsConvergence: entry.branchConvergence.needsConvergence,
|
|
867
|
-
reason: entry.branchConvergence.reason,
|
|
868
|
-
branch: entry.branchConvergence.branch
|
|
869
|
-
} : void 0;
|
|
870
|
-
const preservedMarkers = {};
|
|
871
|
-
for (const field of MESH_COMPACT_PRESERVED_MARKER_FIELDS) {
|
|
872
|
-
if (entry[field] !== void 0) preservedMarkers[field] = entry[field];
|
|
873
|
-
}
|
|
874
|
-
return {
|
|
875
|
-
nodeId: entry.nodeId,
|
|
876
|
-
workspace: entry.workspace,
|
|
877
|
-
daemonId: entry.daemonId,
|
|
878
|
-
health: entry.health,
|
|
879
|
-
branch: entry.branch,
|
|
880
|
-
launchReady: entry.launchReady,
|
|
881
|
-
...entry.providerPriority !== void 0 ? { providerPriority: entry.providerPriority } : {},
|
|
882
|
-
// Keep the routable tag set on quiet/folded nodes — a coordinator planning
|
|
883
|
-
// required_tags routing needs it even for nodes with nothing to converge.
|
|
884
|
-
...entry.capabilityTags !== void 0 ? { capabilityTags: entry.capabilityTags } : {},
|
|
885
|
-
...entry.launchBlockedReason !== void 0 ? { launchBlockedReason: entry.launchBlockedReason } : {},
|
|
886
|
-
...bc ? { branchConvergence: bc } : {},
|
|
887
|
-
...entry.sessionSummary ? { sessionSummary: entry.sessionSummary } : {},
|
|
888
|
-
...preservedMarkers,
|
|
889
|
-
folded: true
|
|
890
|
-
};
|
|
891
|
-
}
|
|
892
|
-
function summarizeNodeSessions(sessions) {
|
|
893
|
-
const list = Array.isArray(sessions) ? sessions : [];
|
|
894
|
-
const byStatus = {};
|
|
895
|
-
const providerCounts = {};
|
|
896
|
-
const selfCoordinatorSessionIds = [];
|
|
897
|
-
for (const s of list) {
|
|
898
|
-
const status = typeof s?.status === "string" && s.status ? s.status : "unknown";
|
|
899
|
-
byStatus[status] = (byStatus[status] ?? 0) + 1;
|
|
900
|
-
const provider = typeof s?.providerType === "string" && s.providerType ? s.providerType : "unknown";
|
|
901
|
-
providerCounts[provider] = (providerCounts[provider] ?? 0) + 1;
|
|
902
|
-
if (s?.isSelfCoordinator === true && s.id) selfCoordinatorSessionIds.push(String(s.id));
|
|
903
|
-
}
|
|
904
|
-
const summary = {
|
|
905
|
-
total: list.length,
|
|
906
|
-
byStatus,
|
|
907
|
-
providerCounts
|
|
908
|
-
};
|
|
909
|
-
if (selfCoordinatorSessionIds.length > 0) {
|
|
910
|
-
summary.selfCoordinatorSessionIds = selfCoordinatorSessionIds;
|
|
911
|
-
}
|
|
912
|
-
return summary;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
// src/tools/mesh-node-identity.ts
|
|
916
|
-
var import_daemon_core = require("@adhdev/daemon-core");
|
|
917
|
-
function resolveCoordinatorNode(ctx) {
|
|
918
|
-
const preferredNodeId = typeof ctx.mesh.coordinator?.preferredNodeId === "string" ? ctx.mesh.coordinator.preferredNodeId.trim() : "";
|
|
919
|
-
if (preferredNodeId) {
|
|
920
|
-
const preferred = ctx.mesh.nodes.find((n) => n.id === preferredNodeId && typeof n.daemonId === "string" && n.daemonId.trim());
|
|
921
|
-
if (preferred) return preferred;
|
|
922
|
-
}
|
|
923
|
-
if (ctx.localMachineId) {
|
|
924
|
-
const byMachine = ctx.mesh.nodes.find((n) => readNodeMachineId(n) === ctx.localMachineId);
|
|
925
|
-
if (byMachine) return byMachine;
|
|
926
|
-
}
|
|
927
|
-
if (ctx.localDaemonId) {
|
|
928
|
-
return ctx.mesh.nodes.find((n) => readNodeDaemonId(n) === ctx.localDaemonId);
|
|
929
|
-
}
|
|
930
|
-
return void 0;
|
|
931
|
-
}
|
|
932
|
-
function resolveCoordinatorDaemonId(ctx) {
|
|
933
|
-
return readString(resolveCoordinatorNode(ctx)?.daemonId) || readString(ctx.localDaemonId) || readString(ctx.localMachineId);
|
|
934
|
-
}
|
|
935
|
-
function readNodeMachineId(node) {
|
|
936
|
-
return readString(node.machineId) || readString(node.machine_id) || readString(node.machine?.id) || readString(node.machine?.machineId) || readString(node.lastProbe?.machineId) || readString(node.last_probe?.machine_id) || readString(node.lastProbe?.machine?.id) || readString(node.lastProbe?.machine?.machineId) || readString(node.last_probe?.machine?.id) || readString(node.last_probe?.machine?.machine_id);
|
|
937
|
-
}
|
|
938
|
-
function readNodeDaemonId(node) {
|
|
939
|
-
return readString(node.daemonId) || readString(node.daemon_id) || readString(node.machine?.daemonId) || readString(node.machine?.daemon_id) || readString(node.lastProbe?.daemonId) || readString(node.last_probe?.daemon_id) || readString(node.lastProbe?.machine?.daemonId) || readString(node.lastProbe?.machine?.daemon_id) || readString(node.last_probe?.machine?.daemonId) || readString(node.last_probe?.machine?.daemon_id);
|
|
940
|
-
}
|
|
941
|
-
function normalizeHostname(value) {
|
|
942
|
-
const hostname = readString(value);
|
|
943
|
-
if (!hostname) return void 0;
|
|
944
|
-
return hostname.toLowerCase().replace(/\.$/, "");
|
|
945
|
-
}
|
|
946
|
-
function readNodeHostname(node) {
|
|
947
|
-
return readString(node.hostname) || readString(node.host) || readString(node.machineHostname) || readString(node.machine_hostname) || readString(node.machine?.hostname) || readString(node.machine?.host) || readString(node.lastProbe?.hostname) || readString(node.last_probe?.hostname) || readString(node.lastProbe?.machine?.hostname) || readString(node.last_probe?.machine?.hostname);
|
|
948
|
-
}
|
|
949
|
-
function readNodeDisplayMachineName(node) {
|
|
950
|
-
return readString(node.machineName) || readString(node.machine_name) || readString(node.machineLabel) || readString(node.machine_label) || readString(node.machineNickname) || readString(node.machine_nickname) || readString(node.alias) || readString(node.machine?.name) || readString(node.machine?.displayName) || readString(node.machine?.display_name) || readString(node.lastProbe?.machineName) || readString(node.last_probe?.machine_name) || readString(node.lastProbe?.machine?.name) || readString(node.last_probe?.machine?.name) || readNodeHostname(node);
|
|
951
|
-
}
|
|
952
|
-
function compactIdentityEvidence(value) {
|
|
953
|
-
if (!value) return void 0;
|
|
954
|
-
return value.length > 24 ? `${value.slice(0, 12)}\u2026${value.slice(-8)}` : value;
|
|
955
|
-
}
|
|
956
|
-
function pushIdentityEvidence(evidence, label, value) {
|
|
957
|
-
const compact = compactIdentityEvidence(value);
|
|
958
|
-
if (compact) evidence.push(`${label}:${compact}`);
|
|
959
|
-
}
|
|
960
|
-
function buildNodeMachineIdentity(ctx, node) {
|
|
961
|
-
const machineId = readNodeMachineId(node);
|
|
962
|
-
const daemonId = readNodeDaemonId(node);
|
|
963
|
-
const hostname = readNodeHostname(node);
|
|
964
|
-
const machineName = readNodeDisplayMachineName(node);
|
|
965
|
-
const coordinatorHostname = readString(ctx.coordinatorHostname);
|
|
966
|
-
const localControlPlaneReason = getLocalControlPlaneMatchReason(ctx, node);
|
|
967
|
-
const directLocal = !!localControlPlaneReason;
|
|
968
|
-
const hostnameMatches = Boolean(
|
|
969
|
-
normalizeHostname(hostname) && normalizeHostname(coordinatorHostname) && normalizeHostname(hostname) === normalizeHostname(coordinatorHostname)
|
|
970
|
-
);
|
|
971
|
-
const sameMachine = directLocal || hostnameMatches;
|
|
972
|
-
const evidence = [];
|
|
973
|
-
pushIdentityEvidence(evidence, "machineName", machineName);
|
|
974
|
-
pushIdentityEvidence(evidence, "hostname", hostname);
|
|
975
|
-
pushIdentityEvidence(evidence, "machineId", machineId);
|
|
976
|
-
pushIdentityEvidence(evidence, "daemonId", daemonId);
|
|
977
|
-
if (localControlPlaneReason) {
|
|
978
|
-
pushIdentityEvidence(evidence, "localMatch", localControlPlaneReason);
|
|
979
|
-
pushIdentityEvidence(evidence, "localMachineId", ctx.localMachineId);
|
|
980
|
-
pushIdentityEvidence(evidence, "localDaemonId", ctx.localDaemonId);
|
|
981
|
-
}
|
|
982
|
-
const locality = sameMachine ? "same_machine" : evidence.length > 0 ? "remote_known" : "remote_or_unknown";
|
|
983
|
-
const localityReason = sameMachine ? localControlPlaneReason || "matched coordinator hostname" : evidence.length > 0 ? `known remote/other machine identity; no local coordinator match (${evidence.join(", ")})` : "no useful machine identity evidence available";
|
|
984
|
-
return {
|
|
985
|
-
daemonId,
|
|
986
|
-
machineId,
|
|
987
|
-
hostname,
|
|
988
|
-
machineName,
|
|
989
|
-
displayName: machineName || hostname || daemonId || machineId,
|
|
990
|
-
coordinatorHostname,
|
|
991
|
-
sameMachine,
|
|
992
|
-
locality,
|
|
993
|
-
localityReason,
|
|
994
|
-
identityEvidence: evidence
|
|
995
|
-
};
|
|
996
|
-
}
|
|
997
|
-
function nodeHasLocalDaemonEvidence(ctx, node) {
|
|
998
|
-
const isLocal = (session) => {
|
|
999
|
-
if (!session || typeof session !== "object") return false;
|
|
1000
|
-
if (ctx.localDaemonId && session.runtime?.owner === ctx.localDaemonId) return true;
|
|
1001
|
-
if (ctx.localDaemonId && session.daemonClient?.daemonId === ctx.localDaemonId) return true;
|
|
1002
|
-
return false;
|
|
1003
|
-
};
|
|
1004
|
-
const sessionArrays = [
|
|
1005
|
-
node?.sessions,
|
|
1006
|
-
node?.activeSessions,
|
|
1007
|
-
node?.active_sessions,
|
|
1008
|
-
node?.lastProbe?.sessions,
|
|
1009
|
-
node?.last_probe?.sessions,
|
|
1010
|
-
node?.lastProbe?.status?.sessions,
|
|
1011
|
-
node?.last_probe?.status?.sessions
|
|
1012
|
-
];
|
|
1013
|
-
for (const arr of sessionArrays) {
|
|
1014
|
-
if (Array.isArray(arr) && arr.some(isLocal)) return true;
|
|
1015
|
-
}
|
|
1016
|
-
const sessionRecords = [
|
|
1017
|
-
node?.activeSession,
|
|
1018
|
-
node?.active_session,
|
|
1019
|
-
node?.currentSession,
|
|
1020
|
-
node?.current_session,
|
|
1021
|
-
node?.runtimeSession,
|
|
1022
|
-
node?.runtime_session,
|
|
1023
|
-
node?.session,
|
|
1024
|
-
node?.lastProbe?.activeSession,
|
|
1025
|
-
node?.last_probe?.active_session,
|
|
1026
|
-
node?.lastProbe?.currentSession,
|
|
1027
|
-
node?.last_probe?.current_session,
|
|
1028
|
-
node?.lastProbe?.session,
|
|
1029
|
-
node?.last_probe?.session
|
|
1030
|
-
];
|
|
1031
|
-
for (const session of sessionRecords) {
|
|
1032
|
-
if (isLocal(session)) return true;
|
|
1033
|
-
}
|
|
1034
|
-
return false;
|
|
1035
|
-
}
|
|
1036
|
-
function isDirectLocalNode(ctx, node) {
|
|
1037
|
-
const machineId = readNodeMachineId(node);
|
|
1038
|
-
const daemonId = readNodeDaemonId(node);
|
|
1039
|
-
return Boolean(
|
|
1040
|
-
ctx.localMachineId && (0, import_daemon_core.daemonIdsEquivalent)(machineId, ctx.localMachineId) || ctx.localDaemonId && (0, import_daemon_core.daemonIdsEquivalent)(daemonId, ctx.localDaemonId) || nodeHasLocalDaemonEvidence(ctx, node)
|
|
1041
|
-
);
|
|
1042
|
-
}
|
|
1043
|
-
function isConfiguredCoordinatorNode(ctx, node) {
|
|
1044
|
-
if (!ctx.localMachineId && !ctx.localDaemonId) return false;
|
|
1045
|
-
const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
|
|
1046
|
-
if (!nodeId) return false;
|
|
1047
|
-
const nodeDaemonId = readNodeDaemonId(node);
|
|
1048
|
-
const nodeMachineId = readNodeMachineId(node);
|
|
1049
|
-
if (nodeDaemonId && ctx.localDaemonId && !(0, import_daemon_core.daemonIdsEquivalent)(nodeDaemonId, ctx.localDaemonId)) return false;
|
|
1050
|
-
if (nodeMachineId && ctx.localMachineId && !(0, import_daemon_core.daemonIdsEquivalent)(nodeMachineId, ctx.localMachineId)) return false;
|
|
1051
|
-
const preferredNodeId = readString(ctx.mesh.coordinator?.preferredNodeId) || readString(ctx.mesh.coordinator?.preferred_node_id);
|
|
1052
|
-
if (preferredNodeId) return nodeId === preferredNodeId;
|
|
1053
|
-
const first = ctx.mesh.nodes?.[0];
|
|
1054
|
-
const firstNodeId = readString(first?.id) || readString(first?.nodeId) || readString(first?.node_id);
|
|
1055
|
-
return !!firstNodeId && nodeId === firstNodeId;
|
|
1056
|
-
}
|
|
1057
|
-
function getLocalControlPlaneMatchReason(ctx, node) {
|
|
1058
|
-
if (isDirectLocalNode(ctx, node)) return "matched coordinator daemon or machine id";
|
|
1059
|
-
if (isConfiguredCoordinatorNode(ctx, node)) return "matched configured coordinator node";
|
|
1060
|
-
if (node.isLocalWorktree === true) {
|
|
1061
|
-
const sourceNode = findClonedFromNode(ctx, node);
|
|
1062
|
-
if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return "matched local cloned-from node";
|
|
1063
|
-
if (sourceNode && isConfiguredCoordinatorNode(ctx, sourceNode)) return "matched configured coordinator source node";
|
|
1064
|
-
}
|
|
1065
|
-
return void 0;
|
|
1066
|
-
}
|
|
1067
|
-
function findClonedFromNode(ctx, node) {
|
|
1068
|
-
const clonedFromNodeId = readString(node.clonedFromNodeId) || readString(node.cloned_from_node_id);
|
|
1069
|
-
if (!clonedFromNodeId) return void 0;
|
|
1070
|
-
return ctx.mesh.nodes.find((n) => (0, import_daemon_core.meshNodeIdMatches)(n, clonedFromNodeId));
|
|
1071
|
-
}
|
|
1072
|
-
function resolvePreferredWorktreeNodeId(ctx) {
|
|
1073
|
-
const worktreeNodes = (ctx.mesh.nodes || []).filter((n) => n.isLocalWorktree === true);
|
|
1074
|
-
if (worktreeNodes.length === 0) return void 0;
|
|
1075
|
-
const chosen = worktreeNodes[worktreeNodes.length - 1];
|
|
1076
|
-
return readString(chosen?.id) || readString(chosen?.nodeId) || readString(chosen?.node_id);
|
|
1077
|
-
}
|
|
1078
|
-
function isLocalControlPlaneNode(ctx, node) {
|
|
1079
|
-
return !!getLocalControlPlaneMatchReason(ctx, node);
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
// src/tools/mesh-tool-schemas.ts
|
|
1083
|
-
var MESH_STATUS_TOOL = {
|
|
1084
|
-
name: "mesh_status",
|
|
1085
|
-
description: "Get the current status of all nodes in the repo mesh \u2014 health, git state, active sessions, recovery hints, and recommended next steps. Use this to decide which node to send work to or how to recover from failures. Also reports the running daemon build per daemonId under top-level daemonBuilds ({commit, commitShort, version}); when a live daemon was built from a commit BEHIND its workspace HEAD it adds staleDaemonBuilds[] + staleDaemonBuildWarning \u2014 meaning a just-merged refinery/mesh-tool fix is NOT yet live on that daemon (awaiting deploy/restart; a local dist rebuild does not update a cloud daemon). Do not repeatedly call this to wait for generating delegated work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
|
|
1086
|
-
inputSchema: {
|
|
1087
|
-
type: "object",
|
|
1088
|
-
properties: {
|
|
1089
|
-
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
|
|
1090
|
-
includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." },
|
|
1091
|
-
includeSessions: { type: "boolean", description: "Opt in to per-node live session arrays. Default false: compact mode returns a per-node sessionSummary (counts) and de-duplicated full session lists under top-level daemonSessions keyed by daemonId (sessions are not repeated for every node that shares a daemon). Set true to also include the full session array on each node." },
|
|
1092
|
-
compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Folds per-node session arrays to sessionSummary and de-duplicates daemon-shared sessions into daemonSessions. Set false (or verbose=true) for the full dashboard-grade payload." },
|
|
1093
|
-
verbose: { type: "boolean", description: "Force the full payload; overrides compact." }
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
};
|
|
1097
|
-
var MESH_LIST_NODES_TOOL = {
|
|
1098
|
-
name: "mesh_list_nodes",
|
|
1099
|
-
description: "List all nodes in the mesh with their capabilities, platform, and workspace paths.",
|
|
1100
|
-
inputSchema: {
|
|
1101
|
-
type: "object",
|
|
1102
|
-
properties: {
|
|
1103
|
-
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
};
|
|
1107
|
-
var MESH_ENQUEUE_TASK_TOOL = {
|
|
1108
|
-
name: "mesh_enqueue_task",
|
|
1109
|
-
description: "Add a new task to the mesh work queue. Idle nodes will automatically pull and execute tasks from this queue. Use this instead of mesh_send_task when you do not need to target a specific node.",
|
|
1110
|
-
inputSchema: {
|
|
1111
|
-
type: "object",
|
|
1112
|
-
properties: {
|
|
1113
|
-
message: { type: "string", description: "The task instruction for the agent." },
|
|
1114
|
-
task_mode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "Optional task-mode contract. live_debug_readonly rejects obvious write/commit/push/deploy/destructive instructions before dispatch." },
|
|
1115
|
-
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
|
|
1116
|
-
requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
|
|
1117
|
-
required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." },
|
|
1118
|
-
target_node_id: { type: "string", description: "Optional: only this node may claim the task. Use to route a queued task to a specific (e.g. freshly cloned) worktree node instead of letting the first idle base node claim it. Takes priority over prefer_worktree." },
|
|
1119
|
-
targetNodeId: { type: "string", description: "CamelCase alias for target_node_id." },
|
|
1120
|
-
prefer_worktree: { type: "boolean", description: "Optional: when true, route this task to the most recently cloned idle worktree node (avoids the main/base workspace preemptively claiming an isolated task). No-op if no worktree node exists; resolves to a target_node_id when one does." },
|
|
1121
|
-
preferWorktree: { type: "boolean", description: "CamelCase alias for prefer_worktree." },
|
|
1122
|
-
depends_on: { type: "array", items: { type: "string" }, description: "Task ids that must complete before this task becomes claimable. Cycles are rejected at enqueue." },
|
|
1123
|
-
dependsOn: { type: "array", items: { type: "string" }, description: "CamelCase alias for depends_on." },
|
|
1124
|
-
mission_id: { type: "string", description: "Mission this task belongs to (mesh_mission record id)." },
|
|
1125
|
-
missionId: { type: "string", description: "CamelCase alias for mission_id." }
|
|
1126
|
-
},
|
|
1127
|
-
required: ["message"]
|
|
676
|
+
};
|
|
677
|
+
var MESH_ENQUEUE_TASK_TOOL = {
|
|
678
|
+
name: "mesh_enqueue_task",
|
|
679
|
+
description: "Add a new task to the mesh work queue. Idle nodes will automatically pull and execute tasks from this queue. Use this instead of mesh_send_task when you do not need to target a specific node.",
|
|
680
|
+
inputSchema: {
|
|
681
|
+
type: "object",
|
|
682
|
+
properties: {
|
|
683
|
+
message: { type: "string", description: "The task instruction for the agent." },
|
|
684
|
+
task_mode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "Optional task-mode contract. live_debug_readonly rejects obvious write/commit/push/deploy/destructive instructions before dispatch." },
|
|
685
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
|
|
686
|
+
requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
|
|
687
|
+
required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." },
|
|
688
|
+
target_node_id: { type: "string", description: "Optional: only this node may claim the task. Use to route a queued task to a specific (e.g. freshly cloned) worktree node instead of letting the first idle base node claim it. Takes priority over prefer_worktree." },
|
|
689
|
+
targetNodeId: { type: "string", description: "CamelCase alias for target_node_id." },
|
|
690
|
+
prefer_worktree: { type: "boolean", description: "Optional: when true, route this task to the most recently cloned idle worktree node (avoids the main/base workspace preemptively claiming an isolated task). No-op if no worktree node exists; resolves to a target_node_id when one does." },
|
|
691
|
+
preferWorktree: { type: "boolean", description: "CamelCase alias for prefer_worktree." },
|
|
692
|
+
depends_on: { type: "array", items: { type: "string" }, description: "Task ids that must complete before this task becomes claimable. Cycles are rejected at enqueue." },
|
|
693
|
+
dependsOn: { type: "array", items: { type: "string" }, description: "CamelCase alias for depends_on." },
|
|
694
|
+
mission_id: { type: "string", description: "Mission this task belongs to (mesh_mission record id)." },
|
|
695
|
+
missionId: { type: "string", description: "CamelCase alias for mission_id." }
|
|
696
|
+
},
|
|
697
|
+
required: ["message"]
|
|
1128
698
|
}
|
|
1129
699
|
};
|
|
1130
700
|
var MESH_VIEW_QUEUE_TOOL = {
|
|
@@ -1231,7 +801,8 @@ var MESH_LAUNCH_SESSION_TOOL = {
|
|
|
1231
801
|
type: "object",
|
|
1232
802
|
properties: {
|
|
1233
803
|
node_id: { type: "string", description: "Target node ID." },
|
|
1234
|
-
type: { type: "string", description: "Optional provider type to launch. Use hermes-cli for Hermes, claude-cli for Claude Code, codex-cli for Codex, gemini-cli for Gemini. When omitted, node.policy.providerPriority is probed in order." }
|
|
804
|
+
type: { type: "string", description: "Optional provider type to launch. Use hermes-cli for Hermes, claude-cli for Claude Code, codex-cli for Codex, gemini-cli for Gemini. When omitted, node.policy.providerPriority is probed in order." },
|
|
805
|
+
force: { type: "boolean", description: "Set true to launch an ADDITIONAL session even when this node already has a live mesh-owned worker session. Default false: if a live worker session for this mesh+node already exists (e.g. an enqueue auto-launch just spawned one), the existing session is returned idempotently instead of creating an empty duplicate. Only pass force when you intentionally want a second concurrent provider/session on the node." }
|
|
1235
806
|
},
|
|
1236
807
|
required: ["node_id"]
|
|
1237
808
|
}
|
|
@@ -1345,269 +916,698 @@ var MESH_APPROVE_TOOL = {
|
|
|
1345
916
|
required: ["node_id", "session_id", "action"]
|
|
1346
917
|
}
|
|
1347
918
|
};
|
|
1348
|
-
var MESH_CLONE_NODE_TOOL = {
|
|
1349
|
-
name: "mesh_clone_node",
|
|
1350
|
-
description: "Create a new worktree-based node from an existing node for isolated parallel work. Creates a git worktree on a new branch so multiple tasks can run on separate branches simultaneously.",
|
|
919
|
+
var MESH_CLONE_NODE_TOOL = {
|
|
920
|
+
name: "mesh_clone_node",
|
|
921
|
+
description: "Create a new worktree-based node from an existing node for isolated parallel work. Creates a git worktree on a new branch so multiple tasks can run on separate branches simultaneously.",
|
|
922
|
+
inputSchema: {
|
|
923
|
+
type: "object",
|
|
924
|
+
properties: {
|
|
925
|
+
source_node_id: { type: "string", description: "Node ID to clone from (from mesh_list_nodes)." },
|
|
926
|
+
branch: { type: "string", description: 'Branch name for the new worktree (e.g. "feat/auth-refactor").' },
|
|
927
|
+
base_branch: { type: "string", description: "Starting point for the branch (default: current HEAD)." }
|
|
928
|
+
},
|
|
929
|
+
required: ["source_node_id", "branch"]
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
var MESH_REMOVE_NODE_TOOL = {
|
|
933
|
+
name: "mesh_remove_node",
|
|
934
|
+
description: "Remove a node from the mesh. If the node is a worktree, also cleans up the git worktree and directory. Session cleanup is controlled by mesh policy sessionCleanupOnNodeRemove unless session_cleanup_mode overrides it for this call. The coordinator's own local base node (same machine, NOT a worktree) is protected \u2014 removing it breaks live mesh membership and is rejected unless force:true is passed.",
|
|
935
|
+
inputSchema: {
|
|
936
|
+
type: "object",
|
|
937
|
+
properties: {
|
|
938
|
+
node_id: { type: "string", description: "Node ID to remove." },
|
|
939
|
+
session_cleanup_mode: {
|
|
940
|
+
type: "string",
|
|
941
|
+
enum: ["preserve", "stop", "delete_stopped", "stop_and_delete"],
|
|
942
|
+
description: "Optional override for cleanup of delegated sessions attached to this node. preserve keeps history/processes; stop stops live runtimes only; delete_stopped removes completed transcripts only; stop_and_delete stops live runtimes and deletes records."
|
|
943
|
+
},
|
|
944
|
+
force: { type: "boolean", description: "Override the coordinator-base-node guard. Only set true to intentionally tear down this mesh; the coordinator must then be re-registered/restarted. Worktree nodes never need force." }
|
|
945
|
+
},
|
|
946
|
+
required: ["node_id"]
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
var MESH_CLEANUP_SESSIONS_TOOL = {
|
|
950
|
+
name: "mesh_cleanup_sessions",
|
|
951
|
+
description: "Manually clean up delegated session records for a mesh node without removing the node. Defaults should preserve reviewable history unless the caller chooses a mode explicitly.",
|
|
952
|
+
inputSchema: {
|
|
953
|
+
type: "object",
|
|
954
|
+
properties: {
|
|
955
|
+
node_id: { type: "string", description: "Node ID whose delegated sessions should be considered for cleanup." },
|
|
956
|
+
mode: {
|
|
957
|
+
type: "string",
|
|
958
|
+
enum: ["preserve", "stop", "delete_stopped", "stop_and_delete"],
|
|
959
|
+
description: "preserve = no-op; stop = release process occupancy by stopping live runtimes; delete_stopped = remove completed/stopped records while leaving live runtimes alone; stop_and_delete = stop live runtimes and delete records."
|
|
960
|
+
},
|
|
961
|
+
session_ids: {
|
|
962
|
+
type: "array",
|
|
963
|
+
items: { type: "string" },
|
|
964
|
+
description: "Optional explicit session IDs to limit cleanup to. When omitted, sessions are matched by node/workspace metadata."
|
|
965
|
+
},
|
|
966
|
+
dry_run: { type: "boolean", description: "Preview matched/stopped/deleted/skipped session IDs without mutating session-host state." }
|
|
967
|
+
},
|
|
968
|
+
required: ["node_id", "mode"]
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
var MESH_TASK_HISTORY_TOOL = {
|
|
972
|
+
name: "mesh_task_history",
|
|
973
|
+
description: "Read the task ledger for this mesh \u2014 dispatched tasks, completions, failures, checkpoints, and node lifecycle events. Use to understand what has been done before deciding next steps, to detect repeated failures, and to inform recovery decisions.",
|
|
974
|
+
inputSchema: {
|
|
975
|
+
type: "object",
|
|
976
|
+
properties: {
|
|
977
|
+
tail: { type: "number", description: "Number of recent entries to return (default: 20; clamped to 40 in compact mode, 200 in verbose)." },
|
|
978
|
+
kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed, direct_fast_forward." },
|
|
979
|
+
compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Truncates long payload strings (message/taskSummary \u2264200, finalSummary \u2264300) and elides any large nested evidence blob (>2KB serialized \u2014 e.g. validationSummary/result/patchEquivalence/submoduleReachability) to a {_elided,_kind,_bytes,_hint} placeholder; full evidence stays accessible via mesh_reconcile_ledger. Set false (or verbose=true) for full untruncated payloads." },
|
|
980
|
+
verbose: { type: "boolean", description: "Force the full untruncated payload; overrides compact." }
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
var MESH_RECORD_NOTE_TOOL = {
|
|
985
|
+
name: "mesh_record_note",
|
|
986
|
+
description: "Record a durable operating note for this mesh \u2014 a runtime-accumulated lesson that future coordinators inherit. Unlike Claude-only memory/CLAUDE.md, this is provider-neutral: it persists in the mesh ledger and is injected into every coordinator's system prompt at launch (codex, hermes, antigravity, claude alike). Use it when you learn something durable: a provider quirk, a pattern to avoid, or a recovery lesson. Keep each note to one concrete, reusable fact. Not for transient task status \u2014 use missions/checkpoints for that.",
|
|
987
|
+
inputSchema: {
|
|
988
|
+
type: "object",
|
|
989
|
+
properties: {
|
|
990
|
+
text: { type: "string", description: "The note \u2014 one concrete, reusable operating fact/lesson. Phrase it so a future coordinator can act on it without this conversation's context." },
|
|
991
|
+
category: {
|
|
992
|
+
type: "string",
|
|
993
|
+
enum: ["provider_quirk", "pattern_to_avoid", "recovery_lesson"],
|
|
994
|
+
description: "Optional classification: provider_quirk (a provider/runtime behaves unexpectedly), pattern_to_avoid (an approach that caused problems), recovery_lesson (how a failure was recovered)."
|
|
995
|
+
}
|
|
996
|
+
},
|
|
997
|
+
required: ["text"]
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
var MESH_RECONCILE_LEDGER_TOOL = {
|
|
1001
|
+
name: "mesh_reconcile_ledger",
|
|
1002
|
+
description: "Reconcile daemon-local mesh ledgers by querying bounded ledger slices over P2P/DataChannel and importing missing entries into the coordinator local JSONL ledger. Cloud/D1 is not used as a ledger source of truth.",
|
|
1003
|
+
inputSchema: {
|
|
1004
|
+
type: "object",
|
|
1005
|
+
properties: {
|
|
1006
|
+
node_ids: { type: "array", items: { type: "string" }, description: "Optional node IDs to query. Defaults to all mesh nodes." },
|
|
1007
|
+
limit: { type: "number", description: "Bounded slice size per node. Defaults to 100 and is clamped by daemon-core." },
|
|
1008
|
+
after_id: { type: "string", description: "Optional cursor entry ID; remote slices return entries strictly after this ID when present." },
|
|
1009
|
+
since: { type: "string", description: "Optional ISO timestamp lower bound for queried entries." },
|
|
1010
|
+
import_entries: { type: "boolean", description: "When false, query and report evidence without importing remote entries. Defaults true." }
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
var MESH_PRUNE_STALE_DIRECT_TOOL = {
|
|
1015
|
+
name: "mesh_prune_stale_direct",
|
|
1016
|
+
description: "Prune orphaned staleDirect dispatch records \u2014 direct task dispatches whose original node/session is no longer present in the live mesh. dry_run (default) reports exactly which records would be pruned without mutating anything; pass execute=true to delete them. Active/pending/assigned/generating work and fresh unacknowledged dispatch failures (node/session still live) are always preserved. The append-only mesh ledger audit history is left intact.",
|
|
1017
|
+
inputSchema: {
|
|
1018
|
+
type: "object",
|
|
1019
|
+
properties: {
|
|
1020
|
+
execute: { type: "boolean", description: "When true, actually delete the orphaned records. Defaults false (dry run). Ignored when dry_run=true." },
|
|
1021
|
+
dry_run: { type: "boolean", description: "Force a preview without mutation even if execute=true. Defaults to dry-run behavior when execute is not set." },
|
|
1022
|
+
include_terminal: { type: "boolean", description: "Also prune terminal (completed/failed) direct dispatch store rows in addition to orphans. Defaults false." }
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
var MESH_REFINE_NODE_TOOL = {
|
|
1027
|
+
name: "mesh_refine_node",
|
|
1028
|
+
description: "The Refinery: validate \u2192 merge \u2192 push \u2192 clean up a completed worktree node onto the base branch. Defaults to dry-run (plan only): returns the validation plan with mergeWillRun:false/cleanupWillRun:false and performs NO merge/push/cleanup. Pass execute=true to actually converge the node. execute=true is async: the immediate response includes async:true, status:'accepted', jobId, interactionId, target node, and startedAt; completion/failure evidence is delivered through pending mesh events and the mesh task ledger. dry_run=true overrides execute. Matches the mesh_refine_batch / mesh_fast_forward_node dry_run/execute contract.",
|
|
1029
|
+
inputSchema: {
|
|
1030
|
+
type: "object",
|
|
1031
|
+
properties: {
|
|
1032
|
+
node_id: { type: "string", description: "Node ID of the completed worktree node to refine and merge." },
|
|
1033
|
+
execute: { type: "boolean", description: "When true, run validation/merge/push/cleanup for this node. Defaults false/dry-run." },
|
|
1034
|
+
dry_run: { type: "boolean", description: "Preview the validation plan without merging. Defaults true unless execute=true; dry_run=true overrides execute." }
|
|
1035
|
+
},
|
|
1036
|
+
required: ["node_id"]
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
var MESH_REFINE_BATCH_TOOL = {
|
|
1040
|
+
name: "mesh_refine_batch",
|
|
1041
|
+
description: "Batch Refinery: converge multiple sibling worktree nodes onto the base branch in one conflict-aware sequential pipeline. Orders nodes by change-area (non-submodule nodes first, submodule-touching nodes serialized last) so each merged sibling advances the base and the next node auto-rebases + re-checks patch-equivalence before its own merge. Each node runs the same validation/patch-equivalence/submodule-reachability/merge/cleanup gates as mesh_refine_node. Conflicting or blocked nodes are isolated as blocked_review while the rest of the batch proceeds. Defaults to dry-run (plan only); set execute=true to converge. Never force-pushes or resets. execute=true is async: the immediate response is async:true / status:'accepted' with the batch jobId and ordered target node list; per-node convergence runs in the background and the aggregate completion/failure (with per-node merged / blocked_review / not_mergeable results) is delivered as a terminal refine event via pending mesh events and the ledger \u2014 do not re-invoke while a batch is in flight. dry_run returns the plan synchronously.",
|
|
1042
|
+
inputSchema: {
|
|
1043
|
+
type: "object",
|
|
1044
|
+
properties: {
|
|
1045
|
+
node_ids: {
|
|
1046
|
+
type: "array",
|
|
1047
|
+
items: { type: "string" },
|
|
1048
|
+
description: "Optional explicit node IDs to converge, in any order (the tool computes the safe merge order). When omitted, all local worktree nodes that need convergence are auto-collected."
|
|
1049
|
+
},
|
|
1050
|
+
execute: { type: "boolean", description: "When true, run validation/rebase/merge for each node in order. Defaults false/dry-run." },
|
|
1051
|
+
dry_run: { type: "boolean", description: "Preview the ordering + per-node validation plan without executing. Defaults true unless execute=true; dry_run=true overrides execute." }
|
|
1052
|
+
},
|
|
1053
|
+
required: []
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
|
|
1057
|
+
name: "mesh_refine_config_schema",
|
|
1058
|
+
description: "Return the Repo Mesh Refinery config JSON schema and supported repo-local config locations. This is the validation source of truth; heuristic command detection is suggestions-only.",
|
|
1059
|
+
inputSchema: { type: "object", properties: {} }
|
|
1060
|
+
};
|
|
1061
|
+
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
1062
|
+
name: "mesh_validate_refine_config",
|
|
1063
|
+
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
1064
|
+
inputSchema: {
|
|
1065
|
+
type: "object",
|
|
1066
|
+
properties: {
|
|
1067
|
+
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
1068
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
|
|
1073
|
+
name: "mesh_suggest_refine_config",
|
|
1074
|
+
description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
|
|
1075
|
+
inputSchema: {
|
|
1076
|
+
type: "object",
|
|
1077
|
+
properties: {
|
|
1078
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
var MESH_CHANGE_IMPACT_CONFIG_SCHEMA_TOOL = {
|
|
1083
|
+
name: "mesh_change_impact_config_schema",
|
|
1084
|
+
description: "Return the Change Impact config JSON schema and supported repo-local config locations. Change Impact config declaratively classifies which package/file changes between the live daemon build and workspace HEAD require a daemon rebuild/restart vs. a web-only redeploy vs. nothing. Declarative only \u2014 config is parsed, never executed.",
|
|
1085
|
+
inputSchema: { type: "object", properties: {} }
|
|
1086
|
+
};
|
|
1087
|
+
var MESH_VALIDATE_CHANGE_IMPACT_CONFIG_TOOL = {
|
|
1088
|
+
name: "mesh_validate_change_impact_config",
|
|
1089
|
+
description: "Validate a Change Impact config for a node/workspace and report valid/errors. Loads .adhdev/change-impact.{json,yaml,yml} (or repo-mesh-change-impact.* alias) from the repo unless an inline config is provided.",
|
|
1090
|
+
inputSchema: {
|
|
1091
|
+
type: "object",
|
|
1092
|
+
properties: {
|
|
1093
|
+
node_id: { type: "string", description: "Optional node/workspace whose change-impact config should be loaded. Defaults to the first mesh node." },
|
|
1094
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
var MESH_SUGGEST_CHANGE_IMPACT_CONFIG_TOOL = {
|
|
1099
|
+
name: "mesh_suggest_change_impact_config",
|
|
1100
|
+
description: "Suggest a Change Impact config scaffold from the repo package layout (web-* \u2192 web-only, others \u2192 daemon-runtime, plus docs/license markers as non-runtime). Heuristic scaffold only \u2014 the draft must be reviewed and saved before it takes effect; nothing is executed.",
|
|
1101
|
+
inputSchema: {
|
|
1102
|
+
type: "object",
|
|
1103
|
+
properties: {
|
|
1104
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
var MESH_INIT_TOOL = {
|
|
1109
|
+
name: "mesh_init",
|
|
1110
|
+
description: "One-click mesh onboarding for an existing git project. Detects installed CLI providers, suggests Refinery (.adhdev/refine.json) and worktree bootstrap (.adhdev/worktree_bootstrap.json) configs, optionally writes them to disk, and recommends a node providerPriority from the detected providers. Suggestions are scaffold only and never execute until saved; providerPriority is a recommendation to apply to node policy, not auto-applied. Defaults to dry-run (no files written) and never overwrites an existing config unless overwrite=true.",
|
|
1351
1111
|
inputSchema: {
|
|
1352
1112
|
type: "object",
|
|
1353
1113
|
properties: {
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
}
|
|
1358
|
-
required: ["source_node_id", "branch"]
|
|
1114
|
+
node_id: { type: "string", description: "Optional node/workspace to onboard. Defaults to the first mesh node with a workspace." },
|
|
1115
|
+
write: { type: "boolean", description: "When true, persist the suggested configs to disk. Defaults false (dry-run preview only)." },
|
|
1116
|
+
overwrite: { type: "boolean", description: "When true, overwrite an existing config file. Defaults false (never clobber an existing refine/bootstrap config)." }
|
|
1117
|
+
}
|
|
1359
1118
|
}
|
|
1360
1119
|
};
|
|
1361
|
-
var
|
|
1362
|
-
name: "
|
|
1363
|
-
description: "
|
|
1120
|
+
var MESH_REFINE_PLAN_TOOL = {
|
|
1121
|
+
name: "mesh_refine_plan",
|
|
1122
|
+
description: "Dry-run Refinery plan for a worktree node: reports config source, validation commands, suggestions/unavailable reason, and merge/cleanup intent without executing validation or git merge.",
|
|
1364
1123
|
inputSchema: {
|
|
1365
1124
|
type: "object",
|
|
1366
1125
|
properties: {
|
|
1367
|
-
node_id: { type: "string", description: "Node ID to
|
|
1368
|
-
session_cleanup_mode: {
|
|
1369
|
-
type: "string",
|
|
1370
|
-
enum: ["preserve", "stop", "delete_stopped", "stop_and_delete"],
|
|
1371
|
-
description: "Optional override for cleanup of delegated sessions attached to this node. preserve keeps history/processes; stop stops live runtimes only; delete_stopped removes completed transcripts only; stop_and_delete stops live runtimes and deletes records."
|
|
1372
|
-
},
|
|
1373
|
-
force: { type: "boolean", description: "Override the coordinator-base-node guard. Only set true to intentionally tear down this mesh; the coordinator must then be re-registered/restarted. Worktree nodes never need force." }
|
|
1126
|
+
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
1374
1127
|
},
|
|
1375
1128
|
required: ["node_id"]
|
|
1376
1129
|
}
|
|
1377
1130
|
};
|
|
1378
|
-
var
|
|
1379
|
-
name: "
|
|
1380
|
-
description: "
|
|
1131
|
+
var MESH_REVIEW_INBOX_TOOL = {
|
|
1132
|
+
name: "mesh_review_inbox",
|
|
1133
|
+
description: "List local worktree nodes that need human review: merge candidates (pushed feature branches ready to merge) and Refinery-blocked review results. Returns evidence summaries, diff stats vs. the default branch, and suggested actions (Refine / Requeue / Dismiss). Remote nodes are excluded in M4.0.",
|
|
1381
1134
|
inputSchema: {
|
|
1382
1135
|
type: "object",
|
|
1383
1136
|
properties: {
|
|
1384
|
-
|
|
1385
|
-
mode: {
|
|
1386
|
-
type: "string",
|
|
1387
|
-
enum: ["preserve", "stop", "delete_stopped", "stop_and_delete"],
|
|
1388
|
-
description: "preserve = no-op; stop = release process occupancy by stopping live runtimes; delete_stopped = remove completed/stopped records while leaving live runtimes alone; stop_and_delete = stop live runtimes and delete records."
|
|
1389
|
-
},
|
|
1390
|
-
session_ids: {
|
|
1391
|
-
type: "array",
|
|
1392
|
-
items: { type: "string" },
|
|
1393
|
-
description: "Optional explicit session IDs to limit cleanup to. When omitted, sessions are matched by node/workspace metadata."
|
|
1394
|
-
},
|
|
1395
|
-
dry_run: { type: "boolean", description: "Preview matched/stopped/deleted/skipped session IDs without mutating session-host state." }
|
|
1137
|
+
mesh_id: { type: "string", description: "Mesh ID (optional \u2014 inferred from active mesh if omitted)." }
|
|
1396
1138
|
},
|
|
1397
|
-
required: [
|
|
1139
|
+
required: []
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
var ALL_MESH_TOOLS = [
|
|
1143
|
+
MESH_STATUS_TOOL,
|
|
1144
|
+
MESH_LIST_NODES_TOOL,
|
|
1145
|
+
MESH_ENQUEUE_TASK_TOOL,
|
|
1146
|
+
MESH_VIEW_QUEUE_TOOL,
|
|
1147
|
+
MESH_QUEUE_CANCEL_TOOL,
|
|
1148
|
+
MESH_QUEUE_REQUEUE_TOOL,
|
|
1149
|
+
MESH_SEND_TASK_TOOL,
|
|
1150
|
+
MESH_READ_CHAT_TOOL,
|
|
1151
|
+
MESH_READ_DEBUG_TOOL,
|
|
1152
|
+
MESH_LAUNCH_SESSION_TOOL,
|
|
1153
|
+
MESH_GIT_STATUS_TOOL,
|
|
1154
|
+
MESH_READ_NODE_LOGS_TOOL,
|
|
1155
|
+
MESH_FAST_FORWARD_NODE_TOOL,
|
|
1156
|
+
MESH_RESTART_DAEMON_TOOL,
|
|
1157
|
+
MESH_CHECKPOINT_TOOL,
|
|
1158
|
+
MESH_APPROVE_TOOL,
|
|
1159
|
+
MESH_CLONE_NODE_TOOL,
|
|
1160
|
+
MESH_REMOVE_NODE_TOOL,
|
|
1161
|
+
MESH_REFINE_NODE_TOOL,
|
|
1162
|
+
MESH_REFINE_BATCH_TOOL,
|
|
1163
|
+
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
1164
|
+
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
1165
|
+
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
1166
|
+
MESH_CHANGE_IMPACT_CONFIG_SCHEMA_TOOL,
|
|
1167
|
+
MESH_VALIDATE_CHANGE_IMPACT_CONFIG_TOOL,
|
|
1168
|
+
MESH_SUGGEST_CHANGE_IMPACT_CONFIG_TOOL,
|
|
1169
|
+
MESH_INIT_TOOL,
|
|
1170
|
+
MESH_REFINE_PLAN_TOOL,
|
|
1171
|
+
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1172
|
+
MESH_PRUNE_STALE_DIRECT_TOOL,
|
|
1173
|
+
MESH_TASK_HISTORY_TOOL,
|
|
1174
|
+
MESH_RECORD_NOTE_TOOL,
|
|
1175
|
+
MESH_RECONCILE_LEDGER_TOOL,
|
|
1176
|
+
MESH_MISSION_UPSERT_TOOL,
|
|
1177
|
+
MESH_MISSION_LIST_TOOL,
|
|
1178
|
+
MESH_REVIEW_INBOX_TOOL
|
|
1179
|
+
];
|
|
1180
|
+
|
|
1181
|
+
// src/tools/mesh-compact.ts
|
|
1182
|
+
function buildCompactGitSnapshot(status) {
|
|
1183
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return void 0;
|
|
1184
|
+
const slim = {};
|
|
1185
|
+
const carry = [
|
|
1186
|
+
"isGitRepo",
|
|
1187
|
+
"branch",
|
|
1188
|
+
"headCommit",
|
|
1189
|
+
"upstream",
|
|
1190
|
+
"upstreamStatus",
|
|
1191
|
+
"ahead",
|
|
1192
|
+
"behind",
|
|
1193
|
+
"dirty",
|
|
1194
|
+
"detached",
|
|
1195
|
+
"submodules"
|
|
1196
|
+
];
|
|
1197
|
+
for (const key of carry) {
|
|
1198
|
+
if (status[key] !== void 0) slim[key] = status[key];
|
|
1199
|
+
}
|
|
1200
|
+
return slim;
|
|
1201
|
+
}
|
|
1202
|
+
function summarizeCompactSubmodules(submodules) {
|
|
1203
|
+
if (!Array.isArray(submodules) || submodules.length === 0) return void 0;
|
|
1204
|
+
const outOfSync = submodules.filter((s) => s?.outOfSync).map((s) => s?.path).filter(Boolean);
|
|
1205
|
+
return {
|
|
1206
|
+
count: submodules.length,
|
|
1207
|
+
...outOfSync.length > 0 ? { outOfSyncPaths: outOfSync } : {}
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
var MESH_COMPACT_PRESERVED_MARKER_FIELDS = ["dataFreshness"];
|
|
1211
|
+
function compactMeshStatusNode(entry) {
|
|
1212
|
+
if (!entry || typeof entry !== "object") return entry;
|
|
1213
|
+
const next = { ...entry };
|
|
1214
|
+
if (next.git !== void 0) {
|
|
1215
|
+
const slimGit = buildCompactGitSnapshot(next.git);
|
|
1216
|
+
if (slimGit) {
|
|
1217
|
+
if (slimGit.submodules !== void 0) {
|
|
1218
|
+
const subSummary = summarizeCompactSubmodules(slimGit.submodules);
|
|
1219
|
+
if (subSummary) slimGit.submodules = subSummary;
|
|
1220
|
+
else delete slimGit.submodules;
|
|
1221
|
+
}
|
|
1222
|
+
next.git = slimGit;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
if (next.machine && typeof next.machine === "object") {
|
|
1226
|
+
const m = next.machine;
|
|
1227
|
+
next.machine = {
|
|
1228
|
+
daemonId: m.daemonId,
|
|
1229
|
+
machineId: m.machineId,
|
|
1230
|
+
hostname: m.hostname,
|
|
1231
|
+
displayName: m.displayName,
|
|
1232
|
+
sameMachine: m.sameMachine,
|
|
1233
|
+
locality: m.locality
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
if (typeof next.submoduleWarning === "string") {
|
|
1237
|
+
next.submodulesOutOfSync = true;
|
|
1238
|
+
delete next.submoduleWarning;
|
|
1239
|
+
}
|
|
1240
|
+
if (next.staleDaemonBuild && typeof next.staleDaemonBuild === "object") {
|
|
1241
|
+
const b = next.staleDaemonBuild;
|
|
1242
|
+
next.staleDaemonBuild = {
|
|
1243
|
+
scope: b.scope,
|
|
1244
|
+
isDaemonAffecting: b.isDaemonAffecting !== false,
|
|
1245
|
+
seeStaleDaemonBuilds: true
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
delete next.capabilityTagsByProvider;
|
|
1249
|
+
const elideSkip = /* @__PURE__ */ new Set(["git", "machine", "branchConvergence", "staleDaemonBuild", "sessions", ...MESH_COMPACT_PRESERVED_MARKER_FIELDS]);
|
|
1250
|
+
for (const k of Object.keys(next)) {
|
|
1251
|
+
if (elideSkip.has(k)) continue;
|
|
1252
|
+
next[k] = elideLargeNestedValue(k, next[k]);
|
|
1253
|
+
}
|
|
1254
|
+
return next;
|
|
1255
|
+
}
|
|
1256
|
+
function compactNodeSeverity(entry) {
|
|
1257
|
+
if (!entry || typeof entry !== "object") return 0;
|
|
1258
|
+
if (entry.error || entry.health && entry.health !== "online" && entry.health !== "dirty") return 5;
|
|
1259
|
+
if (entry.launchReady === false) return 4;
|
|
1260
|
+
if (entry.isDirty === true || entry.health === "dirty") return 3;
|
|
1261
|
+
if (entry.branchConvergence?.needsConvergence === true) return 2;
|
|
1262
|
+
if (entry.staleDaemonBuild || entry.submodulesOutOfSync || entry.recoveryHints) return 1;
|
|
1263
|
+
return 0;
|
|
1264
|
+
}
|
|
1265
|
+
function isNoteworthyCompactNode(entry) {
|
|
1266
|
+
if (!entry || typeof entry !== "object") return true;
|
|
1267
|
+
if (entry.health && entry.health !== "online") return true;
|
|
1268
|
+
if (entry.isDirty === true) return true;
|
|
1269
|
+
if (entry.error) return true;
|
|
1270
|
+
if (entry.launchReady === false) return true;
|
|
1271
|
+
if (entry.staleDaemonBuild) return true;
|
|
1272
|
+
if (entry.submoduleWarning || entry.submodulesOutOfSync) return true;
|
|
1273
|
+
if (entry.recoveryHints) return true;
|
|
1274
|
+
if (Array.isArray(entry.nextStepHints) && entry.nextStepHints.length > 0) return true;
|
|
1275
|
+
if (entry.branchConvergence?.needsConvergence === true) return true;
|
|
1276
|
+
const sessionCount = Array.isArray(entry.sessions) ? entry.sessions.length : entry.sessionSummary?.total ?? 0;
|
|
1277
|
+
if (sessionCount > 0) return true;
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
function minimalCompactNode(entry) {
|
|
1281
|
+
if (!entry || typeof entry !== "object") return entry;
|
|
1282
|
+
const bc = entry.branchConvergence && typeof entry.branchConvergence === "object" ? {
|
|
1283
|
+
status: entry.branchConvergence.status,
|
|
1284
|
+
needsConvergence: entry.branchConvergence.needsConvergence,
|
|
1285
|
+
reason: entry.branchConvergence.reason,
|
|
1286
|
+
branch: entry.branchConvergence.branch
|
|
1287
|
+
} : void 0;
|
|
1288
|
+
const preservedMarkers = {};
|
|
1289
|
+
for (const field of MESH_COMPACT_PRESERVED_MARKER_FIELDS) {
|
|
1290
|
+
if (entry[field] !== void 0) preservedMarkers[field] = entry[field];
|
|
1291
|
+
}
|
|
1292
|
+
return {
|
|
1293
|
+
nodeId: entry.nodeId,
|
|
1294
|
+
workspace: entry.workspace,
|
|
1295
|
+
daemonId: entry.daemonId,
|
|
1296
|
+
health: entry.health,
|
|
1297
|
+
branch: entry.branch,
|
|
1298
|
+
launchReady: entry.launchReady,
|
|
1299
|
+
...entry.providerPriority !== void 0 ? { providerPriority: entry.providerPriority } : {},
|
|
1300
|
+
// Keep the routable tag set on quiet/folded nodes — a coordinator planning
|
|
1301
|
+
// required_tags routing needs it even for nodes with nothing to converge.
|
|
1302
|
+
...entry.capabilityTags !== void 0 ? { capabilityTags: entry.capabilityTags } : {},
|
|
1303
|
+
...entry.launchBlockedReason !== void 0 ? { launchBlockedReason: entry.launchBlockedReason } : {},
|
|
1304
|
+
...bc ? { branchConvergence: bc } : {},
|
|
1305
|
+
...entry.sessionSummary ? { sessionSummary: entry.sessionSummary } : {},
|
|
1306
|
+
...preservedMarkers,
|
|
1307
|
+
folded: true
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
function summarizeNodeSessions(sessions) {
|
|
1311
|
+
const list = Array.isArray(sessions) ? sessions : [];
|
|
1312
|
+
const byStatus = {};
|
|
1313
|
+
const providerCounts = {};
|
|
1314
|
+
const selfCoordinatorSessionIds = [];
|
|
1315
|
+
for (const s of list) {
|
|
1316
|
+
const status = typeof s?.status === "string" && s.status ? s.status : "unknown";
|
|
1317
|
+
byStatus[status] = (byStatus[status] ?? 0) + 1;
|
|
1318
|
+
const provider = typeof s?.providerType === "string" && s.providerType ? s.providerType : "unknown";
|
|
1319
|
+
providerCounts[provider] = (providerCounts[provider] ?? 0) + 1;
|
|
1320
|
+
if (s?.isSelfCoordinator === true && s.id) selfCoordinatorSessionIds.push(String(s.id));
|
|
1321
|
+
}
|
|
1322
|
+
const summary = {
|
|
1323
|
+
total: list.length,
|
|
1324
|
+
byStatus,
|
|
1325
|
+
providerCounts
|
|
1326
|
+
};
|
|
1327
|
+
if (selfCoordinatorSessionIds.length > 0) {
|
|
1328
|
+
summary.selfCoordinatorSessionIds = selfCoordinatorSessionIds;
|
|
1329
|
+
}
|
|
1330
|
+
return summary;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// src/tools/mesh-queue-helpers.ts
|
|
1334
|
+
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
1335
|
+
var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
1336
|
+
var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
|
|
1337
|
+
var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
1338
|
+
function buildQueueLivenessIndex(mesh) {
|
|
1339
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
1340
|
+
const nodeSessionIds = /* @__PURE__ */ new Map();
|
|
1341
|
+
for (const node of Array.isArray(mesh?.nodes) ? mesh.nodes : []) {
|
|
1342
|
+
const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
|
|
1343
|
+
if (!nodeId) continue;
|
|
1344
|
+
nodeIds.add(nodeId);
|
|
1345
|
+
const sessions = collectNodeSessionIds(node);
|
|
1346
|
+
if (sessions.size > 0) nodeSessionIds.set(nodeId, sessions);
|
|
1398
1347
|
}
|
|
1399
|
-
};
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed, direct_fast_forward." },
|
|
1408
|
-
compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Truncates long payload strings (message/taskSummary \u2264200, finalSummary \u2264300) and elides any large nested evidence blob (>2KB serialized \u2014 e.g. validationSummary/result/patchEquivalence/submoduleReachability) to a {_elided,_kind,_bytes,_hint} placeholder; full evidence stays accessible via mesh_reconcile_ledger. Set false (or verbose=true) for full untruncated payloads." },
|
|
1409
|
-
verbose: { type: "boolean", description: "Force the full untruncated payload; overrides compact." }
|
|
1410
|
-
}
|
|
1348
|
+
return { nodeIds, nodeSessionIds };
|
|
1349
|
+
}
|
|
1350
|
+
function queueAssignmentStaleReason(task, liveness) {
|
|
1351
|
+
if (task?.status !== "assigned") return void 0;
|
|
1352
|
+
const nodeId = readString(task.assignedNodeId) || readString(task.nodeId) || readString(task.node_id) || readString(task.targetNodeId);
|
|
1353
|
+
const sessionId = readString(task.assignedSessionId) || readString(task.sessionId) || readString(task.session_id) || readString(task.targetSessionId);
|
|
1354
|
+
if (nodeId && liveness.nodeIds.size > 0 && !liveness.nodeIds.has(nodeId)) {
|
|
1355
|
+
return "assigned node is not present in the current mesh snapshot";
|
|
1411
1356
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
name: "mesh_record_note",
|
|
1415
|
-
description: "Record a durable operating note for this mesh \u2014 a runtime-accumulated lesson that future coordinators inherit. Unlike Claude-only memory/CLAUDE.md, this is provider-neutral: it persists in the mesh ledger and is injected into every coordinator's system prompt at launch (codex, hermes, antigravity, claude alike). Use it when you learn something durable: a provider quirk, a pattern to avoid, or a recovery lesson. Keep each note to one concrete, reusable fact. Not for transient task status \u2014 use missions/checkpoints for that.",
|
|
1416
|
-
inputSchema: {
|
|
1417
|
-
type: "object",
|
|
1418
|
-
properties: {
|
|
1419
|
-
text: { type: "string", description: "The note \u2014 one concrete, reusable operating fact/lesson. Phrase it so a future coordinator can act on it without this conversation's context." },
|
|
1420
|
-
category: {
|
|
1421
|
-
type: "string",
|
|
1422
|
-
enum: ["provider_quirk", "pattern_to_avoid", "recovery_lesson"],
|
|
1423
|
-
description: "Optional classification: provider_quirk (a provider/runtime behaves unexpectedly), pattern_to_avoid (an approach that caused problems), recovery_lesson (how a failure was recovered)."
|
|
1424
|
-
}
|
|
1425
|
-
},
|
|
1426
|
-
required: ["text"]
|
|
1357
|
+
if (nodeId && sessionId && liveness.nodeSessionIds.has(nodeId) && !liveness.nodeSessionIds.get(nodeId).has(sessionId)) {
|
|
1358
|
+
return "assigned session is not live on the assigned node";
|
|
1427
1359
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
inputSchema: {
|
|
1433
|
-
type: "object",
|
|
1434
|
-
properties: {
|
|
1435
|
-
node_ids: { type: "array", items: { type: "string" }, description: "Optional node IDs to query. Defaults to all mesh nodes." },
|
|
1436
|
-
limit: { type: "number", description: "Bounded slice size per node. Defaults to 100 and is clamped by daemon-core." },
|
|
1437
|
-
after_id: { type: "string", description: "Optional cursor entry ID; remote slices return entries strictly after this ID when present." },
|
|
1438
|
-
since: { type: "string", description: "Optional ISO timestamp lower bound for queried entries." },
|
|
1439
|
-
import_entries: { type: "boolean", description: "When false, query and report evidence without importing remote entries. Defaults true." }
|
|
1440
|
-
}
|
|
1360
|
+
const updatedAt = new Date(task.updatedAt).getTime();
|
|
1361
|
+
const ageMs = Number.isFinite(updatedAt) ? Date.now() - updatedAt : null;
|
|
1362
|
+
if (!nodeId && ageMs !== null && ageMs >= STALE_ASSIGNED_QUEUE_MS) {
|
|
1363
|
+
return "assigned task has no assigned node metadata";
|
|
1441
1364
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
include_terminal: { type: "boolean", description: "Also prune terminal (completed/failed) direct dispatch store rows in addition to orphans. Defaults false." }
|
|
1365
|
+
return void 0;
|
|
1366
|
+
}
|
|
1367
|
+
function buildQueueStatusSummary(queue) {
|
|
1368
|
+
const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
|
|
1369
|
+
let staleAssigned = 0;
|
|
1370
|
+
for (const task of queue) {
|
|
1371
|
+
const status = typeof task?.status === "string" ? task.status : void 0;
|
|
1372
|
+
if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
|
|
1373
|
+
counts[status] += 1;
|
|
1452
1374
|
}
|
|
1375
|
+
if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
|
|
1453
1376
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
dry_run: { type: "boolean", description: "Preview the validation plan without merging. Defaults true unless execute=true; dry_run=true overrides execute." }
|
|
1377
|
+
const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
|
|
1378
|
+
return {
|
|
1379
|
+
totalCount: queue.length,
|
|
1380
|
+
activeCount: counts.pending + liveAssigned,
|
|
1381
|
+
historicalCount: counts.completed + counts.failed + counts.cancelled,
|
|
1382
|
+
counts,
|
|
1383
|
+
activeCounts: {
|
|
1384
|
+
pending: counts.pending,
|
|
1385
|
+
assigned: liveAssigned
|
|
1464
1386
|
},
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
name: "mesh_refine_batch",
|
|
1470
|
-
description: "Batch Refinery: converge multiple sibling worktree nodes onto the base branch in one conflict-aware sequential pipeline. Orders nodes by change-area (non-submodule nodes first, submodule-touching nodes serialized last) so each merged sibling advances the base and the next node auto-rebases + re-checks patch-equivalence before its own merge. Each node runs the same validation/patch-equivalence/submodule-reachability/merge/cleanup gates as mesh_refine_node. Conflicting or blocked nodes are isolated as blocked_review while the rest of the batch proceeds. Defaults to dry-run (plan only); set execute=true to converge. Never force-pushes or resets. execute=true is async: the immediate response is async:true / status:'accepted' with the batch jobId and ordered target node list; per-node convergence runs in the background and the aggregate completion/failure (with per-node merged / blocked_review / not_mergeable results) is delivered as a terminal refine event via pending mesh events and the ledger \u2014 do not re-invoke while a batch is in flight. dry_run returns the plan synchronously.",
|
|
1471
|
-
inputSchema: {
|
|
1472
|
-
type: "object",
|
|
1473
|
-
properties: {
|
|
1474
|
-
node_ids: {
|
|
1475
|
-
type: "array",
|
|
1476
|
-
items: { type: "string" },
|
|
1477
|
-
description: "Optional explicit node IDs to converge, in any order (the tool computes the safe merge order). When omitted, all local worktree nodes that need convergence are auto-collected."
|
|
1478
|
-
},
|
|
1479
|
-
execute: { type: "boolean", description: "When true, run validation/rebase/merge for each node in order. Defaults false/dry-run." },
|
|
1480
|
-
dry_run: { type: "boolean", description: "Preview the ordering + per-node validation plan without executing. Defaults true unless execute=true; dry_run=true overrides execute." }
|
|
1387
|
+
staleAssignedCount: staleAssigned,
|
|
1388
|
+
rawActiveCounts: {
|
|
1389
|
+
pending: counts.pending,
|
|
1390
|
+
assigned: counts.assigned
|
|
1481
1391
|
},
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
name: "mesh_refine_config_schema",
|
|
1487
|
-
description: "Return the Repo Mesh Refinery config JSON schema and supported repo-local config locations. This is the validation source of truth; heuristic command detection is suggestions-only.",
|
|
1488
|
-
inputSchema: { type: "object", properties: {} }
|
|
1489
|
-
};
|
|
1490
|
-
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
1491
|
-
name: "mesh_validate_refine_config",
|
|
1492
|
-
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
1493
|
-
inputSchema: {
|
|
1494
|
-
type: "object",
|
|
1495
|
-
properties: {
|
|
1496
|
-
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
1497
|
-
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1392
|
+
historicalCounts: {
|
|
1393
|
+
completed: counts.completed,
|
|
1394
|
+
failed: counts.failed,
|
|
1395
|
+
cancelled: counts.cancelled
|
|
1498
1396
|
}
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
function normalizeQueueViewMode(value) {
|
|
1400
|
+
return value === "active" || value === "historical" || value === "all" ? value : "all";
|
|
1401
|
+
}
|
|
1402
|
+
function sanitizeQueueStatusFilter(value) {
|
|
1403
|
+
if (!Array.isArray(value)) return void 0;
|
|
1404
|
+
const statuses = value.map((item) => typeof item === "string" ? item.trim() : "").filter((status) => ACTIVE_QUEUE_STATUSES.has(status) || HISTORICAL_QUEUE_STATUSES.has(status));
|
|
1405
|
+
return statuses.length ? Array.from(new Set(statuses)) : void 0;
|
|
1406
|
+
}
|
|
1407
|
+
function filterQueueForView(queue, view, statuses) {
|
|
1408
|
+
if (statuses?.length) {
|
|
1409
|
+
const allowed = new Set(statuses);
|
|
1410
|
+
return queue.filter((task) => allowed.has(String(task?.status || "")));
|
|
1499
1411
|
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1412
|
+
if (view === "active") return queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
1413
|
+
if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
1414
|
+
return queue;
|
|
1415
|
+
}
|
|
1416
|
+
function prioritizeActiveQueueRows(queue) {
|
|
1417
|
+
const active = [];
|
|
1418
|
+
const historical = [];
|
|
1419
|
+
const other = [];
|
|
1420
|
+
for (const task of queue) {
|
|
1421
|
+
const status = String(task?.status || "");
|
|
1422
|
+
if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
|
|
1423
|
+
else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
|
|
1424
|
+
else other.push(task);
|
|
1425
|
+
}
|
|
1426
|
+
return [...active, ...other, ...historical];
|
|
1427
|
+
}
|
|
1428
|
+
function slimQueueTask(task) {
|
|
1429
|
+
return {
|
|
1430
|
+
id: task?.id,
|
|
1431
|
+
status: task?.status,
|
|
1432
|
+
assignedNodeId: task?.assignedNodeId,
|
|
1433
|
+
assignedSessionId: task?.assignedSessionId,
|
|
1434
|
+
targetNodeId: task?.targetNodeId,
|
|
1435
|
+
targetSessionId: task?.targetSessionId,
|
|
1436
|
+
updatedAt: task?.updatedAt,
|
|
1437
|
+
staleAssigned: task?.staleAssigned === true,
|
|
1438
|
+
staleReason: task?.staleReason
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
function buildQueueMaintenanceReport(queue) {
|
|
1442
|
+
const now = Date.now();
|
|
1443
|
+
const staleAssignedTasks = queue.filter((task) => task?.status === "assigned" && task?.staleAssigned === true).map(slimQueueTask);
|
|
1444
|
+
const historicalTasks = queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
1445
|
+
const oldHistoricalTasks = historicalTasks.filter((task) => {
|
|
1446
|
+
const updatedAt = new Date(task?.updatedAt).getTime();
|
|
1447
|
+
return Number.isFinite(updatedAt) && now - updatedAt >= OLD_HISTORICAL_QUEUE_RECORD_MS;
|
|
1448
|
+
}).map((task) => ({
|
|
1449
|
+
...slimQueueTask(task),
|
|
1450
|
+
cleanupClass: "old_historical_record",
|
|
1451
|
+
reason: "terminal queue record is older than the read-only maintenance threshold"
|
|
1452
|
+
}));
|
|
1453
|
+
const cleanupCandidates = [
|
|
1454
|
+
...staleAssignedTasks.map((task) => ({
|
|
1455
|
+
...task,
|
|
1456
|
+
cleanupClass: "stale_assigned",
|
|
1457
|
+
reason: typeof task.staleReason === "string" ? task.staleReason : "active assigned task does not match current live mesh node/session state",
|
|
1458
|
+
suggestedOperation: "operator_review_then_requeue_or_cancel"
|
|
1459
|
+
})),
|
|
1460
|
+
...oldHistoricalTasks.map((task) => ({
|
|
1461
|
+
...task,
|
|
1462
|
+
suggestedOperation: "operator_review_then_archive_or_keep"
|
|
1463
|
+
}))
|
|
1464
|
+
];
|
|
1465
|
+
return {
|
|
1466
|
+
readOnly: true,
|
|
1467
|
+
mutationPerformed: false,
|
|
1468
|
+
sourceOfTruth: "mesh_work_queue_file",
|
|
1469
|
+
staleAssignedDefinition: "Only active assigned queue rows are stale candidates, and only when the assigned node/session is absent from the current live mesh snapshot.",
|
|
1470
|
+
historicalDefinition: "completed/failed/cancelled rows are historical ledger records and never active assignments.",
|
|
1471
|
+
staleAssignedTasks,
|
|
1472
|
+
staleAssignedCount: staleAssignedTasks.length,
|
|
1473
|
+
historicalRecordCount: historicalTasks.length,
|
|
1474
|
+
oldHistoricalRecordCount: oldHistoricalTasks.length,
|
|
1475
|
+
cleanupCandidates,
|
|
1476
|
+
cleanupCandidateCount: cleanupCandidates.length
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
function buildCompactQueueMaintenanceReport(maintenance) {
|
|
1480
|
+
const staleAssignedTasks = Array.isArray(maintenance.staleAssignedTasks) ? maintenance.staleAssignedTasks : [];
|
|
1481
|
+
const cleanupCandidateCount = maintenance.cleanupCandidateCount ?? 0;
|
|
1482
|
+
return {
|
|
1483
|
+
readOnly: true,
|
|
1484
|
+
mutationPerformed: false,
|
|
1485
|
+
sourceOfTruth: "mesh_work_queue_file",
|
|
1486
|
+
payloadMode: "compact",
|
|
1487
|
+
staleAssignedDefinition: maintenance.staleAssignedDefinition,
|
|
1488
|
+
historicalDefinition: maintenance.historicalDefinition,
|
|
1489
|
+
// staleAssignedTasks are active assigned rows (not historical) — retain a
|
|
1490
|
+
// bounded sample so coordinators can still see drift without the full array.
|
|
1491
|
+
staleAssignedTasks: staleAssignedTasks.slice(0, 5),
|
|
1492
|
+
staleAssignedSampleLimit: 5,
|
|
1493
|
+
staleAssignedCount: maintenance.staleAssignedCount ?? staleAssignedTasks.length,
|
|
1494
|
+
historicalRecordCount: maintenance.historicalRecordCount ?? 0,
|
|
1495
|
+
oldHistoricalRecordCount: maintenance.oldHistoricalRecordCount ?? 0,
|
|
1496
|
+
cleanupCandidateCount,
|
|
1497
|
+
cleanupCandidatesOmitted: true,
|
|
1498
|
+
cleanupCandidatesHint: "Per-row cleanup candidates are omitted in compact mode; call mesh_view_queue with verbose=true for the full maintenance/cleanupDryRun rows."
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
var COMPACT_MAX_ACTIVE_QUEUE_ROWS = 15;
|
|
1502
|
+
var COMPACT_QUEUE_MESSAGE_CAP = 140;
|
|
1503
|
+
var COMPACT_MAX_ACTIVE_WORK_ROWS = 12;
|
|
1504
|
+
var COMPACT_ACTIVE_WORK_TITLE_CAP = 80;
|
|
1505
|
+
function truncateForCompact(value, cap) {
|
|
1506
|
+
if (typeof value !== "string") return value;
|
|
1507
|
+
return value.length > cap ? value.slice(0, cap) + "\u2026" : value;
|
|
1508
|
+
}
|
|
1509
|
+
function compactQueueRow(task) {
|
|
1510
|
+
if (!task || typeof task !== "object") return task;
|
|
1511
|
+
const slim = {};
|
|
1512
|
+
for (const [k, v] of Object.entries(task)) {
|
|
1513
|
+
if (k === "message") slim[k] = truncateForCompact(v, COMPACT_QUEUE_MESSAGE_CAP);
|
|
1514
|
+
else slim[k] = elideLargeNestedValue(k, v);
|
|
1509
1515
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1524
|
-
}
|
|
1516
|
+
return slim;
|
|
1517
|
+
}
|
|
1518
|
+
function compactQueueRows(rows) {
|
|
1519
|
+
const capped = rows.slice(0, COMPACT_MAX_ACTIVE_QUEUE_ROWS).map(compactQueueRow);
|
|
1520
|
+
return { rows: capped, omitted: Math.max(0, rows.length - capped.length) };
|
|
1521
|
+
}
|
|
1522
|
+
function compactActiveWorkRecord(record) {
|
|
1523
|
+
if (!record || typeof record !== "object") return record;
|
|
1524
|
+
const slim = {};
|
|
1525
|
+
for (const [k, v] of Object.entries(record)) {
|
|
1526
|
+
if (k === "message" || k === "taskSummary") continue;
|
|
1527
|
+
else if (k === "taskTitle") slim[k] = truncateForCompact(v, COMPACT_ACTIVE_WORK_TITLE_CAP);
|
|
1528
|
+
else slim[k] = elideLargeNestedValue(k, v);
|
|
1525
1529
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1530
|
+
return slim;
|
|
1531
|
+
}
|
|
1532
|
+
function compactActiveWorkRecords(records) {
|
|
1533
|
+
if (!Array.isArray(records)) return { records, omitted: 0 };
|
|
1534
|
+
const capped = records.slice(0, COMPACT_MAX_ACTIVE_WORK_ROWS).map(compactActiveWorkRecord);
|
|
1535
|
+
return { records: capped, omitted: Math.max(0, records.length - capped.length) };
|
|
1536
|
+
}
|
|
1537
|
+
function annotateQueueStaleness(queue, mesh) {
|
|
1538
|
+
const liveness = buildQueueLivenessIndex(mesh);
|
|
1539
|
+
const now = Date.now();
|
|
1540
|
+
return queue.map((task) => {
|
|
1541
|
+
const taskStatus = typeof task?.status === "string" ? task.status : void 0;
|
|
1542
|
+
const annotated = {
|
|
1543
|
+
...task,
|
|
1544
|
+
taskStatus,
|
|
1545
|
+
isActive: taskStatus ? ACTIVE_QUEUE_STATUSES.has(taskStatus) : false,
|
|
1546
|
+
isHistorical: taskStatus ? HISTORICAL_QUEUE_STATUSES.has(taskStatus) : false,
|
|
1547
|
+
dispatchedAt: task?.createdAt,
|
|
1548
|
+
...taskStatus === "assigned" ? { activeTaskId: task.id } : {},
|
|
1549
|
+
...taskStatus === "completed" || taskStatus === "failed" ? {
|
|
1550
|
+
completedAt: task.updatedAt
|
|
1551
|
+
} : {}
|
|
1552
|
+
};
|
|
1553
|
+
if (taskStatus !== "assigned") return annotated;
|
|
1554
|
+
const updatedAt = new Date(task.updatedAt).getTime();
|
|
1555
|
+
const ageMs = Number.isFinite(updatedAt) ? now - updatedAt : null;
|
|
1556
|
+
const staleReason = queueAssignmentStaleReason(task, liveness);
|
|
1557
|
+
if (!staleReason) return annotated;
|
|
1558
|
+
return {
|
|
1559
|
+
...annotated,
|
|
1560
|
+
stale: true,
|
|
1561
|
+
staleAssigned: true,
|
|
1562
|
+
staleReason,
|
|
1563
|
+
...ageMs !== null ? { assignedAgeMs: ageMs } : {}
|
|
1564
|
+
};
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// src/tools/read-chat-polling-advisory.ts
|
|
1569
|
+
var RAPID_READ_CHAT_ADVISORY_WINDOW_MS = 5e3;
|
|
1570
|
+
var ACTIVE_READ_STATUSES = /* @__PURE__ */ new Set([
|
|
1571
|
+
"generating",
|
|
1572
|
+
"running",
|
|
1573
|
+
"streaming",
|
|
1574
|
+
"starting",
|
|
1575
|
+
"busy"
|
|
1576
|
+
]);
|
|
1577
|
+
var recentReads = /* @__PURE__ */ new Map();
|
|
1578
|
+
function isActiveReadChatStatus(status) {
|
|
1579
|
+
return typeof status === "string" && ACTIVE_READ_STATUSES.has(status.toLowerCase());
|
|
1580
|
+
}
|
|
1581
|
+
function annotateRapidReadChatAdvisory(payload, options) {
|
|
1582
|
+
const now = options.now ?? Date.now();
|
|
1583
|
+
const status = options.status ?? payload?.status ?? payload?.data?.status ?? payload?.result?.status;
|
|
1584
|
+
const active = isActiveReadChatStatus(status);
|
|
1585
|
+
const previous = recentReads.get(options.key);
|
|
1586
|
+
if (!active) {
|
|
1587
|
+
recentReads.set(options.key, { at: now, status: typeof status === "string" ? status : void 0 });
|
|
1588
|
+
return payload;
|
|
1535
1589
|
}
|
|
1536
|
-
};
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1590
|
+
recentReads.set(options.key, { at: now, status: typeof status === "string" ? status : void 0 });
|
|
1591
|
+
if (!previous || !isActiveReadChatStatus(previous.status)) return payload;
|
|
1592
|
+
const elapsedMs = now - previous.at;
|
|
1593
|
+
if (elapsedMs < 0 || elapsedMs >= RAPID_READ_CHAT_ADVISORY_WINDOW_MS) return payload;
|
|
1594
|
+
return {
|
|
1595
|
+
...payload,
|
|
1596
|
+
pollingAdvisory: {
|
|
1597
|
+
type: "rapid_read_chat_polling",
|
|
1598
|
+
toolName: options.toolName,
|
|
1599
|
+
windowMs: RAPID_READ_CHAT_ADVISORY_WINDOW_MS,
|
|
1600
|
+
elapsedMs,
|
|
1601
|
+
nextSuggestedReadAt: previous.at + RAPID_READ_CHAT_ADVISORY_WINDOW_MS,
|
|
1602
|
+
completionCallbackExpected: Boolean(options.completionCallbackExpected),
|
|
1603
|
+
message: `This session is still ${String(status)}. Avoid repeated ${options.toolName} polling for the same generating session; wait for the completion callback/status event or retry after the suggested time if you are debugging a real stall.`
|
|
1546
1604
|
}
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
var MESH_REFINE_PLAN_TOOL = {
|
|
1550
|
-
name: "mesh_refine_plan",
|
|
1551
|
-
description: "Dry-run Refinery plan for a worktree node: reports config source, validation commands, suggestions/unavailable reason, and merge/cleanup intent without executing validation or git merge.",
|
|
1552
|
-
inputSchema: {
|
|
1553
|
-
type: "object",
|
|
1554
|
-
properties: {
|
|
1555
|
-
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
1556
|
-
},
|
|
1557
|
-
required: ["node_id"]
|
|
1558
|
-
}
|
|
1559
|
-
};
|
|
1560
|
-
var MESH_REVIEW_INBOX_TOOL = {
|
|
1561
|
-
name: "mesh_review_inbox",
|
|
1562
|
-
description: "List local worktree nodes that need human review: merge candidates (pushed feature branches ready to merge) and Refinery-blocked review results. Returns evidence summaries, diff stats vs. the default branch, and suggested actions (Refine / Requeue / Dismiss). Remote nodes are excluded in M4.0.",
|
|
1563
|
-
inputSchema: {
|
|
1564
|
-
type: "object",
|
|
1565
|
-
properties: {
|
|
1566
|
-
mesh_id: { type: "string", description: "Mesh ID (optional \u2014 inferred from active mesh if omitted)." }
|
|
1567
|
-
},
|
|
1568
|
-
required: []
|
|
1569
|
-
}
|
|
1570
|
-
};
|
|
1571
|
-
var ALL_MESH_TOOLS = [
|
|
1572
|
-
MESH_STATUS_TOOL,
|
|
1573
|
-
MESH_LIST_NODES_TOOL,
|
|
1574
|
-
MESH_ENQUEUE_TASK_TOOL,
|
|
1575
|
-
MESH_VIEW_QUEUE_TOOL,
|
|
1576
|
-
MESH_QUEUE_CANCEL_TOOL,
|
|
1577
|
-
MESH_QUEUE_REQUEUE_TOOL,
|
|
1578
|
-
MESH_SEND_TASK_TOOL,
|
|
1579
|
-
MESH_READ_CHAT_TOOL,
|
|
1580
|
-
MESH_READ_DEBUG_TOOL,
|
|
1581
|
-
MESH_LAUNCH_SESSION_TOOL,
|
|
1582
|
-
MESH_GIT_STATUS_TOOL,
|
|
1583
|
-
MESH_READ_NODE_LOGS_TOOL,
|
|
1584
|
-
MESH_FAST_FORWARD_NODE_TOOL,
|
|
1585
|
-
MESH_RESTART_DAEMON_TOOL,
|
|
1586
|
-
MESH_CHECKPOINT_TOOL,
|
|
1587
|
-
MESH_APPROVE_TOOL,
|
|
1588
|
-
MESH_CLONE_NODE_TOOL,
|
|
1589
|
-
MESH_REMOVE_NODE_TOOL,
|
|
1590
|
-
MESH_REFINE_NODE_TOOL,
|
|
1591
|
-
MESH_REFINE_BATCH_TOOL,
|
|
1592
|
-
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
1593
|
-
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
1594
|
-
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
1595
|
-
MESH_CHANGE_IMPACT_CONFIG_SCHEMA_TOOL,
|
|
1596
|
-
MESH_VALIDATE_CHANGE_IMPACT_CONFIG_TOOL,
|
|
1597
|
-
MESH_SUGGEST_CHANGE_IMPACT_CONFIG_TOOL,
|
|
1598
|
-
MESH_INIT_TOOL,
|
|
1599
|
-
MESH_REFINE_PLAN_TOOL,
|
|
1600
|
-
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1601
|
-
MESH_PRUNE_STALE_DIRECT_TOOL,
|
|
1602
|
-
MESH_TASK_HISTORY_TOOL,
|
|
1603
|
-
MESH_RECORD_NOTE_TOOL,
|
|
1604
|
-
MESH_RECONCILE_LEDGER_TOOL,
|
|
1605
|
-
MESH_MISSION_UPSERT_TOOL,
|
|
1606
|
-
MESH_MISSION_LIST_TOOL,
|
|
1607
|
-
MESH_REVIEW_INBOX_TOOL
|
|
1608
|
-
];
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1609
1607
|
|
|
1610
|
-
// src/tools/mesh-tools.ts
|
|
1608
|
+
// src/tools/mesh-tools-internal.ts
|
|
1609
|
+
var import_daemon_core3 = require("@adhdev/daemon-core");
|
|
1610
|
+
var import_node_crypto = require("crypto");
|
|
1611
1611
|
var SESSION_PROVIDER_METADATA_TTL_MS = 30 * 6e4;
|
|
1612
1612
|
var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
1613
1613
|
function getSessionMetadata(key) {
|
|
@@ -2821,32 +2821,115 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
|
2821
2821
|
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
2822
2822
|
if (!injected) surfacedEvents.push(event);
|
|
2823
2823
|
}
|
|
2824
|
-
} catch {
|
|
2825
|
-
}
|
|
2826
|
-
return surfacedEvents;
|
|
2824
|
+
} catch {
|
|
2825
|
+
}
|
|
2826
|
+
return surfacedEvents;
|
|
2827
|
+
}
|
|
2828
|
+
const events = (0, import_daemon_core2.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
|
|
2829
|
+
events.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
2830
|
+
return events;
|
|
2831
|
+
}
|
|
2832
|
+
function isP2pTransportUnavailableError(error) {
|
|
2833
|
+
return (0, import_daemon_core2.isP2pRelayTransportFailure)(error);
|
|
2834
|
+
}
|
|
2835
|
+
function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode, force) {
|
|
2836
|
+
return {
|
|
2837
|
+
meshId: ctx.mesh.id,
|
|
2838
|
+
nodeId,
|
|
2839
|
+
...sessionCleanupMode ? { sessionCleanupMode } : {},
|
|
2840
|
+
...force === true ? { force: true } : {},
|
|
2841
|
+
inlineMesh: ctx.mesh
|
|
2842
|
+
};
|
|
2843
|
+
}
|
|
2844
|
+
function classifyReadChatTransportCause(error) {
|
|
2845
|
+
const message = (error instanceof Error ? error.message : String(error ?? "")).toLowerCase();
|
|
2846
|
+
if (/not acknowledged|delivery failure|channel never opened|connect timed out|not connected|datachannel|disconnected|\bclosed\b|offline|no route|failed to initiate p2p|p2p mesh is not available|connect queue full/.test(message)) {
|
|
2847
|
+
return "not_connected";
|
|
2848
|
+
}
|
|
2849
|
+
return "saturated";
|
|
2850
|
+
}
|
|
2851
|
+
function resolveCachedMeshSessionPreviewFromLedger(ctx, nodeId, sessionId) {
|
|
2852
|
+
const entries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
2853
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
2854
|
+
const entry = entries[i];
|
|
2855
|
+
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
2856
|
+
const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
|
|
2857
|
+
if (entryNodeId && entryNodeId !== nodeId) continue;
|
|
2858
|
+
const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
|
|
2859
|
+
if (entrySessionId !== sessionId) continue;
|
|
2860
|
+
const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : payload;
|
|
2861
|
+
const preview = (0, import_daemon_core2.resolveMeshSurfacedSessionPreview)(metadataEvent);
|
|
2862
|
+
if (preview) {
|
|
2863
|
+
return { ...preview, ledgerKind: entry.kind, timestamp: entry.timestamp };
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
return void 0;
|
|
2867
|
+
}
|
|
2868
|
+
function buildMeshReadChatCacheFallback(ctx, args, node, error) {
|
|
2869
|
+
const classification = (0, import_daemon_core2.classifyP2pRelayFailure)(error, { command: "read_chat", targetDaemonId: node.daemonId });
|
|
2870
|
+
const cause = classifyReadChatTransportCause(error);
|
|
2871
|
+
const errorMessage = error instanceof Error ? error.message : String(error ?? "");
|
|
2872
|
+
const causeNote = cause === "not_connected" ? "the worker daemon is not currently connected over P2P (no live channel)" : "the worker daemon is connected but saturated \u2014 it acknowledged the request but did not return the transcript within the deadline";
|
|
2873
|
+
const cached = resolveCachedMeshSessionPreviewFromLedger(ctx, args.node_id, args.session_id);
|
|
2874
|
+
if (cached) {
|
|
2875
|
+
return JSON.stringify({
|
|
2876
|
+
success: true,
|
|
2877
|
+
source: "coordinator_cache_fallback",
|
|
2878
|
+
fallback: true,
|
|
2879
|
+
nodeId: args.node_id,
|
|
2880
|
+
sessionId: args.session_id,
|
|
2881
|
+
transport: "p2p",
|
|
2882
|
+
transportFailure: {
|
|
2883
|
+
code: classification.code,
|
|
2884
|
+
reason: classification.reason,
|
|
2885
|
+
cause,
|
|
2886
|
+
error: errorMessage
|
|
2887
|
+
},
|
|
2888
|
+
advisory: `Live transcript unavailable (${causeNote}). Showing the cached coordinator-side summary surfaced from the worker's last completion/status event \u2014 a stale point-in-time summary, NOT the live transcript. The full transcript requires a live P2P read_chat once the peer is reachable.`,
|
|
2889
|
+
fullTranscriptRequiresP2p: true,
|
|
2890
|
+
summary: cached.preview,
|
|
2891
|
+
messages: [{
|
|
2892
|
+
role: cached.role,
|
|
2893
|
+
content: cached.preview,
|
|
2894
|
+
cached: true,
|
|
2895
|
+
...cached.receivedAt ? { receivedAt: cached.receivedAt } : {}
|
|
2896
|
+
}],
|
|
2897
|
+
cachedPreview: {
|
|
2898
|
+
role: cached.role,
|
|
2899
|
+
ledgerKind: cached.ledgerKind,
|
|
2900
|
+
ledgerTimestamp: cached.timestamp,
|
|
2901
|
+
...cached.receivedAt ? { receivedAt: cached.receivedAt } : {}
|
|
2902
|
+
}
|
|
2903
|
+
}, null, 2);
|
|
2827
2904
|
}
|
|
2828
|
-
const
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2905
|
+
const failure = buildCoordinatorP2pRelayFailure(error, {
|
|
2906
|
+
command: "read_chat",
|
|
2907
|
+
targetDaemonId: node.daemonId,
|
|
2908
|
+
nodeId: args.node_id,
|
|
2909
|
+
sessionId: args.session_id
|
|
2910
|
+
});
|
|
2911
|
+
return JSON.stringify({
|
|
2912
|
+
...failure,
|
|
2913
|
+
cause,
|
|
2914
|
+
cachedSummaryAvailable: false,
|
|
2915
|
+
fullTranscriptRequiresP2p: true,
|
|
2916
|
+
advisory: `Live transcript unavailable (${causeNote}) and no cached coordinator-side summary exists for this session yet (no completion/status event has been surfaced). The full transcript requires a live P2P read_chat once the peer is reachable.`
|
|
2917
|
+
}, null, 2);
|
|
2834
2918
|
}
|
|
2835
|
-
function
|
|
2836
|
-
return
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
...force === true ? { force: true } : {},
|
|
2841
|
-
inlineMesh: ctx.mesh
|
|
2842
|
-
};
|
|
2919
|
+
function resolveRefineConfigNode(ctx, nodeId) {
|
|
2920
|
+
if (nodeId) return findNode(ctx.mesh, nodeId);
|
|
2921
|
+
const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
|
|
2922
|
+
if (!node) throw new Error("No mesh node with a workspace is available");
|
|
2923
|
+
return node;
|
|
2843
2924
|
}
|
|
2925
|
+
|
|
2926
|
+
// src/tools/mesh-tools-status.ts
|
|
2844
2927
|
async function meshStatus(ctx, args = {}) {
|
|
2845
|
-
const rateResult = (0,
|
|
2928
|
+
const rateResult = (0, import_daemon_core3.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
|
|
2846
2929
|
const compact = args.verbose === true ? false : args.compact ?? true;
|
|
2847
2930
|
await refreshMeshFromDaemon(ctx);
|
|
2848
2931
|
const { mesh, transport } = ctx;
|
|
2849
|
-
let ledgerSummary = (0,
|
|
2932
|
+
let ledgerSummary = (0, import_daemon_core3.getLedgerSummary)(mesh.id);
|
|
2850
2933
|
const results = await Promise.all(mesh.nodes.map(async (node) => {
|
|
2851
2934
|
const entry = {
|
|
2852
2935
|
nodeId: node.id,
|
|
@@ -2902,14 +2985,14 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2902
2985
|
noFallbackReason: failure.noFallbackReason
|
|
2903
2986
|
});
|
|
2904
2987
|
}
|
|
2905
|
-
entry.dataFreshness = (0,
|
|
2988
|
+
entry.dataFreshness = (0, import_daemon_core3.buildMeshNodeProbeFreshness)({
|
|
2906
2989
|
git: entry.git,
|
|
2907
2990
|
liveTruthProbed,
|
|
2908
2991
|
isSelfNode: entry.machine?.sameMachine === true,
|
|
2909
2992
|
daemonId: readNodeDaemonId(node),
|
|
2910
2993
|
node
|
|
2911
2994
|
});
|
|
2912
|
-
const recoveryContext = (0,
|
|
2995
|
+
const recoveryContext = (0, import_daemon_core3.getSessionRecoveryContext)(mesh.id, { nodeId: node.id });
|
|
2913
2996
|
if (recoveryContext.consecutiveNodeFailures > 0) {
|
|
2914
2997
|
entry.recoveryHints = {
|
|
2915
2998
|
consecutiveFailures: recoveryContext.consecutiveNodeFailures,
|
|
@@ -2981,23 +3064,23 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2981
3064
|
}
|
|
2982
3065
|
return entry;
|
|
2983
3066
|
}));
|
|
2984
|
-
let ledgerEntries = (0,
|
|
2985
|
-
let directDispatches = (0,
|
|
3067
|
+
let ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(mesh.id, { tail: 200 });
|
|
3068
|
+
let directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(mesh.id);
|
|
2986
3069
|
const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, results, directDispatches, ledgerEntries);
|
|
2987
3070
|
if (directReconciliation.reconciled > 0) {
|
|
2988
|
-
ledgerEntries = (0,
|
|
2989
|
-
directDispatches = (0,
|
|
2990
|
-
ledgerSummary = (0,
|
|
3071
|
+
ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(mesh.id, { tail: 200 });
|
|
3072
|
+
directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(mesh.id);
|
|
3073
|
+
ledgerSummary = (0, import_daemon_core3.getLedgerSummary)(mesh.id);
|
|
2991
3074
|
}
|
|
2992
|
-
const activeWorkEvidence = (0,
|
|
3075
|
+
const activeWorkEvidence = (0, import_daemon_core3.buildMeshActiveWork)({
|
|
2993
3076
|
meshId: mesh.id,
|
|
2994
|
-
queue: (0,
|
|
3077
|
+
queue: (0, import_daemon_core3.getQueue)(mesh.id),
|
|
2995
3078
|
ledgerEntries,
|
|
2996
3079
|
directDispatches,
|
|
2997
3080
|
nodes: results
|
|
2998
3081
|
});
|
|
2999
3082
|
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
3000
|
-
const staleDirectWorkSummary = (0,
|
|
3083
|
+
const staleDirectWorkSummary = (0, import_daemon_core3.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
|
|
3001
3084
|
note: activeWorkEvidence.staleDirectWorkNote,
|
|
3002
3085
|
detailHint: "Full stale direct entries are omitted from mesh_status by default. Call mesh_status with includeStaleDirectWorkDetails=true or inspect mesh_task_history for ledger detail."
|
|
3003
3086
|
});
|
|
@@ -3185,7 +3268,7 @@ async function meshStatus(ctx, args = {}) {
|
|
|
3185
3268
|
}
|
|
3186
3269
|
try {
|
|
3187
3270
|
if (compact) {
|
|
3188
|
-
const { live, historyFold } = (0,
|
|
3271
|
+
const { live, historyFold } = (0, import_daemon_core3.getMeshStatusMissionsCompact)(mesh.id);
|
|
3189
3272
|
const ranked = [...live].sort((a, b) => String(b.tasks?.lastActivityAt ?? "").localeCompare(String(a.tasks?.lastActivityAt ?? "")));
|
|
3190
3273
|
const kept = [];
|
|
3191
3274
|
const overflow = [];
|
|
@@ -3212,230 +3295,45 @@ async function meshStatus(ctx, args = {}) {
|
|
|
3212
3295
|
}
|
|
3213
3296
|
if (historyFold) response.missionsHistory = historyFold;
|
|
3214
3297
|
} else {
|
|
3215
|
-
const missions = (0,
|
|
3298
|
+
const missions = (0, import_daemon_core3.getMeshStatusMissionSummaries)(mesh.id, { verbose: true });
|
|
3216
3299
|
if (missions.length > 0) {
|
|
3217
3300
|
response.missions = missions.map((mission) => {
|
|
3218
3301
|
try {
|
|
3219
|
-
return { ...mission, stats: (0,
|
|
3220
|
-
} catch {
|
|
3221
|
-
return mission;
|
|
3222
|
-
}
|
|
3223
|
-
});
|
|
3224
|
-
}
|
|
3225
|
-
}
|
|
3226
|
-
} catch {
|
|
3227
|
-
}
|
|
3228
|
-
try {
|
|
3229
|
-
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
3230
|
-
const asyncRefineJobs = (0,
|
|
3231
|
-
meshId: mesh.id,
|
|
3232
|
-
ledgerEntries,
|
|
3233
|
-
pendingEvents
|
|
3234
|
-
});
|
|
3235
|
-
if (asyncRefineJobs.length > 0) {
|
|
3236
|
-
if (compact) {
|
|
3237
|
-
const summary = (0,
|
|
3238
|
-
if (summary.activeJobs.length > 0) response.asyncRefineJobs = summary.activeJobs;
|
|
3239
|
-
response.asyncRefineJobsSummary = {
|
|
3240
|
-
total: summary.total,
|
|
3241
|
-
byStatus: summary.byStatus,
|
|
3242
|
-
...summary.staleTerminal > 0 ? { staleTerminal: summary.staleTerminal } : {}
|
|
3243
|
-
};
|
|
3244
|
-
} else {
|
|
3245
|
-
response.asyncRefineJobs = asyncRefineJobs;
|
|
3246
|
-
}
|
|
3247
|
-
}
|
|
3248
|
-
if (pendingEvents.length > 0) {
|
|
3249
|
-
response.pendingCoordinatorEvents = pendingEvents;
|
|
3250
|
-
}
|
|
3251
|
-
} catch {
|
|
3252
|
-
}
|
|
3253
|
-
return JSON.stringify(response, null, 2);
|
|
3254
|
-
}
|
|
3255
|
-
async function meshTaskHistory(ctx, args) {
|
|
3256
|
-
const { mesh } = ctx;
|
|
3257
|
-
const compact = args.verbose === true ? false : args.compact ?? true;
|
|
3258
|
-
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
3259
|
-
const requestedTail = typeof args.tail === "number" && args.tail > 0 ? Math.floor(args.tail) : 20;
|
|
3260
|
-
const compactCap = requestedTail > 50 ? 20 : 30;
|
|
3261
|
-
const tail = compact ? Math.min(requestedTail, compactCap) : Math.min(requestedTail, 200);
|
|
3262
|
-
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
3263
|
-
const rawEntries = (0, import_daemon_core2.readLedgerEntries)(mesh.id, { tail, kind });
|
|
3264
|
-
const entries = compact ? rawEntries.map((e) => ({
|
|
3265
|
-
...e,
|
|
3266
|
-
payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
|
|
3267
|
-
})) : rawEntries;
|
|
3268
|
-
const summary = (0, import_daemon_core2.getLedgerSummary)(mesh.id);
|
|
3269
|
-
let taskStats;
|
|
3270
|
-
try {
|
|
3271
|
-
const taskIds = [...new Set(rawEntries.map((e) => typeof e.payload?.taskId === "string" ? e.payload.taskId : "").filter(Boolean))];
|
|
3272
|
-
if (taskIds.length > 0) {
|
|
3273
|
-
const stats = (0, import_daemon_core2.computeMeshTaskStats)(mesh.id, { taskIds });
|
|
3274
|
-
if (stats.length > 0) taskStats = stats;
|
|
3275
|
-
}
|
|
3276
|
-
} catch {
|
|
3277
|
-
}
|
|
3278
|
-
return JSON.stringify({
|
|
3279
|
-
meshId: mesh.id,
|
|
3280
|
-
payloadMode: compact ? "compact" : "full",
|
|
3281
|
-
entries,
|
|
3282
|
-
summary,
|
|
3283
|
-
...taskStats ? { taskStats } : {},
|
|
3284
|
-
...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
|
|
3285
|
-
}, null, 2);
|
|
3286
|
-
}
|
|
3287
|
-
async function meshRecordNote(ctx, args) {
|
|
3288
|
-
const { mesh } = ctx;
|
|
3289
|
-
const text = typeof args.text === "string" ? args.text.trim() : "";
|
|
3290
|
-
if (!text) {
|
|
3291
|
-
return JSON.stringify({ success: false, error: "text required" }, null, 2);
|
|
3292
|
-
}
|
|
3293
|
-
const category = args.category === "provider_quirk" || args.category === "pattern_to_avoid" || args.category === "recovery_lesson" ? args.category : void 0;
|
|
3294
|
-
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3295
|
-
const sourceCoordinator = ctx.coordinatorSessionId || ctx.localDaemonId || ctx.coordinatorHostname || void 0;
|
|
3296
|
-
const entry = (0, import_daemon_core2.appendLedgerEntry)(mesh.id, {
|
|
3297
|
-
kind: "coordinator_operating_note",
|
|
3298
|
-
...sourceCoordinator ? { sessionId: sourceCoordinator } : {},
|
|
3299
|
-
payload: {
|
|
3300
|
-
text,
|
|
3301
|
-
...category ? { category } : {},
|
|
3302
|
-
createdAt,
|
|
3303
|
-
...sourceCoordinator ? { sourceCoordinator } : {}
|
|
3304
|
-
}
|
|
3305
|
-
});
|
|
3306
|
-
return JSON.stringify({
|
|
3307
|
-
success: true,
|
|
3308
|
-
meshId: mesh.id,
|
|
3309
|
-
noteId: entry.id,
|
|
3310
|
-
recorded: { text, category: category ?? null, createdAt },
|
|
3311
|
-
note: 'Recorded to the mesh ledger. Future coordinators on this mesh will see it under "## Operating Notes" at launch.'
|
|
3312
|
-
}, null, 2);
|
|
3313
|
-
}
|
|
3314
|
-
async function meshReconcileLedger(ctx, args) {
|
|
3315
|
-
await refreshMeshFromDaemon(ctx);
|
|
3316
|
-
const requestedNodeIds = Array.isArray(args.node_ids) ? new Set(args.node_ids.map((id) => typeof id === "string" ? id.trim() : "").filter(Boolean)) : null;
|
|
3317
|
-
const nodes = ctx.mesh.nodes.filter((node) => !requestedNodeIds || requestedNodeIds.has(node.id));
|
|
3318
|
-
const replicas = [];
|
|
3319
|
-
const shouldImport = args.import_entries !== false;
|
|
3320
|
-
const queryArgs = {
|
|
3321
|
-
meshId: ctx.mesh.id,
|
|
3322
|
-
...typeof args.limit === "number" ? { limit: args.limit } : {},
|
|
3323
|
-
...typeof args.after_id === "string" && args.after_id.trim() ? { afterId: args.after_id.trim() } : {},
|
|
3324
|
-
...typeof args.since === "string" && args.since.trim() ? { since: args.since.trim() } : {}
|
|
3325
|
-
};
|
|
3326
|
-
for (const node of nodes) {
|
|
3327
|
-
try {
|
|
3328
|
-
if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
|
|
3329
|
-
const slice2 = (0, import_daemon_core2.readLedgerSliceFromStore)(ctx.mesh.id, queryArgs);
|
|
3330
|
-
replicas.push((0, import_daemon_core2.buildMeshLedgerReplicaEvidence)({
|
|
3331
|
-
nodeId: node.id,
|
|
3332
|
-
daemonId: node.daemonId,
|
|
3333
|
-
transport: "local",
|
|
3334
|
-
slice: slice2,
|
|
3335
|
-
status: "local"
|
|
3336
|
-
}));
|
|
3337
|
-
continue;
|
|
3338
|
-
}
|
|
3339
|
-
const result = await commandForNode(ctx, node, "get_mesh_ledger_slice", queryArgs);
|
|
3340
|
-
const payload = unwrapCommandPayload(result);
|
|
3341
|
-
if (payload?.success === false) {
|
|
3342
|
-
throw new Error(payload.error || "remote get_mesh_ledger_slice failed");
|
|
3343
|
-
}
|
|
3344
|
-
const slice = payload?.slice ?? payload;
|
|
3345
|
-
if (slice?.protocol !== "adhdev.mesh.ledger.slice.v1" || !Array.isArray(slice.entries)) {
|
|
3346
|
-
throw new Error("remote daemon returned an invalid ledger slice payload");
|
|
3347
|
-
}
|
|
3348
|
-
const importResult = shouldImport ? (0, import_daemon_core2.appendRemoteLedgerEntries)(ctx.mesh.id, slice.entries) : { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
|
|
3349
|
-
replicas.push((0, import_daemon_core2.buildMeshLedgerReplicaEvidence)({
|
|
3350
|
-
nodeId: node.id,
|
|
3351
|
-
daemonId: node.daemonId,
|
|
3352
|
-
transport: "p2p_datachannel",
|
|
3353
|
-
slice,
|
|
3354
|
-
importResult
|
|
3355
|
-
}));
|
|
3356
|
-
if (shouldImport && importResult.accepted > 0) {
|
|
3357
|
-
(0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
|
|
3358
|
-
kind: "ledger_replicated",
|
|
3359
|
-
nodeId: node.id,
|
|
3360
|
-
payload: {
|
|
3361
|
-
protocol: "adhdev.mesh.ledger.slice.v1",
|
|
3362
|
-
imported: importResult.accepted,
|
|
3363
|
-
skippedDuplicate: importResult.skippedDuplicate,
|
|
3364
|
-
rejectedInvalid: importResult.rejectedInvalid,
|
|
3365
|
-
nextAfterId: slice.cursor?.nextAfterId ?? null,
|
|
3366
|
-
via: "p2p_datachannel"
|
|
3367
|
-
}
|
|
3368
|
-
});
|
|
3369
|
-
}
|
|
3370
|
-
} catch (e) {
|
|
3371
|
-
replicas.push((0, import_daemon_core2.buildMeshLedgerReplicaEvidence)({
|
|
3372
|
-
nodeId: node.id,
|
|
3373
|
-
daemonId: node.daemonId,
|
|
3374
|
-
transport: node.daemonId ? "p2p_datachannel" : "local",
|
|
3375
|
-
status: "failed",
|
|
3376
|
-
error: e?.message ?? String(e)
|
|
3377
|
-
}));
|
|
3378
|
-
}
|
|
3379
|
-
}
|
|
3380
|
-
const evidence = (0, import_daemon_core2.buildMeshLedgerReconciliationEvidence)(ctx.mesh.id, replicas);
|
|
3381
|
-
(0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
|
|
3382
|
-
kind: "ledger_reconciled",
|
|
3383
|
-
payload: {
|
|
3384
|
-
protocol: evidence.protocol,
|
|
3385
|
-
sourceOfTruth: evidence.sourceOfTruth,
|
|
3386
|
-
totals: evidence.totals,
|
|
3387
|
-
convergence: evidence.convergence
|
|
3388
|
-
}
|
|
3389
|
-
});
|
|
3390
|
-
return JSON.stringify({ success: true, evidence }, null, 2);
|
|
3391
|
-
}
|
|
3392
|
-
async function meshPruneStaleDirect(ctx, args = {}) {
|
|
3393
|
-
await refreshMeshFromDaemon(ctx);
|
|
3394
|
-
const execute = args.execute === true && args.dry_run !== true;
|
|
3395
|
-
const includeTerminal = args.include_terminal === true;
|
|
3396
|
-
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
3397
|
-
const ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 500 });
|
|
3398
|
-
const directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(ctx.mesh.id);
|
|
3399
|
-
const result = (0, import_daemon_core2.pruneStaleDirectDispatches)({
|
|
3400
|
-
meshId: ctx.mesh.id,
|
|
3401
|
-
queue: (0, import_daemon_core2.getQueue)(ctx.mesh.id),
|
|
3402
|
-
ledgerEntries,
|
|
3403
|
-
directDispatches,
|
|
3404
|
-
nodes: liveNodes,
|
|
3405
|
-
execute,
|
|
3406
|
-
includeTerminal,
|
|
3407
|
-
source: "mesh_prune_stale_direct"
|
|
3408
|
-
});
|
|
3409
|
-
const { prunable, prunedCount, preservedUnacknowledged, preservedLedgerOnly, preservedNotOrphan } = result;
|
|
3410
|
-
const summarize = (records) => records.map((r) => ({
|
|
3411
|
-
taskId: r.taskId,
|
|
3412
|
-
nodeId: r.nodeId,
|
|
3413
|
-
sessionId: r.sessionId,
|
|
3414
|
-
status: r.status,
|
|
3415
|
-
terminal: r.terminal === true,
|
|
3416
|
-
staleReason: r.staleReason,
|
|
3417
|
-
taskTitle: r.taskTitle,
|
|
3418
|
-
createdAt: r.createdAt
|
|
3419
|
-
}));
|
|
3420
|
-
return JSON.stringify({
|
|
3421
|
-
success: true,
|
|
3422
|
-
mode: result.mode,
|
|
3423
|
-
meshId: ctx.mesh.id,
|
|
3424
|
-
includeTerminal,
|
|
3425
|
-
candidateCount: result.candidateCount,
|
|
3426
|
-
prunableCount: prunable.length,
|
|
3427
|
-
prunedCount,
|
|
3428
|
-
prunable: summarize(prunable),
|
|
3429
|
-
preserved: {
|
|
3430
|
-
unacknowledgedCount: preservedUnacknowledged.length,
|
|
3431
|
-
ledgerOnlyCount: preservedLedgerOnly.length,
|
|
3432
|
-
notOrphanCount: preservedNotOrphan.length,
|
|
3433
|
-
unacknowledged: summarize(preservedUnacknowledged),
|
|
3434
|
-
ledgerOnly: summarize(preservedLedgerOnly),
|
|
3435
|
-
notOrphan: summarize(preservedNotOrphan)
|
|
3436
|
-
},
|
|
3437
|
-
note: execute ? `Pruned ${prunedCount} orphaned direct dispatch record(s) from the active staleDirect surface. The append-only mesh ledger audit history is preserved; a direct_dispatch_pruned entry records this prune.` : "Dry run \u2014 nothing was deleted. Re-run with execute=true to prune the listed orphaned records. Fresh unacknowledged dispatch failures (node/session still live) and ledger-only audit entries are always preserved."
|
|
3438
|
-
}, null, 2);
|
|
3302
|
+
return { ...mission, stats: (0, import_daemon_core3.computeMeshMissionStats)(mesh.id, mission.id) };
|
|
3303
|
+
} catch {
|
|
3304
|
+
return mission;
|
|
3305
|
+
}
|
|
3306
|
+
});
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
} catch {
|
|
3310
|
+
}
|
|
3311
|
+
try {
|
|
3312
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
3313
|
+
const asyncRefineJobs = (0, import_daemon_core3.buildMeshAsyncRefineJobs)({
|
|
3314
|
+
meshId: mesh.id,
|
|
3315
|
+
ledgerEntries,
|
|
3316
|
+
pendingEvents
|
|
3317
|
+
});
|
|
3318
|
+
if (asyncRefineJobs.length > 0) {
|
|
3319
|
+
if (compact) {
|
|
3320
|
+
const summary = (0, import_daemon_core3.summarizeMeshAsyncRefineJobs)(asyncRefineJobs);
|
|
3321
|
+
if (summary.activeJobs.length > 0) response.asyncRefineJobs = summary.activeJobs;
|
|
3322
|
+
response.asyncRefineJobsSummary = {
|
|
3323
|
+
total: summary.total,
|
|
3324
|
+
byStatus: summary.byStatus,
|
|
3325
|
+
...summary.staleTerminal > 0 ? { staleTerminal: summary.staleTerminal } : {}
|
|
3326
|
+
};
|
|
3327
|
+
} else {
|
|
3328
|
+
response.asyncRefineJobs = asyncRefineJobs;
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
if (pendingEvents.length > 0) {
|
|
3332
|
+
response.pendingCoordinatorEvents = pendingEvents;
|
|
3333
|
+
}
|
|
3334
|
+
} catch {
|
|
3335
|
+
}
|
|
3336
|
+
return JSON.stringify(response, null, 2);
|
|
3439
3337
|
}
|
|
3440
3338
|
async function meshListNodes(ctx) {
|
|
3441
3339
|
await refreshMeshFromDaemon(ctx);
|
|
@@ -3459,67 +3357,18 @@ async function meshListNodes(ctx) {
|
|
|
3459
3357
|
}))
|
|
3460
3358
|
}, null, 2);
|
|
3461
3359
|
}
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
const mission = (0, import_daemon_core2.upsertMeshMission)(ctx.mesh.id, {
|
|
3465
|
-
id: readString(args.mission_id) || readString(args.missionId) || void 0,
|
|
3466
|
-
title: args.title,
|
|
3467
|
-
goal: typeof args.goal === "string" ? args.goal : void 0,
|
|
3468
|
-
status: readString(args.status) || void 0
|
|
3469
|
-
});
|
|
3470
|
-
return JSON.stringify({
|
|
3471
|
-
success: true,
|
|
3472
|
-
mission,
|
|
3473
|
-
nextAction: "Attach tasks with mesh_enqueue_task mission_id and depends_on. mesh_status shows live task aggregates for this mission."
|
|
3474
|
-
});
|
|
3475
|
-
} catch (e) {
|
|
3476
|
-
const message = e?.message || String(e);
|
|
3477
|
-
const code = message.includes("mission_title_required") ? "mission_title_required" : message.includes("invalid_mission_status") ? "invalid_mission_status" : void 0;
|
|
3478
|
-
return JSON.stringify({ success: false, ...code ? { code } : {}, error: message });
|
|
3479
|
-
}
|
|
3480
|
-
}
|
|
3481
|
-
async function meshMissionList(ctx, args = {}) {
|
|
3482
|
-
try {
|
|
3483
|
-
const rawStatuses = Array.isArray(args.status) ? args.status : typeof args.status === "string" && args.status.trim() ? [args.status] : [];
|
|
3484
|
-
const invalid = rawStatuses.filter((s) => !import_daemon_core2.MESH_MISSION_STATUSES.includes(s));
|
|
3485
|
-
if (invalid.length > 0) {
|
|
3486
|
-
return JSON.stringify({
|
|
3487
|
-
success: false,
|
|
3488
|
-
code: "invalid_mission_status",
|
|
3489
|
-
error: `invalid status filter: ${invalid.join(", ")} (valid: ${import_daemon_core2.MESH_MISSION_STATUSES.join(", ")})`
|
|
3490
|
-
});
|
|
3491
|
-
}
|
|
3492
|
-
const statuses = rawStatuses.length > 0 ? rawStatuses : void 0;
|
|
3493
|
-
const missions = (0, import_daemon_core2.listMeshMissionSummaries)(ctx.mesh.id, {
|
|
3494
|
-
statuses,
|
|
3495
|
-
verbose: args.verbose === true
|
|
3496
|
-
}).map((mission) => {
|
|
3497
|
-
try {
|
|
3498
|
-
return { ...mission, stats: (0, import_daemon_core2.computeMeshMissionStats)(ctx.mesh.id, mission.id) };
|
|
3499
|
-
} catch {
|
|
3500
|
-
return mission;
|
|
3501
|
-
}
|
|
3502
|
-
});
|
|
3503
|
-
return JSON.stringify({
|
|
3504
|
-
success: true,
|
|
3505
|
-
count: missions.length,
|
|
3506
|
-
...statuses ? { statusFilter: statuses } : {},
|
|
3507
|
-
missions
|
|
3508
|
-
}, null, 2);
|
|
3509
|
-
} catch (e) {
|
|
3510
|
-
return JSON.stringify({ success: false, error: e?.message || String(e) });
|
|
3511
|
-
}
|
|
3512
|
-
}
|
|
3360
|
+
|
|
3361
|
+
// src/tools/mesh-tools-queue.ts
|
|
3513
3362
|
async function meshEnqueueTask(ctx, args) {
|
|
3514
3363
|
const taskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
3515
|
-
const requiredTags = (0,
|
|
3364
|
+
const requiredTags = (0, import_daemon_core3.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
|
|
3516
3365
|
const dependsOn = Array.isArray(args.dependsOn) ? args.dependsOn : Array.isArray(args.depends_on) ? args.depends_on : void 0;
|
|
3517
3366
|
const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
|
|
3518
3367
|
const explicitTarget = readString(args.targetNodeId) || readString(args.target_node_id) || void 0;
|
|
3519
3368
|
const preferWorktree = args.preferWorktree === true || args.prefer_worktree === true;
|
|
3520
3369
|
const targetNodeId = explicitTarget || (preferWorktree ? resolvePreferredWorktreeNodeId(ctx) : void 0);
|
|
3521
3370
|
try {
|
|
3522
|
-
const task = (0,
|
|
3371
|
+
const task = (0, import_daemon_core3.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags, dependsOn, missionId, targetNodeId, ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {} });
|
|
3523
3372
|
if (!(ctx.transport instanceof IpcTransport)) {
|
|
3524
3373
|
const queueTrigger = await triggerMeshQueueAndReport(ctx);
|
|
3525
3374
|
return JSON.stringify({
|
|
@@ -3542,14 +3391,14 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
3542
3391
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
3543
3392
|
if (isLocalNode || !node.daemonId) continue;
|
|
3544
3393
|
if (targetNodeId && node.id !== targetNodeId) continue;
|
|
3545
|
-
if (!(0,
|
|
3394
|
+
if (!(0, import_daemon_core3.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core3.buildMeshNodeCapabilityTags)(node))) continue;
|
|
3546
3395
|
dispatchPromises.push(
|
|
3547
3396
|
ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
|
|
3548
3397
|
if (result.success) {
|
|
3549
3398
|
try {
|
|
3550
3399
|
const providerType = result.providerType;
|
|
3551
3400
|
const descriptor = summarizeTaskMessage(args.message);
|
|
3552
|
-
(0,
|
|
3401
|
+
(0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
|
|
3553
3402
|
kind: "task_dispatched",
|
|
3554
3403
|
nodeId: node.id,
|
|
3555
3404
|
sessionId: result.sessionId,
|
|
@@ -3571,7 +3420,7 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
3571
3420
|
}
|
|
3572
3421
|
}).catch((err) => {
|
|
3573
3422
|
try {
|
|
3574
|
-
(0,
|
|
3423
|
+
(0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
|
|
3575
3424
|
kind: "p2p_dispatch_failed",
|
|
3576
3425
|
nodeId: node.id,
|
|
3577
3426
|
payload: {
|
|
@@ -3614,17 +3463,17 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
3614
3463
|
}
|
|
3615
3464
|
}
|
|
3616
3465
|
async function meshViewQueue(ctx, args) {
|
|
3617
|
-
const rateResult = (0,
|
|
3466
|
+
const rateResult = (0, import_daemon_core3.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
|
|
3618
3467
|
const compact = args.verbose === true ? false : args.compact ?? true;
|
|
3619
3468
|
try {
|
|
3620
3469
|
await refreshMeshFromDaemon(ctx);
|
|
3621
3470
|
const statusFilter = sanitizeQueueStatusFilter(args.status);
|
|
3622
3471
|
const view = normalizeQueueViewMode(args.view);
|
|
3623
|
-
const rawQueue = (0,
|
|
3472
|
+
const rawQueue = (0, import_daemon_core3.getQueue)(ctx.mesh.id);
|
|
3624
3473
|
const statusById = new Map(rawQueue.map((task) => [task.id, task.status]));
|
|
3625
3474
|
const withDependencies = rawQueue.map((task) => {
|
|
3626
3475
|
if (!Array.isArray(task.dependsOn) || task.dependsOn.length === 0) return task;
|
|
3627
|
-
const depState = (0,
|
|
3476
|
+
const depState = (0, import_daemon_core3.describeTaskDependencyState)(task, statusById);
|
|
3628
3477
|
return { ...task, ...depState };
|
|
3629
3478
|
});
|
|
3630
3479
|
const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness(withDependencies, ctx.mesh));
|
|
@@ -3633,16 +3482,16 @@ async function meshViewQueue(ctx, args) {
|
|
|
3633
3482
|
const visibleSummary = buildQueueStatusSummary(queue);
|
|
3634
3483
|
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
3635
3484
|
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
3636
|
-
let ledgerEntries = (0,
|
|
3637
|
-
let directDispatches = (0,
|
|
3485
|
+
let ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
3486
|
+
let directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(ctx.mesh.id);
|
|
3638
3487
|
const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries);
|
|
3639
3488
|
if (directReconciliation.reconciled > 0) {
|
|
3640
|
-
ledgerEntries = (0,
|
|
3641
|
-
directDispatches = (0,
|
|
3489
|
+
ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
3490
|
+
directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(ctx.mesh.id);
|
|
3642
3491
|
}
|
|
3643
|
-
(0,
|
|
3644
|
-
directDispatches = (0,
|
|
3645
|
-
const activeWorkEvidence = (0,
|
|
3492
|
+
(0, import_daemon_core3.markStaleDirectDispatches)(ctx.mesh.id);
|
|
3493
|
+
directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(ctx.mesh.id);
|
|
3494
|
+
const activeWorkEvidence = (0, import_daemon_core3.buildMeshActiveWork)({
|
|
3646
3495
|
meshId: ctx.mesh.id,
|
|
3647
3496
|
queue: fullQueue,
|
|
3648
3497
|
ledgerEntries,
|
|
@@ -3667,7 +3516,7 @@ async function meshViewQueue(ctx, args) {
|
|
|
3667
3516
|
const wantActiveQueueArray = view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status));
|
|
3668
3517
|
const wantHistoricalQueueArray = !compact && (view === "historical" || requestedHistoricalRows);
|
|
3669
3518
|
const activeWorkResult = compact ? compactActiveWorkRecords(activeWorkEvidence.activeWork) : { records: activeWorkEvidence.activeWork, omitted: 0 };
|
|
3670
|
-
const staleDirectWorkSummary = (0,
|
|
3519
|
+
const staleDirectWorkSummary = (0, import_daemon_core3.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
|
|
3671
3520
|
note: activeWorkEvidence.staleDirectWorkNote,
|
|
3672
3521
|
detailHint: "Full stale direct entries are omitted from mesh_view_queue in compact mode. Call mesh_view_queue with verbose=true, or inspect mesh_task_history for ledger detail."
|
|
3673
3522
|
});
|
|
@@ -3742,55 +3591,304 @@ async function meshQueueCancel(ctx, args) {
|
|
|
3742
3591
|
try {
|
|
3743
3592
|
const taskId = (args.task_id || args.taskId || "").trim();
|
|
3744
3593
|
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
3745
|
-
const task = (0,
|
|
3594
|
+
const task = (0, import_daemon_core3.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
|
|
3746
3595
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
3747
3596
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
3748
3597
|
});
|
|
3749
3598
|
return JSON.stringify({ success: true, task }, null, 2);
|
|
3750
3599
|
} catch (e) {
|
|
3751
|
-
return JSON.stringify({ success: false, error: e.message });
|
|
3600
|
+
return JSON.stringify({ success: false, error: e.message });
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
async function meshQueueRequeue(ctx, args) {
|
|
3604
|
+
try {
|
|
3605
|
+
const taskId = (args.task_id || args.taskId || "").trim();
|
|
3606
|
+
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
3607
|
+
const targetNodeId = (args.target_node_id || args.targetNodeId || "").trim() || void 0;
|
|
3608
|
+
const targetSessionId = (args.target_session_id || args.targetSessionId || "").trim() || void 0;
|
|
3609
|
+
const keepTargetSession = args.keep_target_session === true || args.keepTargetSession === true;
|
|
3610
|
+
const task = (0, import_daemon_core3.requeueTask)(ctx.mesh.id, taskId, {
|
|
3611
|
+
reason: args.reason,
|
|
3612
|
+
targetNodeId,
|
|
3613
|
+
targetSessionId,
|
|
3614
|
+
clearTargetNode: args.clear_target_node === true || args.clearTargetNode === true,
|
|
3615
|
+
clearTargetSession: targetSessionId ? false : !keepTargetSession,
|
|
3616
|
+
force: args.force === true
|
|
3617
|
+
});
|
|
3618
|
+
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
3619
|
+
if (task.status === "failed" && task.cancelReason?.startsWith("max_retries_exceeded")) {
|
|
3620
|
+
return JSON.stringify({
|
|
3621
|
+
success: false,
|
|
3622
|
+
code: "max_retries_exceeded",
|
|
3623
|
+
error: task.cancelReason,
|
|
3624
|
+
task,
|
|
3625
|
+
hint: "Use force=true to bypass the retry cap for explicit operator recovery."
|
|
3626
|
+
}, null, 2);
|
|
3627
|
+
}
|
|
3628
|
+
const triggerPreferredNodeId = targetNodeId || task.targetNodeId || void 0;
|
|
3629
|
+
ctx.transport.command("trigger_mesh_queue", {
|
|
3630
|
+
meshId: ctx.mesh.id,
|
|
3631
|
+
...triggerPreferredNodeId ? { preferredNodeId: triggerPreferredNodeId } : {}
|
|
3632
|
+
}).catch(() => {
|
|
3633
|
+
});
|
|
3634
|
+
return JSON.stringify({ success: true, task }, null, 2);
|
|
3635
|
+
} catch (e) {
|
|
3636
|
+
return JSON.stringify({ success: false, error: e.message });
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
// src/tools/mesh-tools-mission.ts
|
|
3641
|
+
async function meshTaskHistory(ctx, args) {
|
|
3642
|
+
const { mesh } = ctx;
|
|
3643
|
+
const compact = args.verbose === true ? false : args.compact ?? true;
|
|
3644
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
3645
|
+
const requestedTail = typeof args.tail === "number" && args.tail > 0 ? Math.floor(args.tail) : 20;
|
|
3646
|
+
const compactCap = requestedTail > 50 ? 20 : 30;
|
|
3647
|
+
const tail = compact ? Math.min(requestedTail, compactCap) : Math.min(requestedTail, 200);
|
|
3648
|
+
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
3649
|
+
const rawEntries = (0, import_daemon_core3.readLedgerEntries)(mesh.id, { tail, kind });
|
|
3650
|
+
const entries = compact ? rawEntries.map((e) => ({
|
|
3651
|
+
...e,
|
|
3652
|
+
payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
|
|
3653
|
+
})) : rawEntries;
|
|
3654
|
+
const summary = (0, import_daemon_core3.getLedgerSummary)(mesh.id);
|
|
3655
|
+
let taskStats;
|
|
3656
|
+
try {
|
|
3657
|
+
const taskIds = [...new Set(rawEntries.map((e) => typeof e.payload?.taskId === "string" ? e.payload.taskId : "").filter(Boolean))];
|
|
3658
|
+
if (taskIds.length > 0) {
|
|
3659
|
+
const stats = (0, import_daemon_core3.computeMeshTaskStats)(mesh.id, { taskIds });
|
|
3660
|
+
if (stats.length > 0) taskStats = stats;
|
|
3661
|
+
}
|
|
3662
|
+
} catch {
|
|
3663
|
+
}
|
|
3664
|
+
return JSON.stringify({
|
|
3665
|
+
meshId: mesh.id,
|
|
3666
|
+
payloadMode: compact ? "compact" : "full",
|
|
3667
|
+
entries,
|
|
3668
|
+
summary,
|
|
3669
|
+
...taskStats ? { taskStats } : {},
|
|
3670
|
+
...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
|
|
3671
|
+
}, null, 2);
|
|
3672
|
+
}
|
|
3673
|
+
async function meshRecordNote(ctx, args) {
|
|
3674
|
+
const { mesh } = ctx;
|
|
3675
|
+
const text = typeof args.text === "string" ? args.text.trim() : "";
|
|
3676
|
+
if (!text) {
|
|
3677
|
+
return JSON.stringify({ success: false, error: "text required" }, null, 2);
|
|
3678
|
+
}
|
|
3679
|
+
const category = args.category === "provider_quirk" || args.category === "pattern_to_avoid" || args.category === "recovery_lesson" ? args.category : void 0;
|
|
3680
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3681
|
+
const sourceCoordinator = ctx.coordinatorSessionId || ctx.localDaemonId || ctx.coordinatorHostname || void 0;
|
|
3682
|
+
const entry = (0, import_daemon_core3.appendLedgerEntry)(mesh.id, {
|
|
3683
|
+
kind: "coordinator_operating_note",
|
|
3684
|
+
...sourceCoordinator ? { sessionId: sourceCoordinator } : {},
|
|
3685
|
+
payload: {
|
|
3686
|
+
text,
|
|
3687
|
+
...category ? { category } : {},
|
|
3688
|
+
createdAt,
|
|
3689
|
+
...sourceCoordinator ? { sourceCoordinator } : {}
|
|
3690
|
+
}
|
|
3691
|
+
});
|
|
3692
|
+
return JSON.stringify({
|
|
3693
|
+
success: true,
|
|
3694
|
+
meshId: mesh.id,
|
|
3695
|
+
noteId: entry.id,
|
|
3696
|
+
recorded: { text, category: category ?? null, createdAt },
|
|
3697
|
+
note: 'Recorded to the mesh ledger. Future coordinators on this mesh will see it under "## Operating Notes" at launch.'
|
|
3698
|
+
}, null, 2);
|
|
3699
|
+
}
|
|
3700
|
+
async function meshReconcileLedger(ctx, args) {
|
|
3701
|
+
await refreshMeshFromDaemon(ctx);
|
|
3702
|
+
const requestedNodeIds = Array.isArray(args.node_ids) ? new Set(args.node_ids.map((id) => typeof id === "string" ? id.trim() : "").filter(Boolean)) : null;
|
|
3703
|
+
const nodes = ctx.mesh.nodes.filter((node) => !requestedNodeIds || requestedNodeIds.has(node.id));
|
|
3704
|
+
const replicas = [];
|
|
3705
|
+
const shouldImport = args.import_entries !== false;
|
|
3706
|
+
const queryArgs = {
|
|
3707
|
+
meshId: ctx.mesh.id,
|
|
3708
|
+
...typeof args.limit === "number" ? { limit: args.limit } : {},
|
|
3709
|
+
...typeof args.after_id === "string" && args.after_id.trim() ? { afterId: args.after_id.trim() } : {},
|
|
3710
|
+
...typeof args.since === "string" && args.since.trim() ? { since: args.since.trim() } : {}
|
|
3711
|
+
};
|
|
3712
|
+
for (const node of nodes) {
|
|
3713
|
+
try {
|
|
3714
|
+
if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
|
|
3715
|
+
const slice2 = (0, import_daemon_core3.readLedgerSliceFromStore)(ctx.mesh.id, queryArgs);
|
|
3716
|
+
replicas.push((0, import_daemon_core3.buildMeshLedgerReplicaEvidence)({
|
|
3717
|
+
nodeId: node.id,
|
|
3718
|
+
daemonId: node.daemonId,
|
|
3719
|
+
transport: "local",
|
|
3720
|
+
slice: slice2,
|
|
3721
|
+
status: "local"
|
|
3722
|
+
}));
|
|
3723
|
+
continue;
|
|
3724
|
+
}
|
|
3725
|
+
const result = await commandForNode(ctx, node, "get_mesh_ledger_slice", queryArgs);
|
|
3726
|
+
const payload = unwrapCommandPayload(result);
|
|
3727
|
+
if (payload?.success === false) {
|
|
3728
|
+
throw new Error(payload.error || "remote get_mesh_ledger_slice failed");
|
|
3729
|
+
}
|
|
3730
|
+
const slice = payload?.slice ?? payload;
|
|
3731
|
+
if (slice?.protocol !== "adhdev.mesh.ledger.slice.v1" || !Array.isArray(slice.entries)) {
|
|
3732
|
+
throw new Error("remote daemon returned an invalid ledger slice payload");
|
|
3733
|
+
}
|
|
3734
|
+
const importResult = shouldImport ? (0, import_daemon_core3.appendRemoteLedgerEntries)(ctx.mesh.id, slice.entries) : { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
|
|
3735
|
+
replicas.push((0, import_daemon_core3.buildMeshLedgerReplicaEvidence)({
|
|
3736
|
+
nodeId: node.id,
|
|
3737
|
+
daemonId: node.daemonId,
|
|
3738
|
+
transport: "p2p_datachannel",
|
|
3739
|
+
slice,
|
|
3740
|
+
importResult
|
|
3741
|
+
}));
|
|
3742
|
+
if (shouldImport && importResult.accepted > 0) {
|
|
3743
|
+
(0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
|
|
3744
|
+
kind: "ledger_replicated",
|
|
3745
|
+
nodeId: node.id,
|
|
3746
|
+
payload: {
|
|
3747
|
+
protocol: "adhdev.mesh.ledger.slice.v1",
|
|
3748
|
+
imported: importResult.accepted,
|
|
3749
|
+
skippedDuplicate: importResult.skippedDuplicate,
|
|
3750
|
+
rejectedInvalid: importResult.rejectedInvalid,
|
|
3751
|
+
nextAfterId: slice.cursor?.nextAfterId ?? null,
|
|
3752
|
+
via: "p2p_datachannel"
|
|
3753
|
+
}
|
|
3754
|
+
});
|
|
3755
|
+
}
|
|
3756
|
+
} catch (e) {
|
|
3757
|
+
replicas.push((0, import_daemon_core3.buildMeshLedgerReplicaEvidence)({
|
|
3758
|
+
nodeId: node.id,
|
|
3759
|
+
daemonId: node.daemonId,
|
|
3760
|
+
transport: node.daemonId ? "p2p_datachannel" : "local",
|
|
3761
|
+
status: "failed",
|
|
3762
|
+
error: e?.message ?? String(e)
|
|
3763
|
+
}));
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
const evidence = (0, import_daemon_core3.buildMeshLedgerReconciliationEvidence)(ctx.mesh.id, replicas);
|
|
3767
|
+
(0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
|
|
3768
|
+
kind: "ledger_reconciled",
|
|
3769
|
+
payload: {
|
|
3770
|
+
protocol: evidence.protocol,
|
|
3771
|
+
sourceOfTruth: evidence.sourceOfTruth,
|
|
3772
|
+
totals: evidence.totals,
|
|
3773
|
+
convergence: evidence.convergence
|
|
3774
|
+
}
|
|
3775
|
+
});
|
|
3776
|
+
return JSON.stringify({ success: true, evidence }, null, 2);
|
|
3777
|
+
}
|
|
3778
|
+
async function meshMissionUpsert(ctx, args) {
|
|
3779
|
+
try {
|
|
3780
|
+
const mission = (0, import_daemon_core3.upsertMeshMission)(ctx.mesh.id, {
|
|
3781
|
+
id: readString(args.mission_id) || readString(args.missionId) || void 0,
|
|
3782
|
+
title: args.title,
|
|
3783
|
+
goal: typeof args.goal === "string" ? args.goal : void 0,
|
|
3784
|
+
status: readString(args.status) || void 0
|
|
3785
|
+
});
|
|
3786
|
+
return JSON.stringify({
|
|
3787
|
+
success: true,
|
|
3788
|
+
mission,
|
|
3789
|
+
nextAction: "Attach tasks with mesh_enqueue_task mission_id and depends_on. mesh_status shows live task aggregates for this mission."
|
|
3790
|
+
});
|
|
3791
|
+
} catch (e) {
|
|
3792
|
+
const message = e?.message || String(e);
|
|
3793
|
+
const code = message.includes("mission_title_required") ? "mission_title_required" : message.includes("invalid_mission_status") ? "invalid_mission_status" : void 0;
|
|
3794
|
+
return JSON.stringify({ success: false, ...code ? { code } : {}, error: message });
|
|
3752
3795
|
}
|
|
3753
3796
|
}
|
|
3754
|
-
async function
|
|
3797
|
+
async function meshMissionList(ctx, args = {}) {
|
|
3755
3798
|
try {
|
|
3756
|
-
const
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
const targetSessionId = (args.target_session_id || args.targetSessionId || "").trim() || void 0;
|
|
3760
|
-
const keepTargetSession = args.keep_target_session === true || args.keepTargetSession === true;
|
|
3761
|
-
const task = (0, import_daemon_core2.requeueTask)(ctx.mesh.id, taskId, {
|
|
3762
|
-
reason: args.reason,
|
|
3763
|
-
targetNodeId,
|
|
3764
|
-
targetSessionId,
|
|
3765
|
-
clearTargetNode: args.clear_target_node === true || args.clearTargetNode === true,
|
|
3766
|
-
clearTargetSession: targetSessionId ? false : !keepTargetSession,
|
|
3767
|
-
force: args.force === true
|
|
3768
|
-
});
|
|
3769
|
-
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
3770
|
-
if (task.status === "failed" && task.cancelReason?.startsWith("max_retries_exceeded")) {
|
|
3799
|
+
const rawStatuses = Array.isArray(args.status) ? args.status : typeof args.status === "string" && args.status.trim() ? [args.status] : [];
|
|
3800
|
+
const invalid = rawStatuses.filter((s) => !import_daemon_core3.MESH_MISSION_STATUSES.includes(s));
|
|
3801
|
+
if (invalid.length > 0) {
|
|
3771
3802
|
return JSON.stringify({
|
|
3772
3803
|
success: false,
|
|
3773
|
-
code: "
|
|
3774
|
-
error:
|
|
3775
|
-
|
|
3776
|
-
hint: "Use force=true to bypass the retry cap for explicit operator recovery."
|
|
3777
|
-
}, null, 2);
|
|
3804
|
+
code: "invalid_mission_status",
|
|
3805
|
+
error: `invalid status filter: ${invalid.join(", ")} (valid: ${import_daemon_core3.MESH_MISSION_STATUSES.join(", ")})`
|
|
3806
|
+
});
|
|
3778
3807
|
}
|
|
3779
|
-
const
|
|
3780
|
-
ctx.
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
}).
|
|
3808
|
+
const statuses = rawStatuses.length > 0 ? rawStatuses : void 0;
|
|
3809
|
+
const missions = (0, import_daemon_core3.listMeshMissionSummaries)(ctx.mesh.id, {
|
|
3810
|
+
statuses,
|
|
3811
|
+
verbose: args.verbose === true
|
|
3812
|
+
}).map((mission) => {
|
|
3813
|
+
try {
|
|
3814
|
+
return { ...mission, stats: (0, import_daemon_core3.computeMeshMissionStats)(ctx.mesh.id, mission.id) };
|
|
3815
|
+
} catch {
|
|
3816
|
+
return mission;
|
|
3817
|
+
}
|
|
3784
3818
|
});
|
|
3785
|
-
return JSON.stringify({
|
|
3819
|
+
return JSON.stringify({
|
|
3820
|
+
success: true,
|
|
3821
|
+
count: missions.length,
|
|
3822
|
+
...statuses ? { statusFilter: statuses } : {},
|
|
3823
|
+
missions
|
|
3824
|
+
}, null, 2);
|
|
3786
3825
|
} catch (e) {
|
|
3787
|
-
return JSON.stringify({ success: false, error: e
|
|
3826
|
+
return JSON.stringify({ success: false, error: e?.message || String(e) });
|
|
3788
3827
|
}
|
|
3789
3828
|
}
|
|
3829
|
+
async function meshReviewInbox(ctx, args = {}) {
|
|
3830
|
+
await refreshMeshFromDaemon(ctx);
|
|
3831
|
+
const meshId = (args.mesh_id ?? ctx.mesh.id).trim();
|
|
3832
|
+
const result = await commandForNode(ctx, ctx.mesh.nodes[0], "get_mesh_review_inbox", {
|
|
3833
|
+
meshId,
|
|
3834
|
+
inlineMesh: ctx.mesh
|
|
3835
|
+
});
|
|
3836
|
+
return JSON.stringify(result, null, 2);
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
// src/tools/mesh-tools-session.ts
|
|
3840
|
+
async function meshPruneStaleDirect(ctx, args = {}) {
|
|
3841
|
+
await refreshMeshFromDaemon(ctx);
|
|
3842
|
+
const execute = args.execute === true && args.dry_run !== true;
|
|
3843
|
+
const includeTerminal = args.include_terminal === true;
|
|
3844
|
+
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
3845
|
+
const ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(ctx.mesh.id, { tail: 500 });
|
|
3846
|
+
const directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(ctx.mesh.id);
|
|
3847
|
+
const result = (0, import_daemon_core3.pruneStaleDirectDispatches)({
|
|
3848
|
+
meshId: ctx.mesh.id,
|
|
3849
|
+
queue: (0, import_daemon_core3.getQueue)(ctx.mesh.id),
|
|
3850
|
+
ledgerEntries,
|
|
3851
|
+
directDispatches,
|
|
3852
|
+
nodes: liveNodes,
|
|
3853
|
+
execute,
|
|
3854
|
+
includeTerminal,
|
|
3855
|
+
source: "mesh_prune_stale_direct"
|
|
3856
|
+
});
|
|
3857
|
+
const { prunable, prunedCount, preservedUnacknowledged, preservedLedgerOnly, preservedNotOrphan } = result;
|
|
3858
|
+
const summarize = (records) => records.map((r) => ({
|
|
3859
|
+
taskId: r.taskId,
|
|
3860
|
+
nodeId: r.nodeId,
|
|
3861
|
+
sessionId: r.sessionId,
|
|
3862
|
+
status: r.status,
|
|
3863
|
+
terminal: r.terminal === true,
|
|
3864
|
+
staleReason: r.staleReason,
|
|
3865
|
+
taskTitle: r.taskTitle,
|
|
3866
|
+
createdAt: r.createdAt
|
|
3867
|
+
}));
|
|
3868
|
+
return JSON.stringify({
|
|
3869
|
+
success: true,
|
|
3870
|
+
mode: result.mode,
|
|
3871
|
+
meshId: ctx.mesh.id,
|
|
3872
|
+
includeTerminal,
|
|
3873
|
+
candidateCount: result.candidateCount,
|
|
3874
|
+
prunableCount: prunable.length,
|
|
3875
|
+
prunedCount,
|
|
3876
|
+
prunable: summarize(prunable),
|
|
3877
|
+
preserved: {
|
|
3878
|
+
unacknowledgedCount: preservedUnacknowledged.length,
|
|
3879
|
+
ledgerOnlyCount: preservedLedgerOnly.length,
|
|
3880
|
+
notOrphanCount: preservedNotOrphan.length,
|
|
3881
|
+
unacknowledged: summarize(preservedUnacknowledged),
|
|
3882
|
+
ledgerOnly: summarize(preservedLedgerOnly),
|
|
3883
|
+
notOrphan: summarize(preservedNotOrphan)
|
|
3884
|
+
},
|
|
3885
|
+
note: execute ? `Pruned ${prunedCount} orphaned direct dispatch record(s) from the active staleDirect surface. The append-only mesh ledger audit history is preserved; a direct_dispatch_pruned entry records this prune.` : "Dry run \u2014 nothing was deleted. Re-run with execute=true to prune the listed orphaned records. Fresh unacknowledged dispatch failures (node/session still live) and ledger-only audit entries are always preserved."
|
|
3886
|
+
}, null, 2);
|
|
3887
|
+
}
|
|
3790
3888
|
async function meshSendTask(ctx, args) {
|
|
3791
3889
|
const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
3792
3890
|
const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
|
|
3793
|
-
const modeValidation = (0,
|
|
3891
|
+
const modeValidation = (0, import_daemon_core3.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
|
|
3794
3892
|
if (!modeValidation.valid) {
|
|
3795
3893
|
return JSON.stringify({
|
|
3796
3894
|
success: false,
|
|
@@ -3902,7 +4000,7 @@ async function meshSendTask(ctx, args) {
|
|
|
3902
4000
|
const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3903
4001
|
try {
|
|
3904
4002
|
const providerType = result2.providerType || cached?.providerType;
|
|
3905
|
-
(0,
|
|
4003
|
+
(0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
|
|
3906
4004
|
kind: "task_dispatched",
|
|
3907
4005
|
nodeId: args.node_id,
|
|
3908
4006
|
sessionId: dispatchedSessionId,
|
|
@@ -3914,7 +4012,7 @@ async function meshSendTask(ctx, args) {
|
|
|
3914
4012
|
targetSessionId: dispatchedSessionId
|
|
3915
4013
|
})
|
|
3916
4014
|
});
|
|
3917
|
-
(0,
|
|
4015
|
+
(0, import_daemon_core3.insertDirectDispatch)(ctx.mesh.id, {
|
|
3918
4016
|
taskId,
|
|
3919
4017
|
nodeId: args.node_id,
|
|
3920
4018
|
sessionId: dispatchedSessionId,
|
|
@@ -3925,7 +4023,7 @@ async function meshSendTask(ctx, args) {
|
|
|
3925
4023
|
dispatchedAt
|
|
3926
4024
|
});
|
|
3927
4025
|
if (missionId) {
|
|
3928
|
-
(0,
|
|
4026
|
+
(0, import_daemon_core3.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
|
|
3929
4027
|
id: taskId,
|
|
3930
4028
|
missionId,
|
|
3931
4029
|
assignedNodeId: args.node_id,
|
|
@@ -4084,7 +4182,7 @@ async function meshSendTask(ctx, args) {
|
|
|
4084
4182
|
});
|
|
4085
4183
|
}
|
|
4086
4184
|
try {
|
|
4087
|
-
(0,
|
|
4185
|
+
(0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
|
|
4088
4186
|
kind: "task_dispatched",
|
|
4089
4187
|
nodeId: args.node_id,
|
|
4090
4188
|
sessionId: args.session_id,
|
|
@@ -4099,7 +4197,7 @@ async function meshSendTask(ctx, args) {
|
|
|
4099
4197
|
});
|
|
4100
4198
|
} catch {
|
|
4101
4199
|
}
|
|
4102
|
-
(0,
|
|
4200
|
+
(0, import_daemon_core3.insertDirectDispatch)(ctx.mesh.id, {
|
|
4103
4201
|
taskId,
|
|
4104
4202
|
nodeId: args.node_id,
|
|
4105
4203
|
sessionId: args.session_id,
|
|
@@ -4112,7 +4210,7 @@ async function meshSendTask(ctx, args) {
|
|
|
4112
4210
|
});
|
|
4113
4211
|
if (missionId) {
|
|
4114
4212
|
try {
|
|
4115
|
-
(0,
|
|
4213
|
+
(0, import_daemon_core3.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
|
|
4116
4214
|
id: taskId,
|
|
4117
4215
|
missionId,
|
|
4118
4216
|
assignedNodeId: args.node_id,
|
|
@@ -4157,14 +4255,14 @@ async function meshSendTask(ctx, args) {
|
|
|
4157
4255
|
} : {}
|
|
4158
4256
|
});
|
|
4159
4257
|
}
|
|
4160
|
-
const task = (0,
|
|
4258
|
+
const task = (0, import_daemon_core3.enqueueTask)(ctx.mesh.id, args.message, {
|
|
4161
4259
|
targetNodeId: args.node_id,
|
|
4162
4260
|
targetSessionId: args.session_id,
|
|
4163
4261
|
taskMode,
|
|
4164
4262
|
...missionId ? { missionId } : {}
|
|
4165
4263
|
});
|
|
4166
4264
|
const queueTrigger = await triggerMeshQueueAndReport(ctx);
|
|
4167
|
-
const pendingEvents = (0,
|
|
4265
|
+
const pendingEvents = (0, import_daemon_core3.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId);
|
|
4168
4266
|
const result = {
|
|
4169
4267
|
success: true,
|
|
4170
4268
|
source: "queue",
|
|
@@ -4189,81 +4287,6 @@ async function meshSendTask(ctx, args) {
|
|
|
4189
4287
|
return JSON.stringify(failure);
|
|
4190
4288
|
}
|
|
4191
4289
|
}
|
|
4192
|
-
function classifyReadChatTransportCause(error) {
|
|
4193
|
-
const message = (error instanceof Error ? error.message : String(error ?? "")).toLowerCase();
|
|
4194
|
-
if (/not acknowledged|delivery failure|channel never opened|connect timed out|not connected|datachannel|disconnected|\bclosed\b|offline|no route|failed to initiate p2p|p2p mesh is not available|connect queue full/.test(message)) {
|
|
4195
|
-
return "not_connected";
|
|
4196
|
-
}
|
|
4197
|
-
return "saturated";
|
|
4198
|
-
}
|
|
4199
|
-
function resolveCachedMeshSessionPreviewFromLedger(ctx, nodeId, sessionId) {
|
|
4200
|
-
const entries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
4201
|
-
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
4202
|
-
const entry = entries[i];
|
|
4203
|
-
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
4204
|
-
const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
|
|
4205
|
-
if (entryNodeId && entryNodeId !== nodeId) continue;
|
|
4206
|
-
const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
|
|
4207
|
-
if (entrySessionId !== sessionId) continue;
|
|
4208
|
-
const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : payload;
|
|
4209
|
-
const preview = (0, import_daemon_core2.resolveMeshSurfacedSessionPreview)(metadataEvent);
|
|
4210
|
-
if (preview) {
|
|
4211
|
-
return { ...preview, ledgerKind: entry.kind, timestamp: entry.timestamp };
|
|
4212
|
-
}
|
|
4213
|
-
}
|
|
4214
|
-
return void 0;
|
|
4215
|
-
}
|
|
4216
|
-
function buildMeshReadChatCacheFallback(ctx, args, node, error) {
|
|
4217
|
-
const classification = (0, import_daemon_core2.classifyP2pRelayFailure)(error, { command: "read_chat", targetDaemonId: node.daemonId });
|
|
4218
|
-
const cause = classifyReadChatTransportCause(error);
|
|
4219
|
-
const errorMessage = error instanceof Error ? error.message : String(error ?? "");
|
|
4220
|
-
const causeNote = cause === "not_connected" ? "the worker daemon is not currently connected over P2P (no live channel)" : "the worker daemon is connected but saturated \u2014 it acknowledged the request but did not return the transcript within the deadline";
|
|
4221
|
-
const cached = resolveCachedMeshSessionPreviewFromLedger(ctx, args.node_id, args.session_id);
|
|
4222
|
-
if (cached) {
|
|
4223
|
-
return JSON.stringify({
|
|
4224
|
-
success: true,
|
|
4225
|
-
source: "coordinator_cache_fallback",
|
|
4226
|
-
fallback: true,
|
|
4227
|
-
nodeId: args.node_id,
|
|
4228
|
-
sessionId: args.session_id,
|
|
4229
|
-
transport: "p2p",
|
|
4230
|
-
transportFailure: {
|
|
4231
|
-
code: classification.code,
|
|
4232
|
-
reason: classification.reason,
|
|
4233
|
-
cause,
|
|
4234
|
-
error: errorMessage
|
|
4235
|
-
},
|
|
4236
|
-
advisory: `Live transcript unavailable (${causeNote}). Showing the cached coordinator-side summary surfaced from the worker's last completion/status event \u2014 a stale point-in-time summary, NOT the live transcript. The full transcript requires a live P2P read_chat once the peer is reachable.`,
|
|
4237
|
-
fullTranscriptRequiresP2p: true,
|
|
4238
|
-
summary: cached.preview,
|
|
4239
|
-
messages: [{
|
|
4240
|
-
role: cached.role,
|
|
4241
|
-
content: cached.preview,
|
|
4242
|
-
cached: true,
|
|
4243
|
-
...cached.receivedAt ? { receivedAt: cached.receivedAt } : {}
|
|
4244
|
-
}],
|
|
4245
|
-
cachedPreview: {
|
|
4246
|
-
role: cached.role,
|
|
4247
|
-
ledgerKind: cached.ledgerKind,
|
|
4248
|
-
ledgerTimestamp: cached.timestamp,
|
|
4249
|
-
...cached.receivedAt ? { receivedAt: cached.receivedAt } : {}
|
|
4250
|
-
}
|
|
4251
|
-
}, null, 2);
|
|
4252
|
-
}
|
|
4253
|
-
const failure = buildCoordinatorP2pRelayFailure(error, {
|
|
4254
|
-
command: "read_chat",
|
|
4255
|
-
targetDaemonId: node.daemonId,
|
|
4256
|
-
nodeId: args.node_id,
|
|
4257
|
-
sessionId: args.session_id
|
|
4258
|
-
});
|
|
4259
|
-
return JSON.stringify({
|
|
4260
|
-
...failure,
|
|
4261
|
-
cause,
|
|
4262
|
-
cachedSummaryAvailable: false,
|
|
4263
|
-
fullTranscriptRequiresP2p: true,
|
|
4264
|
-
advisory: `Live transcript unavailable (${causeNote}) and no cached coordinator-side summary exists for this session yet (no completion/status event has been surfaced). The full transcript requires a live P2P read_chat once the peer is reachable.`
|
|
4265
|
-
}, null, 2);
|
|
4266
|
-
}
|
|
4267
4290
|
async function meshReadChat(ctx, args) {
|
|
4268
4291
|
const node = await findOptionalNodeWithRefresh(ctx, args.node_id);
|
|
4269
4292
|
if (!node) {
|
|
@@ -4284,7 +4307,7 @@ async function meshReadChat(ctx, args) {
|
|
|
4284
4307
|
tailLimit: args.tail ?? 10
|
|
4285
4308
|
});
|
|
4286
4309
|
} catch (e) {
|
|
4287
|
-
if (isLocalNode || !(0,
|
|
4310
|
+
if (isLocalNode || !(0, import_daemon_core3.isP2pRelayTransportFailure)(e)) throw e;
|
|
4288
4311
|
return buildMeshReadChatCacheFallback(ctx, args, node, e);
|
|
4289
4312
|
}
|
|
4290
4313
|
const payload = annotateRapidReadChatAdvisory(unwrapCommandPayload(result), {
|
|
@@ -4352,11 +4375,40 @@ async function meshLaunchSession(ctx, args) {
|
|
|
4352
4375
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
4353
4376
|
const coordinatorDaemonId = resolveCoordinatorDaemonId(ctx);
|
|
4354
4377
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
4355
|
-
const delegatedWorkerAutoApprove = (0,
|
|
4378
|
+
const delegatedWorkerAutoApprove = (0, import_daemon_core3.resolveDelegatedWorkerAutoApprove)(ctx.mesh.policy, node.policy);
|
|
4356
4379
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
4357
4380
|
if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
|
|
4358
4381
|
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
4359
4382
|
}
|
|
4383
|
+
if (args.force !== true) {
|
|
4384
|
+
try {
|
|
4385
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
4386
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
4387
|
+
const existing = sessions.find((session) => !isTerminalSessionRecord(session) && isMeshOwnedDelegateSession(session, ctx.mesh.id, args.node_id));
|
|
4388
|
+
if (existing) {
|
|
4389
|
+
const existingSessionId = readSessionRecordId(existing);
|
|
4390
|
+
if (existingSessionId) {
|
|
4391
|
+
const existingProviderType = resolveSessionProviderType(existing) || resolvedProviderType || void 0;
|
|
4392
|
+
const existingStatus = typeof existing?.status === "string" ? existing.status : "unknown";
|
|
4393
|
+
return JSON.stringify({
|
|
4394
|
+
success: true,
|
|
4395
|
+
duplicate: true,
|
|
4396
|
+
launched: false,
|
|
4397
|
+
reused: true,
|
|
4398
|
+
sessionId: existingSessionId,
|
|
4399
|
+
nodeId: args.node_id,
|
|
4400
|
+
...existingProviderType ? { resolvedProviderType: existingProviderType, providerType: existingProviderType } : {},
|
|
4401
|
+
sessionStatus: existingStatus,
|
|
4402
|
+
idle: isIdleSessionRecord(existing),
|
|
4403
|
+
reason: "mesh_launch_session_duplicate_guard",
|
|
4404
|
+
warning: `Node '${args.node_id}' already has a live mesh-owned worker session ('${existingSessionId}', status '${existingStatus}'). Returning it instead of launching an empty duplicate (likely an enqueue auto-launch already spawned it).`,
|
|
4405
|
+
nextAction: `Use session '${existingSessionId}' for mesh_send_task/mesh_read_chat. If you intentionally need a second concurrent session on this node, retry mesh_launch_session with force=true.`
|
|
4406
|
+
}, null, 2);
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
} catch {
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4360
4412
|
let result;
|
|
4361
4413
|
try {
|
|
4362
4414
|
result = await commandForNode(ctx, node, "launch_cli", {
|
|
@@ -4399,7 +4451,7 @@ async function meshLaunchSession(ctx, args) {
|
|
|
4399
4451
|
});
|
|
4400
4452
|
}
|
|
4401
4453
|
try {
|
|
4402
|
-
(0,
|
|
4454
|
+
(0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
|
|
4403
4455
|
kind: "session_launched",
|
|
4404
4456
|
nodeId: args.node_id,
|
|
4405
4457
|
sessionId: runtimeSessionId || void 0,
|
|
@@ -4418,6 +4470,34 @@ async function meshLaunchSession(ctx, args) {
|
|
|
4418
4470
|
}, null, 2);
|
|
4419
4471
|
}
|
|
4420
4472
|
}
|
|
4473
|
+
async function meshApprove(ctx, args) {
|
|
4474
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
4475
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
|
|
4476
|
+
const providerSessionId = cached?.providerSessionId;
|
|
4477
|
+
const result = await commandForNode(ctx, node, "resolve_action", {
|
|
4478
|
+
sessionId: args.session_id,
|
|
4479
|
+
targetSessionId: args.session_id,
|
|
4480
|
+
workspace: node.workspace,
|
|
4481
|
+
...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
|
|
4482
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
4483
|
+
action: args.action === "reject" ? "reject" : "approve"
|
|
4484
|
+
});
|
|
4485
|
+
return JSON.stringify(result, null, 2);
|
|
4486
|
+
}
|
|
4487
|
+
async function meshCleanupSessions(ctx, args) {
|
|
4488
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
4489
|
+
const result = await commandForNode(ctx, node, "cleanup_mesh_sessions", {
|
|
4490
|
+
meshId: ctx.mesh.id,
|
|
4491
|
+
nodeId: args.node_id,
|
|
4492
|
+
mode: args.mode,
|
|
4493
|
+
sessionIds: args.session_ids,
|
|
4494
|
+
dryRun: args.dry_run === true,
|
|
4495
|
+
inlineMesh: ctx.mesh
|
|
4496
|
+
});
|
|
4497
|
+
return JSON.stringify(result, null, 2);
|
|
4498
|
+
}
|
|
4499
|
+
|
|
4500
|
+
// src/tools/mesh-tools-git.ts
|
|
4421
4501
|
async function meshGitStatus(ctx, args) {
|
|
4422
4502
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
4423
4503
|
const autoDiscoverSubmodules = node.policy?.autoDiscoverSubmodules !== false;
|
|
@@ -4552,7 +4632,7 @@ async function meshCheckpoint(ctx, args) {
|
|
|
4552
4632
|
includeUntracked: true
|
|
4553
4633
|
});
|
|
4554
4634
|
try {
|
|
4555
|
-
(0,
|
|
4635
|
+
(0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
|
|
4556
4636
|
kind: "checkpoint_created",
|
|
4557
4637
|
nodeId: args.node_id,
|
|
4558
4638
|
payload: {
|
|
@@ -4567,20 +4647,6 @@ async function meshCheckpoint(ctx, args) {
|
|
|
4567
4647
|
}
|
|
4568
4648
|
return JSON.stringify(result, null, 2);
|
|
4569
4649
|
}
|
|
4570
|
-
async function meshApprove(ctx, args) {
|
|
4571
|
-
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
4572
|
-
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
|
|
4573
|
-
const providerSessionId = cached?.providerSessionId;
|
|
4574
|
-
const result = await commandForNode(ctx, node, "resolve_action", {
|
|
4575
|
-
sessionId: args.session_id,
|
|
4576
|
-
targetSessionId: args.session_id,
|
|
4577
|
-
workspace: node.workspace,
|
|
4578
|
-
...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
|
|
4579
|
-
...providerSessionId ? { providerSessionId } : {},
|
|
4580
|
-
action: args.action === "reject" ? "reject" : "approve"
|
|
4581
|
-
});
|
|
4582
|
-
return JSON.stringify(result, null, 2);
|
|
4583
|
-
}
|
|
4584
4650
|
async function meshCloneNode(ctx, args) {
|
|
4585
4651
|
const sourceNode = await findNodeWithRefresh(ctx, args.source_node_id);
|
|
4586
4652
|
const result = await commandForNode(ctx, sourceNode, "clone_mesh_node", {
|
|
@@ -4600,18 +4666,6 @@ async function meshCloneNode(ctx, args) {
|
|
|
4600
4666
|
}
|
|
4601
4667
|
return JSON.stringify(result, null, 2);
|
|
4602
4668
|
}
|
|
4603
|
-
async function meshCleanupSessions(ctx, args) {
|
|
4604
|
-
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
4605
|
-
const result = await commandForNode(ctx, node, "cleanup_mesh_sessions", {
|
|
4606
|
-
meshId: ctx.mesh.id,
|
|
4607
|
-
nodeId: args.node_id,
|
|
4608
|
-
mode: args.mode,
|
|
4609
|
-
sessionIds: args.session_ids,
|
|
4610
|
-
dryRun: args.dry_run === true,
|
|
4611
|
-
inlineMesh: ctx.mesh
|
|
4612
|
-
});
|
|
4613
|
-
return JSON.stringify(result, null, 2);
|
|
4614
|
-
}
|
|
4615
4669
|
async function meshRemoveNode(ctx, args) {
|
|
4616
4670
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
4617
4671
|
const removeArgs = buildRemoveNodeArgs(ctx, args.node_id, args.session_cleanup_mode, args.force === true);
|
|
@@ -4645,12 +4699,8 @@ async function meshRemoveNode(ctx, args) {
|
|
|
4645
4699
|
}
|
|
4646
4700
|
return JSON.stringify({ ...result || {}, ...transportFallback ? { transportFallback } : {} }, null, 2);
|
|
4647
4701
|
}
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
|
|
4651
|
-
if (!node) throw new Error("No mesh node with a workspace is available");
|
|
4652
|
-
return node;
|
|
4653
|
-
}
|
|
4702
|
+
|
|
4703
|
+
// src/tools/mesh-tools-refine.ts
|
|
4654
4704
|
async function meshRefineConfigSchema(ctx) {
|
|
4655
4705
|
const node = resolveRefineConfigNode(ctx);
|
|
4656
4706
|
const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
|
|
@@ -4752,15 +4802,6 @@ async function meshRefineBatch(ctx, args = {}) {
|
|
|
4752
4802
|
}
|
|
4753
4803
|
return JSON.stringify(result, null, 2);
|
|
4754
4804
|
}
|
|
4755
|
-
async function meshReviewInbox(ctx, args = {}) {
|
|
4756
|
-
await refreshMeshFromDaemon(ctx);
|
|
4757
|
-
const meshId = (args.mesh_id ?? ctx.mesh.id).trim();
|
|
4758
|
-
const result = await commandForNode(ctx, ctx.mesh.nodes[0], "get_mesh_review_inbox", {
|
|
4759
|
-
meshId,
|
|
4760
|
-
inlineMesh: ctx.mesh
|
|
4761
|
-
});
|
|
4762
|
-
return JSON.stringify(result, null, 2);
|
|
4763
|
-
}
|
|
4764
4805
|
|
|
4765
4806
|
// src/help.ts
|
|
4766
4807
|
var STANDARD_TOOLS = [
|