@adhdev/daemon-standalone 0.9.82-rc.376 → 0.9.82-rc.378

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 = {
@@ -1231,7 +801,8 @@ var MESH_LAUNCH_SESSION_TOOL = {
1231
801
  type: "object",
1232
802
  properties: {
1233
803
  node_id: { type: "string", description: "Target node ID." },
1234
- type: { type: "string", description: "Optional provider type to launch. Use hermes-cli for Hermes, claude-cli for Claude Code, codex-cli for Codex, gemini-cli for Gemini. When omitted, node.policy.providerPriority is probed in order." }
804
+ type: { type: "string", description: "Optional provider type to launch. Use hermes-cli for Hermes, claude-cli for Claude Code, codex-cli for Codex, gemini-cli for Gemini. When omitted, node.policy.providerPriority is probed in order." },
805
+ force: { type: "boolean", description: "Set true to launch an ADDITIONAL session even when this node already has a live mesh-owned worker session. Default false: if a live worker session for this mesh+node already exists (e.g. an enqueue auto-launch just spawned one), the existing session is returned idempotently instead of creating an empty duplicate. Only pass force when you intentionally want a second concurrent provider/session on the node." }
1235
806
  },
1236
807
  required: ["node_id"]
1237
808
  }
@@ -1345,269 +916,698 @@ var MESH_APPROVE_TOOL = {
1345
916
  required: ["node_id", "session_id", "action"]
1346
917
  }
1347
918
  };
1348
- var MESH_CLONE_NODE_TOOL = {
1349
- name: "mesh_clone_node",
1350
- description: "Create a new worktree-based node from an existing node for isolated parallel work. Creates a git worktree on a new branch so multiple tasks can run on separate branches simultaneously.",
919
+ var MESH_CLONE_NODE_TOOL = {
920
+ name: "mesh_clone_node",
921
+ description: "Create a new worktree-based node from an existing node for isolated parallel work. Creates a git worktree on a new branch so multiple tasks can run on separate branches simultaneously.",
922
+ inputSchema: {
923
+ type: "object",
924
+ properties: {
925
+ source_node_id: { type: "string", description: "Node ID to clone from (from mesh_list_nodes)." },
926
+ branch: { type: "string", description: 'Branch name for the new worktree (e.g. "feat/auth-refactor").' },
927
+ base_branch: { type: "string", description: "Starting point for the branch (default: current HEAD)." }
928
+ },
929
+ required: ["source_node_id", "branch"]
930
+ }
931
+ };
932
+ var MESH_REMOVE_NODE_TOOL = {
933
+ name: "mesh_remove_node",
934
+ description: "Remove a node from the mesh. If the node is a worktree, also cleans up the git worktree and directory. Session cleanup is controlled by mesh policy sessionCleanupOnNodeRemove unless session_cleanup_mode overrides it for this call. The coordinator's own local base node (same machine, NOT a worktree) is protected \u2014 removing it breaks live mesh membership and is rejected unless force:true is passed.",
935
+ inputSchema: {
936
+ type: "object",
937
+ properties: {
938
+ node_id: { type: "string", description: "Node ID to remove." },
939
+ session_cleanup_mode: {
940
+ type: "string",
941
+ enum: ["preserve", "stop", "delete_stopped", "stop_and_delete"],
942
+ description: "Optional override for cleanup of delegated sessions attached to this node. preserve keeps history/processes; stop stops live runtimes only; delete_stopped removes completed transcripts only; stop_and_delete stops live runtimes and deletes records."
943
+ },
944
+ force: { type: "boolean", description: "Override the coordinator-base-node guard. Only set true to intentionally tear down this mesh; the coordinator must then be re-registered/restarted. Worktree nodes never need force." }
945
+ },
946
+ required: ["node_id"]
947
+ }
948
+ };
949
+ var MESH_CLEANUP_SESSIONS_TOOL = {
950
+ name: "mesh_cleanup_sessions",
951
+ description: "Manually clean up delegated session records for a mesh node without removing the node. Defaults should preserve reviewable history unless the caller chooses a mode explicitly.",
952
+ inputSchema: {
953
+ type: "object",
954
+ properties: {
955
+ node_id: { type: "string", description: "Node ID whose delegated sessions should be considered for cleanup." },
956
+ mode: {
957
+ type: "string",
958
+ enum: ["preserve", "stop", "delete_stopped", "stop_and_delete"],
959
+ description: "preserve = no-op; stop = release process occupancy by stopping live runtimes; delete_stopped = remove completed/stopped records while leaving live runtimes alone; stop_and_delete = stop live runtimes and delete records."
960
+ },
961
+ session_ids: {
962
+ type: "array",
963
+ items: { type: "string" },
964
+ description: "Optional explicit session IDs to limit cleanup to. When omitted, sessions are matched by node/workspace metadata."
965
+ },
966
+ dry_run: { type: "boolean", description: "Preview matched/stopped/deleted/skipped session IDs without mutating session-host state." }
967
+ },
968
+ required: ["node_id", "mode"]
969
+ }
970
+ };
971
+ var MESH_TASK_HISTORY_TOOL = {
972
+ name: "mesh_task_history",
973
+ description: "Read the task ledger for this mesh \u2014 dispatched tasks, completions, failures, checkpoints, and node lifecycle events. Use to understand what has been done before deciding next steps, to detect repeated failures, and to inform recovery decisions.",
974
+ inputSchema: {
975
+ type: "object",
976
+ properties: {
977
+ tail: { type: "number", description: "Number of recent entries to return (default: 20; clamped to 40 in compact mode, 200 in verbose)." },
978
+ kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed, direct_fast_forward." },
979
+ compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Truncates long payload strings (message/taskSummary \u2264200, finalSummary \u2264300) and elides any large nested evidence blob (>2KB serialized \u2014 e.g. validationSummary/result/patchEquivalence/submoduleReachability) to a {_elided,_kind,_bytes,_hint} placeholder; full evidence stays accessible via mesh_reconcile_ledger. Set false (or verbose=true) for full untruncated payloads." },
980
+ verbose: { type: "boolean", description: "Force the full untruncated payload; overrides compact." }
981
+ }
982
+ }
983
+ };
984
+ var MESH_RECORD_NOTE_TOOL = {
985
+ name: "mesh_record_note",
986
+ description: "Record a durable operating note for this mesh \u2014 a runtime-accumulated lesson that future coordinators inherit. Unlike Claude-only memory/CLAUDE.md, this is provider-neutral: it persists in the mesh ledger and is injected into every coordinator's system prompt at launch (codex, hermes, antigravity, claude alike). Use it when you learn something durable: a provider quirk, a pattern to avoid, or a recovery lesson. Keep each note to one concrete, reusable fact. Not for transient task status \u2014 use missions/checkpoints for that.",
987
+ inputSchema: {
988
+ type: "object",
989
+ properties: {
990
+ text: { type: "string", description: "The note \u2014 one concrete, reusable operating fact/lesson. Phrase it so a future coordinator can act on it without this conversation's context." },
991
+ category: {
992
+ type: "string",
993
+ enum: ["provider_quirk", "pattern_to_avoid", "recovery_lesson"],
994
+ description: "Optional classification: provider_quirk (a provider/runtime behaves unexpectedly), pattern_to_avoid (an approach that caused problems), recovery_lesson (how a failure was recovered)."
995
+ }
996
+ },
997
+ required: ["text"]
998
+ }
999
+ };
1000
+ var MESH_RECONCILE_LEDGER_TOOL = {
1001
+ name: "mesh_reconcile_ledger",
1002
+ description: "Reconcile daemon-local mesh ledgers by querying bounded ledger slices over P2P/DataChannel and importing missing entries into the coordinator local JSONL ledger. Cloud/D1 is not used as a ledger source of truth.",
1003
+ inputSchema: {
1004
+ type: "object",
1005
+ properties: {
1006
+ node_ids: { type: "array", items: { type: "string" }, description: "Optional node IDs to query. Defaults to all mesh nodes." },
1007
+ limit: { type: "number", description: "Bounded slice size per node. Defaults to 100 and is clamped by daemon-core." },
1008
+ after_id: { type: "string", description: "Optional cursor entry ID; remote slices return entries strictly after this ID when present." },
1009
+ since: { type: "string", description: "Optional ISO timestamp lower bound for queried entries." },
1010
+ import_entries: { type: "boolean", description: "When false, query and report evidence without importing remote entries. Defaults true." }
1011
+ }
1012
+ }
1013
+ };
1014
+ var MESH_PRUNE_STALE_DIRECT_TOOL = {
1015
+ name: "mesh_prune_stale_direct",
1016
+ description: "Prune orphaned staleDirect dispatch records \u2014 direct task dispatches whose original node/session is no longer present in the live mesh. dry_run (default) reports exactly which records would be pruned without mutating anything; pass execute=true to delete them. Active/pending/assigned/generating work and fresh unacknowledged dispatch failures (node/session still live) are always preserved. The append-only mesh ledger audit history is left intact.",
1017
+ inputSchema: {
1018
+ type: "object",
1019
+ properties: {
1020
+ execute: { type: "boolean", description: "When true, actually delete the orphaned records. Defaults false (dry run). Ignored when dry_run=true." },
1021
+ dry_run: { type: "boolean", description: "Force a preview without mutation even if execute=true. Defaults to dry-run behavior when execute is not set." },
1022
+ include_terminal: { type: "boolean", description: "Also prune terminal (completed/failed) direct dispatch store rows in addition to orphans. Defaults false." }
1023
+ }
1024
+ }
1025
+ };
1026
+ var MESH_REFINE_NODE_TOOL = {
1027
+ name: "mesh_refine_node",
1028
+ description: "The Refinery: validate \u2192 merge \u2192 push \u2192 clean up a completed worktree node onto the base branch. Defaults to dry-run (plan only): returns the validation plan with mergeWillRun:false/cleanupWillRun:false and performs NO merge/push/cleanup. Pass execute=true to actually converge the node. execute=true is async: the immediate response includes async:true, status:'accepted', jobId, interactionId, target node, and startedAt; completion/failure evidence is delivered through pending mesh events and the mesh task ledger. dry_run=true overrides execute. Matches the mesh_refine_batch / mesh_fast_forward_node dry_run/execute contract.",
1029
+ inputSchema: {
1030
+ type: "object",
1031
+ properties: {
1032
+ node_id: { type: "string", description: "Node ID of the completed worktree node to refine and merge." },
1033
+ execute: { type: "boolean", description: "When true, run validation/merge/push/cleanup for this node. Defaults false/dry-run." },
1034
+ dry_run: { type: "boolean", description: "Preview the validation plan without merging. Defaults true unless execute=true; dry_run=true overrides execute." }
1035
+ },
1036
+ required: ["node_id"]
1037
+ }
1038
+ };
1039
+ var MESH_REFINE_BATCH_TOOL = {
1040
+ name: "mesh_refine_batch",
1041
+ description: "Batch Refinery: converge multiple sibling worktree nodes onto the base branch in one conflict-aware sequential pipeline. Orders nodes by change-area (non-submodule nodes first, submodule-touching nodes serialized last) so each merged sibling advances the base and the next node auto-rebases + re-checks patch-equivalence before its own merge. Each node runs the same validation/patch-equivalence/submodule-reachability/merge/cleanup gates as mesh_refine_node. Conflicting or blocked nodes are isolated as blocked_review while the rest of the batch proceeds. Defaults to dry-run (plan only); set execute=true to converge. Never force-pushes or resets. execute=true is async: the immediate response is async:true / status:'accepted' with the batch jobId and ordered target node list; per-node convergence runs in the background and the aggregate completion/failure (with per-node merged / blocked_review / not_mergeable results) is delivered as a terminal refine event via pending mesh events and the ledger \u2014 do not re-invoke while a batch is in flight. dry_run returns the plan synchronously.",
1042
+ inputSchema: {
1043
+ type: "object",
1044
+ properties: {
1045
+ node_ids: {
1046
+ type: "array",
1047
+ items: { type: "string" },
1048
+ description: "Optional explicit node IDs to converge, in any order (the tool computes the safe merge order). When omitted, all local worktree nodes that need convergence are auto-collected."
1049
+ },
1050
+ execute: { type: "boolean", description: "When true, run validation/rebase/merge for each node in order. Defaults false/dry-run." },
1051
+ dry_run: { type: "boolean", description: "Preview the ordering + per-node validation plan without executing. Defaults true unless execute=true; dry_run=true overrides execute." }
1052
+ },
1053
+ required: []
1054
+ }
1055
+ };
1056
+ var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
1057
+ name: "mesh_refine_config_schema",
1058
+ description: "Return the Repo Mesh Refinery config JSON schema and supported repo-local config locations. This is the validation source of truth; heuristic command detection is suggestions-only.",
1059
+ inputSchema: { type: "object", properties: {} }
1060
+ };
1061
+ var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
1062
+ name: "mesh_validate_refine_config",
1063
+ description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
1064
+ inputSchema: {
1065
+ type: "object",
1066
+ properties: {
1067
+ node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
1068
+ config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
1069
+ }
1070
+ }
1071
+ };
1072
+ var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
1073
+ name: "mesh_suggest_refine_config",
1074
+ description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
1075
+ inputSchema: {
1076
+ type: "object",
1077
+ properties: {
1078
+ node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
1079
+ }
1080
+ }
1081
+ };
1082
+ var MESH_CHANGE_IMPACT_CONFIG_SCHEMA_TOOL = {
1083
+ name: "mesh_change_impact_config_schema",
1084
+ description: "Return the Change Impact config JSON schema and supported repo-local config locations. Change Impact config declaratively classifies which package/file changes between the live daemon build and workspace HEAD require a daemon rebuild/restart vs. a web-only redeploy vs. nothing. Declarative only \u2014 config is parsed, never executed.",
1085
+ inputSchema: { type: "object", properties: {} }
1086
+ };
1087
+ var MESH_VALIDATE_CHANGE_IMPACT_CONFIG_TOOL = {
1088
+ name: "mesh_validate_change_impact_config",
1089
+ description: "Validate a Change Impact config for a node/workspace and report valid/errors. Loads .adhdev/change-impact.{json,yaml,yml} (or repo-mesh-change-impact.* alias) from the repo unless an inline config is provided.",
1090
+ inputSchema: {
1091
+ type: "object",
1092
+ properties: {
1093
+ node_id: { type: "string", description: "Optional node/workspace whose change-impact config should be loaded. Defaults to the first mesh node." },
1094
+ config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
1095
+ }
1096
+ }
1097
+ };
1098
+ var MESH_SUGGEST_CHANGE_IMPACT_CONFIG_TOOL = {
1099
+ name: "mesh_suggest_change_impact_config",
1100
+ description: "Suggest a Change Impact config scaffold from the repo package layout (web-* \u2192 web-only, others \u2192 daemon-runtime, plus docs/license markers as non-runtime). Heuristic scaffold only \u2014 the draft must be reviewed and saved before it takes effect; nothing is executed.",
1101
+ inputSchema: {
1102
+ type: "object",
1103
+ properties: {
1104
+ node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
1105
+ }
1106
+ }
1107
+ };
1108
+ var MESH_INIT_TOOL = {
1109
+ name: "mesh_init",
1110
+ description: "One-click mesh onboarding for an existing git project. Detects installed CLI providers, suggests Refinery (.adhdev/refine.json) and worktree bootstrap (.adhdev/worktree_bootstrap.json) configs, optionally writes them to disk, and recommends a node providerPriority from the detected providers. Suggestions are scaffold only and never execute until saved; providerPriority is a recommendation to apply to node policy, not auto-applied. Defaults to dry-run (no files written) and never overwrites an existing config unless overwrite=true.",
1351
1111
  inputSchema: {
1352
1112
  type: "object",
1353
1113
  properties: {
1354
- 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"]
1114
+ node_id: { type: "string", description: "Optional node/workspace to onboard. Defaults to the first mesh node with a workspace." },
1115
+ write: { type: "boolean", description: "When true, persist the suggested configs to disk. Defaults false (dry-run preview only)." },
1116
+ overwrite: { type: "boolean", description: "When true, overwrite an existing config file. Defaults false (never clobber an existing refine/bootstrap config)." }
1117
+ }
1359
1118
  }
1360
1119
  };
1361
- var 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.",
1120
+ var MESH_REFINE_PLAN_TOOL = {
1121
+ name: "mesh_refine_plan",
1122
+ description: "Dry-run Refinery plan for a worktree node: reports config source, validation commands, suggestions/unavailable reason, and merge/cleanup intent without executing validation or git merge.",
1364
1123
  inputSchema: {
1365
1124
  type: "object",
1366
1125
  properties: {
1367
- node_id: { type: "string", description: "Node ID to 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." }
1126
+ node_id: { type: "string", description: "Node ID of the worktree node to plan." }
1374
1127
  },
1375
1128
  required: ["node_id"]
1376
1129
  }
1377
1130
  };
1378
- var 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.",
1131
+ var MESH_REVIEW_INBOX_TOOL = {
1132
+ name: "mesh_review_inbox",
1133
+ description: "List local worktree nodes that need human review: merge candidates (pushed feature branches ready to merge) and Refinery-blocked review results. Returns evidence summaries, diff stats vs. the default branch, and suggested actions (Refine / Requeue / Dismiss). Remote nodes are excluded in M4.0.",
1381
1134
  inputSchema: {
1382
1135
  type: "object",
1383
1136
  properties: {
1384
- 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." }
1137
+ mesh_id: { type: "string", description: "Mesh ID (optional \u2014 inferred from active mesh if omitted)." }
1396
1138
  },
1397
- required: ["node_id", "mode"]
1139
+ required: []
1140
+ }
1141
+ };
1142
+ var ALL_MESH_TOOLS = [
1143
+ MESH_STATUS_TOOL,
1144
+ MESH_LIST_NODES_TOOL,
1145
+ MESH_ENQUEUE_TASK_TOOL,
1146
+ MESH_VIEW_QUEUE_TOOL,
1147
+ MESH_QUEUE_CANCEL_TOOL,
1148
+ MESH_QUEUE_REQUEUE_TOOL,
1149
+ MESH_SEND_TASK_TOOL,
1150
+ MESH_READ_CHAT_TOOL,
1151
+ MESH_READ_DEBUG_TOOL,
1152
+ MESH_LAUNCH_SESSION_TOOL,
1153
+ MESH_GIT_STATUS_TOOL,
1154
+ MESH_READ_NODE_LOGS_TOOL,
1155
+ MESH_FAST_FORWARD_NODE_TOOL,
1156
+ MESH_RESTART_DAEMON_TOOL,
1157
+ MESH_CHECKPOINT_TOOL,
1158
+ MESH_APPROVE_TOOL,
1159
+ MESH_CLONE_NODE_TOOL,
1160
+ MESH_REMOVE_NODE_TOOL,
1161
+ MESH_REFINE_NODE_TOOL,
1162
+ MESH_REFINE_BATCH_TOOL,
1163
+ MESH_REFINE_CONFIG_SCHEMA_TOOL,
1164
+ MESH_VALIDATE_REFINE_CONFIG_TOOL,
1165
+ MESH_SUGGEST_REFINE_CONFIG_TOOL,
1166
+ MESH_CHANGE_IMPACT_CONFIG_SCHEMA_TOOL,
1167
+ MESH_VALIDATE_CHANGE_IMPACT_CONFIG_TOOL,
1168
+ MESH_SUGGEST_CHANGE_IMPACT_CONFIG_TOOL,
1169
+ MESH_INIT_TOOL,
1170
+ MESH_REFINE_PLAN_TOOL,
1171
+ MESH_CLEANUP_SESSIONS_TOOL,
1172
+ MESH_PRUNE_STALE_DIRECT_TOOL,
1173
+ MESH_TASK_HISTORY_TOOL,
1174
+ MESH_RECORD_NOTE_TOOL,
1175
+ MESH_RECONCILE_LEDGER_TOOL,
1176
+ MESH_MISSION_UPSERT_TOOL,
1177
+ MESH_MISSION_LIST_TOOL,
1178
+ MESH_REVIEW_INBOX_TOOL
1179
+ ];
1180
+
1181
+ // src/tools/mesh-compact.ts
1182
+ function buildCompactGitSnapshot(status) {
1183
+ if (!status || typeof status !== "object" || Array.isArray(status)) return void 0;
1184
+ const slim = {};
1185
+ const carry = [
1186
+ "isGitRepo",
1187
+ "branch",
1188
+ "headCommit",
1189
+ "upstream",
1190
+ "upstreamStatus",
1191
+ "ahead",
1192
+ "behind",
1193
+ "dirty",
1194
+ "detached",
1195
+ "submodules"
1196
+ ];
1197
+ for (const key of carry) {
1198
+ if (status[key] !== void 0) slim[key] = status[key];
1199
+ }
1200
+ return slim;
1201
+ }
1202
+ function summarizeCompactSubmodules(submodules) {
1203
+ if (!Array.isArray(submodules) || submodules.length === 0) return void 0;
1204
+ const outOfSync = submodules.filter((s) => s?.outOfSync).map((s) => s?.path).filter(Boolean);
1205
+ return {
1206
+ count: submodules.length,
1207
+ ...outOfSync.length > 0 ? { outOfSyncPaths: outOfSync } : {}
1208
+ };
1209
+ }
1210
+ var MESH_COMPACT_PRESERVED_MARKER_FIELDS = ["dataFreshness"];
1211
+ function compactMeshStatusNode(entry) {
1212
+ if (!entry || typeof entry !== "object") return entry;
1213
+ const next = { ...entry };
1214
+ if (next.git !== void 0) {
1215
+ const slimGit = buildCompactGitSnapshot(next.git);
1216
+ if (slimGit) {
1217
+ if (slimGit.submodules !== void 0) {
1218
+ const subSummary = summarizeCompactSubmodules(slimGit.submodules);
1219
+ if (subSummary) slimGit.submodules = subSummary;
1220
+ else delete slimGit.submodules;
1221
+ }
1222
+ next.git = slimGit;
1223
+ }
1224
+ }
1225
+ if (next.machine && typeof next.machine === "object") {
1226
+ const m = next.machine;
1227
+ next.machine = {
1228
+ daemonId: m.daemonId,
1229
+ machineId: m.machineId,
1230
+ hostname: m.hostname,
1231
+ displayName: m.displayName,
1232
+ sameMachine: m.sameMachine,
1233
+ locality: m.locality
1234
+ };
1235
+ }
1236
+ if (typeof next.submoduleWarning === "string") {
1237
+ next.submodulesOutOfSync = true;
1238
+ delete next.submoduleWarning;
1239
+ }
1240
+ if (next.staleDaemonBuild && typeof next.staleDaemonBuild === "object") {
1241
+ const b = next.staleDaemonBuild;
1242
+ next.staleDaemonBuild = {
1243
+ scope: b.scope,
1244
+ isDaemonAffecting: b.isDaemonAffecting !== false,
1245
+ seeStaleDaemonBuilds: true
1246
+ };
1247
+ }
1248
+ delete next.capabilityTagsByProvider;
1249
+ const elideSkip = /* @__PURE__ */ new Set(["git", "machine", "branchConvergence", "staleDaemonBuild", "sessions", ...MESH_COMPACT_PRESERVED_MARKER_FIELDS]);
1250
+ for (const k of Object.keys(next)) {
1251
+ if (elideSkip.has(k)) continue;
1252
+ next[k] = elideLargeNestedValue(k, next[k]);
1253
+ }
1254
+ return next;
1255
+ }
1256
+ function compactNodeSeverity(entry) {
1257
+ if (!entry || typeof entry !== "object") return 0;
1258
+ if (entry.error || entry.health && entry.health !== "online" && entry.health !== "dirty") return 5;
1259
+ if (entry.launchReady === false) return 4;
1260
+ if (entry.isDirty === true || entry.health === "dirty") return 3;
1261
+ if (entry.branchConvergence?.needsConvergence === true) return 2;
1262
+ if (entry.staleDaemonBuild || entry.submodulesOutOfSync || entry.recoveryHints) return 1;
1263
+ return 0;
1264
+ }
1265
+ function isNoteworthyCompactNode(entry) {
1266
+ if (!entry || typeof entry !== "object") return true;
1267
+ if (entry.health && entry.health !== "online") return true;
1268
+ if (entry.isDirty === true) return true;
1269
+ if (entry.error) return true;
1270
+ if (entry.launchReady === false) return true;
1271
+ if (entry.staleDaemonBuild) return true;
1272
+ if (entry.submoduleWarning || entry.submodulesOutOfSync) return true;
1273
+ if (entry.recoveryHints) return true;
1274
+ if (Array.isArray(entry.nextStepHints) && entry.nextStepHints.length > 0) return true;
1275
+ if (entry.branchConvergence?.needsConvergence === true) return true;
1276
+ const sessionCount = Array.isArray(entry.sessions) ? entry.sessions.length : entry.sessionSummary?.total ?? 0;
1277
+ if (sessionCount > 0) return true;
1278
+ return false;
1279
+ }
1280
+ function minimalCompactNode(entry) {
1281
+ if (!entry || typeof entry !== "object") return entry;
1282
+ const bc = entry.branchConvergence && typeof entry.branchConvergence === "object" ? {
1283
+ status: entry.branchConvergence.status,
1284
+ needsConvergence: entry.branchConvergence.needsConvergence,
1285
+ reason: entry.branchConvergence.reason,
1286
+ branch: entry.branchConvergence.branch
1287
+ } : void 0;
1288
+ const preservedMarkers = {};
1289
+ for (const field of MESH_COMPACT_PRESERVED_MARKER_FIELDS) {
1290
+ if (entry[field] !== void 0) preservedMarkers[field] = entry[field];
1291
+ }
1292
+ return {
1293
+ nodeId: entry.nodeId,
1294
+ workspace: entry.workspace,
1295
+ daemonId: entry.daemonId,
1296
+ health: entry.health,
1297
+ branch: entry.branch,
1298
+ launchReady: entry.launchReady,
1299
+ ...entry.providerPriority !== void 0 ? { providerPriority: entry.providerPriority } : {},
1300
+ // Keep the routable tag set on quiet/folded nodes — a coordinator planning
1301
+ // required_tags routing needs it even for nodes with nothing to converge.
1302
+ ...entry.capabilityTags !== void 0 ? { capabilityTags: entry.capabilityTags } : {},
1303
+ ...entry.launchBlockedReason !== void 0 ? { launchBlockedReason: entry.launchBlockedReason } : {},
1304
+ ...bc ? { branchConvergence: bc } : {},
1305
+ ...entry.sessionSummary ? { sessionSummary: entry.sessionSummary } : {},
1306
+ ...preservedMarkers,
1307
+ folded: true
1308
+ };
1309
+ }
1310
+ function summarizeNodeSessions(sessions) {
1311
+ const list = Array.isArray(sessions) ? sessions : [];
1312
+ const byStatus = {};
1313
+ const providerCounts = {};
1314
+ const selfCoordinatorSessionIds = [];
1315
+ for (const s of list) {
1316
+ const status = typeof s?.status === "string" && s.status ? s.status : "unknown";
1317
+ byStatus[status] = (byStatus[status] ?? 0) + 1;
1318
+ const provider = typeof s?.providerType === "string" && s.providerType ? s.providerType : "unknown";
1319
+ providerCounts[provider] = (providerCounts[provider] ?? 0) + 1;
1320
+ if (s?.isSelfCoordinator === true && s.id) selfCoordinatorSessionIds.push(String(s.id));
1321
+ }
1322
+ const summary = {
1323
+ total: list.length,
1324
+ byStatus,
1325
+ providerCounts
1326
+ };
1327
+ if (selfCoordinatorSessionIds.length > 0) {
1328
+ summary.selfCoordinatorSessionIds = selfCoordinatorSessionIds;
1329
+ }
1330
+ return summary;
1331
+ }
1332
+
1333
+ // src/tools/mesh-queue-helpers.ts
1334
+ var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
1335
+ var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
1336
+ var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
1337
+ var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
1338
+ function buildQueueLivenessIndex(mesh) {
1339
+ const nodeIds = /* @__PURE__ */ new Set();
1340
+ const nodeSessionIds = /* @__PURE__ */ new Map();
1341
+ for (const node of Array.isArray(mesh?.nodes) ? mesh.nodes : []) {
1342
+ const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
1343
+ if (!nodeId) continue;
1344
+ nodeIds.add(nodeId);
1345
+ const sessions = collectNodeSessionIds(node);
1346
+ if (sessions.size > 0) nodeSessionIds.set(nodeId, sessions);
1398
1347
  }
1399
- };
1400
- 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
- }
1348
+ return { nodeIds, nodeSessionIds };
1349
+ }
1350
+ function queueAssignmentStaleReason(task, liveness) {
1351
+ if (task?.status !== "assigned") return void 0;
1352
+ const nodeId = readString(task.assignedNodeId) || readString(task.nodeId) || readString(task.node_id) || readString(task.targetNodeId);
1353
+ const sessionId = readString(task.assignedSessionId) || readString(task.sessionId) || readString(task.session_id) || readString(task.targetSessionId);
1354
+ if (nodeId && liveness.nodeIds.size > 0 && !liveness.nodeIds.has(nodeId)) {
1355
+ return "assigned node is not present in the current mesh snapshot";
1411
1356
  }
1412
- };
1413
- 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"]
1357
+ if (nodeId && sessionId && liveness.nodeSessionIds.has(nodeId) && !liveness.nodeSessionIds.get(nodeId).has(sessionId)) {
1358
+ return "assigned session is not live on the assigned node";
1427
1359
  }
1428
- };
1429
- 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
- }
1360
+ const updatedAt = new Date(task.updatedAt).getTime();
1361
+ const ageMs = Number.isFinite(updatedAt) ? Date.now() - updatedAt : null;
1362
+ if (!nodeId && ageMs !== null && ageMs >= STALE_ASSIGNED_QUEUE_MS) {
1363
+ return "assigned task has no assigned node metadata";
1441
1364
  }
1442
- };
1443
- 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." }
1365
+ return void 0;
1366
+ }
1367
+ function buildQueueStatusSummary(queue) {
1368
+ const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
1369
+ let staleAssigned = 0;
1370
+ for (const task of queue) {
1371
+ const status = typeof task?.status === "string" ? task.status : void 0;
1372
+ if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
1373
+ counts[status] += 1;
1452
1374
  }
1375
+ if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
1453
1376
  }
1454
- };
1455
- 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." }
1377
+ const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
1378
+ return {
1379
+ totalCount: queue.length,
1380
+ activeCount: counts.pending + liveAssigned,
1381
+ historicalCount: counts.completed + counts.failed + counts.cancelled,
1382
+ counts,
1383
+ activeCounts: {
1384
+ pending: counts.pending,
1385
+ assigned: liveAssigned
1464
1386
  },
1465
- 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." }
1387
+ staleAssignedCount: staleAssigned,
1388
+ rawActiveCounts: {
1389
+ pending: counts.pending,
1390
+ assigned: counts.assigned
1481
1391
  },
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." }
1392
+ historicalCounts: {
1393
+ completed: counts.completed,
1394
+ failed: counts.failed,
1395
+ cancelled: counts.cancelled
1498
1396
  }
1397
+ };
1398
+ }
1399
+ function normalizeQueueViewMode(value) {
1400
+ return value === "active" || value === "historical" || value === "all" ? value : "all";
1401
+ }
1402
+ function sanitizeQueueStatusFilter(value) {
1403
+ if (!Array.isArray(value)) return void 0;
1404
+ const statuses = value.map((item) => typeof item === "string" ? item.trim() : "").filter((status) => ACTIVE_QUEUE_STATUSES.has(status) || HISTORICAL_QUEUE_STATUSES.has(status));
1405
+ return statuses.length ? Array.from(new Set(statuses)) : void 0;
1406
+ }
1407
+ function filterQueueForView(queue, view, statuses) {
1408
+ if (statuses?.length) {
1409
+ const allowed = new Set(statuses);
1410
+ return queue.filter((task) => allowed.has(String(task?.status || "")));
1499
1411
  }
1500
- };
1501
- 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
- }
1412
+ if (view === "active") return queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")));
1413
+ if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
1414
+ return queue;
1415
+ }
1416
+ function prioritizeActiveQueueRows(queue) {
1417
+ const active = [];
1418
+ const historical = [];
1419
+ const other = [];
1420
+ for (const task of queue) {
1421
+ const status = String(task?.status || "");
1422
+ if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
1423
+ else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
1424
+ else other.push(task);
1425
+ }
1426
+ return [...active, ...other, ...historical];
1427
+ }
1428
+ function slimQueueTask(task) {
1429
+ return {
1430
+ id: task?.id,
1431
+ status: task?.status,
1432
+ assignedNodeId: task?.assignedNodeId,
1433
+ assignedSessionId: task?.assignedSessionId,
1434
+ targetNodeId: task?.targetNodeId,
1435
+ targetSessionId: task?.targetSessionId,
1436
+ updatedAt: task?.updatedAt,
1437
+ staleAssigned: task?.staleAssigned === true,
1438
+ staleReason: task?.staleReason
1439
+ };
1440
+ }
1441
+ function buildQueueMaintenanceReport(queue) {
1442
+ const now = Date.now();
1443
+ const staleAssignedTasks = queue.filter((task) => task?.status === "assigned" && task?.staleAssigned === true).map(slimQueueTask);
1444
+ const historicalTasks = queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
1445
+ const oldHistoricalTasks = historicalTasks.filter((task) => {
1446
+ const updatedAt = new Date(task?.updatedAt).getTime();
1447
+ return Number.isFinite(updatedAt) && now - updatedAt >= OLD_HISTORICAL_QUEUE_RECORD_MS;
1448
+ }).map((task) => ({
1449
+ ...slimQueueTask(task),
1450
+ cleanupClass: "old_historical_record",
1451
+ reason: "terminal queue record is older than the read-only maintenance threshold"
1452
+ }));
1453
+ const cleanupCandidates = [
1454
+ ...staleAssignedTasks.map((task) => ({
1455
+ ...task,
1456
+ cleanupClass: "stale_assigned",
1457
+ reason: typeof task.staleReason === "string" ? task.staleReason : "active assigned task does not match current live mesh node/session state",
1458
+ suggestedOperation: "operator_review_then_requeue_or_cancel"
1459
+ })),
1460
+ ...oldHistoricalTasks.map((task) => ({
1461
+ ...task,
1462
+ suggestedOperation: "operator_review_then_archive_or_keep"
1463
+ }))
1464
+ ];
1465
+ return {
1466
+ readOnly: true,
1467
+ mutationPerformed: false,
1468
+ sourceOfTruth: "mesh_work_queue_file",
1469
+ staleAssignedDefinition: "Only active assigned queue rows are stale candidates, and only when the assigned node/session is absent from the current live mesh snapshot.",
1470
+ historicalDefinition: "completed/failed/cancelled rows are historical ledger records and never active assignments.",
1471
+ staleAssignedTasks,
1472
+ staleAssignedCount: staleAssignedTasks.length,
1473
+ historicalRecordCount: historicalTasks.length,
1474
+ oldHistoricalRecordCount: oldHistoricalTasks.length,
1475
+ cleanupCandidates,
1476
+ cleanupCandidateCount: cleanupCandidates.length
1477
+ };
1478
+ }
1479
+ function buildCompactQueueMaintenanceReport(maintenance) {
1480
+ const staleAssignedTasks = Array.isArray(maintenance.staleAssignedTasks) ? maintenance.staleAssignedTasks : [];
1481
+ const cleanupCandidateCount = maintenance.cleanupCandidateCount ?? 0;
1482
+ return {
1483
+ readOnly: true,
1484
+ mutationPerformed: false,
1485
+ sourceOfTruth: "mesh_work_queue_file",
1486
+ payloadMode: "compact",
1487
+ staleAssignedDefinition: maintenance.staleAssignedDefinition,
1488
+ historicalDefinition: maintenance.historicalDefinition,
1489
+ // staleAssignedTasks are active assigned rows (not historical) — retain a
1490
+ // bounded sample so coordinators can still see drift without the full array.
1491
+ staleAssignedTasks: staleAssignedTasks.slice(0, 5),
1492
+ staleAssignedSampleLimit: 5,
1493
+ staleAssignedCount: maintenance.staleAssignedCount ?? staleAssignedTasks.length,
1494
+ historicalRecordCount: maintenance.historicalRecordCount ?? 0,
1495
+ oldHistoricalRecordCount: maintenance.oldHistoricalRecordCount ?? 0,
1496
+ cleanupCandidateCount,
1497
+ cleanupCandidatesOmitted: true,
1498
+ cleanupCandidatesHint: "Per-row cleanup candidates are omitted in compact mode; call mesh_view_queue with verbose=true for the full maintenance/cleanupDryRun rows."
1499
+ };
1500
+ }
1501
+ var COMPACT_MAX_ACTIVE_QUEUE_ROWS = 15;
1502
+ var COMPACT_QUEUE_MESSAGE_CAP = 140;
1503
+ var COMPACT_MAX_ACTIVE_WORK_ROWS = 12;
1504
+ var COMPACT_ACTIVE_WORK_TITLE_CAP = 80;
1505
+ function truncateForCompact(value, cap) {
1506
+ if (typeof value !== "string") return value;
1507
+ return value.length > cap ? value.slice(0, cap) + "\u2026" : value;
1508
+ }
1509
+ function compactQueueRow(task) {
1510
+ if (!task || typeof task !== "object") return task;
1511
+ const slim = {};
1512
+ for (const [k, v] of Object.entries(task)) {
1513
+ if (k === "message") slim[k] = truncateForCompact(v, COMPACT_QUEUE_MESSAGE_CAP);
1514
+ else slim[k] = elideLargeNestedValue(k, v);
1509
1515
  }
1510
- };
1511
- 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
- }
1516
+ return slim;
1517
+ }
1518
+ function compactQueueRows(rows) {
1519
+ const capped = rows.slice(0, COMPACT_MAX_ACTIVE_QUEUE_ROWS).map(compactQueueRow);
1520
+ return { rows: capped, omitted: Math.max(0, rows.length - capped.length) };
1521
+ }
1522
+ function compactActiveWorkRecord(record) {
1523
+ if (!record || typeof record !== "object") return record;
1524
+ const slim = {};
1525
+ for (const [k, v] of Object.entries(record)) {
1526
+ if (k === "message" || k === "taskSummary") continue;
1527
+ else if (k === "taskTitle") slim[k] = truncateForCompact(v, COMPACT_ACTIVE_WORK_TITLE_CAP);
1528
+ else slim[k] = elideLargeNestedValue(k, v);
1525
1529
  }
1526
- };
1527
- 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
- }
1530
+ return slim;
1531
+ }
1532
+ function compactActiveWorkRecords(records) {
1533
+ if (!Array.isArray(records)) return { records, omitted: 0 };
1534
+ const capped = records.slice(0, COMPACT_MAX_ACTIVE_WORK_ROWS).map(compactActiveWorkRecord);
1535
+ return { records: capped, omitted: Math.max(0, records.length - capped.length) };
1536
+ }
1537
+ function annotateQueueStaleness(queue, mesh) {
1538
+ const liveness = buildQueueLivenessIndex(mesh);
1539
+ const now = Date.now();
1540
+ return queue.map((task) => {
1541
+ const taskStatus = typeof task?.status === "string" ? task.status : void 0;
1542
+ const annotated = {
1543
+ ...task,
1544
+ taskStatus,
1545
+ isActive: taskStatus ? ACTIVE_QUEUE_STATUSES.has(taskStatus) : false,
1546
+ isHistorical: taskStatus ? HISTORICAL_QUEUE_STATUSES.has(taskStatus) : false,
1547
+ dispatchedAt: task?.createdAt,
1548
+ ...taskStatus === "assigned" ? { activeTaskId: task.id } : {},
1549
+ ...taskStatus === "completed" || taskStatus === "failed" ? {
1550
+ completedAt: task.updatedAt
1551
+ } : {}
1552
+ };
1553
+ if (taskStatus !== "assigned") return annotated;
1554
+ const updatedAt = new Date(task.updatedAt).getTime();
1555
+ const ageMs = Number.isFinite(updatedAt) ? now - updatedAt : null;
1556
+ const staleReason = queueAssignmentStaleReason(task, liveness);
1557
+ if (!staleReason) return annotated;
1558
+ return {
1559
+ ...annotated,
1560
+ stale: true,
1561
+ staleAssigned: true,
1562
+ staleReason,
1563
+ ...ageMs !== null ? { assignedAgeMs: ageMs } : {}
1564
+ };
1565
+ });
1566
+ }
1567
+
1568
+ // src/tools/read-chat-polling-advisory.ts
1569
+ var RAPID_READ_CHAT_ADVISORY_WINDOW_MS = 5e3;
1570
+ var ACTIVE_READ_STATUSES = /* @__PURE__ */ new Set([
1571
+ "generating",
1572
+ "running",
1573
+ "streaming",
1574
+ "starting",
1575
+ "busy"
1576
+ ]);
1577
+ var recentReads = /* @__PURE__ */ new Map();
1578
+ function isActiveReadChatStatus(status) {
1579
+ return typeof status === "string" && ACTIVE_READ_STATUSES.has(status.toLowerCase());
1580
+ }
1581
+ function annotateRapidReadChatAdvisory(payload, options) {
1582
+ const now = options.now ?? Date.now();
1583
+ const status = options.status ?? payload?.status ?? payload?.data?.status ?? payload?.result?.status;
1584
+ const active = isActiveReadChatStatus(status);
1585
+ const previous = recentReads.get(options.key);
1586
+ if (!active) {
1587
+ recentReads.set(options.key, { at: now, status: typeof status === "string" ? status : void 0 });
1588
+ return payload;
1535
1589
  }
1536
- };
1537
- 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)." }
1590
+ recentReads.set(options.key, { at: now, status: typeof status === "string" ? status : void 0 });
1591
+ if (!previous || !isActiveReadChatStatus(previous.status)) return payload;
1592
+ const elapsedMs = now - previous.at;
1593
+ if (elapsedMs < 0 || elapsedMs >= RAPID_READ_CHAT_ADVISORY_WINDOW_MS) return payload;
1594
+ return {
1595
+ ...payload,
1596
+ pollingAdvisory: {
1597
+ type: "rapid_read_chat_polling",
1598
+ toolName: options.toolName,
1599
+ windowMs: RAPID_READ_CHAT_ADVISORY_WINDOW_MS,
1600
+ elapsedMs,
1601
+ nextSuggestedReadAt: previous.at + RAPID_READ_CHAT_ADVISORY_WINDOW_MS,
1602
+ completionCallbackExpected: Boolean(options.completionCallbackExpected),
1603
+ message: `This session is still ${String(status)}. Avoid repeated ${options.toolName} polling for the same generating session; wait for the completion callback/status event or retry after the suggested time if you are debugging a real stall.`
1546
1604
  }
1547
- }
1548
- };
1549
- var MESH_REFINE_PLAN_TOOL = {
1550
- name: "mesh_refine_plan",
1551
- description: "Dry-run Refinery plan for a worktree node: reports config source, validation commands, suggestions/unavailable reason, and merge/cleanup intent without executing validation or git merge.",
1552
- inputSchema: {
1553
- type: "object",
1554
- properties: {
1555
- node_id: { type: "string", description: "Node ID of the worktree node to plan." }
1556
- },
1557
- required: ["node_id"]
1558
- }
1559
- };
1560
- var MESH_REVIEW_INBOX_TOOL = {
1561
- name: "mesh_review_inbox",
1562
- description: "List local worktree nodes that need human review: merge candidates (pushed feature branches ready to merge) and Refinery-blocked review results. Returns evidence summaries, diff stats vs. the default branch, and suggested actions (Refine / Requeue / Dismiss). Remote nodes are excluded in M4.0.",
1563
- inputSchema: {
1564
- type: "object",
1565
- properties: {
1566
- mesh_id: { type: "string", description: "Mesh ID (optional \u2014 inferred from active mesh if omitted)." }
1567
- },
1568
- required: []
1569
- }
1570
- };
1571
- var ALL_MESH_TOOLS = [
1572
- MESH_STATUS_TOOL,
1573
- MESH_LIST_NODES_TOOL,
1574
- MESH_ENQUEUE_TASK_TOOL,
1575
- MESH_VIEW_QUEUE_TOOL,
1576
- MESH_QUEUE_CANCEL_TOOL,
1577
- MESH_QUEUE_REQUEUE_TOOL,
1578
- MESH_SEND_TASK_TOOL,
1579
- MESH_READ_CHAT_TOOL,
1580
- MESH_READ_DEBUG_TOOL,
1581
- MESH_LAUNCH_SESSION_TOOL,
1582
- MESH_GIT_STATUS_TOOL,
1583
- MESH_READ_NODE_LOGS_TOOL,
1584
- MESH_FAST_FORWARD_NODE_TOOL,
1585
- MESH_RESTART_DAEMON_TOOL,
1586
- MESH_CHECKPOINT_TOOL,
1587
- MESH_APPROVE_TOOL,
1588
- MESH_CLONE_NODE_TOOL,
1589
- MESH_REMOVE_NODE_TOOL,
1590
- MESH_REFINE_NODE_TOOL,
1591
- MESH_REFINE_BATCH_TOOL,
1592
- MESH_REFINE_CONFIG_SCHEMA_TOOL,
1593
- MESH_VALIDATE_REFINE_CONFIG_TOOL,
1594
- MESH_SUGGEST_REFINE_CONFIG_TOOL,
1595
- MESH_CHANGE_IMPACT_CONFIG_SCHEMA_TOOL,
1596
- MESH_VALIDATE_CHANGE_IMPACT_CONFIG_TOOL,
1597
- MESH_SUGGEST_CHANGE_IMPACT_CONFIG_TOOL,
1598
- MESH_INIT_TOOL,
1599
- MESH_REFINE_PLAN_TOOL,
1600
- MESH_CLEANUP_SESSIONS_TOOL,
1601
- MESH_PRUNE_STALE_DIRECT_TOOL,
1602
- MESH_TASK_HISTORY_TOOL,
1603
- MESH_RECORD_NOTE_TOOL,
1604
- MESH_RECONCILE_LEDGER_TOOL,
1605
- MESH_MISSION_UPSERT_TOOL,
1606
- MESH_MISSION_LIST_TOOL,
1607
- MESH_REVIEW_INBOX_TOOL
1608
- ];
1605
+ };
1606
+ }
1609
1607
 
1610
- // src/tools/mesh-tools.ts
1608
+ // src/tools/mesh-tools-internal.ts
1609
+ var import_daemon_core3 = require("@adhdev/daemon-core");
1610
+ var import_node_crypto = require("crypto");
1611
1611
  var SESSION_PROVIDER_METADATA_TTL_MS = 30 * 6e4;
1612
1612
  var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
1613
1613
  function getSessionMetadata(key) {
@@ -2821,32 +2821,115 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
2821
2821
  rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
2822
2822
  if (!injected) surfacedEvents.push(event);
2823
2823
  }
2824
- } catch {
2825
- }
2826
- return surfacedEvents;
2824
+ } catch {
2825
+ }
2826
+ return surfacedEvents;
2827
+ }
2828
+ const events = (0, import_daemon_core2.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
2829
+ events.forEach(rememberMeshSessionProviderMetadataFromEvent);
2830
+ return events;
2831
+ }
2832
+ function isP2pTransportUnavailableError(error) {
2833
+ return (0, import_daemon_core2.isP2pRelayTransportFailure)(error);
2834
+ }
2835
+ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode, force) {
2836
+ return {
2837
+ meshId: ctx.mesh.id,
2838
+ nodeId,
2839
+ ...sessionCleanupMode ? { sessionCleanupMode } : {},
2840
+ ...force === true ? { force: true } : {},
2841
+ inlineMesh: ctx.mesh
2842
+ };
2843
+ }
2844
+ function classifyReadChatTransportCause(error) {
2845
+ const message = (error instanceof Error ? error.message : String(error ?? "")).toLowerCase();
2846
+ if (/not acknowledged|delivery failure|channel never opened|connect timed out|not connected|datachannel|disconnected|\bclosed\b|offline|no route|failed to initiate p2p|p2p mesh is not available|connect queue full/.test(message)) {
2847
+ return "not_connected";
2848
+ }
2849
+ return "saturated";
2850
+ }
2851
+ function resolveCachedMeshSessionPreviewFromLedger(ctx, nodeId, sessionId) {
2852
+ const entries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
2853
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
2854
+ const entry = entries[i];
2855
+ const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
2856
+ const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
2857
+ if (entryNodeId && entryNodeId !== nodeId) continue;
2858
+ const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
2859
+ if (entrySessionId !== sessionId) continue;
2860
+ const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : payload;
2861
+ const preview = (0, import_daemon_core2.resolveMeshSurfacedSessionPreview)(metadataEvent);
2862
+ if (preview) {
2863
+ return { ...preview, ledgerKind: entry.kind, timestamp: entry.timestamp };
2864
+ }
2865
+ }
2866
+ return void 0;
2867
+ }
2868
+ function buildMeshReadChatCacheFallback(ctx, args, node, error) {
2869
+ const classification = (0, import_daemon_core2.classifyP2pRelayFailure)(error, { command: "read_chat", targetDaemonId: node.daemonId });
2870
+ const cause = classifyReadChatTransportCause(error);
2871
+ const errorMessage = error instanceof Error ? error.message : String(error ?? "");
2872
+ const causeNote = cause === "not_connected" ? "the worker daemon is not currently connected over P2P (no live channel)" : "the worker daemon is connected but saturated \u2014 it acknowledged the request but did not return the transcript within the deadline";
2873
+ const cached = resolveCachedMeshSessionPreviewFromLedger(ctx, args.node_id, args.session_id);
2874
+ if (cached) {
2875
+ return JSON.stringify({
2876
+ success: true,
2877
+ source: "coordinator_cache_fallback",
2878
+ fallback: true,
2879
+ nodeId: args.node_id,
2880
+ sessionId: args.session_id,
2881
+ transport: "p2p",
2882
+ transportFailure: {
2883
+ code: classification.code,
2884
+ reason: classification.reason,
2885
+ cause,
2886
+ error: errorMessage
2887
+ },
2888
+ advisory: `Live transcript unavailable (${causeNote}). Showing the cached coordinator-side summary surfaced from the worker's last completion/status event \u2014 a stale point-in-time summary, NOT the live transcript. The full transcript requires a live P2P read_chat once the peer is reachable.`,
2889
+ fullTranscriptRequiresP2p: true,
2890
+ summary: cached.preview,
2891
+ messages: [{
2892
+ role: cached.role,
2893
+ content: cached.preview,
2894
+ cached: true,
2895
+ ...cached.receivedAt ? { receivedAt: cached.receivedAt } : {}
2896
+ }],
2897
+ cachedPreview: {
2898
+ role: cached.role,
2899
+ ledgerKind: cached.ledgerKind,
2900
+ ledgerTimestamp: cached.timestamp,
2901
+ ...cached.receivedAt ? { receivedAt: cached.receivedAt } : {}
2902
+ }
2903
+ }, null, 2);
2827
2904
  }
2828
- const 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);
2905
+ const failure = buildCoordinatorP2pRelayFailure(error, {
2906
+ command: "read_chat",
2907
+ targetDaemonId: node.daemonId,
2908
+ nodeId: args.node_id,
2909
+ sessionId: args.session_id
2910
+ });
2911
+ return JSON.stringify({
2912
+ ...failure,
2913
+ cause,
2914
+ cachedSummaryAvailable: false,
2915
+ fullTranscriptRequiresP2p: true,
2916
+ advisory: `Live transcript unavailable (${causeNote}) and no cached coordinator-side summary exists for this session yet (no completion/status event has been surfaced). The full transcript requires a live P2P read_chat once the peer is reachable.`
2917
+ }, null, 2);
2834
2918
  }
2835
- function 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
- };
2919
+ function resolveRefineConfigNode(ctx, nodeId) {
2920
+ if (nodeId) return findNode(ctx.mesh, nodeId);
2921
+ const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
2922
+ if (!node) throw new Error("No mesh node with a workspace is available");
2923
+ return node;
2843
2924
  }
2925
+
2926
+ // src/tools/mesh-tools-status.ts
2844
2927
  async function meshStatus(ctx, args = {}) {
2845
- const rateResult = (0, import_daemon_core2.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
2928
+ const rateResult = (0, import_daemon_core3.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
2846
2929
  const compact = args.verbose === true ? false : args.compact ?? true;
2847
2930
  await refreshMeshFromDaemon(ctx);
2848
2931
  const { mesh, transport } = ctx;
2849
- let ledgerSummary = (0, import_daemon_core2.getLedgerSummary)(mesh.id);
2932
+ let ledgerSummary = (0, import_daemon_core3.getLedgerSummary)(mesh.id);
2850
2933
  const results = await Promise.all(mesh.nodes.map(async (node) => {
2851
2934
  const entry = {
2852
2935
  nodeId: node.id,
@@ -2902,14 +2985,14 @@ async function meshStatus(ctx, args = {}) {
2902
2985
  noFallbackReason: failure.noFallbackReason
2903
2986
  });
2904
2987
  }
2905
- entry.dataFreshness = (0, import_daemon_core2.buildMeshNodeProbeFreshness)({
2988
+ entry.dataFreshness = (0, import_daemon_core3.buildMeshNodeProbeFreshness)({
2906
2989
  git: entry.git,
2907
2990
  liveTruthProbed,
2908
2991
  isSelfNode: entry.machine?.sameMachine === true,
2909
2992
  daemonId: readNodeDaemonId(node),
2910
2993
  node
2911
2994
  });
2912
- const recoveryContext = (0, import_daemon_core2.getSessionRecoveryContext)(mesh.id, { nodeId: node.id });
2995
+ const recoveryContext = (0, import_daemon_core3.getSessionRecoveryContext)(mesh.id, { nodeId: node.id });
2913
2996
  if (recoveryContext.consecutiveNodeFailures > 0) {
2914
2997
  entry.recoveryHints = {
2915
2998
  consecutiveFailures: recoveryContext.consecutiveNodeFailures,
@@ -2981,23 +3064,23 @@ async function meshStatus(ctx, args = {}) {
2981
3064
  }
2982
3065
  return entry;
2983
3066
  }));
2984
- let ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(mesh.id, { tail: 200 });
2985
- let directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(mesh.id);
3067
+ let ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(mesh.id, { tail: 200 });
3068
+ let directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(mesh.id);
2986
3069
  const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, results, directDispatches, ledgerEntries);
2987
3070
  if (directReconciliation.reconciled > 0) {
2988
- ledgerEntries = (0, 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);
3071
+ ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(mesh.id, { tail: 200 });
3072
+ directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(mesh.id);
3073
+ ledgerSummary = (0, import_daemon_core3.getLedgerSummary)(mesh.id);
2991
3074
  }
2992
- const activeWorkEvidence = (0, import_daemon_core2.buildMeshActiveWork)({
3075
+ const activeWorkEvidence = (0, import_daemon_core3.buildMeshActiveWork)({
2993
3076
  meshId: mesh.id,
2994
- queue: (0, import_daemon_core2.getQueue)(mesh.id),
3077
+ queue: (0, import_daemon_core3.getQueue)(mesh.id),
2995
3078
  ledgerEntries,
2996
3079
  directDispatches,
2997
3080
  nodes: results
2998
3081
  });
2999
3082
  const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
3000
- const staleDirectWorkSummary = (0, import_daemon_core2.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
3083
+ const staleDirectWorkSummary = (0, import_daemon_core3.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
3001
3084
  note: activeWorkEvidence.staleDirectWorkNote,
3002
3085
  detailHint: "Full stale direct entries are omitted from mesh_status by default. Call mesh_status with includeStaleDirectWorkDetails=true or inspect mesh_task_history for ledger detail."
3003
3086
  });
@@ -3185,7 +3268,7 @@ async function meshStatus(ctx, args = {}) {
3185
3268
  }
3186
3269
  try {
3187
3270
  if (compact) {
3188
- const { live, historyFold } = (0, import_daemon_core2.getMeshStatusMissionsCompact)(mesh.id);
3271
+ const { live, historyFold } = (0, import_daemon_core3.getMeshStatusMissionsCompact)(mesh.id);
3189
3272
  const ranked = [...live].sort((a, b) => String(b.tasks?.lastActivityAt ?? "").localeCompare(String(a.tasks?.lastActivityAt ?? "")));
3190
3273
  const kept = [];
3191
3274
  const overflow = [];
@@ -3212,230 +3295,45 @@ async function meshStatus(ctx, args = {}) {
3212
3295
  }
3213
3296
  if (historyFold) response.missionsHistory = historyFold;
3214
3297
  } else {
3215
- const missions = (0, import_daemon_core2.getMeshStatusMissionSummaries)(mesh.id, { verbose: true });
3298
+ const missions = (0, import_daemon_core3.getMeshStatusMissionSummaries)(mesh.id, { verbose: true });
3216
3299
  if (missions.length > 0) {
3217
3300
  response.missions = missions.map((mission) => {
3218
3301
  try {
3219
- return { ...mission, stats: (0, 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);
3302
+ return { ...mission, stats: (0, import_daemon_core3.computeMeshMissionStats)(mesh.id, mission.id) };
3303
+ } catch {
3304
+ return mission;
3305
+ }
3306
+ });
3307
+ }
3308
+ }
3309
+ } catch {
3310
+ }
3311
+ try {
3312
+ const pendingEvents = await drainCoordinatorPendingEvents(ctx);
3313
+ const asyncRefineJobs = (0, import_daemon_core3.buildMeshAsyncRefineJobs)({
3314
+ meshId: mesh.id,
3315
+ ledgerEntries,
3316
+ pendingEvents
3317
+ });
3318
+ if (asyncRefineJobs.length > 0) {
3319
+ if (compact) {
3320
+ const summary = (0, import_daemon_core3.summarizeMeshAsyncRefineJobs)(asyncRefineJobs);
3321
+ if (summary.activeJobs.length > 0) response.asyncRefineJobs = summary.activeJobs;
3322
+ response.asyncRefineJobsSummary = {
3323
+ total: summary.total,
3324
+ byStatus: summary.byStatus,
3325
+ ...summary.staleTerminal > 0 ? { staleTerminal: summary.staleTerminal } : {}
3326
+ };
3327
+ } else {
3328
+ response.asyncRefineJobs = asyncRefineJobs;
3329
+ }
3330
+ }
3331
+ if (pendingEvents.length > 0) {
3332
+ response.pendingCoordinatorEvents = pendingEvents;
3333
+ }
3334
+ } catch {
3335
+ }
3336
+ return JSON.stringify(response, null, 2);
3439
3337
  }
3440
3338
  async function meshListNodes(ctx) {
3441
3339
  await refreshMeshFromDaemon(ctx);
@@ -3459,67 +3357,18 @@ async function meshListNodes(ctx) {
3459
3357
  }))
3460
3358
  }, null, 2);
3461
3359
  }
3462
- 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
- }
3360
+
3361
+ // src/tools/mesh-tools-queue.ts
3513
3362
  async function meshEnqueueTask(ctx, args) {
3514
3363
  const taskMode = readString(args.task_mode) || readString(args.taskMode);
3515
- const requiredTags = (0, import_daemon_core2.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
3364
+ const requiredTags = (0, import_daemon_core3.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
3516
3365
  const dependsOn = Array.isArray(args.dependsOn) ? args.dependsOn : Array.isArray(args.depends_on) ? args.depends_on : void 0;
3517
3366
  const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
3518
3367
  const explicitTarget = readString(args.targetNodeId) || readString(args.target_node_id) || void 0;
3519
3368
  const preferWorktree = args.preferWorktree === true || args.prefer_worktree === true;
3520
3369
  const targetNodeId = explicitTarget || (preferWorktree ? resolvePreferredWorktreeNodeId(ctx) : void 0);
3521
3370
  try {
3522
- const task = (0, import_daemon_core2.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags, dependsOn, missionId, targetNodeId, ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {} });
3371
+ const task = (0, import_daemon_core3.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags, dependsOn, missionId, targetNodeId, ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {} });
3523
3372
  if (!(ctx.transport instanceof IpcTransport)) {
3524
3373
  const queueTrigger = await triggerMeshQueueAndReport(ctx);
3525
3374
  return JSON.stringify({
@@ -3542,14 +3391,14 @@ async function meshEnqueueTask(ctx, args) {
3542
3391
  const isLocalNode = isLocalControlPlaneNode(ctx, node);
3543
3392
  if (isLocalNode || !node.daemonId) continue;
3544
3393
  if (targetNodeId && node.id !== targetNodeId) continue;
3545
- if (!(0, import_daemon_core2.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core2.buildMeshNodeCapabilityTags)(node))) continue;
3394
+ if (!(0, import_daemon_core3.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core3.buildMeshNodeCapabilityTags)(node))) continue;
3546
3395
  dispatchPromises.push(
3547
3396
  ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
3548
3397
  if (result.success) {
3549
3398
  try {
3550
3399
  const providerType = result.providerType;
3551
3400
  const descriptor = summarizeTaskMessage(args.message);
3552
- (0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
3401
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
3553
3402
  kind: "task_dispatched",
3554
3403
  nodeId: node.id,
3555
3404
  sessionId: result.sessionId,
@@ -3571,7 +3420,7 @@ async function meshEnqueueTask(ctx, args) {
3571
3420
  }
3572
3421
  }).catch((err) => {
3573
3422
  try {
3574
- (0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
3423
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
3575
3424
  kind: "p2p_dispatch_failed",
3576
3425
  nodeId: node.id,
3577
3426
  payload: {
@@ -3614,17 +3463,17 @@ async function meshEnqueueTask(ctx, args) {
3614
3463
  }
3615
3464
  }
3616
3465
  async function meshViewQueue(ctx, args) {
3617
- const rateResult = (0, import_daemon_core2.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
3466
+ const rateResult = (0, import_daemon_core3.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
3618
3467
  const compact = args.verbose === true ? false : args.compact ?? true;
3619
3468
  try {
3620
3469
  await refreshMeshFromDaemon(ctx);
3621
3470
  const statusFilter = sanitizeQueueStatusFilter(args.status);
3622
3471
  const view = normalizeQueueViewMode(args.view);
3623
- const rawQueue = (0, import_daemon_core2.getQueue)(ctx.mesh.id);
3472
+ const rawQueue = (0, import_daemon_core3.getQueue)(ctx.mesh.id);
3624
3473
  const statusById = new Map(rawQueue.map((task) => [task.id, task.status]));
3625
3474
  const withDependencies = rawQueue.map((task) => {
3626
3475
  if (!Array.isArray(task.dependsOn) || task.dependsOn.length === 0) return task;
3627
- const depState = (0, import_daemon_core2.describeTaskDependencyState)(task, statusById);
3476
+ const depState = (0, import_daemon_core3.describeTaskDependencyState)(task, statusById);
3628
3477
  return { ...task, ...depState };
3629
3478
  });
3630
3479
  const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness(withDependencies, ctx.mesh));
@@ -3633,16 +3482,16 @@ async function meshViewQueue(ctx, args) {
3633
3482
  const visibleSummary = buildQueueStatusSummary(queue);
3634
3483
  const maintenance = buildQueueMaintenanceReport(fullQueue);
3635
3484
  const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
3636
- let ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
3637
- let directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(ctx.mesh.id);
3485
+ let ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
3486
+ let directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(ctx.mesh.id);
3638
3487
  const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries);
3639
3488
  if (directReconciliation.reconciled > 0) {
3640
- ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
3641
- directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(ctx.mesh.id);
3489
+ ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
3490
+ directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(ctx.mesh.id);
3642
3491
  }
3643
- (0, 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)({
3492
+ (0, import_daemon_core3.markStaleDirectDispatches)(ctx.mesh.id);
3493
+ directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(ctx.mesh.id);
3494
+ const activeWorkEvidence = (0, import_daemon_core3.buildMeshActiveWork)({
3646
3495
  meshId: ctx.mesh.id,
3647
3496
  queue: fullQueue,
3648
3497
  ledgerEntries,
@@ -3667,7 +3516,7 @@ async function meshViewQueue(ctx, args) {
3667
3516
  const wantActiveQueueArray = view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status));
3668
3517
  const wantHistoricalQueueArray = !compact && (view === "historical" || requestedHistoricalRows);
3669
3518
  const activeWorkResult = compact ? compactActiveWorkRecords(activeWorkEvidence.activeWork) : { records: activeWorkEvidence.activeWork, omitted: 0 };
3670
- const staleDirectWorkSummary = (0, import_daemon_core2.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
3519
+ const staleDirectWorkSummary = (0, import_daemon_core3.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
3671
3520
  note: activeWorkEvidence.staleDirectWorkNote,
3672
3521
  detailHint: "Full stale direct entries are omitted from mesh_view_queue in compact mode. Call mesh_view_queue with verbose=true, or inspect mesh_task_history for ledger detail."
3673
3522
  });
@@ -3742,55 +3591,304 @@ async function meshQueueCancel(ctx, args) {
3742
3591
  try {
3743
3592
  const taskId = (args.task_id || args.taskId || "").trim();
3744
3593
  if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
3745
- const task = (0, import_daemon_core2.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
3594
+ const task = (0, import_daemon_core3.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
3746
3595
  if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
3747
3596
  ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
3748
3597
  });
3749
3598
  return JSON.stringify({ success: true, task }, null, 2);
3750
3599
  } catch (e) {
3751
- return JSON.stringify({ success: false, error: e.message });
3600
+ return JSON.stringify({ success: false, error: e.message });
3601
+ }
3602
+ }
3603
+ async function meshQueueRequeue(ctx, args) {
3604
+ try {
3605
+ const taskId = (args.task_id || args.taskId || "").trim();
3606
+ if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
3607
+ const targetNodeId = (args.target_node_id || args.targetNodeId || "").trim() || void 0;
3608
+ const targetSessionId = (args.target_session_id || args.targetSessionId || "").trim() || void 0;
3609
+ const keepTargetSession = args.keep_target_session === true || args.keepTargetSession === true;
3610
+ const task = (0, import_daemon_core3.requeueTask)(ctx.mesh.id, taskId, {
3611
+ reason: args.reason,
3612
+ targetNodeId,
3613
+ targetSessionId,
3614
+ clearTargetNode: args.clear_target_node === true || args.clearTargetNode === true,
3615
+ clearTargetSession: targetSessionId ? false : !keepTargetSession,
3616
+ force: args.force === true
3617
+ });
3618
+ if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
3619
+ if (task.status === "failed" && task.cancelReason?.startsWith("max_retries_exceeded")) {
3620
+ return JSON.stringify({
3621
+ success: false,
3622
+ code: "max_retries_exceeded",
3623
+ error: task.cancelReason,
3624
+ task,
3625
+ hint: "Use force=true to bypass the retry cap for explicit operator recovery."
3626
+ }, null, 2);
3627
+ }
3628
+ const triggerPreferredNodeId = targetNodeId || task.targetNodeId || void 0;
3629
+ ctx.transport.command("trigger_mesh_queue", {
3630
+ meshId: ctx.mesh.id,
3631
+ ...triggerPreferredNodeId ? { preferredNodeId: triggerPreferredNodeId } : {}
3632
+ }).catch(() => {
3633
+ });
3634
+ return JSON.stringify({ success: true, task }, null, 2);
3635
+ } catch (e) {
3636
+ return JSON.stringify({ success: false, error: e.message });
3637
+ }
3638
+ }
3639
+
3640
+ // src/tools/mesh-tools-mission.ts
3641
+ async function meshTaskHistory(ctx, args) {
3642
+ const { mesh } = ctx;
3643
+ const compact = args.verbose === true ? false : args.compact ?? true;
3644
+ const pendingEvents = await drainCoordinatorPendingEvents(ctx);
3645
+ const requestedTail = typeof args.tail === "number" && args.tail > 0 ? Math.floor(args.tail) : 20;
3646
+ const compactCap = requestedTail > 50 ? 20 : 30;
3647
+ const tail = compact ? Math.min(requestedTail, compactCap) : Math.min(requestedTail, 200);
3648
+ const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
3649
+ const rawEntries = (0, import_daemon_core3.readLedgerEntries)(mesh.id, { tail, kind });
3650
+ const entries = compact ? rawEntries.map((e) => ({
3651
+ ...e,
3652
+ payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
3653
+ })) : rawEntries;
3654
+ const summary = (0, import_daemon_core3.getLedgerSummary)(mesh.id);
3655
+ let taskStats;
3656
+ try {
3657
+ const taskIds = [...new Set(rawEntries.map((e) => typeof e.payload?.taskId === "string" ? e.payload.taskId : "").filter(Boolean))];
3658
+ if (taskIds.length > 0) {
3659
+ const stats = (0, import_daemon_core3.computeMeshTaskStats)(mesh.id, { taskIds });
3660
+ if (stats.length > 0) taskStats = stats;
3661
+ }
3662
+ } catch {
3663
+ }
3664
+ return JSON.stringify({
3665
+ meshId: mesh.id,
3666
+ payloadMode: compact ? "compact" : "full",
3667
+ entries,
3668
+ summary,
3669
+ ...taskStats ? { taskStats } : {},
3670
+ ...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
3671
+ }, null, 2);
3672
+ }
3673
+ async function meshRecordNote(ctx, args) {
3674
+ const { mesh } = ctx;
3675
+ const text = typeof args.text === "string" ? args.text.trim() : "";
3676
+ if (!text) {
3677
+ return JSON.stringify({ success: false, error: "text required" }, null, 2);
3678
+ }
3679
+ const category = args.category === "provider_quirk" || args.category === "pattern_to_avoid" || args.category === "recovery_lesson" ? args.category : void 0;
3680
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
3681
+ const sourceCoordinator = ctx.coordinatorSessionId || ctx.localDaemonId || ctx.coordinatorHostname || void 0;
3682
+ const entry = (0, import_daemon_core3.appendLedgerEntry)(mesh.id, {
3683
+ kind: "coordinator_operating_note",
3684
+ ...sourceCoordinator ? { sessionId: sourceCoordinator } : {},
3685
+ payload: {
3686
+ text,
3687
+ ...category ? { category } : {},
3688
+ createdAt,
3689
+ ...sourceCoordinator ? { sourceCoordinator } : {}
3690
+ }
3691
+ });
3692
+ return JSON.stringify({
3693
+ success: true,
3694
+ meshId: mesh.id,
3695
+ noteId: entry.id,
3696
+ recorded: { text, category: category ?? null, createdAt },
3697
+ note: 'Recorded to the mesh ledger. Future coordinators on this mesh will see it under "## Operating Notes" at launch.'
3698
+ }, null, 2);
3699
+ }
3700
+ async function meshReconcileLedger(ctx, args) {
3701
+ await refreshMeshFromDaemon(ctx);
3702
+ const requestedNodeIds = Array.isArray(args.node_ids) ? new Set(args.node_ids.map((id) => typeof id === "string" ? id.trim() : "").filter(Boolean)) : null;
3703
+ const nodes = ctx.mesh.nodes.filter((node) => !requestedNodeIds || requestedNodeIds.has(node.id));
3704
+ const replicas = [];
3705
+ const shouldImport = args.import_entries !== false;
3706
+ const queryArgs = {
3707
+ meshId: ctx.mesh.id,
3708
+ ...typeof args.limit === "number" ? { limit: args.limit } : {},
3709
+ ...typeof args.after_id === "string" && args.after_id.trim() ? { afterId: args.after_id.trim() } : {},
3710
+ ...typeof args.since === "string" && args.since.trim() ? { since: args.since.trim() } : {}
3711
+ };
3712
+ for (const node of nodes) {
3713
+ try {
3714
+ if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
3715
+ const slice2 = (0, import_daemon_core3.readLedgerSliceFromStore)(ctx.mesh.id, queryArgs);
3716
+ replicas.push((0, import_daemon_core3.buildMeshLedgerReplicaEvidence)({
3717
+ nodeId: node.id,
3718
+ daemonId: node.daemonId,
3719
+ transport: "local",
3720
+ slice: slice2,
3721
+ status: "local"
3722
+ }));
3723
+ continue;
3724
+ }
3725
+ const result = await commandForNode(ctx, node, "get_mesh_ledger_slice", queryArgs);
3726
+ const payload = unwrapCommandPayload(result);
3727
+ if (payload?.success === false) {
3728
+ throw new Error(payload.error || "remote get_mesh_ledger_slice failed");
3729
+ }
3730
+ const slice = payload?.slice ?? payload;
3731
+ if (slice?.protocol !== "adhdev.mesh.ledger.slice.v1" || !Array.isArray(slice.entries)) {
3732
+ throw new Error("remote daemon returned an invalid ledger slice payload");
3733
+ }
3734
+ const importResult = shouldImport ? (0, import_daemon_core3.appendRemoteLedgerEntries)(ctx.mesh.id, slice.entries) : { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
3735
+ replicas.push((0, import_daemon_core3.buildMeshLedgerReplicaEvidence)({
3736
+ nodeId: node.id,
3737
+ daemonId: node.daemonId,
3738
+ transport: "p2p_datachannel",
3739
+ slice,
3740
+ importResult
3741
+ }));
3742
+ if (shouldImport && importResult.accepted > 0) {
3743
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
3744
+ kind: "ledger_replicated",
3745
+ nodeId: node.id,
3746
+ payload: {
3747
+ protocol: "adhdev.mesh.ledger.slice.v1",
3748
+ imported: importResult.accepted,
3749
+ skippedDuplicate: importResult.skippedDuplicate,
3750
+ rejectedInvalid: importResult.rejectedInvalid,
3751
+ nextAfterId: slice.cursor?.nextAfterId ?? null,
3752
+ via: "p2p_datachannel"
3753
+ }
3754
+ });
3755
+ }
3756
+ } catch (e) {
3757
+ replicas.push((0, import_daemon_core3.buildMeshLedgerReplicaEvidence)({
3758
+ nodeId: node.id,
3759
+ daemonId: node.daemonId,
3760
+ transport: node.daemonId ? "p2p_datachannel" : "local",
3761
+ status: "failed",
3762
+ error: e?.message ?? String(e)
3763
+ }));
3764
+ }
3765
+ }
3766
+ const evidence = (0, import_daemon_core3.buildMeshLedgerReconciliationEvidence)(ctx.mesh.id, replicas);
3767
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
3768
+ kind: "ledger_reconciled",
3769
+ payload: {
3770
+ protocol: evidence.protocol,
3771
+ sourceOfTruth: evidence.sourceOfTruth,
3772
+ totals: evidence.totals,
3773
+ convergence: evidence.convergence
3774
+ }
3775
+ });
3776
+ return JSON.stringify({ success: true, evidence }, null, 2);
3777
+ }
3778
+ async function meshMissionUpsert(ctx, args) {
3779
+ try {
3780
+ const mission = (0, import_daemon_core3.upsertMeshMission)(ctx.mesh.id, {
3781
+ id: readString(args.mission_id) || readString(args.missionId) || void 0,
3782
+ title: args.title,
3783
+ goal: typeof args.goal === "string" ? args.goal : void 0,
3784
+ status: readString(args.status) || void 0
3785
+ });
3786
+ return JSON.stringify({
3787
+ success: true,
3788
+ mission,
3789
+ nextAction: "Attach tasks with mesh_enqueue_task mission_id and depends_on. mesh_status shows live task aggregates for this mission."
3790
+ });
3791
+ } catch (e) {
3792
+ const message = e?.message || String(e);
3793
+ const code = message.includes("mission_title_required") ? "mission_title_required" : message.includes("invalid_mission_status") ? "invalid_mission_status" : void 0;
3794
+ return JSON.stringify({ success: false, ...code ? { code } : {}, error: message });
3752
3795
  }
3753
3796
  }
3754
- async function meshQueueRequeue(ctx, args) {
3797
+ async function meshMissionList(ctx, args = {}) {
3755
3798
  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")) {
3799
+ const rawStatuses = Array.isArray(args.status) ? args.status : typeof args.status === "string" && args.status.trim() ? [args.status] : [];
3800
+ const invalid = rawStatuses.filter((s) => !import_daemon_core3.MESH_MISSION_STATUSES.includes(s));
3801
+ if (invalid.length > 0) {
3771
3802
  return JSON.stringify({
3772
3803
  success: false,
3773
- code: "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);
3804
+ code: "invalid_mission_status",
3805
+ error: `invalid status filter: ${invalid.join(", ")} (valid: ${import_daemon_core3.MESH_MISSION_STATUSES.join(", ")})`
3806
+ });
3778
3807
  }
3779
- const triggerPreferredNodeId = targetNodeId || task.targetNodeId || void 0;
3780
- ctx.transport.command("trigger_mesh_queue", {
3781
- meshId: ctx.mesh.id,
3782
- ...triggerPreferredNodeId ? { preferredNodeId: triggerPreferredNodeId } : {}
3783
- }).catch(() => {
3808
+ const statuses = rawStatuses.length > 0 ? rawStatuses : void 0;
3809
+ const missions = (0, import_daemon_core3.listMeshMissionSummaries)(ctx.mesh.id, {
3810
+ statuses,
3811
+ verbose: args.verbose === true
3812
+ }).map((mission) => {
3813
+ try {
3814
+ return { ...mission, stats: (0, import_daemon_core3.computeMeshMissionStats)(ctx.mesh.id, mission.id) };
3815
+ } catch {
3816
+ return mission;
3817
+ }
3784
3818
  });
3785
- return JSON.stringify({ success: true, task }, null, 2);
3819
+ return JSON.stringify({
3820
+ success: true,
3821
+ count: missions.length,
3822
+ ...statuses ? { statusFilter: statuses } : {},
3823
+ missions
3824
+ }, null, 2);
3786
3825
  } catch (e) {
3787
- return JSON.stringify({ success: false, error: e.message });
3826
+ return JSON.stringify({ success: false, error: e?.message || String(e) });
3788
3827
  }
3789
3828
  }
3829
+ async function meshReviewInbox(ctx, args = {}) {
3830
+ await refreshMeshFromDaemon(ctx);
3831
+ const meshId = (args.mesh_id ?? ctx.mesh.id).trim();
3832
+ const result = await commandForNode(ctx, ctx.mesh.nodes[0], "get_mesh_review_inbox", {
3833
+ meshId,
3834
+ inlineMesh: ctx.mesh
3835
+ });
3836
+ return JSON.stringify(result, null, 2);
3837
+ }
3838
+
3839
+ // src/tools/mesh-tools-session.ts
3840
+ async function meshPruneStaleDirect(ctx, args = {}) {
3841
+ await refreshMeshFromDaemon(ctx);
3842
+ const execute = args.execute === true && args.dry_run !== true;
3843
+ const includeTerminal = args.include_terminal === true;
3844
+ const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
3845
+ const ledgerEntries = (0, import_daemon_core3.readLedgerEntries)(ctx.mesh.id, { tail: 500 });
3846
+ const directDispatches = (0, import_daemon_core3.getActiveDirectDispatches)(ctx.mesh.id);
3847
+ const result = (0, import_daemon_core3.pruneStaleDirectDispatches)({
3848
+ meshId: ctx.mesh.id,
3849
+ queue: (0, import_daemon_core3.getQueue)(ctx.mesh.id),
3850
+ ledgerEntries,
3851
+ directDispatches,
3852
+ nodes: liveNodes,
3853
+ execute,
3854
+ includeTerminal,
3855
+ source: "mesh_prune_stale_direct"
3856
+ });
3857
+ const { prunable, prunedCount, preservedUnacknowledged, preservedLedgerOnly, preservedNotOrphan } = result;
3858
+ const summarize = (records) => records.map((r) => ({
3859
+ taskId: r.taskId,
3860
+ nodeId: r.nodeId,
3861
+ sessionId: r.sessionId,
3862
+ status: r.status,
3863
+ terminal: r.terminal === true,
3864
+ staleReason: r.staleReason,
3865
+ taskTitle: r.taskTitle,
3866
+ createdAt: r.createdAt
3867
+ }));
3868
+ return JSON.stringify({
3869
+ success: true,
3870
+ mode: result.mode,
3871
+ meshId: ctx.mesh.id,
3872
+ includeTerminal,
3873
+ candidateCount: result.candidateCount,
3874
+ prunableCount: prunable.length,
3875
+ prunedCount,
3876
+ prunable: summarize(prunable),
3877
+ preserved: {
3878
+ unacknowledgedCount: preservedUnacknowledged.length,
3879
+ ledgerOnlyCount: preservedLedgerOnly.length,
3880
+ notOrphanCount: preservedNotOrphan.length,
3881
+ unacknowledged: summarize(preservedUnacknowledged),
3882
+ ledgerOnly: summarize(preservedLedgerOnly),
3883
+ notOrphan: summarize(preservedNotOrphan)
3884
+ },
3885
+ note: execute ? `Pruned ${prunedCount} orphaned direct dispatch record(s) from the active staleDirect surface. The append-only mesh ledger audit history is preserved; a direct_dispatch_pruned entry records this prune.` : "Dry run \u2014 nothing was deleted. Re-run with execute=true to prune the listed orphaned records. Fresh unacknowledged dispatch failures (node/session still live) and ledger-only audit entries are always preserved."
3886
+ }, null, 2);
3887
+ }
3790
3888
  async function meshSendTask(ctx, args) {
3791
3889
  const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
3792
3890
  const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
3793
- const modeValidation = (0, import_daemon_core2.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
3891
+ const modeValidation = (0, import_daemon_core3.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
3794
3892
  if (!modeValidation.valid) {
3795
3893
  return JSON.stringify({
3796
3894
  success: false,
@@ -3902,7 +4000,7 @@ async function meshSendTask(ctx, args) {
3902
4000
  const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
3903
4001
  try {
3904
4002
  const providerType = result2.providerType || cached?.providerType;
3905
- (0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
4003
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
3906
4004
  kind: "task_dispatched",
3907
4005
  nodeId: args.node_id,
3908
4006
  sessionId: dispatchedSessionId,
@@ -3914,7 +4012,7 @@ async function meshSendTask(ctx, args) {
3914
4012
  targetSessionId: dispatchedSessionId
3915
4013
  })
3916
4014
  });
3917
- (0, import_daemon_core2.insertDirectDispatch)(ctx.mesh.id, {
4015
+ (0, import_daemon_core3.insertDirectDispatch)(ctx.mesh.id, {
3918
4016
  taskId,
3919
4017
  nodeId: args.node_id,
3920
4018
  sessionId: dispatchedSessionId,
@@ -3925,7 +4023,7 @@ async function meshSendTask(ctx, args) {
3925
4023
  dispatchedAt
3926
4024
  });
3927
4025
  if (missionId) {
3928
- (0, import_daemon_core2.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
4026
+ (0, import_daemon_core3.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
3929
4027
  id: taskId,
3930
4028
  missionId,
3931
4029
  assignedNodeId: args.node_id,
@@ -4084,7 +4182,7 @@ async function meshSendTask(ctx, args) {
4084
4182
  });
4085
4183
  }
4086
4184
  try {
4087
- (0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
4185
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
4088
4186
  kind: "task_dispatched",
4089
4187
  nodeId: args.node_id,
4090
4188
  sessionId: args.session_id,
@@ -4099,7 +4197,7 @@ async function meshSendTask(ctx, args) {
4099
4197
  });
4100
4198
  } catch {
4101
4199
  }
4102
- (0, import_daemon_core2.insertDirectDispatch)(ctx.mesh.id, {
4200
+ (0, import_daemon_core3.insertDirectDispatch)(ctx.mesh.id, {
4103
4201
  taskId,
4104
4202
  nodeId: args.node_id,
4105
4203
  sessionId: args.session_id,
@@ -4112,7 +4210,7 @@ async function meshSendTask(ctx, args) {
4112
4210
  });
4113
4211
  if (missionId) {
4114
4212
  try {
4115
- (0, import_daemon_core2.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
4213
+ (0, import_daemon_core3.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
4116
4214
  id: taskId,
4117
4215
  missionId,
4118
4216
  assignedNodeId: args.node_id,
@@ -4157,14 +4255,14 @@ async function meshSendTask(ctx, args) {
4157
4255
  } : {}
4158
4256
  });
4159
4257
  }
4160
- const task = (0, import_daemon_core2.enqueueTask)(ctx.mesh.id, args.message, {
4258
+ const task = (0, import_daemon_core3.enqueueTask)(ctx.mesh.id, args.message, {
4161
4259
  targetNodeId: args.node_id,
4162
4260
  targetSessionId: args.session_id,
4163
4261
  taskMode,
4164
4262
  ...missionId ? { missionId } : {}
4165
4263
  });
4166
4264
  const queueTrigger = await triggerMeshQueueAndReport(ctx);
4167
- const pendingEvents = (0, import_daemon_core2.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId);
4265
+ const pendingEvents = (0, import_daemon_core3.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId);
4168
4266
  const result = {
4169
4267
  success: true,
4170
4268
  source: "queue",
@@ -4189,81 +4287,6 @@ async function meshSendTask(ctx, args) {
4189
4287
  return JSON.stringify(failure);
4190
4288
  }
4191
4289
  }
4192
- function classifyReadChatTransportCause(error) {
4193
- const message = (error instanceof Error ? error.message : String(error ?? "")).toLowerCase();
4194
- if (/not acknowledged|delivery failure|channel never opened|connect timed out|not connected|datachannel|disconnected|\bclosed\b|offline|no route|failed to initiate p2p|p2p mesh is not available|connect queue full/.test(message)) {
4195
- return "not_connected";
4196
- }
4197
- return "saturated";
4198
- }
4199
- function resolveCachedMeshSessionPreviewFromLedger(ctx, nodeId, sessionId) {
4200
- const entries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
4201
- for (let i = entries.length - 1; i >= 0; i -= 1) {
4202
- const entry = entries[i];
4203
- const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
4204
- const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
4205
- if (entryNodeId && entryNodeId !== nodeId) continue;
4206
- const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
4207
- if (entrySessionId !== sessionId) continue;
4208
- const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : payload;
4209
- const preview = (0, import_daemon_core2.resolveMeshSurfacedSessionPreview)(metadataEvent);
4210
- if (preview) {
4211
- return { ...preview, ledgerKind: entry.kind, timestamp: entry.timestamp };
4212
- }
4213
- }
4214
- return void 0;
4215
- }
4216
- function buildMeshReadChatCacheFallback(ctx, args, node, error) {
4217
- const classification = (0, import_daemon_core2.classifyP2pRelayFailure)(error, { command: "read_chat", targetDaemonId: node.daemonId });
4218
- const cause = classifyReadChatTransportCause(error);
4219
- const errorMessage = error instanceof Error ? error.message : String(error ?? "");
4220
- const causeNote = cause === "not_connected" ? "the worker daemon is not currently connected over P2P (no live channel)" : "the worker daemon is connected but saturated \u2014 it acknowledged the request but did not return the transcript within the deadline";
4221
- const cached = resolveCachedMeshSessionPreviewFromLedger(ctx, args.node_id, args.session_id);
4222
- if (cached) {
4223
- return JSON.stringify({
4224
- success: true,
4225
- source: "coordinator_cache_fallback",
4226
- fallback: true,
4227
- nodeId: args.node_id,
4228
- sessionId: args.session_id,
4229
- transport: "p2p",
4230
- transportFailure: {
4231
- code: classification.code,
4232
- reason: classification.reason,
4233
- cause,
4234
- error: errorMessage
4235
- },
4236
- advisory: `Live transcript unavailable (${causeNote}). Showing the cached coordinator-side summary surfaced from the worker's last completion/status event \u2014 a stale point-in-time summary, NOT the live transcript. The full transcript requires a live P2P read_chat once the peer is reachable.`,
4237
- fullTranscriptRequiresP2p: true,
4238
- summary: cached.preview,
4239
- messages: [{
4240
- role: cached.role,
4241
- content: cached.preview,
4242
- cached: true,
4243
- ...cached.receivedAt ? { receivedAt: cached.receivedAt } : {}
4244
- }],
4245
- cachedPreview: {
4246
- role: cached.role,
4247
- ledgerKind: cached.ledgerKind,
4248
- ledgerTimestamp: cached.timestamp,
4249
- ...cached.receivedAt ? { receivedAt: cached.receivedAt } : {}
4250
- }
4251
- }, null, 2);
4252
- }
4253
- const failure = buildCoordinatorP2pRelayFailure(error, {
4254
- command: "read_chat",
4255
- targetDaemonId: node.daemonId,
4256
- nodeId: args.node_id,
4257
- sessionId: args.session_id
4258
- });
4259
- return JSON.stringify({
4260
- ...failure,
4261
- cause,
4262
- cachedSummaryAvailable: false,
4263
- fullTranscriptRequiresP2p: true,
4264
- advisory: `Live transcript unavailable (${causeNote}) and no cached coordinator-side summary exists for this session yet (no completion/status event has been surfaced). The full transcript requires a live P2P read_chat once the peer is reachable.`
4265
- }, null, 2);
4266
- }
4267
4290
  async function meshReadChat(ctx, args) {
4268
4291
  const node = await findOptionalNodeWithRefresh(ctx, args.node_id);
4269
4292
  if (!node) {
@@ -4284,7 +4307,7 @@ async function meshReadChat(ctx, args) {
4284
4307
  tailLimit: args.tail ?? 10
4285
4308
  });
4286
4309
  } catch (e) {
4287
- if (isLocalNode || !(0, import_daemon_core2.isP2pRelayTransportFailure)(e)) throw e;
4310
+ if (isLocalNode || !(0, import_daemon_core3.isP2pRelayTransportFailure)(e)) throw e;
4288
4311
  return buildMeshReadChatCacheFallback(ctx, args, node, e);
4289
4312
  }
4290
4313
  const payload = annotateRapidReadChatAdvisory(unwrapCommandPayload(result), {
@@ -4352,11 +4375,40 @@ async function meshLaunchSession(ctx, args) {
4352
4375
  const coordinatorNode = resolveCoordinatorNode(ctx);
4353
4376
  const coordinatorDaemonId = resolveCoordinatorDaemonId(ctx);
4354
4377
  const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
4355
- const delegatedWorkerAutoApprove = (0, import_daemon_core2.resolveDelegatedWorkerAutoApprove)(ctx.mesh.policy, node.policy);
4378
+ const delegatedWorkerAutoApprove = (0, import_daemon_core3.resolveDelegatedWorkerAutoApprove)(ctx.mesh.policy, node.policy);
4356
4379
  const isLocalNode = isLocalControlPlaneNode(ctx, node);
4357
4380
  if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
4358
4381
  return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
4359
4382
  }
4383
+ if (args.force !== true) {
4384
+ try {
4385
+ const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
4386
+ const sessions = extractStatusMetadataSessions(statusResult);
4387
+ const existing = sessions.find((session) => !isTerminalSessionRecord(session) && isMeshOwnedDelegateSession(session, ctx.mesh.id, args.node_id));
4388
+ if (existing) {
4389
+ const existingSessionId = readSessionRecordId(existing);
4390
+ if (existingSessionId) {
4391
+ const existingProviderType = resolveSessionProviderType(existing) || resolvedProviderType || void 0;
4392
+ const existingStatus = typeof existing?.status === "string" ? existing.status : "unknown";
4393
+ return JSON.stringify({
4394
+ success: true,
4395
+ duplicate: true,
4396
+ launched: false,
4397
+ reused: true,
4398
+ sessionId: existingSessionId,
4399
+ nodeId: args.node_id,
4400
+ ...existingProviderType ? { resolvedProviderType: existingProviderType, providerType: existingProviderType } : {},
4401
+ sessionStatus: existingStatus,
4402
+ idle: isIdleSessionRecord(existing),
4403
+ reason: "mesh_launch_session_duplicate_guard",
4404
+ warning: `Node '${args.node_id}' already has a live mesh-owned worker session ('${existingSessionId}', status '${existingStatus}'). Returning it instead of launching an empty duplicate (likely an enqueue auto-launch already spawned it).`,
4405
+ nextAction: `Use session '${existingSessionId}' for mesh_send_task/mesh_read_chat. If you intentionally need a second concurrent session on this node, retry mesh_launch_session with force=true.`
4406
+ }, null, 2);
4407
+ }
4408
+ }
4409
+ } catch {
4410
+ }
4411
+ }
4360
4412
  let result;
4361
4413
  try {
4362
4414
  result = await commandForNode(ctx, node, "launch_cli", {
@@ -4399,7 +4451,7 @@ async function meshLaunchSession(ctx, args) {
4399
4451
  });
4400
4452
  }
4401
4453
  try {
4402
- (0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
4454
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
4403
4455
  kind: "session_launched",
4404
4456
  nodeId: args.node_id,
4405
4457
  sessionId: runtimeSessionId || void 0,
@@ -4418,6 +4470,34 @@ async function meshLaunchSession(ctx, args) {
4418
4470
  }, null, 2);
4419
4471
  }
4420
4472
  }
4473
+ async function meshApprove(ctx, args) {
4474
+ const node = await findNodeWithRefresh(ctx, args.node_id);
4475
+ const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
4476
+ const providerSessionId = cached?.providerSessionId;
4477
+ const result = await commandForNode(ctx, node, "resolve_action", {
4478
+ sessionId: args.session_id,
4479
+ targetSessionId: args.session_id,
4480
+ workspace: node.workspace,
4481
+ ...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
4482
+ ...providerSessionId ? { providerSessionId } : {},
4483
+ action: args.action === "reject" ? "reject" : "approve"
4484
+ });
4485
+ return JSON.stringify(result, null, 2);
4486
+ }
4487
+ async function meshCleanupSessions(ctx, args) {
4488
+ const node = await findNodeWithRefresh(ctx, args.node_id);
4489
+ const result = await commandForNode(ctx, node, "cleanup_mesh_sessions", {
4490
+ meshId: ctx.mesh.id,
4491
+ nodeId: args.node_id,
4492
+ mode: args.mode,
4493
+ sessionIds: args.session_ids,
4494
+ dryRun: args.dry_run === true,
4495
+ inlineMesh: ctx.mesh
4496
+ });
4497
+ return JSON.stringify(result, null, 2);
4498
+ }
4499
+
4500
+ // src/tools/mesh-tools-git.ts
4421
4501
  async function meshGitStatus(ctx, args) {
4422
4502
  const node = await findNodeWithRefresh(ctx, args.node_id);
4423
4503
  const autoDiscoverSubmodules = node.policy?.autoDiscoverSubmodules !== false;
@@ -4552,7 +4632,7 @@ async function meshCheckpoint(ctx, args) {
4552
4632
  includeUntracked: true
4553
4633
  });
4554
4634
  try {
4555
- (0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
4635
+ (0, import_daemon_core3.appendLedgerEntry)(ctx.mesh.id, {
4556
4636
  kind: "checkpoint_created",
4557
4637
  nodeId: args.node_id,
4558
4638
  payload: {
@@ -4567,20 +4647,6 @@ async function meshCheckpoint(ctx, args) {
4567
4647
  }
4568
4648
  return JSON.stringify(result, null, 2);
4569
4649
  }
4570
- async function meshApprove(ctx, args) {
4571
- const node = await findNodeWithRefresh(ctx, args.node_id);
4572
- const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
4573
- const providerSessionId = cached?.providerSessionId;
4574
- const result = await commandForNode(ctx, node, "resolve_action", {
4575
- sessionId: args.session_id,
4576
- targetSessionId: args.session_id,
4577
- workspace: node.workspace,
4578
- ...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
4579
- ...providerSessionId ? { providerSessionId } : {},
4580
- action: args.action === "reject" ? "reject" : "approve"
4581
- });
4582
- return JSON.stringify(result, null, 2);
4583
- }
4584
4650
  async function meshCloneNode(ctx, args) {
4585
4651
  const sourceNode = await findNodeWithRefresh(ctx, args.source_node_id);
4586
4652
  const result = await commandForNode(ctx, sourceNode, "clone_mesh_node", {
@@ -4600,18 +4666,6 @@ async function meshCloneNode(ctx, args) {
4600
4666
  }
4601
4667
  return JSON.stringify(result, null, 2);
4602
4668
  }
4603
- async function meshCleanupSessions(ctx, args) {
4604
- const node = await findNodeWithRefresh(ctx, args.node_id);
4605
- const result = await commandForNode(ctx, node, "cleanup_mesh_sessions", {
4606
- meshId: ctx.mesh.id,
4607
- nodeId: args.node_id,
4608
- mode: args.mode,
4609
- sessionIds: args.session_ids,
4610
- dryRun: args.dry_run === true,
4611
- inlineMesh: ctx.mesh
4612
- });
4613
- return JSON.stringify(result, null, 2);
4614
- }
4615
4669
  async function meshRemoveNode(ctx, args) {
4616
4670
  const node = await findNodeWithRefresh(ctx, args.node_id);
4617
4671
  const removeArgs = buildRemoveNodeArgs(ctx, args.node_id, args.session_cleanup_mode, args.force === true);
@@ -4645,12 +4699,8 @@ async function meshRemoveNode(ctx, args) {
4645
4699
  }
4646
4700
  return JSON.stringify({ ...result || {}, ...transportFallback ? { transportFallback } : {} }, null, 2);
4647
4701
  }
4648
- 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
- }
4702
+
4703
+ // src/tools/mesh-tools-refine.ts
4654
4704
  async function meshRefineConfigSchema(ctx) {
4655
4705
  const node = resolveRefineConfigNode(ctx);
4656
4706
  const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
@@ -4752,15 +4802,6 @@ async function meshRefineBatch(ctx, args = {}) {
4752
4802
  }
4753
4803
  return JSON.stringify(result, null, 2);
4754
4804
  }
4755
- async function meshReviewInbox(ctx, args = {}) {
4756
- await refreshMeshFromDaemon(ctx);
4757
- const meshId = (args.mesh_id ?? ctx.mesh.id).trim();
4758
- const result = await commandForNode(ctx, ctx.mesh.nodes[0], "get_mesh_review_inbox", {
4759
- meshId,
4760
- inlineMesh: ctx.mesh
4761
- });
4762
- return JSON.stringify(result, null, 2);
4763
- }
4764
4805
 
4765
4806
  // src/help.ts
4766
4807
  var STANDARD_TOOLS = [