@adhdev/daemon-standalone 0.9.82-rc.375 → 0.9.82-rc.377

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.
@@ -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/read-chat-polling-advisory.ts
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-queue-helpers.ts
529
- var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
530
- var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
531
- var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
532
- var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
533
- function buildQueueLivenessIndex(mesh) {
534
- const nodeIds = /* @__PURE__ */ new Set();
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 (nodeId && sessionId && liveness.nodeSessionIds.has(nodeId) && !liveness.nodeSessionIds.get(nodeId).has(sessionId)) {
553
- return "assigned session is not live on the assigned node";
493
+ if (ctx.localMachineId) {
494
+ const byMachine = ctx.mesh.nodes.find((n) => readNodeMachineId(n) === ctx.localMachineId);
495
+ if (byMachine) return byMachine;
554
496
  }
555
- const updatedAt = new Date(task.updatedAt).getTime();
556
- const ageMs = Number.isFinite(updatedAt) ? Date.now() - updatedAt : null;
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 buildQueueStatusSummary(queue) {
563
- const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
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 normalizeQueueViewMode(value) {
595
- return value === "active" || value === "historical" || value === "all" ? value : "all";
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 sanitizeQueueStatusFilter(value) {
598
- if (!Array.isArray(value)) return void 0;
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 filterQueueForView(queue, view, statuses) {
603
- if (statuses?.length) {
604
- const allowed = new Set(statuses);
605
- return queue.filter((task) => allowed.has(String(task?.status || "")));
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 prioritizeActiveQueueRows(queue) {
612
- const active = [];
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 slimQueueTask(task) {
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 buildQueueMaintenanceReport(queue) {
637
- const now = Date.now();
638
- const staleAssignedTasks = queue.filter((task) => task?.status === "assigned" && task?.staleAssigned === true).map(slimQueueTask);
639
- const historicalTasks = queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
640
- const oldHistoricalTasks = historicalTasks.filter((task) => {
641
- const updatedAt = new Date(task?.updatedAt).getTime();
642
- return Number.isFinite(updatedAt) && now - updatedAt >= OLD_HISTORICAL_QUEUE_RECORD_MS;
643
- }).map((task) => ({
644
- ...slimQueueTask(task),
645
- cleanupClass: "old_historical_record",
646
- reason: "terminal queue record is older than the read-only maintenance threshold"
647
- }));
648
- const cleanupCandidates = [
649
- ...staleAssignedTasks.map((task) => ({
650
- ...task,
651
- cleanupClass: "stale_assigned",
652
- reason: typeof task.staleReason === "string" ? task.staleReason : "active assigned task does not match current live mesh node/session state",
653
- suggestedOperation: "operator_review_then_requeue_or_cancel"
654
- })),
655
- ...oldHistoricalTasks.map((task) => ({
656
- ...task,
657
- suggestedOperation: "operator_review_then_archive_or_keep"
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
- readOnly: true,
662
- mutationPerformed: false,
663
- sourceOfTruth: "mesh_work_queue_file",
664
- staleAssignedDefinition: "Only active assigned queue rows are stale candidates, and only when the assigned node/session is absent from the current live mesh snapshot.",
665
- historicalDefinition: "completed/failed/cancelled rows are historical ledger records and never active assignments.",
666
- staleAssignedTasks,
667
- staleAssignedCount: staleAssignedTasks.length,
668
- historicalRecordCount: historicalTasks.length,
669
- oldHistoricalRecordCount: oldHistoricalTasks.length,
670
- cleanupCandidates,
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 buildCompactQueueMaintenanceReport(maintenance) {
675
- const staleAssignedTasks = Array.isArray(maintenance.staleAssignedTasks) ? maintenance.staleAssignedTasks : [];
676
- const cleanupCandidateCount = maintenance.cleanupCandidateCount ?? 0;
677
- return {
678
- readOnly: true,
679
- mutationPerformed: false,
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
- var COMPACT_MAX_ACTIVE_QUEUE_ROWS = 15;
697
- var COMPACT_QUEUE_MESSAGE_CAP = 140;
698
- var COMPACT_MAX_ACTIVE_WORK_ROWS = 12;
699
- var COMPACT_ACTIVE_WORK_TITLE_CAP = 80;
700
- function truncateForCompact(value, cap) {
701
- if (typeof value !== "string") return value;
702
- return value.length > cap ? value.slice(0, cap) + "\u2026" : value;
703
- }
704
- function compactQueueRow(task) {
705
- if (!task || typeof task !== "object") return task;
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
- return slim;
712
- }
713
- function compactQueueRows(rows) {
714
- const capped = rows.slice(0, COMPACT_MAX_ACTIVE_QUEUE_ROWS).map(compactQueueRow);
715
- return { rows: capped, omitted: Math.max(0, rows.length - capped.length) };
716
- }
717
- function compactActiveWorkRecord(record) {
718
- if (!record || typeof record !== "object") return record;
719
- const slim = {};
720
- for (const [k, v] of Object.entries(record)) {
721
- if (k === "message" || k === "taskSummary") continue;
722
- else if (k === "taskTitle") slim[k] = truncateForCompact(v, COMPACT_ACTIVE_WORK_TITLE_CAP);
723
- else slim[k] = elideLargeNestedValue(k, v);
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 slim;
604
+ return false;
726
605
  }
727
- function compactActiveWorkRecords(records) {
728
- if (!Array.isArray(records)) return { records, omitted: 0 };
729
- const capped = records.slice(0, COMPACT_MAX_ACTIVE_WORK_ROWS).map(compactActiveWorkRecord);
730
- return { records: capped, omitted: Math.max(0, records.length - capped.length) };
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 annotateQueueStaleness(queue, mesh) {
733
- const liveness = buildQueueLivenessIndex(mesh);
734
- const now = Date.now();
735
- return queue.map((task) => {
736
- const taskStatus = typeof task?.status === "string" ? task.status : void 0;
737
- const annotated = {
738
- ...task,
739
- taskStatus,
740
- isActive: taskStatus ? ACTIVE_QUEUE_STATUSES.has(taskStatus) : false,
741
- isHistorical: taskStatus ? HISTORICAL_QUEUE_STATUSES.has(taskStatus) : false,
742
- dispatchedAt: task?.createdAt,
743
- ...taskStatus === "assigned" ? { activeTaskId: task.id } : {},
744
- ...taskStatus === "completed" || taskStatus === "failed" ? {
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
- // src/tools/mesh-compact.ts
764
- function buildCompactGitSnapshot(status) {
765
- if (!status || typeof status !== "object" || Array.isArray(status)) return void 0;
766
- const slim = {};
767
- const carry = [
768
- "isGitRepo",
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 slim;
635
+ return void 0;
783
636
  }
784
- function summarizeCompactSubmodules(submodules) {
785
- if (!Array.isArray(submodules) || submodules.length === 0) return void 0;
786
- const outOfSync = submodules.filter((s) => s?.outOfSync).map((s) => s?.path).filter(Boolean);
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
- var MESH_COMPACT_PRESERVED_MARKER_FIELDS = ["dataFreshness"];
793
- function compactMeshStatusNode(entry) {
794
- if (!entry || typeof entry !== "object") return entry;
795
- const next = { ...entry };
796
- if (next.git !== void 0) {
797
- const slimGit = buildCompactGitSnapshot(next.git);
798
- if (slimGit) {
799
- if (slimGit.submodules !== void 0) {
800
- const subSummary = summarizeCompactSubmodules(slimGit.submodules);
801
- if (subSummary) slimGit.submodules = subSummary;
802
- else delete slimGit.submodules;
803
- }
804
- next.git = slimGit;
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
- if (next.machine && typeof next.machine === "object") {
808
- const m = next.machine;
809
- next.machine = {
810
- daemonId: m.daemonId,
811
- machineId: m.machineId,
812
- hostname: m.hostname,
813
- displayName: m.displayName,
814
- sameMachine: m.sameMachine,
815
- locality: m.locality
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
- delete next.capabilityTagsByProvider;
831
- const elideSkip = /* @__PURE__ */ new Set(["git", "machine", "branchConvergence", "staleDaemonBuild", "sessions", ...MESH_COMPACT_PRESERVED_MARKER_FIELDS]);
832
- for (const k of Object.keys(next)) {
833
- if (elideSkip.has(k)) continue;
834
- next[k] = elideLargeNestedValue(k, next[k]);
835
- }
836
- return next;
837
- }
838
- function compactNodeSeverity(entry) {
839
- if (!entry || typeof entry !== "object") return 0;
840
- if (entry.error || entry.health && entry.health !== "online" && entry.health !== "dirty") return 5;
841
- if (entry.launchReady === false) return 4;
842
- if (entry.isDirty === true || entry.health === "dirty") return 3;
843
- if (entry.branchConvergence?.needsConvergence === true) return 2;
844
- if (entry.staleDaemonBuild || entry.submodulesOutOfSync || entry.recoveryHints) return 1;
845
- return 0;
846
- }
847
- function isNoteworthyCompactNode(entry) {
848
- if (!entry || typeof entry !== "object") return true;
849
- if (entry.health && entry.health !== "online") return true;
850
- if (entry.isDirty === true) return true;
851
- if (entry.error) return true;
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 = {
@@ -1345,269 +915,698 @@ var MESH_APPROVE_TOOL = {
1345
915
  required: ["node_id", "session_id", "action"]
1346
916
  }
1347
917
  };
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.",
918
+ var MESH_CLONE_NODE_TOOL = {
919
+ name: "mesh_clone_node",
920
+ 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.",
921
+ inputSchema: {
922
+ type: "object",
923
+ properties: {
924
+ source_node_id: { type: "string", description: "Node ID to clone from (from mesh_list_nodes)." },
925
+ branch: { type: "string", description: 'Branch name for the new worktree (e.g. "feat/auth-refactor").' },
926
+ base_branch: { type: "string", description: "Starting point for the branch (default: current HEAD)." }
927
+ },
928
+ required: ["source_node_id", "branch"]
929
+ }
930
+ };
931
+ var MESH_REMOVE_NODE_TOOL = {
932
+ name: "mesh_remove_node",
933
+ 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.",
934
+ inputSchema: {
935
+ type: "object",
936
+ properties: {
937
+ node_id: { type: "string", description: "Node ID to remove." },
938
+ session_cleanup_mode: {
939
+ type: "string",
940
+ enum: ["preserve", "stop", "delete_stopped", "stop_and_delete"],
941
+ 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."
942
+ },
943
+ 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." }
944
+ },
945
+ required: ["node_id"]
946
+ }
947
+ };
948
+ var MESH_CLEANUP_SESSIONS_TOOL = {
949
+ name: "mesh_cleanup_sessions",
950
+ 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.",
951
+ inputSchema: {
952
+ type: "object",
953
+ properties: {
954
+ node_id: { type: "string", description: "Node ID whose delegated sessions should be considered for cleanup." },
955
+ mode: {
956
+ type: "string",
957
+ enum: ["preserve", "stop", "delete_stopped", "stop_and_delete"],
958
+ 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."
959
+ },
960
+ session_ids: {
961
+ type: "array",
962
+ items: { type: "string" },
963
+ description: "Optional explicit session IDs to limit cleanup to. When omitted, sessions are matched by node/workspace metadata."
964
+ },
965
+ dry_run: { type: "boolean", description: "Preview matched/stopped/deleted/skipped session IDs without mutating session-host state." }
966
+ },
967
+ required: ["node_id", "mode"]
968
+ }
969
+ };
970
+ var MESH_TASK_HISTORY_TOOL = {
971
+ name: "mesh_task_history",
972
+ 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.",
973
+ inputSchema: {
974
+ type: "object",
975
+ properties: {
976
+ tail: { type: "number", description: "Number of recent entries to return (default: 20; clamped to 40 in compact mode, 200 in verbose)." },
977
+ 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." },
978
+ 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." },
979
+ verbose: { type: "boolean", description: "Force the full untruncated payload; overrides compact." }
980
+ }
981
+ }
982
+ };
983
+ var MESH_RECORD_NOTE_TOOL = {
984
+ name: "mesh_record_note",
985
+ 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.",
986
+ inputSchema: {
987
+ type: "object",
988
+ properties: {
989
+ 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." },
990
+ category: {
991
+ type: "string",
992
+ enum: ["provider_quirk", "pattern_to_avoid", "recovery_lesson"],
993
+ 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)."
994
+ }
995
+ },
996
+ required: ["text"]
997
+ }
998
+ };
999
+ var MESH_RECONCILE_LEDGER_TOOL = {
1000
+ name: "mesh_reconcile_ledger",
1001
+ 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.",
1002
+ inputSchema: {
1003
+ type: "object",
1004
+ properties: {
1005
+ node_ids: { type: "array", items: { type: "string" }, description: "Optional node IDs to query. Defaults to all mesh nodes." },
1006
+ limit: { type: "number", description: "Bounded slice size per node. Defaults to 100 and is clamped by daemon-core." },
1007
+ after_id: { type: "string", description: "Optional cursor entry ID; remote slices return entries strictly after this ID when present." },
1008
+ since: { type: "string", description: "Optional ISO timestamp lower bound for queried entries." },
1009
+ import_entries: { type: "boolean", description: "When false, query and report evidence without importing remote entries. Defaults true." }
1010
+ }
1011
+ }
1012
+ };
1013
+ var MESH_PRUNE_STALE_DIRECT_TOOL = {
1014
+ name: "mesh_prune_stale_direct",
1015
+ 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.",
1016
+ inputSchema: {
1017
+ type: "object",
1018
+ properties: {
1019
+ execute: { type: "boolean", description: "When true, actually delete the orphaned records. Defaults false (dry run). Ignored when dry_run=true." },
1020
+ dry_run: { type: "boolean", description: "Force a preview without mutation even if execute=true. Defaults to dry-run behavior when execute is not set." },
1021
+ include_terminal: { type: "boolean", description: "Also prune terminal (completed/failed) direct dispatch store rows in addition to orphans. Defaults false." }
1022
+ }
1023
+ }
1024
+ };
1025
+ var MESH_REFINE_NODE_TOOL = {
1026
+ name: "mesh_refine_node",
1027
+ 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.",
1028
+ inputSchema: {
1029
+ type: "object",
1030
+ properties: {
1031
+ node_id: { type: "string", description: "Node ID of the completed worktree node to refine and merge." },
1032
+ execute: { type: "boolean", description: "When true, run validation/merge/push/cleanup for this node. Defaults false/dry-run." },
1033
+ dry_run: { type: "boolean", description: "Preview the validation plan without merging. Defaults true unless execute=true; dry_run=true overrides execute." }
1034
+ },
1035
+ required: ["node_id"]
1036
+ }
1037
+ };
1038
+ var MESH_REFINE_BATCH_TOOL = {
1039
+ name: "mesh_refine_batch",
1040
+ 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.",
1041
+ inputSchema: {
1042
+ type: "object",
1043
+ properties: {
1044
+ node_ids: {
1045
+ type: "array",
1046
+ items: { type: "string" },
1047
+ 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."
1048
+ },
1049
+ execute: { type: "boolean", description: "When true, run validation/rebase/merge for each node in order. Defaults false/dry-run." },
1050
+ dry_run: { type: "boolean", description: "Preview the ordering + per-node validation plan without executing. Defaults true unless execute=true; dry_run=true overrides execute." }
1051
+ },
1052
+ required: []
1053
+ }
1054
+ };
1055
+ var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
1056
+ name: "mesh_refine_config_schema",
1057
+ 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.",
1058
+ inputSchema: { type: "object", properties: {} }
1059
+ };
1060
+ var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
1061
+ name: "mesh_validate_refine_config",
1062
+ description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
1063
+ inputSchema: {
1064
+ type: "object",
1065
+ properties: {
1066
+ node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
1067
+ config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
1068
+ }
1069
+ }
1070
+ };
1071
+ var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
1072
+ name: "mesh_suggest_refine_config",
1073
+ description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
1074
+ inputSchema: {
1075
+ type: "object",
1076
+ properties: {
1077
+ node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
1078
+ }
1079
+ }
1080
+ };
1081
+ var MESH_CHANGE_IMPACT_CONFIG_SCHEMA_TOOL = {
1082
+ name: "mesh_change_impact_config_schema",
1083
+ 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.",
1084
+ inputSchema: { type: "object", properties: {} }
1085
+ };
1086
+ var MESH_VALIDATE_CHANGE_IMPACT_CONFIG_TOOL = {
1087
+ name: "mesh_validate_change_impact_config",
1088
+ 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.",
1089
+ inputSchema: {
1090
+ type: "object",
1091
+ properties: {
1092
+ node_id: { type: "string", description: "Optional node/workspace whose change-impact config should be loaded. Defaults to the first mesh node." },
1093
+ config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
1094
+ }
1095
+ }
1096
+ };
1097
+ var MESH_SUGGEST_CHANGE_IMPACT_CONFIG_TOOL = {
1098
+ name: "mesh_suggest_change_impact_config",
1099
+ 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.",
1100
+ inputSchema: {
1101
+ type: "object",
1102
+ properties: {
1103
+ node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
1104
+ }
1105
+ }
1106
+ };
1107
+ var MESH_INIT_TOOL = {
1108
+ name: "mesh_init",
1109
+ 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
1110
  inputSchema: {
1352
1111
  type: "object",
1353
1112
  properties: {
1354
- source_node_id: { type: "string", description: "Node ID to clone from (from mesh_list_nodes)." },
1355
- branch: { type: "string", description: 'Branch name for the new worktree (e.g. "feat/auth-refactor").' },
1356
- base_branch: { type: "string", description: "Starting point for the branch (default: current HEAD)." }
1357
- },
1358
- required: ["source_node_id", "branch"]
1113
+ node_id: { type: "string", description: "Optional node/workspace to onboard. Defaults to the first mesh node with a workspace." },
1114
+ write: { type: "boolean", description: "When true, persist the suggested configs to disk. Defaults false (dry-run preview only)." },
1115
+ overwrite: { type: "boolean", description: "When true, overwrite an existing config file. Defaults false (never clobber an existing refine/bootstrap config)." }
1116
+ }
1359
1117
  }
1360
1118
  };
1361
- var MESH_REMOVE_NODE_TOOL = {
1362
- name: "mesh_remove_node",
1363
- 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.",
1119
+ var MESH_REFINE_PLAN_TOOL = {
1120
+ name: "mesh_refine_plan",
1121
+ 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
1122
  inputSchema: {
1365
1123
  type: "object",
1366
1124
  properties: {
1367
- node_id: { type: "string", description: "Node ID to remove." },
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." }
1125
+ node_id: { type: "string", description: "Node ID of the worktree node to plan." }
1374
1126
  },
1375
1127
  required: ["node_id"]
1376
1128
  }
1377
1129
  };
1378
- var MESH_CLEANUP_SESSIONS_TOOL = {
1379
- name: "mesh_cleanup_sessions",
1380
- 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.",
1130
+ var MESH_REVIEW_INBOX_TOOL = {
1131
+ name: "mesh_review_inbox",
1132
+ 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
1133
  inputSchema: {
1382
1134
  type: "object",
1383
1135
  properties: {
1384
- node_id: { type: "string", description: "Node ID whose delegated sessions should be considered for cleanup." },
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." }
1136
+ mesh_id: { type: "string", description: "Mesh ID (optional \u2014 inferred from active mesh if omitted)." }
1396
1137
  },
1397
- required: ["node_id", "mode"]
1138
+ required: []
1139
+ }
1140
+ };
1141
+ var ALL_MESH_TOOLS = [
1142
+ MESH_STATUS_TOOL,
1143
+ MESH_LIST_NODES_TOOL,
1144
+ MESH_ENQUEUE_TASK_TOOL,
1145
+ MESH_VIEW_QUEUE_TOOL,
1146
+ MESH_QUEUE_CANCEL_TOOL,
1147
+ MESH_QUEUE_REQUEUE_TOOL,
1148
+ MESH_SEND_TASK_TOOL,
1149
+ MESH_READ_CHAT_TOOL,
1150
+ MESH_READ_DEBUG_TOOL,
1151
+ MESH_LAUNCH_SESSION_TOOL,
1152
+ MESH_GIT_STATUS_TOOL,
1153
+ MESH_READ_NODE_LOGS_TOOL,
1154
+ MESH_FAST_FORWARD_NODE_TOOL,
1155
+ MESH_RESTART_DAEMON_TOOL,
1156
+ MESH_CHECKPOINT_TOOL,
1157
+ MESH_APPROVE_TOOL,
1158
+ MESH_CLONE_NODE_TOOL,
1159
+ MESH_REMOVE_NODE_TOOL,
1160
+ MESH_REFINE_NODE_TOOL,
1161
+ MESH_REFINE_BATCH_TOOL,
1162
+ MESH_REFINE_CONFIG_SCHEMA_TOOL,
1163
+ MESH_VALIDATE_REFINE_CONFIG_TOOL,
1164
+ MESH_SUGGEST_REFINE_CONFIG_TOOL,
1165
+ MESH_CHANGE_IMPACT_CONFIG_SCHEMA_TOOL,
1166
+ MESH_VALIDATE_CHANGE_IMPACT_CONFIG_TOOL,
1167
+ MESH_SUGGEST_CHANGE_IMPACT_CONFIG_TOOL,
1168
+ MESH_INIT_TOOL,
1169
+ MESH_REFINE_PLAN_TOOL,
1170
+ MESH_CLEANUP_SESSIONS_TOOL,
1171
+ MESH_PRUNE_STALE_DIRECT_TOOL,
1172
+ MESH_TASK_HISTORY_TOOL,
1173
+ MESH_RECORD_NOTE_TOOL,
1174
+ MESH_RECONCILE_LEDGER_TOOL,
1175
+ MESH_MISSION_UPSERT_TOOL,
1176
+ MESH_MISSION_LIST_TOOL,
1177
+ MESH_REVIEW_INBOX_TOOL
1178
+ ];
1179
+
1180
+ // src/tools/mesh-compact.ts
1181
+ function buildCompactGitSnapshot(status) {
1182
+ if (!status || typeof status !== "object" || Array.isArray(status)) return void 0;
1183
+ const slim = {};
1184
+ const carry = [
1185
+ "isGitRepo",
1186
+ "branch",
1187
+ "headCommit",
1188
+ "upstream",
1189
+ "upstreamStatus",
1190
+ "ahead",
1191
+ "behind",
1192
+ "dirty",
1193
+ "detached",
1194
+ "submodules"
1195
+ ];
1196
+ for (const key of carry) {
1197
+ if (status[key] !== void 0) slim[key] = status[key];
1198
+ }
1199
+ return slim;
1200
+ }
1201
+ function summarizeCompactSubmodules(submodules) {
1202
+ if (!Array.isArray(submodules) || submodules.length === 0) return void 0;
1203
+ const outOfSync = submodules.filter((s) => s?.outOfSync).map((s) => s?.path).filter(Boolean);
1204
+ return {
1205
+ count: submodules.length,
1206
+ ...outOfSync.length > 0 ? { outOfSyncPaths: outOfSync } : {}
1207
+ };
1208
+ }
1209
+ var MESH_COMPACT_PRESERVED_MARKER_FIELDS = ["dataFreshness"];
1210
+ function compactMeshStatusNode(entry) {
1211
+ if (!entry || typeof entry !== "object") return entry;
1212
+ const next = { ...entry };
1213
+ if (next.git !== void 0) {
1214
+ const slimGit = buildCompactGitSnapshot(next.git);
1215
+ if (slimGit) {
1216
+ if (slimGit.submodules !== void 0) {
1217
+ const subSummary = summarizeCompactSubmodules(slimGit.submodules);
1218
+ if (subSummary) slimGit.submodules = subSummary;
1219
+ else delete slimGit.submodules;
1220
+ }
1221
+ next.git = slimGit;
1222
+ }
1223
+ }
1224
+ if (next.machine && typeof next.machine === "object") {
1225
+ const m = next.machine;
1226
+ next.machine = {
1227
+ daemonId: m.daemonId,
1228
+ machineId: m.machineId,
1229
+ hostname: m.hostname,
1230
+ displayName: m.displayName,
1231
+ sameMachine: m.sameMachine,
1232
+ locality: m.locality
1233
+ };
1234
+ }
1235
+ if (typeof next.submoduleWarning === "string") {
1236
+ next.submodulesOutOfSync = true;
1237
+ delete next.submoduleWarning;
1238
+ }
1239
+ if (next.staleDaemonBuild && typeof next.staleDaemonBuild === "object") {
1240
+ const b = next.staleDaemonBuild;
1241
+ next.staleDaemonBuild = {
1242
+ scope: b.scope,
1243
+ isDaemonAffecting: b.isDaemonAffecting !== false,
1244
+ seeStaleDaemonBuilds: true
1245
+ };
1246
+ }
1247
+ delete next.capabilityTagsByProvider;
1248
+ const elideSkip = /* @__PURE__ */ new Set(["git", "machine", "branchConvergence", "staleDaemonBuild", "sessions", ...MESH_COMPACT_PRESERVED_MARKER_FIELDS]);
1249
+ for (const k of Object.keys(next)) {
1250
+ if (elideSkip.has(k)) continue;
1251
+ next[k] = elideLargeNestedValue(k, next[k]);
1252
+ }
1253
+ return next;
1254
+ }
1255
+ function compactNodeSeverity(entry) {
1256
+ if (!entry || typeof entry !== "object") return 0;
1257
+ if (entry.error || entry.health && entry.health !== "online" && entry.health !== "dirty") return 5;
1258
+ if (entry.launchReady === false) return 4;
1259
+ if (entry.isDirty === true || entry.health === "dirty") return 3;
1260
+ if (entry.branchConvergence?.needsConvergence === true) return 2;
1261
+ if (entry.staleDaemonBuild || entry.submodulesOutOfSync || entry.recoveryHints) return 1;
1262
+ return 0;
1263
+ }
1264
+ function isNoteworthyCompactNode(entry) {
1265
+ if (!entry || typeof entry !== "object") return true;
1266
+ if (entry.health && entry.health !== "online") return true;
1267
+ if (entry.isDirty === true) return true;
1268
+ if (entry.error) return true;
1269
+ if (entry.launchReady === false) return true;
1270
+ if (entry.staleDaemonBuild) return true;
1271
+ if (entry.submoduleWarning || entry.submodulesOutOfSync) return true;
1272
+ if (entry.recoveryHints) return true;
1273
+ if (Array.isArray(entry.nextStepHints) && entry.nextStepHints.length > 0) return true;
1274
+ if (entry.branchConvergence?.needsConvergence === true) return true;
1275
+ const sessionCount = Array.isArray(entry.sessions) ? entry.sessions.length : entry.sessionSummary?.total ?? 0;
1276
+ if (sessionCount > 0) return true;
1277
+ return false;
1278
+ }
1279
+ function minimalCompactNode(entry) {
1280
+ if (!entry || typeof entry !== "object") return entry;
1281
+ const bc = entry.branchConvergence && typeof entry.branchConvergence === "object" ? {
1282
+ status: entry.branchConvergence.status,
1283
+ needsConvergence: entry.branchConvergence.needsConvergence,
1284
+ reason: entry.branchConvergence.reason,
1285
+ branch: entry.branchConvergence.branch
1286
+ } : void 0;
1287
+ const preservedMarkers = {};
1288
+ for (const field of MESH_COMPACT_PRESERVED_MARKER_FIELDS) {
1289
+ if (entry[field] !== void 0) preservedMarkers[field] = entry[field];
1290
+ }
1291
+ return {
1292
+ nodeId: entry.nodeId,
1293
+ workspace: entry.workspace,
1294
+ daemonId: entry.daemonId,
1295
+ health: entry.health,
1296
+ branch: entry.branch,
1297
+ launchReady: entry.launchReady,
1298
+ ...entry.providerPriority !== void 0 ? { providerPriority: entry.providerPriority } : {},
1299
+ // Keep the routable tag set on quiet/folded nodes — a coordinator planning
1300
+ // required_tags routing needs it even for nodes with nothing to converge.
1301
+ ...entry.capabilityTags !== void 0 ? { capabilityTags: entry.capabilityTags } : {},
1302
+ ...entry.launchBlockedReason !== void 0 ? { launchBlockedReason: entry.launchBlockedReason } : {},
1303
+ ...bc ? { branchConvergence: bc } : {},
1304
+ ...entry.sessionSummary ? { sessionSummary: entry.sessionSummary } : {},
1305
+ ...preservedMarkers,
1306
+ folded: true
1307
+ };
1308
+ }
1309
+ function summarizeNodeSessions(sessions) {
1310
+ const list = Array.isArray(sessions) ? sessions : [];
1311
+ const byStatus = {};
1312
+ const providerCounts = {};
1313
+ const selfCoordinatorSessionIds = [];
1314
+ for (const s of list) {
1315
+ const status = typeof s?.status === "string" && s.status ? s.status : "unknown";
1316
+ byStatus[status] = (byStatus[status] ?? 0) + 1;
1317
+ const provider = typeof s?.providerType === "string" && s.providerType ? s.providerType : "unknown";
1318
+ providerCounts[provider] = (providerCounts[provider] ?? 0) + 1;
1319
+ if (s?.isSelfCoordinator === true && s.id) selfCoordinatorSessionIds.push(String(s.id));
1320
+ }
1321
+ const summary = {
1322
+ total: list.length,
1323
+ byStatus,
1324
+ providerCounts
1325
+ };
1326
+ if (selfCoordinatorSessionIds.length > 0) {
1327
+ summary.selfCoordinatorSessionIds = selfCoordinatorSessionIds;
1328
+ }
1329
+ return summary;
1330
+ }
1331
+
1332
+ // src/tools/mesh-queue-helpers.ts
1333
+ var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
1334
+ var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
1335
+ var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
1336
+ var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
1337
+ function buildQueueLivenessIndex(mesh) {
1338
+ const nodeIds = /* @__PURE__ */ new Set();
1339
+ const nodeSessionIds = /* @__PURE__ */ new Map();
1340
+ for (const node of Array.isArray(mesh?.nodes) ? mesh.nodes : []) {
1341
+ const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
1342
+ if (!nodeId) continue;
1343
+ nodeIds.add(nodeId);
1344
+ const sessions = collectNodeSessionIds(node);
1345
+ if (sessions.size > 0) nodeSessionIds.set(nodeId, sessions);
1398
1346
  }
1399
- };
1400
- var MESH_TASK_HISTORY_TOOL = {
1401
- name: "mesh_task_history",
1402
- 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.",
1403
- inputSchema: {
1404
- type: "object",
1405
- properties: {
1406
- tail: { type: "number", description: "Number of recent entries to return (default: 20; clamped to 40 in compact mode, 200 in verbose)." },
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
- }
1347
+ return { nodeIds, nodeSessionIds };
1348
+ }
1349
+ function queueAssignmentStaleReason(task, liveness) {
1350
+ if (task?.status !== "assigned") return void 0;
1351
+ const nodeId = readString(task.assignedNodeId) || readString(task.nodeId) || readString(task.node_id) || readString(task.targetNodeId);
1352
+ const sessionId = readString(task.assignedSessionId) || readString(task.sessionId) || readString(task.session_id) || readString(task.targetSessionId);
1353
+ if (nodeId && liveness.nodeIds.size > 0 && !liveness.nodeIds.has(nodeId)) {
1354
+ return "assigned node is not present in the current mesh snapshot";
1411
1355
  }
1412
- };
1413
- var MESH_RECORD_NOTE_TOOL = {
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"]
1356
+ if (nodeId && sessionId && liveness.nodeSessionIds.has(nodeId) && !liveness.nodeSessionIds.get(nodeId).has(sessionId)) {
1357
+ return "assigned session is not live on the assigned node";
1427
1358
  }
1428
- };
1429
- var MESH_RECONCILE_LEDGER_TOOL = {
1430
- name: "mesh_reconcile_ledger",
1431
- 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.",
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
- }
1359
+ const updatedAt = new Date(task.updatedAt).getTime();
1360
+ const ageMs = Number.isFinite(updatedAt) ? Date.now() - updatedAt : null;
1361
+ if (!nodeId && ageMs !== null && ageMs >= STALE_ASSIGNED_QUEUE_MS) {
1362
+ return "assigned task has no assigned node metadata";
1441
1363
  }
1442
- };
1443
- var MESH_PRUNE_STALE_DIRECT_TOOL = {
1444
- name: "mesh_prune_stale_direct",
1445
- 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.",
1446
- inputSchema: {
1447
- type: "object",
1448
- properties: {
1449
- execute: { type: "boolean", description: "When true, actually delete the orphaned records. Defaults false (dry run). Ignored when dry_run=true." },
1450
- dry_run: { type: "boolean", description: "Force a preview without mutation even if execute=true. Defaults to dry-run behavior when execute is not set." },
1451
- include_terminal: { type: "boolean", description: "Also prune terminal (completed/failed) direct dispatch store rows in addition to orphans. Defaults false." }
1364
+ return void 0;
1365
+ }
1366
+ function buildQueueStatusSummary(queue) {
1367
+ const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
1368
+ let staleAssigned = 0;
1369
+ for (const task of queue) {
1370
+ const status = typeof task?.status === "string" ? task.status : void 0;
1371
+ if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
1372
+ counts[status] += 1;
1452
1373
  }
1374
+ if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
1453
1375
  }
1454
- };
1455
- var MESH_REFINE_NODE_TOOL = {
1456
- name: "mesh_refine_node",
1457
- 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.",
1458
- inputSchema: {
1459
- type: "object",
1460
- properties: {
1461
- node_id: { type: "string", description: "Node ID of the completed worktree node to refine and merge." },
1462
- execute: { type: "boolean", description: "When true, run validation/merge/push/cleanup for this node. Defaults false/dry-run." },
1463
- dry_run: { type: "boolean", description: "Preview the validation plan without merging. Defaults true unless execute=true; dry_run=true overrides execute." }
1376
+ const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
1377
+ return {
1378
+ totalCount: queue.length,
1379
+ activeCount: counts.pending + liveAssigned,
1380
+ historicalCount: counts.completed + counts.failed + counts.cancelled,
1381
+ counts,
1382
+ activeCounts: {
1383
+ pending: counts.pending,
1384
+ assigned: liveAssigned
1464
1385
  },
1465
- required: ["node_id"]
1466
- }
1467
- };
1468
- var MESH_REFINE_BATCH_TOOL = {
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." }
1386
+ staleAssignedCount: staleAssigned,
1387
+ rawActiveCounts: {
1388
+ pending: counts.pending,
1389
+ assigned: counts.assigned
1481
1390
  },
1482
- required: []
1483
- }
1484
- };
1485
- var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
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." }
1391
+ historicalCounts: {
1392
+ completed: counts.completed,
1393
+ failed: counts.failed,
1394
+ cancelled: counts.cancelled
1498
1395
  }
1396
+ };
1397
+ }
1398
+ function normalizeQueueViewMode(value) {
1399
+ return value === "active" || value === "historical" || value === "all" ? value : "all";
1400
+ }
1401
+ function sanitizeQueueStatusFilter(value) {
1402
+ if (!Array.isArray(value)) return void 0;
1403
+ const statuses = value.map((item) => typeof item === "string" ? item.trim() : "").filter((status) => ACTIVE_QUEUE_STATUSES.has(status) || HISTORICAL_QUEUE_STATUSES.has(status));
1404
+ return statuses.length ? Array.from(new Set(statuses)) : void 0;
1405
+ }
1406
+ function filterQueueForView(queue, view, statuses) {
1407
+ if (statuses?.length) {
1408
+ const allowed = new Set(statuses);
1409
+ return queue.filter((task) => allowed.has(String(task?.status || "")));
1499
1410
  }
1500
- };
1501
- var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
1502
- name: "mesh_suggest_refine_config",
1503
- description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
1504
- inputSchema: {
1505
- type: "object",
1506
- properties: {
1507
- node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
1508
- }
1411
+ if (view === "active") return queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")));
1412
+ if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
1413
+ return queue;
1414
+ }
1415
+ function prioritizeActiveQueueRows(queue) {
1416
+ const active = [];
1417
+ const historical = [];
1418
+ const other = [];
1419
+ for (const task of queue) {
1420
+ const status = String(task?.status || "");
1421
+ if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
1422
+ else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
1423
+ else other.push(task);
1424
+ }
1425
+ return [...active, ...other, ...historical];
1426
+ }
1427
+ function slimQueueTask(task) {
1428
+ return {
1429
+ id: task?.id,
1430
+ status: task?.status,
1431
+ assignedNodeId: task?.assignedNodeId,
1432
+ assignedSessionId: task?.assignedSessionId,
1433
+ targetNodeId: task?.targetNodeId,
1434
+ targetSessionId: task?.targetSessionId,
1435
+ updatedAt: task?.updatedAt,
1436
+ staleAssigned: task?.staleAssigned === true,
1437
+ staleReason: task?.staleReason
1438
+ };
1439
+ }
1440
+ function buildQueueMaintenanceReport(queue) {
1441
+ const now = Date.now();
1442
+ const staleAssignedTasks = queue.filter((task) => task?.status === "assigned" && task?.staleAssigned === true).map(slimQueueTask);
1443
+ const historicalTasks = queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
1444
+ const oldHistoricalTasks = historicalTasks.filter((task) => {
1445
+ const updatedAt = new Date(task?.updatedAt).getTime();
1446
+ return Number.isFinite(updatedAt) && now - updatedAt >= OLD_HISTORICAL_QUEUE_RECORD_MS;
1447
+ }).map((task) => ({
1448
+ ...slimQueueTask(task),
1449
+ cleanupClass: "old_historical_record",
1450
+ reason: "terminal queue record is older than the read-only maintenance threshold"
1451
+ }));
1452
+ const cleanupCandidates = [
1453
+ ...staleAssignedTasks.map((task) => ({
1454
+ ...task,
1455
+ cleanupClass: "stale_assigned",
1456
+ reason: typeof task.staleReason === "string" ? task.staleReason : "active assigned task does not match current live mesh node/session state",
1457
+ suggestedOperation: "operator_review_then_requeue_or_cancel"
1458
+ })),
1459
+ ...oldHistoricalTasks.map((task) => ({
1460
+ ...task,
1461
+ suggestedOperation: "operator_review_then_archive_or_keep"
1462
+ }))
1463
+ ];
1464
+ return {
1465
+ readOnly: true,
1466
+ mutationPerformed: false,
1467
+ sourceOfTruth: "mesh_work_queue_file",
1468
+ staleAssignedDefinition: "Only active assigned queue rows are stale candidates, and only when the assigned node/session is absent from the current live mesh snapshot.",
1469
+ historicalDefinition: "completed/failed/cancelled rows are historical ledger records and never active assignments.",
1470
+ staleAssignedTasks,
1471
+ staleAssignedCount: staleAssignedTasks.length,
1472
+ historicalRecordCount: historicalTasks.length,
1473
+ oldHistoricalRecordCount: oldHistoricalTasks.length,
1474
+ cleanupCandidates,
1475
+ cleanupCandidateCount: cleanupCandidates.length
1476
+ };
1477
+ }
1478
+ function buildCompactQueueMaintenanceReport(maintenance) {
1479
+ const staleAssignedTasks = Array.isArray(maintenance.staleAssignedTasks) ? maintenance.staleAssignedTasks : [];
1480
+ const cleanupCandidateCount = maintenance.cleanupCandidateCount ?? 0;
1481
+ return {
1482
+ readOnly: true,
1483
+ mutationPerformed: false,
1484
+ sourceOfTruth: "mesh_work_queue_file",
1485
+ payloadMode: "compact",
1486
+ staleAssignedDefinition: maintenance.staleAssignedDefinition,
1487
+ historicalDefinition: maintenance.historicalDefinition,
1488
+ // staleAssignedTasks are active assigned rows (not historical) — retain a
1489
+ // bounded sample so coordinators can still see drift without the full array.
1490
+ staleAssignedTasks: staleAssignedTasks.slice(0, 5),
1491
+ staleAssignedSampleLimit: 5,
1492
+ staleAssignedCount: maintenance.staleAssignedCount ?? staleAssignedTasks.length,
1493
+ historicalRecordCount: maintenance.historicalRecordCount ?? 0,
1494
+ oldHistoricalRecordCount: maintenance.oldHistoricalRecordCount ?? 0,
1495
+ cleanupCandidateCount,
1496
+ cleanupCandidatesOmitted: true,
1497
+ cleanupCandidatesHint: "Per-row cleanup candidates are omitted in compact mode; call mesh_view_queue with verbose=true for the full maintenance/cleanupDryRun rows."
1498
+ };
1499
+ }
1500
+ var COMPACT_MAX_ACTIVE_QUEUE_ROWS = 15;
1501
+ var COMPACT_QUEUE_MESSAGE_CAP = 140;
1502
+ var COMPACT_MAX_ACTIVE_WORK_ROWS = 12;
1503
+ var COMPACT_ACTIVE_WORK_TITLE_CAP = 80;
1504
+ function truncateForCompact(value, cap) {
1505
+ if (typeof value !== "string") return value;
1506
+ return value.length > cap ? value.slice(0, cap) + "\u2026" : value;
1507
+ }
1508
+ function compactQueueRow(task) {
1509
+ if (!task || typeof task !== "object") return task;
1510
+ const slim = {};
1511
+ for (const [k, v] of Object.entries(task)) {
1512
+ if (k === "message") slim[k] = truncateForCompact(v, COMPACT_QUEUE_MESSAGE_CAP);
1513
+ else slim[k] = elideLargeNestedValue(k, v);
1509
1514
  }
1510
- };
1511
- var MESH_CHANGE_IMPACT_CONFIG_SCHEMA_TOOL = {
1512
- name: "mesh_change_impact_config_schema",
1513
- 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.",
1514
- inputSchema: { type: "object", properties: {} }
1515
- };
1516
- var MESH_VALIDATE_CHANGE_IMPACT_CONFIG_TOOL = {
1517
- name: "mesh_validate_change_impact_config",
1518
- 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.",
1519
- inputSchema: {
1520
- type: "object",
1521
- properties: {
1522
- node_id: { type: "string", description: "Optional node/workspace whose change-impact config should be loaded. Defaults to the first mesh node." },
1523
- config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
1524
- }
1515
+ return slim;
1516
+ }
1517
+ function compactQueueRows(rows) {
1518
+ const capped = rows.slice(0, COMPACT_MAX_ACTIVE_QUEUE_ROWS).map(compactQueueRow);
1519
+ return { rows: capped, omitted: Math.max(0, rows.length - capped.length) };
1520
+ }
1521
+ function compactActiveWorkRecord(record) {
1522
+ if (!record || typeof record !== "object") return record;
1523
+ const slim = {};
1524
+ for (const [k, v] of Object.entries(record)) {
1525
+ if (k === "message" || k === "taskSummary") continue;
1526
+ else if (k === "taskTitle") slim[k] = truncateForCompact(v, COMPACT_ACTIVE_WORK_TITLE_CAP);
1527
+ else slim[k] = elideLargeNestedValue(k, v);
1525
1528
  }
1526
- };
1527
- var MESH_SUGGEST_CHANGE_IMPACT_CONFIG_TOOL = {
1528
- name: "mesh_suggest_change_impact_config",
1529
- 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.",
1530
- inputSchema: {
1531
- type: "object",
1532
- properties: {
1533
- node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
1534
- }
1529
+ return slim;
1530
+ }
1531
+ function compactActiveWorkRecords(records) {
1532
+ if (!Array.isArray(records)) return { records, omitted: 0 };
1533
+ const capped = records.slice(0, COMPACT_MAX_ACTIVE_WORK_ROWS).map(compactActiveWorkRecord);
1534
+ return { records: capped, omitted: Math.max(0, records.length - capped.length) };
1535
+ }
1536
+ function annotateQueueStaleness(queue, mesh) {
1537
+ const liveness = buildQueueLivenessIndex(mesh);
1538
+ const now = Date.now();
1539
+ return queue.map((task) => {
1540
+ const taskStatus = typeof task?.status === "string" ? task.status : void 0;
1541
+ const annotated = {
1542
+ ...task,
1543
+ taskStatus,
1544
+ isActive: taskStatus ? ACTIVE_QUEUE_STATUSES.has(taskStatus) : false,
1545
+ isHistorical: taskStatus ? HISTORICAL_QUEUE_STATUSES.has(taskStatus) : false,
1546
+ dispatchedAt: task?.createdAt,
1547
+ ...taskStatus === "assigned" ? { activeTaskId: task.id } : {},
1548
+ ...taskStatus === "completed" || taskStatus === "failed" ? {
1549
+ completedAt: task.updatedAt
1550
+ } : {}
1551
+ };
1552
+ if (taskStatus !== "assigned") return annotated;
1553
+ const updatedAt = new Date(task.updatedAt).getTime();
1554
+ const ageMs = Number.isFinite(updatedAt) ? now - updatedAt : null;
1555
+ const staleReason = queueAssignmentStaleReason(task, liveness);
1556
+ if (!staleReason) return annotated;
1557
+ return {
1558
+ ...annotated,
1559
+ stale: true,
1560
+ staleAssigned: true,
1561
+ staleReason,
1562
+ ...ageMs !== null ? { assignedAgeMs: ageMs } : {}
1563
+ };
1564
+ });
1565
+ }
1566
+
1567
+ // src/tools/read-chat-polling-advisory.ts
1568
+ var RAPID_READ_CHAT_ADVISORY_WINDOW_MS = 5e3;
1569
+ var ACTIVE_READ_STATUSES = /* @__PURE__ */ new Set([
1570
+ "generating",
1571
+ "running",
1572
+ "streaming",
1573
+ "starting",
1574
+ "busy"
1575
+ ]);
1576
+ var recentReads = /* @__PURE__ */ new Map();
1577
+ function isActiveReadChatStatus(status) {
1578
+ return typeof status === "string" && ACTIVE_READ_STATUSES.has(status.toLowerCase());
1579
+ }
1580
+ function annotateRapidReadChatAdvisory(payload, options) {
1581
+ const now = options.now ?? Date.now();
1582
+ const status = options.status ?? payload?.status ?? payload?.data?.status ?? payload?.result?.status;
1583
+ const active = isActiveReadChatStatus(status);
1584
+ const previous = recentReads.get(options.key);
1585
+ if (!active) {
1586
+ recentReads.set(options.key, { at: now, status: typeof status === "string" ? status : void 0 });
1587
+ return payload;
1535
1588
  }
1536
- };
1537
- var MESH_INIT_TOOL = {
1538
- name: "mesh_init",
1539
- 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.",
1540
- inputSchema: {
1541
- type: "object",
1542
- properties: {
1543
- node_id: { type: "string", description: "Optional node/workspace to onboard. Defaults to the first mesh node with a workspace." },
1544
- write: { type: "boolean", description: "When true, persist the suggested configs to disk. Defaults false (dry-run preview only)." },
1545
- overwrite: { type: "boolean", description: "When true, overwrite an existing config file. Defaults false (never clobber an existing refine/bootstrap config)." }
1589
+ recentReads.set(options.key, { at: now, status: typeof status === "string" ? status : void 0 });
1590
+ if (!previous || !isActiveReadChatStatus(previous.status)) return payload;
1591
+ const elapsedMs = now - previous.at;
1592
+ if (elapsedMs < 0 || elapsedMs >= RAPID_READ_CHAT_ADVISORY_WINDOW_MS) return payload;
1593
+ return {
1594
+ ...payload,
1595
+ pollingAdvisory: {
1596
+ type: "rapid_read_chat_polling",
1597
+ toolName: options.toolName,
1598
+ windowMs: RAPID_READ_CHAT_ADVISORY_WINDOW_MS,
1599
+ elapsedMs,
1600
+ nextSuggestedReadAt: previous.at + RAPID_READ_CHAT_ADVISORY_WINDOW_MS,
1601
+ completionCallbackExpected: Boolean(options.completionCallbackExpected),
1602
+ 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
1603
  }
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
- ];
1604
+ };
1605
+ }
1609
1606
 
1610
- // src/tools/mesh-tools.ts
1607
+ // src/tools/mesh-tools-internal.ts
1608
+ var import_daemon_core3 = require("@adhdev/daemon-core");
1609
+ var import_node_crypto = require("crypto");
1611
1610
  var SESSION_PROVIDER_METADATA_TTL_MS = 30 * 6e4;
1612
1611
  var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
1613
1612
  function getSessionMetadata(key) {
@@ -2821,32 +2820,115 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
2821
2820
  rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
2822
2821
  if (!injected) surfacedEvents.push(event);
2823
2822
  }
2824
- } catch {
2825
- }
2826
- return surfacedEvents;
2823
+ } catch {
2824
+ }
2825
+ return surfacedEvents;
2826
+ }
2827
+ const events = (0, import_daemon_core2.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
2828
+ events.forEach(rememberMeshSessionProviderMetadataFromEvent);
2829
+ return events;
2830
+ }
2831
+ function isP2pTransportUnavailableError(error) {
2832
+ return (0, import_daemon_core2.isP2pRelayTransportFailure)(error);
2833
+ }
2834
+ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode, force) {
2835
+ return {
2836
+ meshId: ctx.mesh.id,
2837
+ nodeId,
2838
+ ...sessionCleanupMode ? { sessionCleanupMode } : {},
2839
+ ...force === true ? { force: true } : {},
2840
+ inlineMesh: ctx.mesh
2841
+ };
2842
+ }
2843
+ function classifyReadChatTransportCause(error) {
2844
+ const message = (error instanceof Error ? error.message : String(error ?? "")).toLowerCase();
2845
+ 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)) {
2846
+ return "not_connected";
2847
+ }
2848
+ return "saturated";
2849
+ }
2850
+ function resolveCachedMeshSessionPreviewFromLedger(ctx, nodeId, sessionId) {
2851
+ const entries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
2852
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
2853
+ const entry = entries[i];
2854
+ const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
2855
+ const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
2856
+ if (entryNodeId && entryNodeId !== nodeId) continue;
2857
+ const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
2858
+ if (entrySessionId !== sessionId) continue;
2859
+ const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : payload;
2860
+ const preview = (0, import_daemon_core2.resolveMeshSurfacedSessionPreview)(metadataEvent);
2861
+ if (preview) {
2862
+ return { ...preview, ledgerKind: entry.kind, timestamp: entry.timestamp };
2863
+ }
2864
+ }
2865
+ return void 0;
2866
+ }
2867
+ function buildMeshReadChatCacheFallback(ctx, args, node, error) {
2868
+ const classification = (0, import_daemon_core2.classifyP2pRelayFailure)(error, { command: "read_chat", targetDaemonId: node.daemonId });
2869
+ const cause = classifyReadChatTransportCause(error);
2870
+ const errorMessage = error instanceof Error ? error.message : String(error ?? "");
2871
+ 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";
2872
+ const cached = resolveCachedMeshSessionPreviewFromLedger(ctx, args.node_id, args.session_id);
2873
+ if (cached) {
2874
+ return JSON.stringify({
2875
+ success: true,
2876
+ source: "coordinator_cache_fallback",
2877
+ fallback: true,
2878
+ nodeId: args.node_id,
2879
+ sessionId: args.session_id,
2880
+ transport: "p2p",
2881
+ transportFailure: {
2882
+ code: classification.code,
2883
+ reason: classification.reason,
2884
+ cause,
2885
+ error: errorMessage
2886
+ },
2887
+ 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.`,
2888
+ fullTranscriptRequiresP2p: true,
2889
+ summary: cached.preview,
2890
+ messages: [{
2891
+ role: cached.role,
2892
+ content: cached.preview,
2893
+ cached: true,
2894
+ ...cached.receivedAt ? { receivedAt: cached.receivedAt } : {}
2895
+ }],
2896
+ cachedPreview: {
2897
+ role: cached.role,
2898
+ ledgerKind: cached.ledgerKind,
2899
+ ledgerTimestamp: cached.timestamp,
2900
+ ...cached.receivedAt ? { receivedAt: cached.receivedAt } : {}
2901
+ }
2902
+ }, null, 2);
2827
2903
  }
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);
2904
+ const failure = buildCoordinatorP2pRelayFailure(error, {
2905
+ command: "read_chat",
2906
+ targetDaemonId: node.daemonId,
2907
+ nodeId: args.node_id,
2908
+ sessionId: args.session_id
2909
+ });
2910
+ return JSON.stringify({
2911
+ ...failure,
2912
+ cause,
2913
+ cachedSummaryAvailable: false,
2914
+ fullTranscriptRequiresP2p: true,
2915
+ 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.`
2916
+ }, null, 2);
2834
2917
  }
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
- };
2918
+ function resolveRefineConfigNode(ctx, nodeId) {
2919
+ if (nodeId) return findNode(ctx.mesh, nodeId);
2920
+ const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
2921
+ if (!node) throw new Error("No mesh node with a workspace is available");
2922
+ return node;
2843
2923
  }
2924
+
2925
+ // src/tools/mesh-tools-status.ts
2844
2926
  async function meshStatus(ctx, args = {}) {
2845
- const rateResult = (0, import_daemon_core2.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
2927
+ const rateResult = (0, import_daemon_core3.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
2846
2928
  const compact = args.verbose === true ? false : args.compact ?? true;
2847
2929
  await refreshMeshFromDaemon(ctx);
2848
2930
  const { mesh, transport } = ctx;
2849
- let ledgerSummary = (0, import_daemon_core2.getLedgerSummary)(mesh.id);
2931
+ let ledgerSummary = (0, import_daemon_core3.getLedgerSummary)(mesh.id);
2850
2932
  const results = await Promise.all(mesh.nodes.map(async (node) => {
2851
2933
  const entry = {
2852
2934
  nodeId: node.id,
@@ -2902,14 +2984,14 @@ async function meshStatus(ctx, args = {}) {
2902
2984
  noFallbackReason: failure.noFallbackReason
2903
2985
  });
2904
2986
  }
2905
- entry.dataFreshness = (0, import_daemon_core2.buildMeshNodeProbeFreshness)({
2987
+ entry.dataFreshness = (0, import_daemon_core3.buildMeshNodeProbeFreshness)({
2906
2988
  git: entry.git,
2907
2989
  liveTruthProbed,
2908
2990
  isSelfNode: entry.machine?.sameMachine === true,
2909
2991
  daemonId: readNodeDaemonId(node),
2910
2992
  node
2911
2993
  });
2912
- const recoveryContext = (0, import_daemon_core2.getSessionRecoveryContext)(mesh.id, { nodeId: node.id });
2994
+ const recoveryContext = (0, import_daemon_core3.getSessionRecoveryContext)(mesh.id, { nodeId: node.id });
2913
2995
  if (recoveryContext.consecutiveNodeFailures > 0) {
2914
2996
  entry.recoveryHints = {
2915
2997
  consecutiveFailures: recoveryContext.consecutiveNodeFailures,
@@ -2981,23 +3063,23 @@ async function meshStatus(ctx, args = {}) {
2981
3063
  }
2982
3064
  return entry;
2983
3065
  }));
2984
- let ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(mesh.id, { tail: 200 });
2985
- let directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(mesh.id);
3066
+ let ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(mesh.id, { tail: 200 });
3067
+ let directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(mesh.id);
2986
3068
  const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, results, directDispatches, ledgerEntries);
2987
3069
  if (directReconciliation.reconciled > 0) {
2988
- ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(mesh.id, { tail: 200 });
2989
- directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(mesh.id);
2990
- ledgerSummary = (0, import_daemon_core2.getLedgerSummary)(mesh.id);
3070
+ ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(mesh.id, { tail: 200 });
3071
+ directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(mesh.id);
3072
+ ledgerSummary = (0, import_daemon_core3.getLedgerSummary)(mesh.id);
2991
3073
  }
2992
- const activeWorkEvidence = (0, import_daemon_core2.buildMeshActiveWork)({
3074
+ const activeWorkEvidence = (0, import_daemon_core3.buildMeshActiveWork)({
2993
3075
  meshId: mesh.id,
2994
- queue: (0, import_daemon_core2.getQueue)(mesh.id),
3076
+ queue: (0, import_daemon_core3.getQueue)(mesh.id),
2995
3077
  ledgerEntries,
2996
3078
  directDispatches,
2997
3079
  nodes: results
2998
3080
  });
2999
3081
  const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
3000
- const staleDirectWorkSummary = (0, import_daemon_core2.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
3082
+ const staleDirectWorkSummary = (0, import_daemon_core3.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
3001
3083
  note: activeWorkEvidence.staleDirectWorkNote,
3002
3084
  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
3085
  });
@@ -3185,7 +3267,7 @@ async function meshStatus(ctx, args = {}) {
3185
3267
  }
3186
3268
  try {
3187
3269
  if (compact) {
3188
- const { live, historyFold } = (0, import_daemon_core2.getMeshStatusMissionsCompact)(mesh.id);
3270
+ const { live, historyFold } = (0, import_daemon_core3.getMeshStatusMissionsCompact)(mesh.id);
3189
3271
  const ranked = [...live].sort((a, b) => String(b.tasks?.lastActivityAt ?? "").localeCompare(String(a.tasks?.lastActivityAt ?? "")));
3190
3272
  const kept = [];
3191
3273
  const overflow = [];
@@ -3211,231 +3293,46 @@ async function meshStatus(ctx, args = {}) {
3211
3293
  };
3212
3294
  }
3213
3295
  if (historyFold) response.missionsHistory = historyFold;
3214
- } else {
3215
- const missions = (0, import_daemon_core2.getMeshStatusMissionSummaries)(mesh.id, { verbose: true });
3216
- if (missions.length > 0) {
3217
- response.missions = missions.map((mission) => {
3218
- try {
3219
- return { ...mission, stats: (0, import_daemon_core2.computeMeshMissionStats)(mesh.id, mission.id) };
3220
- } catch {
3221
- return mission;
3222
- }
3223
- });
3224
- }
3225
- }
3226
- } catch {
3227
- }
3228
- try {
3229
- const pendingEvents = await drainCoordinatorPendingEvents(ctx);
3230
- const asyncRefineJobs = (0, import_daemon_core2.buildMeshAsyncRefineJobs)({
3231
- meshId: mesh.id,
3232
- ledgerEntries,
3233
- pendingEvents
3234
- });
3235
- if (asyncRefineJobs.length > 0) {
3236
- if (compact) {
3237
- const summary = (0, import_daemon_core2.summarizeMeshAsyncRefineJobs)(asyncRefineJobs);
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);
3296
+ } else {
3297
+ const missions = (0, import_daemon_core3.getMeshStatusMissionSummaries)(mesh.id, { verbose: true });
3298
+ if (missions.length > 0) {
3299
+ response.missions = missions.map((mission) => {
3300
+ try {
3301
+ return { ...mission, stats: (0, import_daemon_core3.computeMeshMissionStats)(mesh.id, mission.id) };
3302
+ } catch {
3303
+ return mission;
3304
+ }
3305
+ });
3306
+ }
3307
+ }
3308
+ } catch {
3309
+ }
3310
+ try {
3311
+ const pendingEvents = await drainCoordinatorPendingEvents(ctx);
3312
+ const asyncRefineJobs = (0, import_daemon_core3.buildMeshAsyncRefineJobs)({
3313
+ meshId: mesh.id,
3314
+ ledgerEntries,
3315
+ pendingEvents
3316
+ });
3317
+ if (asyncRefineJobs.length > 0) {
3318
+ if (compact) {
3319
+ const summary = (0, import_daemon_core3.summarizeMeshAsyncRefineJobs)(asyncRefineJobs);
3320
+ if (summary.activeJobs.length > 0) response.asyncRefineJobs = summary.activeJobs;
3321
+ response.asyncRefineJobsSummary = {
3322
+ total: summary.total,
3323
+ byStatus: summary.byStatus,
3324
+ ...summary.staleTerminal > 0 ? { staleTerminal: summary.staleTerminal } : {}
3325
+ };
3326
+ } else {
3327
+ response.asyncRefineJobs = asyncRefineJobs;
3328
+ }
3329
+ }
3330
+ if (pendingEvents.length > 0) {
3331
+ response.pendingCoordinatorEvents = pendingEvents;
3332
+ }
3333
+ } catch {
3334
+ }
3335
+ return JSON.stringify(response, null, 2);
3439
3336
  }
3440
3337
  async function meshListNodes(ctx) {
3441
3338
  await refreshMeshFromDaemon(ctx);
@@ -3459,67 +3356,18 @@ async function meshListNodes(ctx) {
3459
3356
  }))
3460
3357
  }, null, 2);
3461
3358
  }
3462
- async function meshMissionUpsert(ctx, args) {
3463
- try {
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
- }
3359
+
3360
+ // src/tools/mesh-tools-queue.ts
3513
3361
  async function meshEnqueueTask(ctx, args) {
3514
3362
  const taskMode = readString(args.task_mode) || readString(args.taskMode);
3515
- const requiredTags = (0, import_daemon_core2.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
3363
+ const requiredTags = (0, import_daemon_core3.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
3516
3364
  const dependsOn = Array.isArray(args.dependsOn) ? args.dependsOn : Array.isArray(args.depends_on) ? args.depends_on : void 0;
3517
3365
  const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
3518
3366
  const explicitTarget = readString(args.targetNodeId) || readString(args.target_node_id) || void 0;
3519
3367
  const preferWorktree = args.preferWorktree === true || args.prefer_worktree === true;
3520
3368
  const targetNodeId = explicitTarget || (preferWorktree ? resolvePreferredWorktreeNodeId(ctx) : void 0);
3521
3369
  try {
3522
- const task = (0, import_daemon_core2.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags, dependsOn, missionId, targetNodeId, ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {} });
3370
+ const task = (0, import_daemon_core3.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags, dependsOn, missionId, targetNodeId, ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {} });
3523
3371
  if (!(ctx.transport instanceof IpcTransport)) {
3524
3372
  const queueTrigger = await triggerMeshQueueAndReport(ctx);
3525
3373
  return JSON.stringify({
@@ -3542,14 +3390,14 @@ async function meshEnqueueTask(ctx, args) {
3542
3390
  const isLocalNode = isLocalControlPlaneNode(ctx, node);
3543
3391
  if (isLocalNode || !node.daemonId) continue;
3544
3392
  if (targetNodeId && node.id !== targetNodeId) continue;
3545
- if (!(0, import_daemon_core2.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core2.buildMeshNodeCapabilityTags)(node))) continue;
3393
+ if (!(0, import_daemon_core3.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core3.buildMeshNodeCapabilityTags)(node))) continue;
3546
3394
  dispatchPromises.push(
3547
3395
  ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
3548
3396
  if (result.success) {
3549
3397
  try {
3550
3398
  const providerType = result.providerType;
3551
3399
  const descriptor = summarizeTaskMessage(args.message);
3552
- (0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
3400
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
3553
3401
  kind: "task_dispatched",
3554
3402
  nodeId: node.id,
3555
3403
  sessionId: result.sessionId,
@@ -3571,7 +3419,7 @@ async function meshEnqueueTask(ctx, args) {
3571
3419
  }
3572
3420
  }).catch((err) => {
3573
3421
  try {
3574
- (0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
3422
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
3575
3423
  kind: "p2p_dispatch_failed",
3576
3424
  nodeId: node.id,
3577
3425
  payload: {
@@ -3614,17 +3462,17 @@ async function meshEnqueueTask(ctx, args) {
3614
3462
  }
3615
3463
  }
3616
3464
  async function meshViewQueue(ctx, args) {
3617
- const rateResult = (0, import_daemon_core2.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
3465
+ const rateResult = (0, import_daemon_core3.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
3618
3466
  const compact = args.verbose === true ? false : args.compact ?? true;
3619
3467
  try {
3620
3468
  await refreshMeshFromDaemon(ctx);
3621
3469
  const statusFilter = sanitizeQueueStatusFilter(args.status);
3622
3470
  const view = normalizeQueueViewMode(args.view);
3623
- const rawQueue = (0, import_daemon_core2.getQueue)(ctx.mesh.id);
3471
+ const rawQueue = (0, import_daemon_core3.getQueue)(ctx.mesh.id);
3624
3472
  const statusById = new Map(rawQueue.map((task) => [task.id, task.status]));
3625
3473
  const withDependencies = rawQueue.map((task) => {
3626
3474
  if (!Array.isArray(task.dependsOn) || task.dependsOn.length === 0) return task;
3627
- const depState = (0, import_daemon_core2.describeTaskDependencyState)(task, statusById);
3475
+ const depState = (0, import_daemon_core3.describeTaskDependencyState)(task, statusById);
3628
3476
  return { ...task, ...depState };
3629
3477
  });
3630
3478
  const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness(withDependencies, ctx.mesh));
@@ -3633,16 +3481,16 @@ async function meshViewQueue(ctx, args) {
3633
3481
  const visibleSummary = buildQueueStatusSummary(queue);
3634
3482
  const maintenance = buildQueueMaintenanceReport(fullQueue);
3635
3483
  const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
3636
- let ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
3637
- let directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(ctx.mesh.id);
3484
+ let ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
3485
+ let directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(ctx.mesh.id);
3638
3486
  const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries);
3639
3487
  if (directReconciliation.reconciled > 0) {
3640
- ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
3641
- directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(ctx.mesh.id);
3488
+ ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
3489
+ directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(ctx.mesh.id);
3642
3490
  }
3643
- (0, import_daemon_core2.markStaleDirectDispatches)(ctx.mesh.id);
3644
- directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(ctx.mesh.id);
3645
- const activeWorkEvidence = (0, import_daemon_core2.buildMeshActiveWork)({
3491
+ (0, import_daemon_core3.markStaleDirectDispatches)(ctx.mesh.id);
3492
+ directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(ctx.mesh.id);
3493
+ const activeWorkEvidence = (0, import_daemon_core3.buildMeshActiveWork)({
3646
3494
  meshId: ctx.mesh.id,
3647
3495
  queue: fullQueue,
3648
3496
  ledgerEntries,
@@ -3667,7 +3515,7 @@ async function meshViewQueue(ctx, args) {
3667
3515
  const wantActiveQueueArray = view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status));
3668
3516
  const wantHistoricalQueueArray = !compact && (view === "historical" || requestedHistoricalRows);
3669
3517
  const activeWorkResult = compact ? compactActiveWorkRecords(activeWorkEvidence.activeWork) : { records: activeWorkEvidence.activeWork, omitted: 0 };
3670
- const staleDirectWorkSummary = (0, import_daemon_core2.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
3518
+ const staleDirectWorkSummary = (0, import_daemon_core3.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
3671
3519
  note: activeWorkEvidence.staleDirectWorkNote,
3672
3520
  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
3521
  });
@@ -3742,55 +3590,304 @@ async function meshQueueCancel(ctx, args) {
3742
3590
  try {
3743
3591
  const taskId = (args.task_id || args.taskId || "").trim();
3744
3592
  if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
3745
- const task = (0, import_daemon_core2.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
3593
+ const task = (0, import_daemon_core3.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
3746
3594
  if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
3747
3595
  ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
3748
3596
  });
3749
- return JSON.stringify({ success: true, task }, null, 2);
3597
+ return JSON.stringify({ success: true, task }, null, 2);
3598
+ } catch (e) {
3599
+ return JSON.stringify({ success: false, error: e.message });
3600
+ }
3601
+ }
3602
+ async function meshQueueRequeue(ctx, args) {
3603
+ try {
3604
+ const taskId = (args.task_id || args.taskId || "").trim();
3605
+ if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
3606
+ const targetNodeId = (args.target_node_id || args.targetNodeId || "").trim() || void 0;
3607
+ const targetSessionId = (args.target_session_id || args.targetSessionId || "").trim() || void 0;
3608
+ const keepTargetSession = args.keep_target_session === true || args.keepTargetSession === true;
3609
+ const task = (0, import_daemon_core3.requeueTask)(ctx.mesh.id, taskId, {
3610
+ reason: args.reason,
3611
+ targetNodeId,
3612
+ targetSessionId,
3613
+ clearTargetNode: args.clear_target_node === true || args.clearTargetNode === true,
3614
+ clearTargetSession: targetSessionId ? false : !keepTargetSession,
3615
+ force: args.force === true
3616
+ });
3617
+ if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
3618
+ if (task.status === "failed" && task.cancelReason?.startsWith("max_retries_exceeded")) {
3619
+ return JSON.stringify({
3620
+ success: false,
3621
+ code: "max_retries_exceeded",
3622
+ error: task.cancelReason,
3623
+ task,
3624
+ hint: "Use force=true to bypass the retry cap for explicit operator recovery."
3625
+ }, null, 2);
3626
+ }
3627
+ const triggerPreferredNodeId = targetNodeId || task.targetNodeId || void 0;
3628
+ ctx.transport.command("trigger_mesh_queue", {
3629
+ meshId: ctx.mesh.id,
3630
+ ...triggerPreferredNodeId ? { preferredNodeId: triggerPreferredNodeId } : {}
3631
+ }).catch(() => {
3632
+ });
3633
+ return JSON.stringify({ success: true, task }, null, 2);
3634
+ } catch (e) {
3635
+ return JSON.stringify({ success: false, error: e.message });
3636
+ }
3637
+ }
3638
+
3639
+ // src/tools/mesh-tools-mission.ts
3640
+ async function meshTaskHistory(ctx, args) {
3641
+ const { mesh } = ctx;
3642
+ const compact = args.verbose === true ? false : args.compact ?? true;
3643
+ const pendingEvents = await drainCoordinatorPendingEvents(ctx);
3644
+ const requestedTail = typeof args.tail === "number" && args.tail > 0 ? Math.floor(args.tail) : 20;
3645
+ const compactCap = requestedTail > 50 ? 20 : 30;
3646
+ const tail = compact ? Math.min(requestedTail, compactCap) : Math.min(requestedTail, 200);
3647
+ const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
3648
+ const rawEntries = (0, import_daemon_core3.readLedgerEntries)(mesh.id, { tail, kind });
3649
+ const entries = compact ? rawEntries.map((e) => ({
3650
+ ...e,
3651
+ payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
3652
+ })) : rawEntries;
3653
+ const summary = (0, import_daemon_core3.getLedgerSummary)(mesh.id);
3654
+ let taskStats;
3655
+ try {
3656
+ const taskIds = [...new Set(rawEntries.map((e) => typeof e.payload?.taskId === "string" ? e.payload.taskId : "").filter(Boolean))];
3657
+ if (taskIds.length > 0) {
3658
+ const stats = (0, import_daemon_core3.computeMeshTaskStats)(mesh.id, { taskIds });
3659
+ if (stats.length > 0) taskStats = stats;
3660
+ }
3661
+ } catch {
3662
+ }
3663
+ return JSON.stringify({
3664
+ meshId: mesh.id,
3665
+ payloadMode: compact ? "compact" : "full",
3666
+ entries,
3667
+ summary,
3668
+ ...taskStats ? { taskStats } : {},
3669
+ ...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
3670
+ }, null, 2);
3671
+ }
3672
+ async function meshRecordNote(ctx, args) {
3673
+ const { mesh } = ctx;
3674
+ const text = typeof args.text === "string" ? args.text.trim() : "";
3675
+ if (!text) {
3676
+ return JSON.stringify({ success: false, error: "text required" }, null, 2);
3677
+ }
3678
+ const category = args.category === "provider_quirk" || args.category === "pattern_to_avoid" || args.category === "recovery_lesson" ? args.category : void 0;
3679
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
3680
+ const sourceCoordinator = ctx.coordinatorSessionId || ctx.localDaemonId || ctx.coordinatorHostname || void 0;
3681
+ const entry = (0, import_daemon_core3.appendLedgerEntry)(mesh.id, {
3682
+ kind: "coordinator_operating_note",
3683
+ ...sourceCoordinator ? { sessionId: sourceCoordinator } : {},
3684
+ payload: {
3685
+ text,
3686
+ ...category ? { category } : {},
3687
+ createdAt,
3688
+ ...sourceCoordinator ? { sourceCoordinator } : {}
3689
+ }
3690
+ });
3691
+ return JSON.stringify({
3692
+ success: true,
3693
+ meshId: mesh.id,
3694
+ noteId: entry.id,
3695
+ recorded: { text, category: category ?? null, createdAt },
3696
+ note: 'Recorded to the mesh ledger. Future coordinators on this mesh will see it under "## Operating Notes" at launch.'
3697
+ }, null, 2);
3698
+ }
3699
+ async function meshReconcileLedger(ctx, args) {
3700
+ await refreshMeshFromDaemon(ctx);
3701
+ const requestedNodeIds = Array.isArray(args.node_ids) ? new Set(args.node_ids.map((id) => typeof id === "string" ? id.trim() : "").filter(Boolean)) : null;
3702
+ const nodes = ctx.mesh.nodes.filter((node) => !requestedNodeIds || requestedNodeIds.has(node.id));
3703
+ const replicas = [];
3704
+ const shouldImport = args.import_entries !== false;
3705
+ const queryArgs = {
3706
+ meshId: ctx.mesh.id,
3707
+ ...typeof args.limit === "number" ? { limit: args.limit } : {},
3708
+ ...typeof args.after_id === "string" && args.after_id.trim() ? { afterId: args.after_id.trim() } : {},
3709
+ ...typeof args.since === "string" && args.since.trim() ? { since: args.since.trim() } : {}
3710
+ };
3711
+ for (const node of nodes) {
3712
+ try {
3713
+ if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
3714
+ const slice2 = (0, import_daemon_core3.readLedgerSliceFromStore)(ctx.mesh.id, queryArgs);
3715
+ replicas.push((0, import_daemon_core3.buildMeshLedgerReplicaEvidence)({
3716
+ nodeId: node.id,
3717
+ daemonId: node.daemonId,
3718
+ transport: "local",
3719
+ slice: slice2,
3720
+ status: "local"
3721
+ }));
3722
+ continue;
3723
+ }
3724
+ const result = await commandForNode(ctx, node, "get_mesh_ledger_slice", queryArgs);
3725
+ const payload = unwrapCommandPayload(result);
3726
+ if (payload?.success === false) {
3727
+ throw new Error(payload.error || "remote get_mesh_ledger_slice failed");
3728
+ }
3729
+ const slice = payload?.slice ?? payload;
3730
+ if (slice?.protocol !== "adhdev.mesh.ledger.slice.v1" || !Array.isArray(slice.entries)) {
3731
+ throw new Error("remote daemon returned an invalid ledger slice payload");
3732
+ }
3733
+ const importResult = shouldImport ? (0, import_daemon_core3.appendRemoteLedgerEntries)(ctx.mesh.id, slice.entries) : { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
3734
+ replicas.push((0, import_daemon_core3.buildMeshLedgerReplicaEvidence)({
3735
+ nodeId: node.id,
3736
+ daemonId: node.daemonId,
3737
+ transport: "p2p_datachannel",
3738
+ slice,
3739
+ importResult
3740
+ }));
3741
+ if (shouldImport && importResult.accepted > 0) {
3742
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
3743
+ kind: "ledger_replicated",
3744
+ nodeId: node.id,
3745
+ payload: {
3746
+ protocol: "adhdev.mesh.ledger.slice.v1",
3747
+ imported: importResult.accepted,
3748
+ skippedDuplicate: importResult.skippedDuplicate,
3749
+ rejectedInvalid: importResult.rejectedInvalid,
3750
+ nextAfterId: slice.cursor?.nextAfterId ?? null,
3751
+ via: "p2p_datachannel"
3752
+ }
3753
+ });
3754
+ }
3755
+ } catch (e) {
3756
+ replicas.push((0, import_daemon_core3.buildMeshLedgerReplicaEvidence)({
3757
+ nodeId: node.id,
3758
+ daemonId: node.daemonId,
3759
+ transport: node.daemonId ? "p2p_datachannel" : "local",
3760
+ status: "failed",
3761
+ error: e?.message ?? String(e)
3762
+ }));
3763
+ }
3764
+ }
3765
+ const evidence = (0, import_daemon_core3.buildMeshLedgerReconciliationEvidence)(ctx.mesh.id, replicas);
3766
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
3767
+ kind: "ledger_reconciled",
3768
+ payload: {
3769
+ protocol: evidence.protocol,
3770
+ sourceOfTruth: evidence.sourceOfTruth,
3771
+ totals: evidence.totals,
3772
+ convergence: evidence.convergence
3773
+ }
3774
+ });
3775
+ return JSON.stringify({ success: true, evidence }, null, 2);
3776
+ }
3777
+ async function meshMissionUpsert(ctx, args) {
3778
+ try {
3779
+ const mission = (0, import_daemon_core3.upsertMeshMission)(ctx.mesh.id, {
3780
+ id: readString(args.mission_id) || readString(args.missionId) || void 0,
3781
+ title: args.title,
3782
+ goal: typeof args.goal === "string" ? args.goal : void 0,
3783
+ status: readString(args.status) || void 0
3784
+ });
3785
+ return JSON.stringify({
3786
+ success: true,
3787
+ mission,
3788
+ nextAction: "Attach tasks with mesh_enqueue_task mission_id and depends_on. mesh_status shows live task aggregates for this mission."
3789
+ });
3750
3790
  } catch (e) {
3751
- return JSON.stringify({ success: false, error: e.message });
3791
+ const message = e?.message || String(e);
3792
+ const code = message.includes("mission_title_required") ? "mission_title_required" : message.includes("invalid_mission_status") ? "invalid_mission_status" : void 0;
3793
+ return JSON.stringify({ success: false, ...code ? { code } : {}, error: message });
3752
3794
  }
3753
3795
  }
3754
- async function meshQueueRequeue(ctx, args) {
3796
+ async function meshMissionList(ctx, args = {}) {
3755
3797
  try {
3756
- const taskId = (args.task_id || args.taskId || "").trim();
3757
- if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
3758
- const targetNodeId = (args.target_node_id || args.targetNodeId || "").trim() || void 0;
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")) {
3798
+ const rawStatuses = Array.isArray(args.status) ? args.status : typeof args.status === "string" && args.status.trim() ? [args.status] : [];
3799
+ const invalid = rawStatuses.filter((s) => !import_daemon_core3.MESH_MISSION_STATUSES.includes(s));
3800
+ if (invalid.length > 0) {
3771
3801
  return JSON.stringify({
3772
3802
  success: false,
3773
- code: "max_retries_exceeded",
3774
- error: task.cancelReason,
3775
- task,
3776
- hint: "Use force=true to bypass the retry cap for explicit operator recovery."
3777
- }, null, 2);
3803
+ code: "invalid_mission_status",
3804
+ error: `invalid status filter: ${invalid.join(", ")} (valid: ${import_daemon_core3.MESH_MISSION_STATUSES.join(", ")})`
3805
+ });
3778
3806
  }
3779
- const triggerPreferredNodeId = targetNodeId || task.targetNodeId || void 0;
3780
- ctx.transport.command("trigger_mesh_queue", {
3781
- meshId: ctx.mesh.id,
3782
- ...triggerPreferredNodeId ? { preferredNodeId: triggerPreferredNodeId } : {}
3783
- }).catch(() => {
3807
+ const statuses = rawStatuses.length > 0 ? rawStatuses : void 0;
3808
+ const missions = (0, import_daemon_core3.listMeshMissionSummaries)(ctx.mesh.id, {
3809
+ statuses,
3810
+ verbose: args.verbose === true
3811
+ }).map((mission) => {
3812
+ try {
3813
+ return { ...mission, stats: (0, import_daemon_core3.computeMeshMissionStats)(ctx.mesh.id, mission.id) };
3814
+ } catch {
3815
+ return mission;
3816
+ }
3784
3817
  });
3785
- return JSON.stringify({ success: true, task }, null, 2);
3818
+ return JSON.stringify({
3819
+ success: true,
3820
+ count: missions.length,
3821
+ ...statuses ? { statusFilter: statuses } : {},
3822
+ missions
3823
+ }, null, 2);
3786
3824
  } catch (e) {
3787
- return JSON.stringify({ success: false, error: e.message });
3825
+ return JSON.stringify({ success: false, error: e?.message || String(e) });
3788
3826
  }
3789
3827
  }
3828
+ async function meshReviewInbox(ctx, args = {}) {
3829
+ await refreshMeshFromDaemon(ctx);
3830
+ const meshId = (args.mesh_id ?? ctx.mesh.id).trim();
3831
+ const result = await commandForNode(ctx, ctx.mesh.nodes[0], "get_mesh_review_inbox", {
3832
+ meshId,
3833
+ inlineMesh: ctx.mesh
3834
+ });
3835
+ return JSON.stringify(result, null, 2);
3836
+ }
3837
+
3838
+ // src/tools/mesh-tools-session.ts
3839
+ async function meshPruneStaleDirect(ctx, args = {}) {
3840
+ await refreshMeshFromDaemon(ctx);
3841
+ const execute = args.execute === true && args.dry_run !== true;
3842
+ const includeTerminal = args.include_terminal === true;
3843
+ const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
3844
+ const ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(ctx.mesh.id, { tail: 500 });
3845
+ const directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(ctx.mesh.id);
3846
+ const result = (0, import_daemon_core3.pruneStaleDirectDispatches)({
3847
+ meshId: ctx.mesh.id,
3848
+ queue: (0, import_daemon_core3.getQueue)(ctx.mesh.id),
3849
+ ledgerEntries,
3850
+ directDispatches,
3851
+ nodes: liveNodes,
3852
+ execute,
3853
+ includeTerminal,
3854
+ source: "mesh_prune_stale_direct"
3855
+ });
3856
+ const { prunable, prunedCount, preservedUnacknowledged, preservedLedgerOnly, preservedNotOrphan } = result;
3857
+ const summarize = (records) => records.map((r) => ({
3858
+ taskId: r.taskId,
3859
+ nodeId: r.nodeId,
3860
+ sessionId: r.sessionId,
3861
+ status: r.status,
3862
+ terminal: r.terminal === true,
3863
+ staleReason: r.staleReason,
3864
+ taskTitle: r.taskTitle,
3865
+ createdAt: r.createdAt
3866
+ }));
3867
+ return JSON.stringify({
3868
+ success: true,
3869
+ mode: result.mode,
3870
+ meshId: ctx.mesh.id,
3871
+ includeTerminal,
3872
+ candidateCount: result.candidateCount,
3873
+ prunableCount: prunable.length,
3874
+ prunedCount,
3875
+ prunable: summarize(prunable),
3876
+ preserved: {
3877
+ unacknowledgedCount: preservedUnacknowledged.length,
3878
+ ledgerOnlyCount: preservedLedgerOnly.length,
3879
+ notOrphanCount: preservedNotOrphan.length,
3880
+ unacknowledged: summarize(preservedUnacknowledged),
3881
+ ledgerOnly: summarize(preservedLedgerOnly),
3882
+ notOrphan: summarize(preservedNotOrphan)
3883
+ },
3884
+ 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."
3885
+ }, null, 2);
3886
+ }
3790
3887
  async function meshSendTask(ctx, args) {
3791
3888
  const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
3792
3889
  const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
3793
- const modeValidation = (0, import_daemon_core2.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
3890
+ const modeValidation = (0, import_daemon_core3.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
3794
3891
  if (!modeValidation.valid) {
3795
3892
  return JSON.stringify({
3796
3893
  success: false,
@@ -3902,7 +3999,7 @@ async function meshSendTask(ctx, args) {
3902
3999
  const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
3903
4000
  try {
3904
4001
  const providerType = result2.providerType || cached?.providerType;
3905
- (0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
4002
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
3906
4003
  kind: "task_dispatched",
3907
4004
  nodeId: args.node_id,
3908
4005
  sessionId: dispatchedSessionId,
@@ -3914,7 +4011,7 @@ async function meshSendTask(ctx, args) {
3914
4011
  targetSessionId: dispatchedSessionId
3915
4012
  })
3916
4013
  });
3917
- (0, import_daemon_core2.insertDirectDispatch)(ctx.mesh.id, {
4014
+ (0, import_daemon_core3.insertDirectDispatch)(ctx.mesh.id, {
3918
4015
  taskId,
3919
4016
  nodeId: args.node_id,
3920
4017
  sessionId: dispatchedSessionId,
@@ -3925,7 +4022,7 @@ async function meshSendTask(ctx, args) {
3925
4022
  dispatchedAt
3926
4023
  });
3927
4024
  if (missionId) {
3928
- (0, import_daemon_core2.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
4025
+ (0, import_daemon_core3.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
3929
4026
  id: taskId,
3930
4027
  missionId,
3931
4028
  assignedNodeId: args.node_id,
@@ -4084,7 +4181,7 @@ async function meshSendTask(ctx, args) {
4084
4181
  });
4085
4182
  }
4086
4183
  try {
4087
- (0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
4184
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
4088
4185
  kind: "task_dispatched",
4089
4186
  nodeId: args.node_id,
4090
4187
  sessionId: args.session_id,
@@ -4099,7 +4196,7 @@ async function meshSendTask(ctx, args) {
4099
4196
  });
4100
4197
  } catch {
4101
4198
  }
4102
- (0, import_daemon_core2.insertDirectDispatch)(ctx.mesh.id, {
4199
+ (0, import_daemon_core3.insertDirectDispatch)(ctx.mesh.id, {
4103
4200
  taskId,
4104
4201
  nodeId: args.node_id,
4105
4202
  sessionId: args.session_id,
@@ -4112,7 +4209,7 @@ async function meshSendTask(ctx, args) {
4112
4209
  });
4113
4210
  if (missionId) {
4114
4211
  try {
4115
- (0, import_daemon_core2.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
4212
+ (0, import_daemon_core3.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
4116
4213
  id: taskId,
4117
4214
  missionId,
4118
4215
  assignedNodeId: args.node_id,
@@ -4157,14 +4254,14 @@ async function meshSendTask(ctx, args) {
4157
4254
  } : {}
4158
4255
  });
4159
4256
  }
4160
- const task = (0, import_daemon_core2.enqueueTask)(ctx.mesh.id, args.message, {
4257
+ const task = (0, import_daemon_core3.enqueueTask)(ctx.mesh.id, args.message, {
4161
4258
  targetNodeId: args.node_id,
4162
4259
  targetSessionId: args.session_id,
4163
4260
  taskMode,
4164
4261
  ...missionId ? { missionId } : {}
4165
4262
  });
4166
4263
  const queueTrigger = await triggerMeshQueueAndReport(ctx);
4167
- const pendingEvents = (0, import_daemon_core2.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId);
4264
+ const pendingEvents = (0, import_daemon_core3.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId);
4168
4265
  const result = {
4169
4266
  success: true,
4170
4267
  source: "queue",
@@ -4189,81 +4286,6 @@ async function meshSendTask(ctx, args) {
4189
4286
  return JSON.stringify(failure);
4190
4287
  }
4191
4288
  }
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
4289
  async function meshReadChat(ctx, args) {
4268
4290
  const node = await findOptionalNodeWithRefresh(ctx, args.node_id);
4269
4291
  if (!node) {
@@ -4284,7 +4306,7 @@ async function meshReadChat(ctx, args) {
4284
4306
  tailLimit: args.tail ?? 10
4285
4307
  });
4286
4308
  } catch (e) {
4287
- if (isLocalNode || !(0, import_daemon_core2.isP2pRelayTransportFailure)(e)) throw e;
4309
+ if (isLocalNode || !(0, import_daemon_core3.isP2pRelayTransportFailure)(e)) throw e;
4288
4310
  return buildMeshReadChatCacheFallback(ctx, args, node, e);
4289
4311
  }
4290
4312
  const payload = annotateRapidReadChatAdvisory(unwrapCommandPayload(result), {
@@ -4352,7 +4374,7 @@ async function meshLaunchSession(ctx, args) {
4352
4374
  const coordinatorNode = resolveCoordinatorNode(ctx);
4353
4375
  const coordinatorDaemonId = resolveCoordinatorDaemonId(ctx);
4354
4376
  const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
4355
- const delegatedWorkerAutoApprove = (0, import_daemon_core2.resolveDelegatedWorkerAutoApprove)(ctx.mesh.policy, node.policy);
4377
+ const delegatedWorkerAutoApprove = (0, import_daemon_core3.resolveDelegatedWorkerAutoApprove)(ctx.mesh.policy, node.policy);
4356
4378
  const isLocalNode = isLocalControlPlaneNode(ctx, node);
4357
4379
  if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
4358
4380
  return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
@@ -4399,7 +4421,7 @@ async function meshLaunchSession(ctx, args) {
4399
4421
  });
4400
4422
  }
4401
4423
  try {
4402
- (0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
4424
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
4403
4425
  kind: "session_launched",
4404
4426
  nodeId: args.node_id,
4405
4427
  sessionId: runtimeSessionId || void 0,
@@ -4418,6 +4440,34 @@ async function meshLaunchSession(ctx, args) {
4418
4440
  }, null, 2);
4419
4441
  }
4420
4442
  }
4443
+ async function meshApprove(ctx, args) {
4444
+ const node = await findNodeWithRefresh(ctx, args.node_id);
4445
+ const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
4446
+ const providerSessionId = cached?.providerSessionId;
4447
+ const result = await commandForNode(ctx, node, "resolve_action", {
4448
+ sessionId: args.session_id,
4449
+ targetSessionId: args.session_id,
4450
+ workspace: node.workspace,
4451
+ ...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
4452
+ ...providerSessionId ? { providerSessionId } : {},
4453
+ action: args.action === "reject" ? "reject" : "approve"
4454
+ });
4455
+ return JSON.stringify(result, null, 2);
4456
+ }
4457
+ async function meshCleanupSessions(ctx, args) {
4458
+ const node = await findNodeWithRefresh(ctx, args.node_id);
4459
+ const result = await commandForNode(ctx, node, "cleanup_mesh_sessions", {
4460
+ meshId: ctx.mesh.id,
4461
+ nodeId: args.node_id,
4462
+ mode: args.mode,
4463
+ sessionIds: args.session_ids,
4464
+ dryRun: args.dry_run === true,
4465
+ inlineMesh: ctx.mesh
4466
+ });
4467
+ return JSON.stringify(result, null, 2);
4468
+ }
4469
+
4470
+ // src/tools/mesh-tools-git.ts
4421
4471
  async function meshGitStatus(ctx, args) {
4422
4472
  const node = await findNodeWithRefresh(ctx, args.node_id);
4423
4473
  const autoDiscoverSubmodules = node.policy?.autoDiscoverSubmodules !== false;
@@ -4552,7 +4602,7 @@ async function meshCheckpoint(ctx, args) {
4552
4602
  includeUntracked: true
4553
4603
  });
4554
4604
  try {
4555
- (0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
4605
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
4556
4606
  kind: "checkpoint_created",
4557
4607
  nodeId: args.node_id,
4558
4608
  payload: {
@@ -4567,20 +4617,6 @@ async function meshCheckpoint(ctx, args) {
4567
4617
  }
4568
4618
  return JSON.stringify(result, null, 2);
4569
4619
  }
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
4620
  async function meshCloneNode(ctx, args) {
4585
4621
  const sourceNode = await findNodeWithRefresh(ctx, args.source_node_id);
4586
4622
  const result = await commandForNode(ctx, sourceNode, "clone_mesh_node", {
@@ -4600,18 +4636,6 @@ async function meshCloneNode(ctx, args) {
4600
4636
  }
4601
4637
  return JSON.stringify(result, null, 2);
4602
4638
  }
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
4639
  async function meshRemoveNode(ctx, args) {
4616
4640
  const node = await findNodeWithRefresh(ctx, args.node_id);
4617
4641
  const removeArgs = buildRemoveNodeArgs(ctx, args.node_id, args.session_cleanup_mode, args.force === true);
@@ -4645,12 +4669,8 @@ async function meshRemoveNode(ctx, args) {
4645
4669
  }
4646
4670
  return JSON.stringify({ ...result || {}, ...transportFallback ? { transportFallback } : {} }, null, 2);
4647
4671
  }
4648
- function resolveRefineConfigNode(ctx, nodeId) {
4649
- if (nodeId) return findNode(ctx.mesh, nodeId);
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
- }
4672
+
4673
+ // src/tools/mesh-tools-refine.ts
4654
4674
  async function meshRefineConfigSchema(ctx) {
4655
4675
  const node = resolveRefineConfigNode(ctx);
4656
4676
  const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
@@ -4752,15 +4772,6 @@ async function meshRefineBatch(ctx, args = {}) {
4752
4772
  }
4753
4773
  return JSON.stringify(result, null, 2);
4754
4774
  }
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
4775
 
4765
4776
  // src/help.ts
4766
4777
  var STANDARD_TOOLS = [