@adhdev/daemon-standalone 1.0.56-rc.2 → 1.0.56-rc.4

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.
@@ -40,6 +40,7 @@ var import_daemon_core = require("@adhdev/daemon-core");
40
40
  var DEFAULT_IPC_PORT = import_daemon_core.IDENTITY.defaultPort;
41
41
  var DEFAULT_IPC_PATH = "/ipc";
42
42
  var DEFAULT_IPC_COMMAND_TIMEOUT_MS = 15e3;
43
+ var IPC_COMMAND_TIMEOUT_ENV = "ADHDEV_IPC_COMMAND_TIMEOUT_MS";
43
44
  var IPC_COMMAND_TIMEOUTS_MS = {
44
45
  mesh_relay_command: 12e4,
45
46
  agent_command: 3e4,
@@ -67,6 +68,42 @@ var IPC_COMMAND_TIMEOUTS_MS = {
67
68
  refine_mesh_node: 3e4,
68
69
  batch_refine_mesh_nodes: 3e4
69
70
  };
71
+ var IPC_PROBE_RETRYABLE_COMMANDS = /* @__PURE__ */ new Set([
72
+ "get_status_metadata",
73
+ "get_mesh",
74
+ "mesh_status",
75
+ "git_status",
76
+ "git_diff_summary",
77
+ "git_log",
78
+ "git_diff_file",
79
+ "read_chat",
80
+ "get_spec_debug",
81
+ "get_chat_debug_bundle",
82
+ "list_coordinator_prompts",
83
+ "list_provider_availability"
84
+ ]);
85
+ var IPC_PROBE_RETRY_MAX_ENV = "ADHDEV_IPC_PROBE_RETRY_MAX";
86
+ var IPC_PROBE_RETRY_BACKOFF_MS_ENV = "ADHDEV_IPC_PROBE_RETRY_BACKOFF_MS";
87
+ var DEFAULT_IPC_PROBE_RETRY_MAX = 1;
88
+ var DEFAULT_IPC_PROBE_RETRY_BACKOFF_MS = 2e3;
89
+ function parsePositiveInt(raw, fallback) {
90
+ if (raw === void 0 || raw.trim() === "") return fallback;
91
+ const parsed = Number.parseInt(raw, 10);
92
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
93
+ }
94
+ function resolveDefaultCommandTimeoutMs() {
95
+ const parsed = parsePositiveInt(process.env[IPC_COMMAND_TIMEOUT_ENV], DEFAULT_IPC_COMMAND_TIMEOUT_MS);
96
+ return parsed > 0 ? parsed : DEFAULT_IPC_COMMAND_TIMEOUT_MS;
97
+ }
98
+ function resolveProbeRetryMax() {
99
+ return parsePositiveInt(process.env[IPC_PROBE_RETRY_MAX_ENV], DEFAULT_IPC_PROBE_RETRY_MAX);
100
+ }
101
+ function resolveProbeRetryBackoffMs() {
102
+ return parsePositiveInt(process.env[IPC_PROBE_RETRY_BACKOFF_MS_ENV], DEFAULT_IPC_PROBE_RETRY_BACKOFF_MS);
103
+ }
104
+ function isRetryableProbeCommand(type) {
105
+ return IPC_PROBE_RETRYABLE_COMMANDS.has(type);
106
+ }
70
107
  var WS_CONNECTING = 0;
71
108
  var WS_OPEN = 1;
72
109
  var POOL_IDLE_EVICT_MS = 5 * 6e4;
@@ -76,9 +113,10 @@ function buildRequestId() {
76
113
  return `mcp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
77
114
  }
78
115
  function getTimeoutMs(type, nestedCommand) {
116
+ const fallback = resolveDefaultCommandTimeoutMs();
79
117
  return Math.max(
80
- IPC_COMMAND_TIMEOUTS_MS[type] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS,
81
- IPC_COMMAND_TIMEOUTS_MS[nestedCommand] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS
118
+ IPC_COMMAND_TIMEOUTS_MS[type] ?? fallback,
119
+ IPC_COMMAND_TIMEOUTS_MS[nestedCommand] ?? fallback
82
120
  );
83
121
  }
84
122
  function getOrCreateConnection(WebSocketCtor, url) {
@@ -203,7 +241,20 @@ var IpcTransport = class {
203
241
  args
204
242
  });
205
243
  }
206
- sendIpcCommand(type, args) {
244
+ async sendIpcCommand(type, args) {
245
+ const maxRetries = isRetryableProbeCommand(type) ? resolveProbeRetryMax() : 0;
246
+ for (let attempt = 0; ; attempt += 1) {
247
+ try {
248
+ return await this.sendIpcCommandOnce(type, args);
249
+ } catch (e) {
250
+ if (!e?.ipcTimeout || attempt >= maxRetries) throw e;
251
+ const backoffMs = resolveProbeRetryBackoffMs();
252
+ console.error(`[ipc] probe command '${type}' timed out; retrying (${attempt + 1}/${maxRetries}) in ${backoffMs}ms \u2014 read-only command, safe to re-send`);
253
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
254
+ }
255
+ }
256
+ }
257
+ sendIpcCommandOnce(type, args) {
207
258
  const WebSocketCtor = globalThis.WebSocket;
208
259
  if (!WebSocketCtor) {
209
260
  return Promise.reject(new Error("WebSocket is not available in this Node runtime; Node 20+ is required for daemon IPC mode"));
@@ -229,7 +280,9 @@ var IpcTransport = class {
229
280
  }
230
281
  const timer = setTimeout(() => {
231
282
  conn.pending.delete(requestId);
232
- reject(new Error(`Daemon IPC ${diagnosticParts.join(" ")} timed out after ${Math.round(timeoutMs / 1e3)}s (requestId=${requestId})`));
283
+ const error = new Error(`Daemon IPC ${diagnosticParts.join(" ")} timed out after ${Math.round(timeoutMs / 1e3)}s (requestId=${requestId})`);
284
+ error.ipcTimeout = true;
285
+ reject(error);
233
286
  }, timeoutMs);
234
287
  conn.pending.set(requestId, { resolve, reject, timer });
235
288
  conn.lastUsedAt = Date.now();
@@ -577,6 +630,10 @@ var CANONICAL_MESH_TOOL_NAMES = [
577
630
  "mesh_enqueue_task",
578
631
  "mesh_enqueue_batch",
579
632
  "mesh_view_queue",
633
+ // GRAPH-ORCHESTRATION Phase E — the coordinator gate + graph view surface.
634
+ "mesh_graph_view",
635
+ "mesh_graph_gate_claim",
636
+ "mesh_graph_gate_release",
580
637
  "mesh_queue_cancel",
581
638
  "mesh_queue_requeue",
582
639
  "mesh_send_task",
@@ -635,7 +692,7 @@ function withStatusProbeMarker(args = {}) {
635
692
  }
636
693
 
637
694
  // src/tools/mesh-tools-internal.ts
638
- var import_daemon_core4 = require("@adhdev/daemon-core");
695
+ var import_daemon_core5 = require("@adhdev/daemon-core");
639
696
 
640
697
  // src/tools/mesh-tool-shared.ts
641
698
  function readString(value) {
@@ -1013,7 +1070,7 @@ var MESH_ENQUEUE_TASK_TOOL = {
1013
1070
  };
1014
1071
  var MESH_ENQUEUE_BATCH_TOOL = {
1015
1072
  name: "mesh_enqueue_batch",
1016
- description: "G5: atomically enqueue a dependency-wired set of tasks \u2014 either EVERY task in the batch is inserted or NONE is (a mid-batch error such as a dependency cycle, invalid difficulty, or guardrail violation rolls the whole batch back). Use this instead of N sequential mesh_enqueue_task calls whenever you are wiring a multi-task graph: give each task a batch-local `ref` label and name sibling refs in `depends_on` (forward references allowed \u2014 array order does not matter). A depends_on value that is not a ref in this batch must be an EXISTING queue task id; anything else is rejected. Per-task fields are the same as mesh_enqueue_task. Top-level mission_id applies to every task that lacks its own.",
1073
+ description: "G5: atomically enqueue a dependency-wired set of tasks \u2014 either EVERY task in the batch is inserted or NONE is (a mid-batch error such as a dependency cycle, invalid difficulty, or guardrail violation rolls the whole batch back). Use this instead of N sequential mesh_enqueue_task calls whenever you are wiring a multi-task graph: give each task a batch-local `ref` label and name sibling refs in `depends_on` (forward references allowed \u2014 array order does not matter). A depends_on value that is not a ref in this batch must be an EXISTING queue task id; anything else is rejected. Per-task fields are the same as mesh_enqueue_task. Top-level mission_id applies to every task that lacks its own. Beyond plain ordering it also accepts a full orchestration graph, so you can submit the WHOLE known plan once instead of enqueueing each step as the previous one finishes: `inputs_from` binds selected predecessor outputs into a later task (no hand-copying worker text), `run_if` branches on those outputs, `gates` declare steps that stop for a coordinator action (Refinery landing, approval, CI wait, publish, deploy) with downstream tasks pointing at them via `gated_by`, and `workspaces` + `workspace_ref` prepare a worktree later for a task that needs one. These are strictly additive: a batch using only message/depends_on behaves exactly as before. Only graph-using batches create a graph, inspectable with mesh_graph_view.",
1017
1074
  inputSchema: {
1018
1075
  type: "object",
1019
1076
  properties: {
@@ -1046,21 +1103,158 @@ var MESH_ENQUEUE_BATCH_TOOL = {
1046
1103
  not_before: { type: "number", description: "G7 delayed execution: hold the task pending until this time (epoch-ms, relative-ms, or ISO string)." },
1047
1104
  notBefore: { type: "number", description: "CamelCase alias for not_before. Also accepts an ISO-8601 timestamp string." },
1048
1105
  max_retries: { type: "number", description: "P3 retry cap (same semantics as mesh_enqueue_task)." },
1049
- maxRetries: { type: "number", description: "CamelCase alias for max_retries." }
1106
+ maxRetries: { type: "number", description: "CamelCase alias for max_retries." },
1107
+ // ── batch v2 graph fields (design :568-570). All optional; a batch
1108
+ // using none of them takes the unchanged compatibility path. ──
1109
+ inputs_from: {
1110
+ type: "array",
1111
+ description: "Bind SELECTED outputs of predecessor steps into this task, instead of hand-copying a worker's text into the instruction. Each entry is {from: <ref of a predecessor task or gate>, select: <RFC-6901 JSON Pointer into that step's completion envelope>, as: <binding name>, required?: bool}. Bound values are appended to your immutable instruction inside a clearly-marked untrusted-evidence envelope with provenance and a digest \u2014 they can never alter routing, permissions, task mode or model. Using this makes the task wait for the graph to bind it before it becomes claimable.",
1112
+ items: { type: "object" }
1113
+ },
1114
+ inputsFrom: { type: "array", description: "CamelCase alias for inputs_from.", items: { type: "object" } },
1115
+ run_if: { type: "object", description: "Declarative condition deciding whether this task runs at all, evaluated against predecessor outputs and released gate outcomes (e.g. only run the deploy when the gate outcome was `passed`). A condition that is false SKIPS the task \u2014 skipped is terminal and, deliberately, is NOT treated as completed, so it never satisfies a downstream dependency. A malformed condition fails closed rather than defaulting to true." },
1116
+ runIf: { type: "object", description: "CamelCase alias for run_if." },
1117
+ on_false: { type: "string", enum: ["skip"], description: "What to do when run_if is false. Only `skip` is defined (and is the default)." },
1118
+ onFalse: { type: "string", enum: ["skip"], description: "CamelCase alias for on_false." },
1119
+ on_upstream_skip: { type: "string", enum: ["skip", "omit_dependency"], description: 'What happens to this task when an upstream step is SKIPPED. `skip` (default) propagates the skip. `omit_dependency` drops that edge from this task\'s dependency projection so it can still run \u2014 the explicit way to say "run anyway if that branch was not taken".' },
1120
+ onUpstreamSkip: { type: "string", enum: ["skip", "omit_dependency"], description: "CamelCase alias for on_upstream_skip." },
1121
+ workspace_ref: { type: "string", description: "Run this task in a worktree declared in the top-level `workspaces` array, prepared LATER rather than before the batch is accepted. The task stays held until that worktree is ready, then it is pinned to it automatically. Worktree preparation is a compensated saga: it is reported separately as workspacePreparation and is never part of the batch's DB atomicity." },
1122
+ workspaceRef: { type: "string", description: "CamelCase alias for workspace_ref." },
1123
+ gated_by: { type: "array", items: { type: "string" }, description: 'Refs of gates in this batch\'s `gates` array that must be RELEASED before this task may run. This is how you say "do not start until I have landed/approved/deployed". Do NOT put a gate ref in depends_on \u2014 a gate is an intentional stop, not an ordinary queue dependency, and listing one there is rejected.' },
1124
+ gatedBy: { type: "array", items: { type: "string" }, description: "CamelCase alias for gated_by." }
1050
1125
  },
1051
1126
  required: ["message", "difficulty"]
1052
1127
  }
1053
1128
  },
1129
+ gates: {
1130
+ type: "array",
1131
+ description: "Coordinator GATES: graph steps that intentionally stop progress until YOU act (Refinery landing, approval, CI wait, publish, deploy). The daemon never performs the action and never auto-passes a gate \u2014 you claim it (mesh_graph_gate_claim), do the thing, then release it (mesh_graph_gate_release). Declare the gate here and point downstream tasks at it with gated_by, so the whole plan can be submitted at once instead of waiting to enqueue the later steps by hand. Gate refs share ONE namespace with task and workspace refs.",
1132
+ items: {
1133
+ type: "object",
1134
+ properties: {
1135
+ ref: { type: "string", description: "Label for this gate; downstream tasks name it in gated_by." },
1136
+ action: { type: "string", enum: ["refinery", "approval", "ci_wait", "publish", "deploy", "custom"], description: "What kind of action this gate is waiting for. A metadata label only \u2014 it tells you (and the view) what the gate means; the daemon never executes it." },
1137
+ instructions: { type: "string", description: "What the coordinator must do at this gate, shown when the gate opens." },
1138
+ depends_on: { type: "array", items: { type: "string" }, description: "Refs of tasks/gates in this batch that must complete before this gate OPENS for a coordinator." },
1139
+ on_timeout: { type: "string", enum: ["hold", "cancel_downstream", "fail_graph"], description: "What happens when the gate passes its deadline. `hold` (default) keeps downstream blocked for an explicit reclaim; `cancel_downstream` cancels the pending downstream branch; `fail_graph` fails the graph. There is deliberately NO auto-release option: a timeout is never treated as the action having succeeded." },
1140
+ deadline_seconds: { type: "number", description: "Seconds from when the gate OPENS until its on_timeout policy fires." },
1141
+ lease_seconds: { type: "number", description: "Default claim lease length for this gate (a claim may override it)." },
1142
+ eligible_coordinator_session_id: { type: "string", description: "Restrict claiming to one coordinator session." }
1143
+ },
1144
+ required: ["ref"]
1145
+ }
1146
+ },
1147
+ workspaces: {
1148
+ type: "array",
1149
+ description: 'Worktrees to prepare LATER for tasks that name them via workspace_ref \u2014 the way to plan "clone a worktree, then work in it" as one batch instead of enqueueing, waiting, and enqueueing again. Preparation is a compensated saga with owned-resource cleanup: it happens outside the batch transaction and is reported as workspacePreparation, so `atomic: true` never claims the git side effects happened.',
1150
+ items: {
1151
+ type: "object",
1152
+ properties: {
1153
+ ref: { type: "string", description: "Label tasks use in workspace_ref. Shares one namespace with task and gate refs." },
1154
+ source_node_id: { type: "string", description: "Node whose workspace the worktree is cloned from." },
1155
+ purpose: { type: "string", description: "Short label folded into the derived branch name." },
1156
+ base_revision: { type: "string", description: "Base revision to prepare from." },
1157
+ desired_path: { type: "string", description: "Optional explicit worktree path." },
1158
+ cleanup_on_graph_failure: { type: "boolean", description: "Remove the worktree this graph created if the graph fails. Only ever removes a worktree the saga itself owns." }
1159
+ },
1160
+ required: ["ref"]
1161
+ }
1162
+ },
1163
+ batch_id: { type: "string", description: "Your own idempotency key for this batch. Re-sending the SAME batch_id with an identical plan replays and inserts nothing new; the same batch_id with a different plan is rejected (batch_id_conflict). Use it whenever a retry might duplicate work. Supplying it records the batch as a graph (so the key can be enforced), which is why the response then carries graphId \u2014 task behavior is unchanged." },
1164
+ batchId: { type: "string", description: "CamelCase alias for batch_id." },
1165
+ orchestration_decision: {
1166
+ type: "object",
1167
+ description: "Optional record of your planning decision, for adoption measurement: {decision, ready_worker_tasks, known_graph_steps, single_reason, capability_blockers}. It is stored as provenance with the graph and never changes execution."
1168
+ },
1169
+ orchestrationDecision: { type: "object", description: "CamelCase alias for orchestration_decision." },
1054
1170
  mission_id: { type: "string", description: "Mission every task in this batch belongs to unless an entry overrides it (full/exact id). For multi-task work, create the mission first (mesh_mission_upsert) and pass it here. An unresolvable id rejects the WHOLE batch (atomic) before anything is inserted." },
1055
1171
  missionId: { type: "string", description: "CamelCase alias for mission_id." },
1056
1172
  block_duplicate: { type: "boolean", description: "G4: when any entry matches an in-flight task with the same message+target, refuse the WHOLE batch (it is atomic) with code duplicate_suspect. Default false = warn-only via duplicateSuspects in the response." },
1057
1173
  blockDuplicate: { type: "boolean", description: "CamelCase alias for block_duplicate." },
1058
1174
  allow_duplicate: { type: "boolean", description: "G4: skip duplicate detection entirely for every entry (intentional re-enqueue)." },
1059
- allowDuplicate: { type: "boolean", description: "CamelCase alias for allow_duplicate." }
1175
+ allowDuplicate: { type: "boolean", description: "CamelCase alias for allow_duplicate." },
1176
+ on_dependency_failure: {
1177
+ type: "string",
1178
+ enum: ["block", "cancel"],
1179
+ description: "on_dependency_failure controls downstream tasks when a required worker task fails or is cancelled. `block` (default) keeps downstream pending and automatically recovers if the predecessor is retried and later completes. `cancel` terminally cancels the dependent branch; it is not revived by predecessor retry."
1180
+ },
1181
+ onDependencyFailure: {
1182
+ type: "string",
1183
+ enum: ["block", "cancel"],
1184
+ description: "CamelCase alias for on_dependency_failure."
1185
+ }
1060
1186
  },
1061
1187
  required: ["tasks"]
1062
1188
  }
1063
1189
  };
1190
+ var MESH_GRAPH_GATE_CLAIM_TOOL = {
1191
+ name: "mesh_graph_gate_claim",
1192
+ description: "Take the lease on a coordinator GATE that is awaiting a coordinator. A gate is a graph step that intentionally STOPS progress until a human/coordinator does something the daemon must not do itself \u2014 a Refinery landing, an approval, waiting on CI, a publish, a deploy. The daemon NEVER performs a gate action and NEVER auto-passes a gate: the only way through is your own mesh_graph_gate_release. Claim returns a monotonically increasing leaseGeneration and an opaque fencingToken \u2014 you MUST present both at release, so keep them. A gate whose lease has lapsed can be taken over by a new claim at a HIGHER generation; when that happens the response sets ambiguousExternalOutcome, meaning the previous owner may already have performed the external side effect \u2014 reconcile external evidence (did the merge/publish already land?) before doing it again. Use mesh_graph_view to find gates awaiting a coordinator.",
1193
+ inputSchema: {
1194
+ type: "object",
1195
+ properties: {
1196
+ gate_id: { type: "string", description: "The gate to claim (from mesh_graph_view or the mesh_enqueue_batch response)." },
1197
+ gateId: { type: "string", description: "CamelCase alias for gate_id." },
1198
+ lease_seconds: { type: "number", description: "How long to hold the lease before it lapses. Defaults to the gate spec's lease_seconds, then 900s. Pick a duration that covers the real action \u2014 a lapsed lease cannot release (elapsed time is never completion evidence)." },
1199
+ leaseSeconds: { type: "number", description: "CamelCase alias for lease_seconds." },
1200
+ extend_deadline_seconds: { type: "number", description: "Push the gate DEADLINE out by this many seconds from now. Distinct from the lease: the deadline is when the on_timeout policy (hold / cancel_downstream / fail_graph) fires. Reclaiming a gate that expired under on_timeout=hold does NOT refresh its deadline unless you pass this, so the next sweep would expire it again." },
1201
+ extendDeadlineSeconds: { type: "number", description: "CamelCase alias for extend_deadline_seconds." },
1202
+ coordinator_session_id: { type: "string", description: "Owner session for the lease. Defaults to this coordinator session; pass explicitly only when driving a gate on behalf of another session." },
1203
+ coordinatorSessionId: { type: "string", description: "CamelCase alias for coordinator_session_id." }
1204
+ },
1205
+ required: ["gate_id"]
1206
+ }
1207
+ };
1208
+ var MESH_GRAPH_GATE_RELEASE_TOOL = {
1209
+ name: "mesh_graph_gate_release",
1210
+ description: "Release a coordinator gate you hold the lease on \u2014 the ONLY way a gate lets its downstream work run. Requires the leaseGeneration + fencingToken from your mesh_graph_gate_claim: a stale generation or wrong token is refused (stale_fence), and an EXPIRED lease can never release (re-claim first, and reconcile whether the external action already happened). Pass an idempotency_key of your choosing: re-sending the identical release with the same key is a safe no-op success, while the same key with a DIFFERENT payload is rejected as a conflict. `outcome` (passed / failed / rejected, or an action-specific label) and any structured `result` / `evidence` become readable by downstream tasks through run_if and inputs_from, so a gate decision can steer the rest of the graph. Validation failures roll the WHOLE release back: the gate stays claimed and downstream stays blocked.",
1211
+ inputSchema: {
1212
+ type: "object",
1213
+ properties: {
1214
+ gate_id: { type: "string", description: "The gate being released." },
1215
+ gateId: { type: "string", description: "CamelCase alias for gate_id." },
1216
+ fencing_token: { type: "string", description: "The opaque token returned by mesh_graph_gate_claim. Required." },
1217
+ fencingToken: { type: "string", description: "CamelCase alias for fencing_token." },
1218
+ lease_generation: { type: "number", description: "The leaseGeneration returned by mesh_graph_gate_claim. Required \u2014 a stale generation is refused." },
1219
+ leaseGeneration: { type: "number", description: "CamelCase alias for lease_generation." },
1220
+ idempotency_key: { type: "string", description: "Your own key for this release. Re-sending the identical release with the same key is a no-op success; the same key with a different payload is a conflict. Required." },
1221
+ idempotencyKey: { type: "string", description: "CamelCase alias for idempotency_key." },
1222
+ outcome: { type: "string", description: "The gate outcome: passed | failed | rejected, or an action-specific structured label. Downstream run_if conditions read it as /gate_outcome." },
1223
+ result: { type: "object", description: "Optional action-specific structured result, exposed to downstream bindings as /result/... (e.g. the merged commit sha)." },
1224
+ evidence: { type: "object", description: "Optional evidence references/digests, exposed to downstream bindings as /evidence/... ." },
1225
+ patches: {
1226
+ type: "array",
1227
+ description: "Optional pre-assignment patches to DIRECT downstream nodes. Only run_if, on_false, inputs_from and workspace_ref may be patched \u2014 message, routing, permissions, task mode and model are immutable by policy, and a task that is already claimed cannot be patched at all.",
1228
+ items: {
1229
+ type: "object",
1230
+ properties: {
1231
+ node: { type: "string", description: "Ref or node id of a DIRECT downstream node of this gate." },
1232
+ base_spec_patch: { type: "object", description: "Keys to merge into that node's spec. Allowed keys: run_if, on_false, inputs_from, workspace_ref." },
1233
+ baseSpecPatch: { type: "object", description: "CamelCase alias for base_spec_patch." }
1234
+ },
1235
+ required: ["node"]
1236
+ }
1237
+ }
1238
+ },
1239
+ required: ["gate_id", "fencing_token", "lease_generation", "idempotency_key", "outcome"]
1240
+ }
1241
+ };
1242
+ var MESH_GRAPH_VIEW_TOOL = {
1243
+ name: "mesh_graph_view",
1244
+ description: "Inspect orchestration GRAPHS on this mesh: node states and refs, active edges, materialization receipts, coordinator gates (with who holds the lease and what is blocked), delayed workspace sagas, derived dependency failures, and the next action a coordinator actually has to take. Read-only. Defaults to in-flight graphs; pass include_terminal=true for completed ones. Only mesh_enqueue_batch requests that use graph features (gates, inputs_from, run_if, workspace_ref) create a graph \u2014 a plain depends_on batch runs on the ordinary queue and appears in mesh_view_queue instead. Do not poll this waiting for generating work; use it when you need to know why something is blocked or which gate is waiting on you.",
1245
+ inputSchema: {
1246
+ type: "object",
1247
+ properties: {
1248
+ graph_id: { type: "string", description: "Show exactly this graph (including terminal ones)." },
1249
+ graphId: { type: "string", description: "CamelCase alias for graph_id." },
1250
+ batch_id: { type: "string", description: "Show the graph committed under this batch_id." },
1251
+ batchId: { type: "string", description: "CamelCase alias for batch_id." },
1252
+ include_terminal: { type: "boolean", description: "Include completed/failed/cancelled graphs. Default false (in-flight only)." },
1253
+ includeTerminal: { type: "boolean", description: "CamelCase alias for include_terminal." },
1254
+ limit: { type: "number", description: "Max graphs to return (default 20)." }
1255
+ }
1256
+ }
1257
+ };
1064
1258
  var MESH_VIEW_QUEUE_TOOL = {
1065
1259
  name: "mesh_view_queue",
1066
1260
  description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records. Do not repeatedly call this to wait for generating assigned work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
@@ -1892,6 +2086,11 @@ var ALL_MESH_TOOLS = [
1892
2086
  MESH_ENQUEUE_TASK_TOOL,
1893
2087
  MESH_ENQUEUE_BATCH_TOOL,
1894
2088
  MESH_VIEW_QUEUE_TOOL,
2089
+ // GRAPH-ORCHESTRATION Phase E — placed next to the queue/enqueue tools so a
2090
+ // coordinator that loaded the batch schema also discovers how to pass a gate.
2091
+ MESH_GRAPH_VIEW_TOOL,
2092
+ MESH_GRAPH_GATE_CLAIM_TOOL,
2093
+ MESH_GRAPH_GATE_RELEASE_TOOL,
1895
2094
  MESH_QUEUE_CANCEL_TOOL,
1896
2095
  MESH_QUEUE_REQUEUE_TOOL,
1897
2096
  MESH_SEND_TASK_TOOL,
@@ -1945,6 +2144,8 @@ var ALL_MESH_TOOLS = [
1945
2144
  ];
1946
2145
 
1947
2146
  // src/tools/mesh-compact.ts
2147
+ var import_daemon_core4 = require("@adhdev/daemon-core");
2148
+ var QUOTA_STALE_AFTER_MS = import_daemon_core4.DEFAULT_QUOTA_ROUTING_POLICY.staleAfterMs;
1948
2149
  function buildCompactGitSnapshot(status) {
1949
2150
  if (!status || typeof status !== "object" || Array.isArray(status)) return void 0;
1950
2151
  const slim = {};
@@ -1974,19 +2175,58 @@ function summarizeCompactSubmodules(submodules) {
1974
2175
  };
1975
2176
  }
1976
2177
  var MESH_COMPACT_PRESERVED_MARKER_FIELDS = ["dataFreshness", "quota", "isLocalWorktree"];
1977
- function summarizeNodeQuota(quota) {
2178
+ function quotaSnapshotAgeMs(snapshot, now = Date.now()) {
2179
+ const updatedAt = typeof snapshot?.updatedAt === "number" ? snapshot.updatedAt : NaN;
2180
+ if (!Number.isFinite(updatedAt)) return null;
2181
+ return Math.max(0, now - updatedAt);
2182
+ }
2183
+ function isQuotaSnapshotStale(snapshot, now = Date.now()) {
2184
+ const ageMs = quotaSnapshotAgeMs(snapshot, now);
2185
+ return ageMs === null || ageMs >= QUOTA_STALE_AFTER_MS;
2186
+ }
2187
+ function formatQuotaSnapshotAge(snapshot, now) {
2188
+ const ageMs = quotaSnapshotAgeMs(snapshot, now);
2189
+ if (ageMs === null) return "?";
2190
+ return `${Math.round(ageMs / 6e4)}m`;
2191
+ }
2192
+ function annotateQuotaSnapshotFreshness(quota, now = Date.now()) {
2193
+ if (!quota || typeof quota !== "object" || Array.isArray(quota)) return quota;
2194
+ const out = {};
2195
+ for (const [provider, snapshot] of Object.entries(quota)) {
2196
+ if (!snapshot || typeof snapshot !== "object") {
2197
+ out[provider] = snapshot;
2198
+ continue;
2199
+ }
2200
+ const ageMs = quotaSnapshotAgeMs(snapshot, now);
2201
+ out[provider] = {
2202
+ ...snapshot,
2203
+ ageMs: ageMs ?? -1,
2204
+ stale: isQuotaSnapshotStale(snapshot, now)
2205
+ };
2206
+ }
2207
+ return out;
2208
+ }
2209
+ function summarizeNodeQuota(quota, now = Date.now()) {
1978
2210
  if (!quota || typeof quota !== "object" || Array.isArray(quota)) return void 0;
1979
2211
  const out = {};
1980
2212
  for (const [provider, snapshot] of Object.entries(quota)) {
1981
2213
  if (!snapshot || typeof snapshot !== "object") continue;
1982
2214
  const status = typeof snapshot.status === "string" ? snapshot.status : "unknown";
1983
- if (status !== "ok") {
2215
+ const lastGood = snapshot.metadata?.lastGoodWindows === true;
2216
+ const pct = (w) => w && Number.isFinite(w.usedPercent) ? `${Math.round(w.usedPercent)}%` : void 0;
2217
+ const weekly = pct(snapshot.weekly);
2218
+ const session = pct(snapshot.session);
2219
+ if (weekly === void 0 && session === void 0) {
1984
2220
  const kind = typeof snapshot.metadata?.failureKind === "string" ? snapshot.metadata.failureKind : void 0;
1985
2221
  out[provider] = kind ? `${status}:${kind}` : status;
1986
2222
  continue;
1987
2223
  }
1988
- const pct = (w) => w && Number.isFinite(w.usedPercent) ? `${Math.round(w.usedPercent)}%` : "\u2014";
1989
- out[provider] = `${pct(snapshot.session)}/${pct(snapshot.weekly)}`;
2224
+ const age = formatQuotaSnapshotAge(snapshot, now);
2225
+ const stale = isQuotaSnapshotStale(snapshot, now);
2226
+ let line = `7d ${weekly ?? "\u2014"} \xB7 5h ${session ?? "\u2014"} \xB7 ${age}${stale ? " stale" : ""}`;
2227
+ if (lastGood) line += " \xB7 refreshing";
2228
+ else if (status !== "ok") line += ` \xB7 ${status}`;
2229
+ out[provider] = line;
1990
2230
  }
1991
2231
  return Object.keys(out).length > 0 ? out : void 0;
1992
2232
  }
@@ -2427,7 +2667,7 @@ function annotateRapidReadChatAdvisory(payload, options) {
2427
2667
  }
2428
2668
 
2429
2669
  // src/tools/mesh-tools-internal.ts
2430
- var import_daemon_core5 = require("@adhdev/daemon-core");
2670
+ var import_daemon_core6 = require("@adhdev/daemon-core");
2431
2671
  var import_node_crypto = require("crypto");
2432
2672
  var SESSION_PROVIDER_METADATA_TTL_MS = 30 * 6e4;
2433
2673
  var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
@@ -2487,7 +2727,7 @@ function buildDirectTaskPayload(message, via, opts) {
2487
2727
  };
2488
2728
  }
2489
2729
  function findNode(mesh, nodeId) {
2490
- const node = mesh.nodes.find((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2730
+ const node = mesh.nodes.find((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, nodeId));
2491
2731
  if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
2492
2732
  return node;
2493
2733
  }
@@ -2502,13 +2742,13 @@ async function refreshMeshFromDaemon(ctx) {
2502
2742
  for (const existing of ctx.mesh.nodes) {
2503
2743
  const existingId = existing?.id;
2504
2744
  if (!existingId) continue;
2505
- if (merged.some((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, existingId))) continue;
2745
+ if (merged.some((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, existingId))) continue;
2506
2746
  if (isLocalControlPlaneNode(ctx, existing)) {
2507
2747
  settledNodeIds.add(existingId);
2508
2748
  continue;
2509
2749
  }
2510
2750
  const clonedFromNodeId = readString(existing?.clonedFromNodeId) || readString(existing?.cloned_from_node_id);
2511
- if (clonedFromNodeId && refreshedNodes.some((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, clonedFromNodeId))) {
2751
+ if (clonedFromNodeId && refreshedNodes.some((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, clonedFromNodeId))) {
2512
2752
  settledNodeIds.add(existingId);
2513
2753
  continue;
2514
2754
  }
@@ -2537,7 +2777,7 @@ async function resolveNodeFromOwningDaemons(ctx, nodeId) {
2537
2777
  const candidates = [];
2538
2778
  const pushCandidate = (id) => {
2539
2779
  if (typeof id !== "string" || !id.trim()) return;
2540
- if (localDaemonId && (0, import_daemon_core4.daemonIdsEquivalent)(id, localDaemonId)) return;
2780
+ if (localDaemonId && (0, import_daemon_core5.daemonIdsEquivalent)(id, localDaemonId)) return;
2541
2781
  if (!candidates.includes(id)) candidates.push(id);
2542
2782
  };
2543
2783
  for (const node of ctx.mesh.nodes) pushCandidate(node?.daemonId);
@@ -2557,9 +2797,9 @@ async function resolveNodeFromOwningDaemons(ctx, nodeId) {
2557
2797
  if (result && result.success === false) ownerUnreachable = true;
2558
2798
  continue;
2559
2799
  }
2560
- const found = nodes.find((n) => n?.id && (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2800
+ const found = nodes.find((n) => n?.id && (0, import_daemon_core5.meshNodeIdMatches)(n, nodeId));
2561
2801
  if (!found) continue;
2562
- const existingIndex = ctx.mesh.nodes.findIndex((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2802
+ const existingIndex = ctx.mesh.nodes.findIndex((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, nodeId));
2563
2803
  if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = found;
2564
2804
  else ctx.mesh.nodes.push(found);
2565
2805
  return { node: found, ownerUnreachable: false };
@@ -2567,10 +2807,10 @@ async function resolveNodeFromOwningDaemons(ctx, nodeId) {
2567
2807
  return { node: null, ownerUnreachable };
2568
2808
  }
2569
2809
  async function findNodeWithRefresh(ctx, nodeId) {
2570
- const hit = ctx.mesh.nodes.find((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2810
+ const hit = ctx.mesh.nodes.find((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, nodeId));
2571
2811
  if (hit && !hit.isLocalWorktree) return hit;
2572
2812
  const { settledNodeIds } = await refreshMeshFromDaemon(ctx);
2573
- const refreshed = ctx.mesh.nodes.find((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2813
+ const refreshed = ctx.mesh.nodes.find((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, nodeId));
2574
2814
  if (refreshed) return refreshed;
2575
2815
  if (settledNodeIds.has(nodeId)) {
2576
2816
  throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
@@ -2587,10 +2827,10 @@ async function findNodeWithRefresh(ctx, nodeId) {
2587
2827
  throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
2588
2828
  }
2589
2829
  async function findOptionalNodeWithRefresh(ctx, nodeId) {
2590
- const hit = ctx.mesh.nodes.find((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2830
+ const hit = ctx.mesh.nodes.find((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, nodeId));
2591
2831
  if (hit && !hit.isLocalWorktree) return hit;
2592
2832
  const { settledNodeIds } = await refreshMeshFromDaemon(ctx);
2593
- const refreshed = ctx.mesh.nodes.find((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, nodeId));
2833
+ const refreshed = ctx.mesh.nodes.find((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, nodeId));
2594
2834
  if (refreshed) return refreshed;
2595
2835
  if (settledNodeIds.has(nodeId)) return null;
2596
2836
  const owned = await resolveNodeFromOwningDaemons(ctx, nodeId);
@@ -2599,7 +2839,7 @@ async function findOptionalNodeWithRefresh(ctx, nodeId) {
2599
2839
  function hasRecentDuplicateDispatch(ctx, args) {
2600
2840
  const now = Date.now();
2601
2841
  const normalizedMessage = args.message.trim();
2602
- for (const task of (0, import_daemon_core4.getQueue)(ctx.mesh.id)) {
2842
+ for (const task of (0, import_daemon_core5.getQueue)(ctx.mesh.id)) {
2603
2843
  const timestamp = new Date(task.updatedAt || task.createdAt).getTime();
2604
2844
  if (!Number.isFinite(timestamp) || now - timestamp > DUPLICATE_DISPATCH_WINDOW_MS) continue;
2605
2845
  if (task.targetNodeId && task.targetNodeId !== args.node_id) continue;
@@ -2609,7 +2849,7 @@ function hasRecentDuplicateDispatch(ctx, args) {
2609
2849
  return { duplicate: true, entry: task, source: "queue" };
2610
2850
  }
2611
2851
  }
2612
- const entries = (0, import_daemon_core4.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
2852
+ const entries = (0, import_daemon_core5.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
2613
2853
  for (let i = entries.length - 1; i >= 0; i -= 1) {
2614
2854
  const entry = entries[i];
2615
2855
  const timestamp = new Date(entry.timestamp).getTime();
@@ -2626,7 +2866,7 @@ function hasRecentDuplicateDispatch(ctx, args) {
2626
2866
  }
2627
2867
  function buildMissionInactiveWarning(ctx, missionId) {
2628
2868
  if (!missionId) return void 0;
2629
- const mission = (0, import_daemon_core4.getMeshMission)(ctx.mesh.id, missionId);
2869
+ const mission = (0, import_daemon_core5.getMeshMission)(ctx.mesh.id, missionId);
2630
2870
  if (!mission || mission.status === "active") return void 0;
2631
2871
  const hintByStatus = {
2632
2872
  paused: `Mission '${missionId}' (${mission.title}) is paused \u2014 a new task was just attached to it anyway. Mission status is never auto-transitioned; if this mission should be active again, call mesh_mission_upsert(mission_id: '${missionId}', status: 'active').`,
@@ -2640,7 +2880,7 @@ function buildMissionInactiveWarning(ctx, missionId) {
2640
2880
  };
2641
2881
  }
2642
2882
  function buildMissingNodeReadChatRecovery(ctx, args) {
2643
- const entries = (0, import_daemon_core4.readLedgerEntries)(ctx.mesh.id, { tail: 300 });
2883
+ const entries = (0, import_daemon_core5.readLedgerEntries)(ctx.mesh.id, { tail: 300 });
2644
2884
  const relatedEntries = entries.filter((entry) => entry.nodeId === args.node_id || entry.sessionId === args.session_id);
2645
2885
  const completedEntries = relatedEntries.filter((entry) => entry.kind === "task_completed");
2646
2886
  const lastDispatch = [...relatedEntries].reverse().find((entry) => entry.kind === "task_dispatched");
@@ -2764,7 +3004,7 @@ function readFinalAssistantTranscriptEvidence(payload) {
2764
3004
  }
2765
3005
  function findNodeSession(nodes, nodeId, sessionId) {
2766
3006
  if (!nodeId || !sessionId) return {};
2767
- const node = nodes.find((candidate) => (0, import_daemon_core4.meshNodeIdMatches)(candidate, nodeId));
3007
+ const node = nodes.find((candidate) => (0, import_daemon_core5.meshNodeIdMatches)(candidate, nodeId));
2768
3008
  if (!node) return {};
2769
3009
  const sessions = Array.isArray(node.sessions) ? node.sessions : [];
2770
3010
  const session = sessions.find((candidate) => readSessionRecordId(candidate) === sessionId);
@@ -2833,10 +3073,10 @@ async function reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, d
2833
3073
  });
2834
3074
  const payload = unwrapCommandPayload(readResult);
2835
3075
  if (payload?.success === false) continue;
2836
- if ((0, import_daemon_core4.hasTrailingToolActivityAfterFinalAssistant)(Array.isArray(payload?.messages) ? payload.messages : [])) continue;
3076
+ if ((0, import_daemon_core5.hasTrailingToolActivityAfterFinalAssistant)(Array.isArray(payload?.messages) ? payload.messages : [])) continue;
2837
3077
  const evidence = readFinalAssistantTranscriptEvidence(payload);
2838
3078
  if (!evidence.finalSummary) continue;
2839
- const result = (0, import_daemon_core4.reconcileDirectDispatchCompletionFromTranscript)({
3079
+ const result = (0, import_daemon_core5.reconcileDirectDispatchCompletionFromTranscript)({
2840
3080
  meshId: ctx.mesh.id,
2841
3081
  nodeId,
2842
3082
  sessionId,
@@ -3021,7 +3261,7 @@ function extractLaunchPayload(value) {
3021
3261
  function classifyMeshLaunchFailure(error) {
3022
3262
  const message = error instanceof Error ? error.message : String(error || "launch failed");
3023
3263
  const lower = message.toLowerCase();
3024
- const p2pClassification = (0, import_daemon_core4.classifyP2pRelayFailure)(error, { command: "launch_cli" });
3264
+ const p2pClassification = (0, import_daemon_core5.classifyP2pRelayFailure)(error, { command: "launch_cli" });
3025
3265
  if (p2pClassification.recoverable) {
3026
3266
  return p2pClassification;
3027
3267
  }
@@ -3096,7 +3336,7 @@ function buildRecoverableLaunchFailure(ctx, node, providerType, error) {
3096
3336
  function recordRecoverableLaunchFailure(ctx, node, providerType, error) {
3097
3337
  const failure = buildRecoverableLaunchFailure(ctx, node, providerType, error);
3098
3338
  try {
3099
- (0, import_daemon_core4.appendLedgerEntry)(ctx.mesh.id, {
3339
+ (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
3100
3340
  kind: "recovery_attempted",
3101
3341
  nodeId: node.id,
3102
3342
  providerType,
@@ -3110,7 +3350,7 @@ function recordRecoverableLaunchFailure(ctx, node, providerType, error) {
3110
3350
  return failure;
3111
3351
  }
3112
3352
  function getLatestActiveLaunchFailure(meshId, nodeId) {
3113
- const entries = (0, import_daemon_core4.readLedgerEntries)(meshId, { tail: 200 });
3353
+ const entries = (0, import_daemon_core5.readLedgerEntries)(meshId, { tail: 200 });
3114
3354
  for (let i = entries.length - 1; i >= 0; i -= 1) {
3115
3355
  const entry = entries[i];
3116
3356
  if (entry.nodeId !== nodeId) continue;
@@ -3122,7 +3362,7 @@ function getLatestActiveLaunchFailure(meshId, nodeId) {
3122
3362
  return null;
3123
3363
  }
3124
3364
  function buildCoordinatorP2pRelayFailure(error, context) {
3125
- const payload = (0, import_daemon_core4.buildP2pRelayFailurePayload)(error, {
3365
+ const payload = (0, import_daemon_core5.buildP2pRelayFailurePayload)(error, {
3126
3366
  command: context.command,
3127
3367
  targetDaemonId: context.targetDaemonId
3128
3368
  });
@@ -3305,7 +3545,7 @@ function rememberMeshSessionProviderMetadataFromEvent(event) {
3305
3545
  });
3306
3546
  }
3307
3547
  function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
3308
- const entries = (0, import_daemon_core4.readLedgerEntries)(ctx.mesh.id, { tail: 50 });
3548
+ const entries = (0, import_daemon_core5.readLedgerEntries)(ctx.mesh.id, { tail: 50 });
3309
3549
  for (let i = entries.length - 1; i >= 0; i -= 1) {
3310
3550
  const entry = entries[i];
3311
3551
  const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
@@ -3443,12 +3683,12 @@ function readNodeSupportedProviders(policy) {
3443
3683
  }
3444
3684
  function buildNodeCapabilityExposure(node) {
3445
3685
  const providers = readNodeSupportedProviders(node.policy);
3446
- const capabilityTags = (0, import_daemon_core4.buildMeshNodeCapabilityTags)(node);
3686
+ const capabilityTags = (0, import_daemon_core5.buildMeshNodeCapabilityTags)(node);
3447
3687
  const exposure = { capabilityTags };
3448
3688
  if (providers.length) {
3449
3689
  const byProvider = {};
3450
3690
  for (const provider of providers) {
3451
- byProvider[provider] = (0, import_daemon_core4.buildMeshNodeCapabilityTags)(node, provider);
3691
+ byProvider[provider] = (0, import_daemon_core5.buildMeshNodeCapabilityTags)(node, provider);
3452
3692
  }
3453
3693
  exposure.capabilityTagsByProvider = byProvider;
3454
3694
  }
@@ -3785,7 +4025,7 @@ function buildMeshForwardPayloadFromPendingEvent(event) {
3785
4025
  // eventId (idempotency) and unicast routing intact, matching the reconcile-loop
3786
4026
  // relay path (buildForwardPayloadFromPending). Spread LAST so the authoritative
3787
4027
  // envelope always wins. Empty for a v1 event (version-skew safe).
3788
- ...(0, import_daemon_core4.serializeV2EnvelopeToWire)(event)
4028
+ ...(0, import_daemon_core5.serializeV2EnvelopeToWire)(event)
3789
4029
  };
3790
4030
  }
3791
4031
  async function drainCoordinatorPendingEvents(ctx, opts) {
@@ -3859,11 +4099,11 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
3859
4099
  }
3860
4100
  return surfacedEvents;
3861
4101
  }
3862
- const drainerIdentity = (0, import_daemon_core4.coordinatorIdentityFromEmitFields)({
4102
+ const drainerIdentity = (0, import_daemon_core5.coordinatorIdentityFromEmitFields)({
3863
4103
  daemonId: ctx.localDaemonId,
3864
4104
  sessionId: ctx.coordinatorSessionId
3865
4105
  });
3866
- const events = (0, import_daemon_core4.drainPendingMeshCoordinatorEvents)(
4106
+ const events = (0, import_daemon_core5.drainPendingMeshCoordinatorEvents)(
3867
4107
  ctx.mesh.id,
3868
4108
  ctx.localDaemonId,
3869
4109
  drainerIdentity ? { drainerIdentity } : void 0
@@ -3872,7 +4112,7 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
3872
4112
  return events;
3873
4113
  }
3874
4114
  function isP2pTransportUnavailableError(error) {
3875
- return (0, import_daemon_core4.isP2pRelayTransportFailure)(error);
4115
+ return (0, import_daemon_core5.isP2pRelayTransportFailure)(error);
3876
4116
  }
3877
4117
  function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode, force) {
3878
4118
  return {
@@ -3891,7 +4131,7 @@ function classifyReadChatTransportCause(error) {
3891
4131
  return "saturated";
3892
4132
  }
3893
4133
  function resolveCachedMeshSessionPreviewFromLedger(ctx, nodeId, sessionId) {
3894
- const entries = (0, import_daemon_core4.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
4134
+ const entries = (0, import_daemon_core5.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
3895
4135
  for (let i = entries.length - 1; i >= 0; i -= 1) {
3896
4136
  const entry = entries[i];
3897
4137
  const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
@@ -3900,7 +4140,7 @@ function resolveCachedMeshSessionPreviewFromLedger(ctx, nodeId, sessionId) {
3900
4140
  const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
3901
4141
  if (entrySessionId !== sessionId) continue;
3902
4142
  const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : payload;
3903
- const preview = (0, import_daemon_core4.resolveMeshSurfacedSessionPreview)(metadataEvent);
4143
+ const preview = (0, import_daemon_core5.resolveMeshSurfacedSessionPreview)(metadataEvent);
3904
4144
  if (preview) {
3905
4145
  return { ...preview, ledgerKind: entry.kind, timestamp: entry.timestamp };
3906
4146
  }
@@ -3908,7 +4148,7 @@ function resolveCachedMeshSessionPreviewFromLedger(ctx, nodeId, sessionId) {
3908
4148
  return void 0;
3909
4149
  }
3910
4150
  function buildMeshReadChatCacheFallback(ctx, args, node, error) {
3911
- const classification = (0, import_daemon_core4.classifyP2pRelayFailure)(error, { command: "read_chat", targetDaemonId: node.daemonId });
4151
+ const classification = (0, import_daemon_core5.classifyP2pRelayFailure)(error, { command: "read_chat", targetDaemonId: node.daemonId });
3912
4152
  const cause = classifyReadChatTransportCause(error);
3913
4153
  const errorMessage = error instanceof Error ? error.message : String(error ?? "");
3914
4154
  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";
@@ -3989,12 +4229,12 @@ function summarizePendingEventProtocolMetrics(pendingEvents) {
3989
4229
  };
3990
4230
  }
3991
4231
  async function meshStatus(ctx, args = {}) {
3992
- const rateResult = (0, import_daemon_core5.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
4232
+ const rateResult = (0, import_daemon_core6.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
3993
4233
  const compact = args.verbose === true ? false : args.compact ?? true;
3994
4234
  await refreshMeshFromDaemon(ctx);
3995
4235
  const { mesh, transport } = ctx;
3996
- let ledgerSummary = (0, import_daemon_core5.getLedgerSummary)(mesh.id);
3997
- const schedulingRuntime = (0, import_daemon_core5.buildMeshSchedulingRuntime)(mesh, (0, import_daemon_core5.getQueue)(mesh.id));
4236
+ let ledgerSummary = (0, import_daemon_core6.getLedgerSummary)(mesh.id);
4237
+ const schedulingRuntime = (0, import_daemon_core6.buildMeshSchedulingRuntime)(mesh, (0, import_daemon_core6.getQueue)(mesh.id));
3998
4238
  const schedulingByNode = new Map(schedulingRuntime.nodes.map((n) => [n.nodeId, n]));
3999
4239
  const results = await Promise.all(mesh.nodes.map(async (node) => {
4000
4240
  const entry = {
@@ -4015,7 +4255,7 @@ async function meshStatus(ctx, args = {}) {
4015
4255
  const { nodeId: _omit, ...rest } = nodeScheduling;
4016
4256
  entry.scheduling = compact ? { load: rest.load, capReached: rest.capReached } : rest;
4017
4257
  }
4018
- const lastQuotaRanking = (0, import_daemon_core5.getLastQuotaRanking)(node.id);
4258
+ const lastQuotaRanking = (0, import_daemon_core6.getLastQuotaRanking)(node.id);
4019
4259
  if (lastQuotaRanking) {
4020
4260
  entry.scheduling = { ...entry.scheduling ?? {}, lastQuotaRanking };
4021
4261
  }
@@ -4066,14 +4306,14 @@ async function meshStatus(ctx, args = {}) {
4066
4306
  noFallbackReason: failure.noFallbackReason
4067
4307
  });
4068
4308
  }
4069
- entry.dataFreshness = (0, import_daemon_core5.buildMeshNodeProbeFreshness)({
4309
+ entry.dataFreshness = (0, import_daemon_core6.buildMeshNodeProbeFreshness)({
4070
4310
  git: entry.git,
4071
4311
  liveTruthProbed,
4072
4312
  isSelfNode: entry.machine?.sameMachine === true,
4073
4313
  daemonId: readNodeDaemonId(node),
4074
4314
  node
4075
4315
  });
4076
- const recoveryContext = (0, import_daemon_core5.getSessionRecoveryContext)(mesh.id, { nodeId: node.id });
4316
+ const recoveryContext = (0, import_daemon_core6.getSessionRecoveryContext)(mesh.id, { nodeId: node.id });
4077
4317
  if (recoveryContext.consecutiveNodeFailures > 0) {
4078
4318
  entry.recoveryHints = {
4079
4319
  consecutiveFailures: recoveryContext.consecutiveNodeFailures,
@@ -4163,23 +4403,23 @@ async function meshStatus(ctx, args = {}) {
4163
4403
  }
4164
4404
  return entry;
4165
4405
  }));
4166
- let ledgerEntries = (0, import_daemon_core5.readLedgerEntries)(mesh.id, { tail: 200 });
4167
- let directDispatches = (0, import_daemon_core5.getActiveDirectDispatches)(mesh.id);
4406
+ let ledgerEntries = (0, import_daemon_core6.readLedgerEntries)(mesh.id, { tail: 200 });
4407
+ let directDispatches = (0, import_daemon_core6.getActiveDirectDispatches)(mesh.id);
4168
4408
  const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, results, directDispatches, ledgerEntries);
4169
4409
  if (directReconciliation.reconciled > 0) {
4170
- ledgerEntries = (0, import_daemon_core5.readLedgerEntries)(mesh.id, { tail: 200 });
4171
- directDispatches = (0, import_daemon_core5.getActiveDirectDispatches)(mesh.id);
4172
- ledgerSummary = (0, import_daemon_core5.getLedgerSummary)(mesh.id);
4410
+ ledgerEntries = (0, import_daemon_core6.readLedgerEntries)(mesh.id, { tail: 200 });
4411
+ directDispatches = (0, import_daemon_core6.getActiveDirectDispatches)(mesh.id);
4412
+ ledgerSummary = (0, import_daemon_core6.getLedgerSummary)(mesh.id);
4173
4413
  }
4174
- const activeWorkEvidence = (0, import_daemon_core5.buildMeshActiveWork)({
4414
+ const activeWorkEvidence = (0, import_daemon_core6.buildMeshActiveWork)({
4175
4415
  meshId: mesh.id,
4176
- queue: (0, import_daemon_core5.getQueue)(mesh.id),
4416
+ queue: (0, import_daemon_core6.getQueue)(mesh.id),
4177
4417
  ledgerEntries,
4178
4418
  directDispatches,
4179
4419
  nodes: results
4180
4420
  });
4181
4421
  const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
4182
- const staleDirectWorkSummary = (0, import_daemon_core5.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
4422
+ const staleDirectWorkSummary = (0, import_daemon_core6.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
4183
4423
  note: activeWorkEvidence.staleDirectWorkNote,
4184
4424
  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."
4185
4425
  });
@@ -4224,7 +4464,7 @@ async function meshStatus(ctx, args = {}) {
4224
4464
  const daemonId = typeof entry?.daemonId === "string" && entry.daemonId ? entry.daemonId : "";
4225
4465
  if (!daemonId) continue;
4226
4466
  if (entry?.machine && !(daemonId in daemonMachines)) daemonMachines[daemonId] = entry.machine;
4227
- if (entry?.quota && !(daemonId in daemonQuotas)) daemonQuotas[daemonId] = entry.quota;
4467
+ if (entry?.quota && !(daemonId in daemonQuotas)) daemonQuotas[daemonId] = annotateQuotaSnapshotFreshness(entry.quota);
4228
4468
  }
4229
4469
  const daemonUpgradeFailures = {};
4230
4470
  for (const entry of results) {
@@ -4443,13 +4683,13 @@ async function meshStatus(ctx, args = {}) {
4443
4683
  }
4444
4684
  if (args.includeUsage === true) {
4445
4685
  try {
4446
- response.usage = (0, import_daemon_core5.summarizeMeshUsage)(mesh.id);
4686
+ response.usage = (0, import_daemon_core6.summarizeMeshUsage)(mesh.id);
4447
4687
  } catch {
4448
4688
  }
4449
4689
  }
4450
4690
  try {
4451
4691
  if (compact) {
4452
- const { live, historyFold } = (0, import_daemon_core5.getMeshStatusMissionsCompact)(mesh.id);
4692
+ const { live, historyFold } = (0, import_daemon_core6.getMeshStatusMissionsCompact)(mesh.id);
4453
4693
  const ranked = [...live].sort((a, b) => String(b.tasks?.lastActivityAt ?? "").localeCompare(String(a.tasks?.lastActivityAt ?? "")));
4454
4694
  const kept = [];
4455
4695
  const overflow = [];
@@ -4476,11 +4716,11 @@ async function meshStatus(ctx, args = {}) {
4476
4716
  }
4477
4717
  if (historyFold) response.missionsHistory = historyFold;
4478
4718
  } else {
4479
- const missions = (0, import_daemon_core5.getMeshStatusMissionSummaries)(mesh.id, { verbose: true });
4719
+ const missions = (0, import_daemon_core6.getMeshStatusMissionSummaries)(mesh.id, { verbose: true });
4480
4720
  if (missions.length > 0) {
4481
4721
  response.missions = missions.map((mission) => {
4482
4722
  try {
4483
- return { ...mission, stats: (0, import_daemon_core5.computeMeshMissionStats)(mesh.id, mission.id) };
4723
+ return { ...mission, stats: (0, import_daemon_core6.computeMeshMissionStats)(mesh.id, mission.id) };
4484
4724
  } catch {
4485
4725
  return mission;
4486
4726
  }
@@ -4491,14 +4731,14 @@ async function meshStatus(ctx, args = {}) {
4491
4731
  }
4492
4732
  try {
4493
4733
  const pendingEvents = await drainCoordinatorPendingEvents(ctx);
4494
- const asyncRefineJobs = (0, import_daemon_core5.buildMeshAsyncRefineJobs)({
4734
+ const asyncRefineJobs = (0, import_daemon_core6.buildMeshAsyncRefineJobs)({
4495
4735
  meshId: mesh.id,
4496
4736
  ledgerEntries,
4497
4737
  pendingEvents
4498
4738
  });
4499
4739
  if (asyncRefineJobs.length > 0) {
4500
4740
  if (compact) {
4501
- const summary = (0, import_daemon_core5.summarizeMeshAsyncRefineJobs)(asyncRefineJobs);
4741
+ const summary = (0, import_daemon_core6.summarizeMeshAsyncRefineJobs)(asyncRefineJobs);
4502
4742
  if (summary.activeJobs.length > 0) response.asyncRefineJobs = summary.activeJobs;
4503
4743
  response.asyncRefineJobsSummary = {
4504
4744
  total: summary.total,
@@ -4509,9 +4749,9 @@ async function meshStatus(ctx, args = {}) {
4509
4749
  response.asyncRefineJobs = asyncRefineJobs;
4510
4750
  }
4511
4751
  }
4512
- const magiActivity = (0, import_daemon_core5.buildMeshMagiActivity)({ meshId: mesh.id, ledgerEntries });
4752
+ const magiActivity = (0, import_daemon_core6.buildMeshMagiActivity)({ meshId: mesh.id, ledgerEntries });
4513
4753
  if (magiActivity.length > 0) {
4514
- const fold = (0, import_daemon_core5.summarizeMeshMagiActivity)(magiActivity);
4754
+ const fold = (0, import_daemon_core6.summarizeMeshMagiActivity)(magiActivity);
4515
4755
  if (compact) {
4516
4756
  if (fold.groups.length > 0) response.magiActivity = fold.groups;
4517
4757
  response.magiActivitySummary = {
@@ -4563,6 +4803,277 @@ async function meshListNodes(ctx) {
4563
4803
  }, null, 2);
4564
4804
  }
4565
4805
 
4806
+ // src/tools/mesh-tools-graph.ts
4807
+ function readGraphTaskFields(entry) {
4808
+ const inputsFrom = entry.inputs_from ?? entry.inputsFrom;
4809
+ const runIf = entry.run_if ?? entry.runIf;
4810
+ const onFalse = entry.on_false ?? entry.onFalse;
4811
+ const onUpstreamSkip = readString(entry.on_upstream_skip) || readString(entry.onUpstreamSkip) || void 0;
4812
+ const workspaceRef = readString(entry.workspace_ref) || readString(entry.workspaceRef) || void 0;
4813
+ const rawGatedBy = entry.gated_by ?? entry.gatedBy;
4814
+ const gatedBy = Array.isArray(rawGatedBy) ? rawGatedBy.map((g) => readString(g)).filter((g) => !!g) : void 0;
4815
+ return {
4816
+ ...inputsFrom !== void 0 ? { inputs_from: inputsFrom } : {},
4817
+ ...runIf !== void 0 ? { run_if: runIf } : {},
4818
+ ...onFalse !== void 0 ? { on_false: onFalse } : {},
4819
+ ...onUpstreamSkip ? { on_upstream_skip: onUpstreamSkip } : {},
4820
+ ...workspaceRef ? { workspace_ref: workspaceRef } : {},
4821
+ ...gatedBy && gatedBy.length > 0 ? { gated_by: gatedBy } : {}
4822
+ };
4823
+ }
4824
+ function normalizeWorkspaceDeclarations(raw) {
4825
+ if (!Array.isArray(raw)) return [];
4826
+ return raw.map((w) => ({
4827
+ ref: readString(w?.ref) || "",
4828
+ ...readString(w?.source_node_id ?? w?.sourceNodeId) ? { source_node_id: readString(w.source_node_id ?? w.sourceNodeId) } : {},
4829
+ ...readString(w?.purpose) ? { purpose: readString(w.purpose) } : {},
4830
+ ...readString(w?.base_revision ?? w?.baseRevision) ? { base_revision: readString(w.base_revision ?? w.baseRevision) } : {},
4831
+ ...readString(w?.desired_path ?? w?.desiredPath) ? { desired_path: readString(w.desired_path ?? w.desiredPath) } : {},
4832
+ ...(w?.cleanup_on_graph_failure ?? w?.cleanupOnGraphFailure) === true ? { cleanup_on_graph_failure: true } : {}
4833
+ }));
4834
+ }
4835
+ function buildGraphPlanShape(specs, rawEntries, gates, workspaces, hasExplicitBatchId) {
4836
+ const gateSpecs = Array.isArray(gates) ? gates : [];
4837
+ const workspaceSpecs = normalizeWorkspaceDeclarations(workspaces);
4838
+ const tasks = specs.map((spec, i) => ({ ...spec, ...readGraphTaskFields(rawEntries[i] ?? {}) }));
4839
+ return {
4840
+ tasks,
4841
+ gates: gateSpecs,
4842
+ workspaces: workspaceSpecs,
4843
+ useGraphPath: hasExplicitBatchId || (0, import_daemon_core6.requestUsesGraphV2)({ tasks, gates: gateSpecs, workspaces: workspaceSpecs })
4844
+ };
4845
+ }
4846
+ var GATE_RELEASE_ERROR_CODES = [
4847
+ "gate_not_found",
4848
+ "gate_release_conflict",
4849
+ "gate_already_released",
4850
+ "gate_not_claimed",
4851
+ "stale_fence",
4852
+ "gate_lease_expired",
4853
+ "gate_upstream_unsettled",
4854
+ "gate_patch_not_downstream",
4855
+ "gate_patch_forbidden",
4856
+ "task_already_claimed",
4857
+ "graph_node_not_found"
4858
+ ];
4859
+ function classifyGateError(message) {
4860
+ return GATE_RELEASE_ERROR_CODES.find((code) => message.startsWith(`${code}:`) || message.includes(`${code}:`));
4861
+ }
4862
+ function resolveGateSession(ctx, explicit) {
4863
+ return readString(explicit) || ctx.coordinatorSessionId || void 0;
4864
+ }
4865
+ async function meshGraphGateClaim(ctx, args) {
4866
+ (0, import_daemon_core6.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_graph_gate_claim" });
4867
+ const gateId = readString(args.gate_id) || readString(args.gateId);
4868
+ if (!gateId) {
4869
+ return JSON.stringify({
4870
+ success: false,
4871
+ code: "missing_gate_id",
4872
+ error: "mesh_graph_gate_claim requires gate_id. Use mesh_graph_view to list gates awaiting a coordinator."
4873
+ });
4874
+ }
4875
+ const coordinatorSessionId = resolveGateSession(ctx, args.coordinator_session_id ?? args.coordinatorSessionId);
4876
+ if (!coordinatorSessionId) {
4877
+ return JSON.stringify({
4878
+ success: false,
4879
+ code: "missing_coordinator_session",
4880
+ error: "No coordinator session id is available for this call, so a gate lease cannot be attributed to an owner. Pass coordinator_session_id explicitly."
4881
+ });
4882
+ }
4883
+ const leaseSeconds = readNumber(args.lease_seconds ?? args.leaseSeconds);
4884
+ const extendDeadlineSeconds = readNumber(args.extend_deadline_seconds ?? args.extendDeadlineSeconds);
4885
+ try {
4886
+ const result = (0, import_daemon_core6.claimMeshGraphGate)({
4887
+ meshId: ctx.mesh.id,
4888
+ gateId,
4889
+ coordinatorSessionId,
4890
+ ...leaseSeconds !== void 0 ? { leaseSeconds } : {},
4891
+ ...extendDeadlineSeconds !== void 0 ? { extendDeadlineSeconds } : {}
4892
+ });
4893
+ if (!result.claimed) {
4894
+ return JSON.stringify({
4895
+ success: false,
4896
+ claimed: false,
4897
+ code: result.reason ?? "gate_not_claimable",
4898
+ gateId,
4899
+ ...result.gate ? { gateState: result.gate.state, action: result.gate.action } : {},
4900
+ error: describeClaimRefusal(result.reason, result.gate?.state)
4901
+ });
4902
+ }
4903
+ (0, import_daemon_core6.recordGraphGateClaimed)(ctx.mesh.id, {
4904
+ graphId: result.gate.graphId,
4905
+ gateId,
4906
+ ref: result.gate.ref,
4907
+ action: result.gate.action,
4908
+ generation: result.leaseGeneration,
4909
+ ownerSessionId: coordinatorSessionId,
4910
+ leaseExpiresAt: result.leaseExpiresAt,
4911
+ ambiguousExternalOutcome: result.ambiguousExternalOutcome,
4912
+ previousLeaseOwnerSessionId: result.previousLeaseOwnerSessionId
4913
+ });
4914
+ return JSON.stringify({
4915
+ success: true,
4916
+ claimed: true,
4917
+ gateId,
4918
+ graphId: result.gate.graphId,
4919
+ ...result.gate.ref ? { ref: result.gate.ref } : {},
4920
+ action: result.gate.action,
4921
+ ...result.gate.instructions ? { instructions: result.gate.instructions } : {},
4922
+ // ★ Both values are REQUIRED by mesh_graph_gate_release. Losing them
4923
+ // means the lease must lapse before anyone can act on the gate again.
4924
+ leaseGeneration: result.leaseGeneration,
4925
+ fencingToken: result.fencingToken,
4926
+ leaseExpiresAt: result.leaseExpiresAt,
4927
+ ...result.deadlineAt ? { deadlineAt: result.deadlineAt } : {},
4928
+ onTimeout: result.gate.onTimeout,
4929
+ ...result.ambiguousExternalOutcome ? {
4930
+ ambiguousExternalOutcome: true,
4931
+ previousLeaseOwnerSessionId: result.previousLeaseOwnerSessionId,
4932
+ ambiguousExternalOutcomeHint: "This claim TOOK OVER from a previous owner whose lease lapsed. That owner may already have performed the external side effect (merge, publish, deploy). Reconcile external evidence \u2014 check whether the action already landed \u2014 BEFORE performing it again, then release with the real outcome."
4933
+ } : {},
4934
+ nextStep: "Perform the gate action yourself, then call mesh_graph_gate_release with this leaseGeneration + fencingToken and an idempotency_key. The daemon never performs a gate action and never auto-releases: the gate stays shut until you release it (a deadline can only EXPIRE it, never pass it)."
4935
+ });
4936
+ } catch (e) {
4937
+ const message = e?.message || String(e);
4938
+ return JSON.stringify({ success: false, claimed: false, gateId, code: classifyGateError(message), error: message });
4939
+ }
4940
+ }
4941
+ function describeClaimRefusal(reason, state) {
4942
+ switch (reason) {
4943
+ case "gate_not_found":
4944
+ return "No such gate on this mesh. Use mesh_graph_view to list gates.";
4945
+ case "gate_not_eligible":
4946
+ return "This gate is restricted to a different coordinator session (eligible_coordinator_session_id).";
4947
+ case "gate_not_awaiting":
4948
+ return "The gate is still `declared` \u2014 its predecessors have not completed yet, so it is not open for a coordinator. Wait for the upstream work; the gate opens itself when its predecessors settle.";
4949
+ case "gate_lease_held":
4950
+ return "Another coordinator holds a LIVE lease on this gate. Wait for the lease to lapse \u2014 a live lease is never taken over, because the holder may be mid-action.";
4951
+ case "gate_claim_race":
4952
+ return "Another claim won the race for this gate. Re-read the gate state and retry if it is still awaiting.";
4953
+ default:
4954
+ if (reason?.startsWith("gate_terminal:")) {
4955
+ return `The gate is terminal (${state ?? reason.slice("gate_terminal:".length)}) and cannot be claimed. A released gate stays released; an expired gate under cancel_downstream/fail_graph has already applied its policy.`;
4956
+ }
4957
+ return `The gate could not be claimed (${reason ?? "unknown reason"}).`;
4958
+ }
4959
+ }
4960
+ async function meshGraphGateRelease(ctx, args) {
4961
+ (0, import_daemon_core6.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_graph_gate_release" });
4962
+ const gateId = readString(args.gate_id) || readString(args.gateId);
4963
+ const fencingToken = readString(args.fencing_token) || readString(args.fencingToken);
4964
+ const leaseGeneration = readNumber(args.lease_generation ?? args.leaseGeneration);
4965
+ const idempotencyKey = readString(args.idempotency_key) || readString(args.idempotencyKey);
4966
+ const outcome = readString(args.outcome);
4967
+ const missing = [
4968
+ !gateId ? "gate_id" : null,
4969
+ !fencingToken ? "fencing_token" : null,
4970
+ leaseGeneration === void 0 ? "lease_generation" : null,
4971
+ !idempotencyKey ? "idempotency_key" : null,
4972
+ !outcome ? "outcome" : null
4973
+ ].filter((f) => f !== null);
4974
+ if (missing.length > 0) {
4975
+ return JSON.stringify({
4976
+ success: false,
4977
+ code: "missing_release_fields",
4978
+ missing,
4979
+ error: `mesh_graph_gate_release requires ${missing.join(", ")}. fencing_token and lease_generation come from the mesh_graph_gate_claim response; idempotency_key is yours to choose and makes a retried release a no-op instead of a double release.`
4980
+ });
4981
+ }
4982
+ const patches = (args.patches ?? []).map((p) => ({
4983
+ node: readString(p?.node) || "",
4984
+ baseSpecPatch: p?.base_spec_patch ?? p?.baseSpecPatch ?? {}
4985
+ })).filter((p) => p.node.length > 0);
4986
+ try {
4987
+ const result = (0, import_daemon_core6.releaseMeshGraphGate)({
4988
+ meshId: ctx.mesh.id,
4989
+ gateId,
4990
+ fencingToken,
4991
+ leaseGeneration,
4992
+ idempotencyKey,
4993
+ outcome,
4994
+ ...args.result !== void 0 ? { result: args.result } : {},
4995
+ ...args.evidence !== void 0 ? { evidence: args.evidence } : {},
4996
+ ...patches.length > 0 ? { patches } : {}
4997
+ });
4998
+ (0, import_daemon_core6.recordGraphGateReleased)(ctx.mesh.id, {
4999
+ graphId: result.gate.graphId,
5000
+ gateId,
5001
+ ref: result.gate.ref,
5002
+ action: result.gate.action,
5003
+ outcome,
5004
+ generation: leaseGeneration,
5005
+ releaseDigest: result.gate.releaseEvidenceDigest,
5006
+ materializedNodeIds: result.materializedNodeIds,
5007
+ duplicate: result.duplicate
5008
+ });
5009
+ const queueTrigger = result.materializedNodeIds.length > 0 ? await triggerMeshQueueAndReport(ctx) : void 0;
5010
+ return JSON.stringify({
5011
+ success: true,
5012
+ released: true,
5013
+ // A replayed release (same key + same digest) is a NO-OP SUCCESS, not an
5014
+ // error — that is what makes a retried release safe (design :420-421).
5015
+ duplicate: result.duplicate,
5016
+ gateId,
5017
+ graphId: result.gate.graphId,
5018
+ ...result.gate.ref ? { ref: result.gate.ref } : {},
5019
+ outcome,
5020
+ materializedNodeIds: result.materializedNodeIds,
5021
+ materializedCount: result.materializedNodeIds.length,
5022
+ ...queueTrigger ? { queueTrigger } : {},
5023
+ ...result.duplicate ? { duplicateHint: "This idempotency_key + payload was already committed; nothing changed. Re-sending an identical release is safe." } : {}
5024
+ });
5025
+ } catch (e) {
5026
+ const message = e?.message || String(e);
5027
+ const code = classifyGateError(message);
5028
+ return JSON.stringify({
5029
+ success: false,
5030
+ released: false,
5031
+ gateId,
5032
+ ...code ? { code } : {},
5033
+ error: message,
5034
+ ...code === "gate_lease_expired" ? {
5035
+ hint: "The lease expired before the release. Elapsed time is never completion evidence, so the release is refused. Re-claim the gate (you get a HIGHER generation), reconcile whether the external action already landed, then release."
5036
+ } : {},
5037
+ ...code === "stale_fence" ? { hint: "Another coordinator has claimed this gate since your claim. Your token is stale \u2014 re-claim before releasing." } : {},
5038
+ ...code === "gate_release_conflict" ? { hint: "This idempotency_key was already used with a DIFFERENT payload. Use a new key, or re-send the identical payload." } : {}
5039
+ });
5040
+ }
5041
+ }
5042
+ async function meshGraphView(ctx, args) {
5043
+ (0, import_daemon_core6.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_graph_view" });
5044
+ try {
5045
+ await refreshMeshFromDaemon(ctx);
5046
+ const graphId = readString(args.graph_id) || readString(args.graphId);
5047
+ const batchId = readString(args.batch_id) || readString(args.batchId);
5048
+ const includeTerminal = args.include_terminal === true || args.includeTerminal === true;
5049
+ const graphs = (0, import_daemon_core6.buildMeshGraphViews)(ctx.mesh.id, {
5050
+ ...graphId ? { graphId } : {},
5051
+ ...batchId ? { batchId } : {},
5052
+ activeOnly: !includeTerminal,
5053
+ ...readNumber(args.limit) !== void 0 ? { limit: readNumber(args.limit) } : {}
5054
+ });
5055
+ const pendingActions = graphs.flatMap((g) => (g.nextCoordinatorAction ?? []).map((a) => ({ graphId: g.graphId, ...a })));
5056
+ return JSON.stringify({
5057
+ success: true,
5058
+ meshId: ctx.mesh.id,
5059
+ graphCount: graphs.length,
5060
+ ...graphId || batchId ? {} : { scope: includeTerminal ? "all" : "in_flight" },
5061
+ graphs,
5062
+ ...pendingActions.length > 0 ? { pendingCoordinatorActions: pendingActions } : {},
5063
+ ...graphs.length === 0 ? {
5064
+ hint: graphId || batchId ? "No graph with that id on this mesh." : "No in-flight orchestration graphs. Only mesh_enqueue_batch requests that use graph features (gates, inputs_from, run_if, workspace_ref) create a graph; a plain depends_on batch runs on the unchanged queue path. Pass include_terminal=true to see completed graphs."
5065
+ } : {}
5066
+ });
5067
+ } catch (e) {
5068
+ return JSON.stringify({ success: false, error: e?.message || String(e) });
5069
+ }
5070
+ }
5071
+ function readNumber(value) {
5072
+ if (typeof value === "number" && Number.isFinite(value)) return value;
5073
+ if (typeof value === "string" && value.trim() && Number.isFinite(Number(value))) return Number(value);
5074
+ return void 0;
5075
+ }
5076
+
4566
5077
  // src/tools/mesh-tools-queue.ts
4567
5078
  function normalizeDedupMessage(message) {
4568
5079
  return (message || "").trim().replace(/\s+/g, " ").toLowerCase();
@@ -4570,12 +5081,12 @@ function normalizeDedupMessage(message) {
4570
5081
  function findInFlightDuplicate(ctx, message, targetNodeId) {
4571
5082
  const fingerprint = normalizeDedupMessage(message);
4572
5083
  if (!fingerprint) return null;
4573
- for (const task of (0, import_daemon_core5.getQueue)(ctx.mesh.id)) {
5084
+ for (const task of (0, import_daemon_core6.getQueue)(ctx.mesh.id)) {
4574
5085
  if (task.status !== "pending" && task.status !== "assigned") continue;
4575
5086
  if (normalizeDedupMessage(task.message) !== fingerprint) continue;
4576
5087
  if (targetNodeId) {
4577
5088
  const existingTarget = task.targetNodeId || task.assignedNodeId;
4578
- if (!existingTarget || !(0, import_daemon_core5.meshNodeIdMatches)({ id: existingTarget }, targetNodeId)) continue;
5089
+ if (!existingTarget || !(0, import_daemon_core6.meshNodeIdMatches)({ id: existingTarget }, targetNodeId)) continue;
4579
5090
  }
4580
5091
  return { id: task.id, status: task.status, assignedNodeId: task.assignedNodeId, targetNodeId: task.targetNodeId };
4581
5092
  }
@@ -4602,10 +5113,10 @@ function normalizeEnqueueTaskArgs(ctx, args, callerLabel) {
4602
5113
  }
4603
5114
  const taskMode = readString(args.task_mode) || readString(args.taskMode);
4604
5115
  const readonly = args.readonly === true || args.read_only === true;
4605
- const requiredTags = (0, import_daemon_core5.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
5116
+ const requiredTags = (0, import_daemon_core6.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
4606
5117
  const dependsOn = Array.isArray(args.dependsOn) ? args.dependsOn : Array.isArray(args.depends_on) ? args.depends_on : void 0;
4607
5118
  const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
4608
- if (missionId && !(0, import_daemon_core5.getMeshMission)(ctx.mesh.id, missionId)) {
5119
+ if (missionId && !(0, import_daemon_core6.getMeshMission)(ctx.mesh.id, missionId)) {
4609
5120
  return {
4610
5121
  ok: false,
4611
5122
  code: "mission_not_found",
@@ -4613,19 +5124,19 @@ function normalizeEnqueueTaskArgs(ctx, args, callerLabel) {
4613
5124
  extra: { missionId }
4614
5125
  };
4615
5126
  }
4616
- const priority = (0, import_daemon_core5.normalizeMeshTaskPriority)(readString(args.priority)) || void 0;
5127
+ const priority = (0, import_daemon_core6.normalizeMeshTaskPriority)(readString(args.priority)) || void 0;
4617
5128
  const model = readString(args.model) || void 0;
4618
5129
  const thinkingLevel = readString(args.thinkingLevel) || void 0;
4619
5130
  const difficulty = readString(args.difficulty) || void 0;
4620
5131
  const notBeforeRaw = args.notBefore !== void 0 ? args.notBefore : args.not_before;
4621
- const notBefore = (0, import_daemon_core5.resolveNotBefore)(notBeforeRaw);
5132
+ const notBefore = (0, import_daemon_core6.resolveNotBefore)(notBeforeRaw);
4622
5133
  const maxRetriesRaw = typeof args.maxRetries === "number" ? args.maxRetries : typeof args.max_retries === "number" ? args.max_retries : void 0;
4623
5134
  const maxRetries = typeof maxRetriesRaw === "number" && Number.isFinite(maxRetriesRaw) && maxRetriesRaw >= 0 ? Math.floor(maxRetriesRaw) : void 0;
4624
5135
  const explicitTargetRaw = readString(args.targetNodeId) || readString(args.target_node_id) || readString(args.targetNode) || readString(args.target_node) || void 0;
4625
5136
  const preferWorktree = args.preferWorktree === true || args.prefer_worktree === true;
4626
5137
  let targetNodeId;
4627
5138
  if (explicitTargetRaw) {
4628
- const matched = ctx.mesh.nodes.find((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, explicitTargetRaw));
5139
+ const matched = ctx.mesh.nodes.find((n) => (0, import_daemon_core6.meshNodeIdMatches)(n, explicitTargetRaw));
4629
5140
  if (!matched) {
4630
5141
  return {
4631
5142
  ok: false,
@@ -4668,7 +5179,7 @@ function eagerPushTaskToRemoteNodes(ctx, task, message, targetNodeId, requiredTa
4668
5179
  const isLocalNode = isLocalControlPlaneNode(ctx, node);
4669
5180
  if (isLocalNode || !node.daemonId) continue;
4670
5181
  if (targetNodeId && node.id !== targetNodeId) continue;
4671
- if (!(0, import_daemon_core5.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core5.buildMeshNodeCapabilityTags)(node))) continue;
5182
+ if (!(0, import_daemon_core6.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core6.buildMeshNodeCapabilityTags)(node))) continue;
4672
5183
  dispatchPromises.push(
4673
5184
  ipcDispatchToRemoteAgent(ctx, node, {
4674
5185
  message,
@@ -4683,7 +5194,7 @@ function eagerPushTaskToRemoteNodes(ctx, task, message, targetNodeId, requiredTa
4683
5194
  try {
4684
5195
  const providerType = result.providerType;
4685
5196
  const descriptor = summarizeTaskMessage(message);
4686
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
5197
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
4687
5198
  kind: "task_dispatched",
4688
5199
  nodeId: node.id,
4689
5200
  sessionId: result.sessionId,
@@ -4705,7 +5216,7 @@ function eagerPushTaskToRemoteNodes(ctx, task, message, targetNodeId, requiredTa
4705
5216
  }
4706
5217
  }).catch((err) => {
4707
5218
  try {
4708
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
5219
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
4709
5220
  kind: "p2p_dispatch_failed",
4710
5221
  nodeId: node.id,
4711
5222
  payload: {
@@ -4757,7 +5268,7 @@ async function meshEnqueueTask(ctx, args) {
4757
5268
  });
4758
5269
  }
4759
5270
  try {
4760
- const task = (0, import_daemon_core5.enqueueTask)(ctx.mesh.id, message, {
5271
+ const task = (0, import_daemon_core6.enqueueTask)(ctx.mesh.id, message, {
4761
5272
  taskMode,
4762
5273
  ...readonly ? { readonly: true } : {},
4763
5274
  requiredTags,
@@ -4808,9 +5319,9 @@ async function meshEnqueueTask(ctx, args) {
4808
5319
  {
4809
5320
  const queueTrigger = await triggerMeshQueueAndReport(ctx);
4810
5321
  const dependencyStatusById = new Map(
4811
- (0, import_daemon_core5.getQueue)(ctx.mesh.id).map((t) => [t.id, t.status])
5322
+ (0, import_daemon_core6.getQueue)(ctx.mesh.id).map((t) => [t.id, t.status])
4812
5323
  );
4813
- const eagerPushDeferred = !(0, import_daemon_core5.taskDependenciesSatisfied)(task, dependencyStatusById);
5324
+ const eagerPushDeferred = !(0, import_daemon_core6.taskDependenciesSatisfied)(task, dependencyStatusById);
4814
5325
  const coordinatorDaemonId = resolveCoordinatorDaemonId(ctx);
4815
5326
  const dispatchPromises = eagerPushDeferred ? [] : eagerPushTaskToRemoteNodes(ctx, task, message, targetNodeId, requiredTags, coordinatorDaemonId);
4816
5327
  Promise.all(dispatchPromises).catch(() => {
@@ -4853,7 +5364,8 @@ var BATCH_ENQUEUE_ERROR_CODES = [
4853
5364
  "task_graph_too_large",
4854
5365
  "empty_task_graph",
4855
5366
  "missing_task_difficulty",
4856
- "invalid_task_difficulty"
5367
+ "invalid_task_difficulty",
5368
+ "invalid_on_dependency_failure"
4857
5369
  ];
4858
5370
  async function meshEnqueueBatch(ctx, args) {
4859
5371
  const rawTasks = Array.isArray(args.tasks) ? args.tasks : void 0;
@@ -4864,15 +5376,29 @@ async function meshEnqueueBatch(ctx, args) {
4864
5376
  error: "mesh_enqueue_batch requires a non-empty `tasks` array."
4865
5377
  });
4866
5378
  }
4867
- if (rawTasks.length > import_daemon_core5.MESH_TASK_GRAPH_MAX_TASKS) {
5379
+ if (rawTasks.length > import_daemon_core6.MESH_TASK_GRAPH_MAX_TASKS) {
4868
5380
  return JSON.stringify({
4869
5381
  success: false,
4870
5382
  code: "task_graph_too_large",
4871
- error: `mesh_enqueue_batch accepts at most ${import_daemon_core5.MESH_TASK_GRAPH_MAX_TASKS} tasks per call (got ${rawTasks.length}). Split the graph, or reconsider whether one batch really needs this many tasks.`
5383
+ error: `mesh_enqueue_batch accepts at most ${import_daemon_core6.MESH_TASK_GRAPH_MAX_TASKS} tasks per call (got ${rawTasks.length}). Split the graph, or reconsider whether one batch really needs this many tasks.`
4872
5384
  });
4873
5385
  }
4874
5386
  const batchMissionId = readString(args.missionId) || readString(args.mission_id) || void 0;
4875
- if (batchMissionId && !(0, import_daemon_core5.getMeshMission)(ctx.mesh.id, batchMissionId)) {
5387
+ const rawFailurePolicy = args.on_dependency_failure ?? args.onDependencyFailure;
5388
+ let onDependencyFailure;
5389
+ if (rawFailurePolicy !== void 0) {
5390
+ try {
5391
+ onDependencyFailure = (0, import_daemon_core6.parseOnDependencyFailurePolicy)(rawFailurePolicy);
5392
+ } catch (e) {
5393
+ const message = e instanceof import_daemon_core6.MeshGraphPolicyError || e instanceof Error ? e.message : "invalid_on_dependency_failure";
5394
+ return JSON.stringify({
5395
+ success: false,
5396
+ code: "invalid_on_dependency_failure",
5397
+ error: message
5398
+ });
5399
+ }
5400
+ }
5401
+ if (batchMissionId && !(0, import_daemon_core6.getMeshMission)(ctx.mesh.id, batchMissionId)) {
4876
5402
  return JSON.stringify({
4877
5403
  success: false,
4878
5404
  code: "mission_not_found",
@@ -4883,6 +5409,7 @@ async function meshEnqueueBatch(ctx, args) {
4883
5409
  const allowDuplicate = args.allowDuplicate === true || args.allow_duplicate === true;
4884
5410
  const blockDuplicate = args.blockDuplicate === true || args.block_duplicate === true;
4885
5411
  const specs = [];
5412
+ const rawGraphEntries = [];
4886
5413
  const normalizedEntries = [];
4887
5414
  const duplicateSuspects = [];
4888
5415
  for (let i = 0; i < rawTasks.length; i++) {
@@ -4914,6 +5441,7 @@ async function meshEnqueueBatch(ctx, args) {
4914
5441
  }
4915
5442
  }
4916
5443
  normalizedEntries.push({ ...v, ...ref ? { ref } : {} });
5444
+ rawGraphEntries.push(entry);
4917
5445
  specs.push({
4918
5446
  ...ref ? { ref } : {},
4919
5447
  message: v.message,
@@ -4942,18 +5470,75 @@ async function meshEnqueueBatch(ctx, args) {
4942
5470
  atomic: true
4943
5471
  });
4944
5472
  }
5473
+ const batchIdArg = readString(args.batch_id) || readString(args.batchId) || void 0;
5474
+ const plan = buildGraphPlanShape(specs, rawGraphEntries, args.gates, args.workspaces, !!batchIdArg);
5475
+ const useGraphPath = plan.useGraphPath;
5476
+ const decision = (0, import_daemon_core6.normalizeOrchestrationDecision)(
5477
+ args.orchestration_decision ?? args.orchestrationDecision,
5478
+ "batch"
5479
+ );
4945
5480
  let tasks;
5481
+ let graphPlan;
4946
5482
  try {
4947
- tasks = (0, import_daemon_core5.enqueueTaskGraph)(ctx.mesh.id, specs);
5483
+ if (useGraphPath) {
5484
+ graphPlan = (0, import_daemon_core6.commitMeshGraphPlan)({
5485
+ meshId: ctx.mesh.id,
5486
+ tasks: plan.tasks,
5487
+ gates: plan.gates,
5488
+ workspaces: plan.workspaces,
5489
+ ...batchIdArg ? { batchId: batchIdArg } : {},
5490
+ ...batchMissionId ? { missionId: batchMissionId } : {},
5491
+ ...onDependencyFailure ? { onDependencyFailure } : {},
5492
+ ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {},
5493
+ enqueueSurface: "batch",
5494
+ orchestrationDecision: decision.decision
5495
+ });
5496
+ tasks = graphPlan.tasks;
5497
+ } else {
5498
+ tasks = (0, import_daemon_core6.enqueueTaskGraph)(ctx.mesh.id, specs);
5499
+ }
4948
5500
  } catch (e) {
4949
5501
  const message = e?.message || String(e);
4950
- const code = BATCH_ENQUEUE_ERROR_CODES.find((c) => message.includes(c));
5502
+ const code = e instanceof import_daemon_core6.MeshGraphPlanError ? e.code : BATCH_ENQUEUE_ERROR_CODES.find((c) => message.includes(c));
5503
+ if (useGraphPath) {
5504
+ (0, import_daemon_core6.recordGraphEnqueueRolledBack)(ctx.mesh.id, {
5505
+ code: code ?? "graph_plan_failed",
5506
+ ...batchIdArg ? { batchId: batchIdArg } : {},
5507
+ taskCount: specs.length,
5508
+ error: message
5509
+ });
5510
+ } else {
5511
+ (0, import_daemon_core6.recordGraphEnqueueValidationFailed)(ctx.mesh.id, {
5512
+ code: code ?? "batch_enqueue_failed",
5513
+ ...batchIdArg ? { batchId: batchIdArg } : {},
5514
+ taskCount: specs.length
5515
+ });
5516
+ }
4951
5517
  return JSON.stringify({
4952
5518
  success: false,
4953
5519
  ...code ? { code } : {},
4954
5520
  error: message,
4955
5521
  enqueued: 0,
4956
- atomic: true
5522
+ atomic: true,
5523
+ ...e instanceof import_daemon_core6.MeshGraphPlanError && e.extra ? e.extra : {}
5524
+ });
5525
+ }
5526
+ if (graphPlan) {
5527
+ (0, import_daemon_core6.recordGraphEnqueueCommitted)(ctx.mesh.id, {
5528
+ graphId: graphPlan.graphId,
5529
+ batchId: graphPlan.batchId,
5530
+ enqueueSurface: "batch",
5531
+ schemaVersion: 2,
5532
+ planDigest: graphPlan.planDigest,
5533
+ ...batchMissionId ? { missionId: batchMissionId } : {},
5534
+ ...ctx.coordinatorSessionId ? { coordinatorSessionId: ctx.coordinatorSessionId } : {},
5535
+ taskCount: graphPlan.tasks.length,
5536
+ gateCount: graphPlan.gates.length,
5537
+ workspaceCount: graphPlan.workspaces.length,
5538
+ dependencyEdgeCount: graphPlan.dependencyEdgeCount,
5539
+ onDependencyFailure: onDependencyFailure ?? "block",
5540
+ orchestrationDecision: decision.decision,
5541
+ ...graphPlan.replayed ? { replayed: true } : {}
4957
5542
  });
4958
5543
  }
4959
5544
  const distinctMissionIds = [...new Set(specs.map((s) => s.missionId).filter((m) => !!m))];
@@ -4976,13 +5561,14 @@ async function meshEnqueueBatch(ctx, args) {
4976
5561
  const queueTrigger = await triggerMeshQueueAndReport(ctx);
4977
5562
  let eagerPushDeferredCount = 0;
4978
5563
  if (ctx.transport instanceof IpcTransport) {
4979
- const dependencyStatusById = new Map(
4980
- (0, import_daemon_core5.getQueue)(ctx.mesh.id).map((t) => [t.id, t.status])
4981
- );
5564
+ const liveQueue = (0, import_daemon_core6.getQueue)(ctx.mesh.id);
5565
+ const dependencyStatusById = new Map(liveQueue.map((t) => [t.id, t.status]));
5566
+ const liveById = new Map(liveQueue.map((t) => [t.id, t]));
4982
5567
  const coordinatorDaemonId = resolveCoordinatorDaemonId(ctx);
4983
5568
  const dispatchPromises = [];
4984
- tasks.forEach((task, i) => {
4985
- if (!(0, import_daemon_core5.taskDependenciesSatisfied)(task, dependencyStatusById)) {
5569
+ tasks.forEach((snapshot, i) => {
5570
+ const task = liveById.get(snapshot.id) ?? snapshot;
5571
+ if (!(0, import_daemon_core6.taskDependenciesSatisfied)(task, dependencyStatusById)) {
4986
5572
  eagerPushDeferredCount++;
4987
5573
  return;
4988
5574
  }
@@ -5003,11 +5589,37 @@ async function meshEnqueueBatch(ctx, args) {
5003
5589
  source: "queue",
5004
5590
  atomic: true,
5005
5591
  enqueued: tasks.length,
5592
+ ...onDependencyFailure ? { on_dependency_failure: onDependencyFailure } : {},
5593
+ // ── batch v2 additive response fields (design :582) ──
5594
+ // Present only on the graph path: an old-path batch creates no graph, so
5595
+ // reporting a graphId for it would be a lie.
5596
+ ...graphPlan ? {
5597
+ graphId: graphPlan.graphId,
5598
+ batchId: graphPlan.batchId,
5599
+ planDigest: graphPlan.planDigest,
5600
+ // design :585-588 — DB atomicity NEVER implies the git worktree exists.
5601
+ workspacePreparation: graphPlan.workspacePreparation,
5602
+ ...graphPlan.replayed ? {
5603
+ replayed: true,
5604
+ replayedHint: "A graph with this batch_id and an identical plan digest already existed; nothing was re-inserted. Re-sending the same batch_id with a DIFFERENT plan is rejected as batch_id_conflict."
5605
+ } : {},
5606
+ ...graphPlan.gates.length > 0 ? {
5607
+ gates: graphPlan.gates,
5608
+ gateHint: "Gates are declared shut. When their predecessors complete they open (awaiting_coordinator) and BLOCK their downstream tasks. Pass one with mesh_graph_gate_claim \u2192 do the action yourself \u2192 mesh_graph_gate_release. The daemon never performs a gate action and a deadline can only expire a gate, never pass it."
5609
+ } : {},
5610
+ ...graphPlan.workspaces.length > 0 ? { workspaces: graphPlan.workspaces } : {},
5611
+ ...graphPlan.heldNodeIds.length > 0 ? {
5612
+ graphHeldTasks: graphPlan.heldNodeIds.length,
5613
+ graphHeldHint: "Tasks declaring graph features (bound upstream outputs, a condition, a delayed worktree, or a gate) are held until the graph settles them \u2014 that is what makes their final instruction knowable. Tasks with only plain dependencies are NOT held; they use the unchanged queue dependency predicate."
5614
+ } : {}
5615
+ } : {},
5616
+ ...decision.batchCapabilityAvailable ? { batchCapabilityAvailable: decision.batchCapabilityAvailable } : {},
5006
5617
  tasks: tasks.map((task, i) => ({
5007
5618
  ...normalizedEntries[i].ref ? { ref: normalizedEntries[i].ref } : {},
5008
5619
  taskId: task.id,
5009
5620
  status: task.status,
5010
5621
  taskMode: task.taskMode,
5622
+ ...graphPlan?.nodeIdByIndex[i] ? { nodeId: graphPlan.nodeIdByIndex[i] } : {},
5011
5623
  ...Array.isArray(task.dependsOn) && task.dependsOn.length > 0 ? { dependsOn: task.dependsOn } : {},
5012
5624
  ...task.targetNodeId ? { targetNodeId: task.targetNodeId } : {},
5013
5625
  ...task.priority ? { priority: task.priority } : {},
@@ -5025,17 +5637,18 @@ async function meshEnqueueBatch(ctx, args) {
5025
5637
  });
5026
5638
  }
5027
5639
  async function meshViewQueue(ctx, args) {
5028
- const rateResult = (0, import_daemon_core5.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
5640
+ const rateResult = (0, import_daemon_core6.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
5029
5641
  const compact = args.verbose === true ? false : args.compact ?? true;
5030
5642
  try {
5031
5643
  await refreshMeshFromDaemon(ctx);
5032
5644
  const statusFilter = sanitizeQueueStatusFilter(args.status);
5033
5645
  const view = normalizeQueueViewMode(args.view);
5034
- const rawQueue = (0, import_daemon_core5.getQueue)(ctx.mesh.id);
5646
+ const rawQueue = (0, import_daemon_core6.getQueue)(ctx.mesh.id);
5035
5647
  const statusById = new Map(rawQueue.map((task) => [task.id, task.status]));
5648
+ const depMetaById = new Map(rawQueue.map((task) => [task.id, task]));
5036
5649
  const withDependencies = rawQueue.map((task) => {
5037
5650
  if (!Array.isArray(task.dependsOn) || task.dependsOn.length === 0) return task;
5038
- const depState = (0, import_daemon_core5.describeTaskDependencyState)(task, statusById);
5651
+ const depState = (0, import_daemon_core6.describeTaskDependencyState)(task, statusById, depMetaById);
5039
5652
  return { ...task, ...depState };
5040
5653
  });
5041
5654
  const liveNodes = await collectMeshViewQueueNodesWithLiveSessionsVerified(ctx);
@@ -5044,16 +5657,16 @@ async function meshViewQueue(ctx, args) {
5044
5657
  const summary = buildQueueStatusSummary(fullQueue);
5045
5658
  const visibleSummary = buildQueueStatusSummary(queue);
5046
5659
  const maintenance = buildQueueMaintenanceReport(fullQueue);
5047
- let ledgerEntries = (0, import_daemon_core5.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
5048
- let directDispatches = (0, import_daemon_core5.getActiveDirectDispatches)(ctx.mesh.id);
5660
+ let ledgerEntries = (0, import_daemon_core6.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
5661
+ let directDispatches = (0, import_daemon_core6.getActiveDirectDispatches)(ctx.mesh.id);
5049
5662
  const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries);
5050
5663
  if (directReconciliation.reconciled > 0) {
5051
- ledgerEntries = (0, import_daemon_core5.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
5052
- directDispatches = (0, import_daemon_core5.getActiveDirectDispatches)(ctx.mesh.id);
5664
+ ledgerEntries = (0, import_daemon_core6.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
5665
+ directDispatches = (0, import_daemon_core6.getActiveDirectDispatches)(ctx.mesh.id);
5053
5666
  }
5054
- (0, import_daemon_core5.markStaleDirectDispatches)(ctx.mesh.id);
5055
- directDispatches = (0, import_daemon_core5.getActiveDirectDispatches)(ctx.mesh.id);
5056
- const activeWorkEvidence = (0, import_daemon_core5.buildMeshActiveWork)({
5667
+ (0, import_daemon_core6.markStaleDirectDispatches)(ctx.mesh.id);
5668
+ directDispatches = (0, import_daemon_core6.getActiveDirectDispatches)(ctx.mesh.id);
5669
+ const activeWorkEvidence = (0, import_daemon_core6.buildMeshActiveWork)({
5057
5670
  meshId: ctx.mesh.id,
5058
5671
  queue: fullQueue,
5059
5672
  ledgerEntries,
@@ -5078,7 +5691,7 @@ async function meshViewQueue(ctx, args) {
5078
5691
  const wantActiveQueueArray = view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status));
5079
5692
  const wantHistoricalQueueArray = !compact && (view === "historical" || requestedHistoricalRows);
5080
5693
  const activeWorkResult = compact ? compactActiveWorkRecords(activeWorkEvidence.activeWork) : { records: activeWorkEvidence.activeWork, omitted: 0 };
5081
- const staleDirectWorkSummary = (0, import_daemon_core5.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
5694
+ const staleDirectWorkSummary = (0, import_daemon_core6.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
5082
5695
  note: activeWorkEvidence.staleDirectWorkNote,
5083
5696
  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."
5084
5697
  });
@@ -5153,12 +5766,12 @@ async function meshQueueCancel(ctx, args) {
5153
5766
  try {
5154
5767
  const taskId = (args.task_id || args.taskId || "").trim();
5155
5768
  if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
5156
- const preCancel = (0, import_daemon_core5.getQueue)(ctx.mesh.id).find((t) => t?.id === taskId);
5769
+ const preCancel = (0, import_daemon_core6.getQueue)(ctx.mesh.id).find((t) => t?.id === taskId);
5157
5770
  const wasAssigned = preCancel?.status === "assigned";
5158
5771
  const assignedSessionId = readString(preCancel?.assignedSessionId) || void 0;
5159
5772
  const assignedNodeId = readString(preCancel?.assignedNodeId) || void 0;
5160
5773
  const assignedProviderType = readString(preCancel?.assignedProviderType) || void 0;
5161
- const task = (0, import_daemon_core5.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
5774
+ const task = (0, import_daemon_core6.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
5162
5775
  if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
5163
5776
  ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
5164
5777
  });
@@ -5188,7 +5801,7 @@ async function meshQueueCancel(ctx, args) {
5188
5801
  let orphanedPinnedTasks = [];
5189
5802
  if (workerStop.attempted && assignedSessionId) {
5190
5803
  try {
5191
- orphanedPinnedTasks = (0, import_daemon_core5.notifyCoordinatorOfOrphanedPins)(ctx.mesh.id, assignedSessionId, {
5804
+ orphanedPinnedTasks = (0, import_daemon_core6.notifyCoordinatorOfOrphanedPins)(ctx.mesh.id, assignedSessionId, {
5192
5805
  excludeTaskId: taskId,
5193
5806
  cause: `Cancelling task ${taskId}`,
5194
5807
  ...assignedNodeId ? { nodeId: assignedNodeId } : {},
@@ -5207,7 +5820,7 @@ async function meshQueueCancel(ctx, args) {
5207
5820
  // can act without waiting for the event round-trip.
5208
5821
  ...orphanedPinnedTasks.length > 0 ? {
5209
5822
  orphanedPinnedTasks,
5210
- orphanedPinnedTasksWarning: (0, import_daemon_core5.buildOrphanedPinNotice)(
5823
+ orphanedPinnedTasksWarning: (0, import_daemon_core6.buildOrphanedPinNotice)(
5211
5824
  orphanedPinnedTasks,
5212
5825
  assignedSessionId,
5213
5826
  `Cancelling task ${taskId}`
@@ -5262,7 +5875,7 @@ async function meshQueueRequeue(ctx, args) {
5262
5875
  });
5263
5876
  return JSON.stringify({ success: true, task: task2 }, null, 2);
5264
5877
  }
5265
- const task = (0, import_daemon_core5.requeueTask)(ctx.mesh.id, taskId, {
5878
+ const task = (0, import_daemon_core6.requeueTask)(ctx.mesh.id, taskId, {
5266
5879
  reason: args.reason,
5267
5880
  targetNodeId,
5268
5881
  targetSessionId,
@@ -5301,17 +5914,17 @@ async function meshTaskHistory(ctx, args) {
5301
5914
  const compactCap = requestedTail > 50 ? 20 : 30;
5302
5915
  const tail = compact ? Math.min(requestedTail, compactCap) : Math.min(requestedTail, 200);
5303
5916
  const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
5304
- const rawEntries = (0, import_daemon_core5.readLedgerEntries)(mesh.id, { tail, kind });
5917
+ const rawEntries = (0, import_daemon_core6.readLedgerEntries)(mesh.id, { tail, kind });
5305
5918
  const entries = compact ? rawEntries.map((e) => ({
5306
5919
  ...e,
5307
5920
  payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
5308
5921
  })) : rawEntries;
5309
- const summary = (0, import_daemon_core5.getLedgerSummary)(mesh.id);
5922
+ const summary = (0, import_daemon_core6.getLedgerSummary)(mesh.id);
5310
5923
  let taskStats;
5311
5924
  try {
5312
5925
  const taskIds = [...new Set(rawEntries.map((e) => typeof e.payload?.taskId === "string" ? e.payload.taskId : "").filter(Boolean))];
5313
5926
  if (taskIds.length > 0) {
5314
- const stats = (0, import_daemon_core5.computeMeshTaskStats)(mesh.id, { taskIds });
5927
+ const stats = (0, import_daemon_core6.computeMeshTaskStats)(mesh.id, { taskIds });
5315
5928
  if (stats.length > 0) taskStats = stats;
5316
5929
  }
5317
5930
  } catch {
@@ -5333,8 +5946,8 @@ async function meshLedgerQuery(ctx, args) {
5333
5946
  const node = typeof args.node === "string" && args.node.trim() ? args.node.trim() : void 0;
5334
5947
  const requestedTail = typeof args.tail === "number" && args.tail > 0 ? Math.floor(args.tail) : 50;
5335
5948
  const tail = Math.min(requestedTail, 500);
5336
- const entries = (0, import_daemon_core5.readLedgerEntries)(mesh.id, { tail, kind, since, node });
5337
- const summary = (0, import_daemon_core5.getLedgerSummary)(mesh.id);
5949
+ const entries = (0, import_daemon_core6.readLedgerEntries)(mesh.id, { tail, kind, since, node });
5950
+ const summary = (0, import_daemon_core6.getLedgerSummary)(mesh.id);
5338
5951
  return JSON.stringify({
5339
5952
  meshId: mesh.id,
5340
5953
  query: {
@@ -5367,7 +5980,7 @@ async function meshRecordNote(ctx, args) {
5367
5980
  const supersedes = typeof args.supersedes === "string" && args.supersedes.trim() ? args.supersedes.trim() : void 0;
5368
5981
  const subjectKey = typeof args.subject_key === "string" && args.subject_key.trim() ? args.subject_key.trim() : void 0;
5369
5982
  const sourceCoordinator = ctx.coordinatorSessionId || ctx.localDaemonId || ctx.coordinatorHostname || void 0;
5370
- const entry = (0, import_daemon_core5.appendLedgerEntry)(mesh.id, {
5983
+ const entry = (0, import_daemon_core6.appendLedgerEntry)(mesh.id, {
5371
5984
  kind: "coordinator_operating_note",
5372
5985
  ...sourceCoordinator ? { sessionId: sourceCoordinator } : {},
5373
5986
  payload: {
@@ -5405,7 +6018,7 @@ async function meshForgetNote(ctx, args) {
5405
6018
  return JSON.stringify({ success: false, error: "note_id or text required" }, null, 2);
5406
6019
  }
5407
6020
  try {
5408
- const { tombstone, matched } = (0, import_daemon_core5.tombstoneOperatingNote)(mesh.id, {
6021
+ const { tombstone, matched } = (0, import_daemon_core6.tombstoneOperatingNote)(mesh.id, {
5409
6022
  ...noteId ? { noteId } : {},
5410
6023
  ...text ? { text } : {},
5411
6024
  ...typeof args.reason === "string" && args.reason.trim() ? { reason: args.reason.trim() } : {}
@@ -5438,8 +6051,8 @@ async function meshReconcileLedger(ctx, args) {
5438
6051
  const reconcileNode = async (node) => {
5439
6052
  try {
5440
6053
  if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
5441
- const slice2 = (0, import_daemon_core5.readLedgerSliceFromStore)(ctx.mesh.id, queryArgs);
5442
- return (0, import_daemon_core5.buildMeshLedgerReplicaEvidence)({
6054
+ const slice2 = (0, import_daemon_core6.readLedgerSliceFromStore)(ctx.mesh.id, queryArgs);
6055
+ return (0, import_daemon_core6.buildMeshLedgerReplicaEvidence)({
5443
6056
  nodeId: node.id,
5444
6057
  daemonId: node.daemonId,
5445
6058
  transport: "local",
@@ -5456,9 +6069,9 @@ async function meshReconcileLedger(ctx, args) {
5456
6069
  if (slice?.protocol !== "adhdev.mesh.ledger.slice.v1" || !Array.isArray(slice.entries)) {
5457
6070
  throw new Error("remote daemon returned an invalid ledger slice payload");
5458
6071
  }
5459
- const importResult = shouldImport ? (0, import_daemon_core5.appendRemoteLedgerEntries)(ctx.mesh.id, slice.entries) : { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
6072
+ const importResult = shouldImport ? (0, import_daemon_core6.appendRemoteLedgerEntries)(ctx.mesh.id, slice.entries) : { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
5460
6073
  if (shouldImport && importResult.accepted > 0) {
5461
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
6074
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
5462
6075
  kind: "ledger_replicated",
5463
6076
  nodeId: node.id,
5464
6077
  payload: {
@@ -5471,7 +6084,7 @@ async function meshReconcileLedger(ctx, args) {
5471
6084
  }
5472
6085
  });
5473
6086
  }
5474
- return (0, import_daemon_core5.buildMeshLedgerReplicaEvidence)({
6087
+ return (0, import_daemon_core6.buildMeshLedgerReplicaEvidence)({
5475
6088
  nodeId: node.id,
5476
6089
  daemonId: node.daemonId,
5477
6090
  transport: "p2p_datachannel",
@@ -5479,7 +6092,7 @@ async function meshReconcileLedger(ctx, args) {
5479
6092
  importResult
5480
6093
  });
5481
6094
  } catch (e) {
5482
- return (0, import_daemon_core5.buildMeshLedgerReplicaEvidence)({
6095
+ return (0, import_daemon_core6.buildMeshLedgerReplicaEvidence)({
5483
6096
  nodeId: node.id,
5484
6097
  daemonId: node.daemonId,
5485
6098
  transport: node.daemonId ? "p2p_datachannel" : "local",
@@ -5494,7 +6107,7 @@ async function meshReconcileLedger(ctx, args) {
5494
6107
  replicas.push(outcome.value);
5495
6108
  } else {
5496
6109
  const node = nodes[idx];
5497
- replicas.push((0, import_daemon_core5.buildMeshLedgerReplicaEvidence)({
6110
+ replicas.push((0, import_daemon_core6.buildMeshLedgerReplicaEvidence)({
5498
6111
  nodeId: node.id,
5499
6112
  daemonId: node.daemonId,
5500
6113
  transport: node.daemonId ? "p2p_datachannel" : "local",
@@ -5503,8 +6116,8 @@ async function meshReconcileLedger(ctx, args) {
5503
6116
  }));
5504
6117
  }
5505
6118
  });
5506
- const evidence = (0, import_daemon_core5.buildMeshLedgerReconciliationEvidence)(ctx.mesh.id, replicas);
5507
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
6119
+ const evidence = (0, import_daemon_core6.buildMeshLedgerReconciliationEvidence)(ctx.mesh.id, replicas);
6120
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
5508
6121
  kind: "ledger_reconciled",
5509
6122
  payload: {
5510
6123
  protocol: evidence.protocol,
@@ -5525,7 +6138,7 @@ async function meshRequeueHeldEvents(ctx, args) {
5525
6138
  ...readString(raw.reason) ? { reason: readString(raw.reason) } : {},
5526
6139
  ...readString(raw.since) ? { since: readString(raw.since) } : {}
5527
6140
  } : void 0;
5528
- const result = (0, import_daemon_core5.requeueHeldMeshCoordinatorEvents)(mesh.id, filter && Object.keys(filter).length > 0 ? filter : void 0);
6141
+ const result = (0, import_daemon_core6.requeueHeldMeshCoordinatorEvents)(mesh.id, filter && Object.keys(filter).length > 0 ? filter : void 0);
5529
6142
  const note = result.matched === 0 ? "No recoverable held events matched. Nothing to requeue." : `${result.requeued} held event(s) restored to the pending queue` + (result.dedupSuppressed > 0 ? ` (${result.dedupSuppressed} collapsed onto still-live duplicates)` : "") + (result.alreadyRequeued > 0 ? `; ${result.alreadyRequeued} already recovered by a prior pass` : "") + (result.unrecoverable > 0 ? `; ${result.unrecoverable} had no restorable original event` : "") + ". A coordinator will drain them on its next poll.";
5530
6143
  return JSON.stringify({ success: true, ...result, note }, null, 2);
5531
6144
  }
@@ -5543,7 +6156,7 @@ async function meshMissionUpsert(ctx, args) {
5543
6156
  error: "mission_title_required: single-mission upsert needs a non-empty title. For a bulk status transition pass mission_ids (array) + status instead."
5544
6157
  });
5545
6158
  }
5546
- const mission = (0, import_daemon_core5.upsertMeshMission)(ctx.mesh.id, {
6159
+ const mission = (0, import_daemon_core6.upsertMeshMission)(ctx.mesh.id, {
5547
6160
  id: readString(args.mission_id) || readString(args.missionId) || void 0,
5548
6161
  title,
5549
6162
  goal: typeof args.goal === "string" ? args.goal : void 0,
@@ -5579,9 +6192,9 @@ async function meshMissionUpsertBulk(ctx, missionIds, status) {
5579
6192
  }
5580
6193
  const results = missionIds.map((id) => {
5581
6194
  try {
5582
- const existing = (0, import_daemon_core5.getMeshMission)(ctx.mesh.id, id);
6195
+ const existing = (0, import_daemon_core6.getMeshMission)(ctx.mesh.id, id);
5583
6196
  if (!existing) return { id, ok: false, error: "mission_not_found" };
5584
- const updated = (0, import_daemon_core5.upsertMeshMission)(ctx.mesh.id, {
6197
+ const updated = (0, import_daemon_core6.upsertMeshMission)(ctx.mesh.id, {
5585
6198
  id,
5586
6199
  title: existing.title,
5587
6200
  status
@@ -5608,12 +6221,12 @@ async function meshMissionUpsertBulk(ctx, missionIds, status) {
5608
6221
  async function meshMissionList(ctx, args = {}) {
5609
6222
  try {
5610
6223
  const rawStatuses = Array.isArray(args.status) ? args.status : typeof args.status === "string" && args.status.trim() ? [args.status] : [];
5611
- const invalid = rawStatuses.filter((s) => !import_daemon_core5.MESH_MISSION_STATUSES.includes(s));
6224
+ const invalid = rawStatuses.filter((s) => !import_daemon_core6.MESH_MISSION_STATUSES.includes(s));
5612
6225
  if (invalid.length > 0) {
5613
6226
  return JSON.stringify({
5614
6227
  success: false,
5615
6228
  code: "invalid_mission_status",
5616
- error: `invalid status filter: ${invalid.join(", ")} (valid: ${import_daemon_core5.MESH_MISSION_STATUSES.join(", ")})`
6229
+ error: `invalid status filter: ${invalid.join(", ")} (valid: ${import_daemon_core6.MESH_MISSION_STATUSES.join(", ")})`
5617
6230
  });
5618
6231
  }
5619
6232
  const statuses = rawStatuses.length > 0 ? rawStatuses : void 0;
@@ -5621,7 +6234,7 @@ async function meshMissionList(ctx, args = {}) {
5621
6234
  const verbose = args.verbose === true;
5622
6235
  const withStats = verbose || (args.include_stats ?? args.includeStats) === true;
5623
6236
  const limit = typeof args.limit === "number" && Number.isFinite(args.limit) && args.limit > 0 ? Math.floor(args.limit) : void 0;
5624
- const result = (0, import_daemon_core5.listMeshMissionsForTool)(ctx.mesh.id, {
6237
+ const result = (0, import_daemon_core6.listMeshMissionsForTool)(ctx.mesh.id, {
5625
6238
  statuses,
5626
6239
  verbose,
5627
6240
  includeMagi,
@@ -5654,7 +6267,7 @@ async function meshReviewInbox(ctx, args = {}) {
5654
6267
  }
5655
6268
 
5656
6269
  // src/tools/mesh-tools-magi.ts
5657
- var import_daemon_core6 = require("@adhdev/daemon-core");
6270
+ var import_daemon_core7 = require("@adhdev/daemon-core");
5658
6271
  var MAGI_MAX_REPLICAS = 12;
5659
6272
  var MAGI_MIN_TARGETS = 2;
5660
6273
  var MAGI_CLUSTER_JACCARD = 0.4;
@@ -6138,19 +6751,19 @@ function buildMagiFanoutPlan(slots, nodes, opts = {}) {
6138
6751
  slotList.forEach((slot, slotIndex) => {
6139
6752
  const provider = slot.provider;
6140
6753
  const model = typeof slot.model === "string" && slot.model.trim() ? slot.model.trim() : void 0;
6141
- const capabilityTags = (0, import_daemon_core5.normalizeMeshCapabilityTags)(slot.capabilityTags);
6142
- const requiredTags = (0, import_daemon_core5.normalizeMeshCapabilityTags)([`provider=${provider}`, ...capabilityTags]);
6754
+ const capabilityTags = (0, import_daemon_core6.normalizeMeshCapabilityTags)(slot.capabilityTags);
6755
+ const requiredTags = (0, import_daemon_core6.normalizeMeshCapabilityTags)([`provider=${provider}`, ...capabilityTags]);
6143
6756
  const count = replicaCountFor(slot, defaultN, opts.n);
6144
6757
  let targetNodeId;
6145
6758
  let candidateNodes = [];
6146
6759
  if (slot.nodeId) {
6147
- const node = nodes.find((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, slot.nodeId));
6760
+ const node = nodes.find((n) => (0, import_daemon_core6.meshNodeIdMatches)(n, slot.nodeId));
6148
6761
  if (node) {
6149
6762
  targetNodeId = node.id;
6150
6763
  candidateNodes = [node];
6151
6764
  }
6152
6765
  } else {
6153
- candidateNodes = nodes.filter((n) => (0, import_daemon_core5.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core5.buildMeshNodeCapabilityTags)(n)));
6766
+ candidateNodes = nodes.filter((n) => (0, import_daemon_core6.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core6.buildMeshNodeCapabilityTags)(n)));
6154
6767
  }
6155
6768
  const available = candidateNodes.length > 0;
6156
6769
  if (!available) {
@@ -6164,9 +6777,9 @@ function buildMagiFanoutPlan(slots, nodes, opts = {}) {
6164
6777
  slotResolutions.push({ slotIndex, provider, nodeId: slot.nodeId, capabilityTags, available: false, gitStale: false, unhealthy: false, excluded: true, reason: "unavailable" });
6165
6778
  return;
6166
6779
  }
6167
- const launchableCandidates = candidateNodes.filter((n) => (0, import_daemon_core5.isMeshNodeHealthLaunchable)(n));
6780
+ const launchableCandidates = candidateNodes.filter((n) => (0, import_daemon_core6.isMeshNodeHealthLaunchable)(n));
6168
6781
  if (launchableCandidates.length === 0) {
6169
- const health = (0, import_daemon_core5.resolveEffectiveMeshNodeHealth)(candidateNodes[0]);
6782
+ const health = (0, import_daemon_core6.resolveEffectiveMeshNodeHealth)(candidateNodes[0]);
6170
6783
  unhealthySlots.push({
6171
6784
  slotIndex,
6172
6785
  provider,
@@ -6231,7 +6844,7 @@ function buildMagiFanoutPlan(slots, nodes, opts = {}) {
6231
6844
  // Candidate pool was already narrowed to launch-ready nodes above, so an
6232
6845
  // included slot is health-launchable by construction.
6233
6846
  unhealthy: false,
6234
- health: (0, import_daemon_core5.resolveEffectiveMeshNodeHealth)(candidateNodes[0]),
6847
+ health: (0, import_daemon_core6.resolveEffectiveMeshNodeHealth)(candidateNodes[0]),
6235
6848
  excluded: false
6236
6849
  };
6237
6850
  if (gitStale && !includeStale) {
@@ -6404,13 +7017,13 @@ function collectMagiCandidateTexts(payload) {
6404
7017
  }
6405
7018
  function replicaCompletionIsWeak(meshId, taskId) {
6406
7019
  try {
6407
- const entries = (0, import_daemon_core5.readLedgerEntries)(meshId, { kind: ["task_completed"], tail: 200 });
7020
+ const entries = (0, import_daemon_core6.readLedgerEntries)(meshId, { kind: ["task_completed"], tail: 200 });
6408
7021
  for (let i = entries.length - 1; i >= 0; i -= 1) {
6409
7022
  const entry = entries[i];
6410
7023
  const payload = entry?.payload && typeof entry.payload === "object" ? entry.payload : void 0;
6411
7024
  const entryTaskId = readString(payload?.taskId) || readString(entry?.taskId);
6412
7025
  if (!entryTaskId || entryTaskId !== taskId) continue;
6413
- if ((0, import_daemon_core5.isWeakCompletionEvidence)(payload)) return true;
7026
+ if ((0, import_daemon_core6.isWeakCompletionEvidence)(payload)) return true;
6414
7027
  const diag = payload?.completionDiagnostic;
6415
7028
  if (diag && typeof diag === "object" && !Array.isArray(diag) && readString(diag.reason) === "short_generating_suppressed") {
6416
7029
  return true;
@@ -6437,9 +7050,9 @@ async function meshMagiKindPanelSet(ctx, args) {
6437
7050
  const meshId = ctx.mesh.id;
6438
7051
  const scope = magiPanelScope(meshId, ctx.mesh.name);
6439
7052
  try {
6440
- const current = (0, import_daemon_core5.getMagiKindPanel)(kind, meshId) ?? [];
7053
+ const current = (0, import_daemon_core6.getMagiKindPanel)(kind, meshId) ?? [];
6441
7054
  if (!write) {
6442
- const preview = (0, import_daemon_core5.normalizeMagiSlots)(args.slots, ctx.mesh.nodes.map((n) => n.id));
7055
+ const preview = (0, import_daemon_core6.normalizeMagiSlots)(args.slots, ctx.mesh.nodes.map((n) => n.id));
6443
7056
  return JSON.stringify({
6444
7057
  success: true,
6445
7058
  dryRun: true,
@@ -6451,7 +7064,7 @@ async function meshMagiKindPanelSet(ctx, args) {
6451
7064
  note: `Dry-run only \u2014 no file written. This is a WHOLESALE replacement of the kind's slot list for mesh '${meshId}' (machine-local ~/.adhdev/meshes.json); the currentSlots would be fully replaced. Other meshes on this machine are unaffected. Re-run with write=true after explicit user approval.`
6452
7065
  }, null, 2);
6453
7066
  }
6454
- const slots = (0, import_daemon_core5.setMagiKindPanel)(kind, args.slots, meshId);
7067
+ const slots = (0, import_daemon_core6.setMagiKindPanel)(kind, args.slots, meshId);
6455
7068
  return JSON.stringify({
6456
7069
  success: true,
6457
7070
  written: true,
@@ -6472,9 +7085,9 @@ async function meshMagiKindPanelList(ctx, args = {}) {
6472
7085
  const only = readString(args.task_kind) || readString(args.kind);
6473
7086
  const meshId = ctx.mesh.id;
6474
7087
  const scope = magiPanelScope(meshId, ctx.mesh.name);
6475
- const all = (0, import_daemon_core5.listMagiKindPanels)(meshId);
7088
+ const all = (0, import_daemon_core6.listMagiKindPanels)(meshId);
6476
7089
  if (only) {
6477
- const slots = (0, import_daemon_core5.getMagiKindPanel)(only, meshId);
7090
+ const slots = (0, import_daemon_core6.getMagiKindPanel)(only, meshId);
6478
7091
  if (slots === void 0) {
6479
7092
  return JSON.stringify({
6480
7093
  success: false,
@@ -6507,7 +7120,7 @@ async function meshMagiReview(ctx, args) {
6507
7120
  const referenceSubmoduleKey = resolveMagiReferenceSubmoduleKey(ctx);
6508
7121
  const taskKind = normalizeMagiTaskKind(explicitTaskKind);
6509
7122
  const panelName = `(kind:${taskKind})`;
6510
- const slots = (0, import_daemon_core5.getMagiKindPanel)(taskKind, ctx.mesh.id);
7123
+ const slots = (0, import_daemon_core6.getMagiKindPanel)(taskKind, ctx.mesh.id);
6511
7124
  if (!slots || slots.length === 0) {
6512
7125
  return JSON.stringify({
6513
7126
  success: false,
@@ -6515,13 +7128,13 @@ async function meshMagiReview(ctx, args) {
6515
7128
  error: `No panel slots are configured for this task_kind in mesh '${ctx.mesh.id}' settings. Add at least one (machine + provider + model) slot in settings \u2014 task_kind '${taskKind}' has no configured kind-panel.`,
6516
7129
  taskKind,
6517
7130
  meshId: ctx.mesh.id,
6518
- configuredKinds: Object.keys((0, import_daemon_core5.listMagiKindPanels)(ctx.mesh.id)),
7131
+ configuredKinds: Object.keys((0, import_daemon_core6.listMagiKindPanels)(ctx.mesh.id)),
6519
7132
  hint: "Configure this kind in mesh settings (MagiKindPanelEditor), or set it with mesh_magi_kind_panel_set, then retry."
6520
7133
  }, null, 2);
6521
7134
  }
6522
7135
  let planSlots;
6523
7136
  try {
6524
- planSlots = (0, import_daemon_core5.normalizeMagiSlots)(slots);
7137
+ planSlots = (0, import_daemon_core6.normalizeMagiSlots)(slots);
6525
7138
  } catch (e) {
6526
7139
  return JSON.stringify({
6527
7140
  success: false,
@@ -6577,7 +7190,7 @@ async function meshMagiReview(ctx, args) {
6577
7190
  const waitTimeoutMs = Math.min(MAGI_MAX_WAIT_MS, Math.max(MAGI_POLL_INTERVAL_MS, Number(args.wait_timeout_ms ?? args.waitTimeoutMs) || MAGI_DEFAULT_WAIT_MS));
6578
7191
  const consensusGroupId = `magi_${(0, import_node_crypto.randomUUID)().replace(/-/g, "")}`;
6579
7192
  const titleQ = question.length > 80 ? `${question.slice(0, 77)}...` : question;
6580
- const mission = (0, import_daemon_core5.upsertMeshMission)(ctx.mesh.id, {
7193
+ const mission = (0, import_daemon_core6.upsertMeshMission)(ctx.mesh.id, {
6581
7194
  title: `MAGI: ${titleQ}`,
6582
7195
  goal: `Cross-verify (read-only) across panel '${panelName}': ${question}${args.target ? `
6583
7196
  Target: ${args.target}` : ""}`,
@@ -6589,7 +7202,7 @@ Target: ${args.target}` : ""}`,
6589
7202
  const replicaRecords = [];
6590
7203
  for (const replica of plan.replicas) {
6591
7204
  try {
6592
- const task = (0, import_daemon_core5.enqueueTask)(ctx.mesh.id, prompt, {
7205
+ const task = (0, import_daemon_core6.enqueueTask)(ctx.mesh.id, prompt, {
6593
7206
  readonly: true,
6594
7207
  taskMode: "live_debug_readonly",
6595
7208
  // DIFFICULTY-REQUIRED (MAGI decision): a fixed 'freeform' sentinel, NOT an
@@ -6618,7 +7231,7 @@ Target: ${args.target}` : ""}`,
6618
7231
  replicaRecords.push({ taskId: task.id, provider: replica.provider, targetNodeId: replica.targetNodeId, requiredTags: replica.requiredTags });
6619
7232
  } catch (e) {
6620
7233
  try {
6621
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
7234
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
6622
7235
  kind: "magi_replica_enqueue_failed",
6623
7236
  payload: { consensusGroupId, missionId: mission.id, provider: replica.provider, error: e?.message || String(e) }
6624
7237
  });
@@ -6708,7 +7321,7 @@ Target: ${args.target}` : ""}`,
6708
7321
  });
6709
7322
  closeMagiMissionIfTerminal(ctx, mission.id, collected.terminal);
6710
7323
  const cleanupMode = resolveMagiAutoCleanupMode(ctx, args.auto_cleanup ?? args.autoCleanup);
6711
- const cleanupReplicaTasks = findMagiReplicaTasks((0, import_daemon_core5.getQueue)(ctx.mesh.id), consensusGroupId);
7324
+ const cleanupReplicaTasks = findMagiReplicaTasks((0, import_daemon_core6.getQueue)(ctx.mesh.id), consensusGroupId);
6712
7325
  const cleanup = await cleanupMagiAutoLaunchedSessions(ctx, {
6713
7326
  replicaTasks: cleanupReplicaTasks,
6714
7327
  terminal: collected.terminal,
@@ -6738,7 +7351,7 @@ async function meshMagiCollect(ctx, args) {
6738
7351
  await refreshMeshFromDaemon(ctx);
6739
7352
  const explicitKind = args.task_kind ?? args.taskKind;
6740
7353
  const taskKind = explicitKind !== void 0 ? normalizeMagiTaskKind(explicitKind) : recoverMagiTaskKind(ctx, consensusGroupId);
6741
- const replicaTasks = findMagiReplicaTasks((0, import_daemon_core5.getQueue)(ctx.mesh.id), consensusGroupId);
7354
+ const replicaTasks = findMagiReplicaTasks((0, import_daemon_core6.getQueue)(ctx.mesh.id), consensusGroupId);
6742
7355
  if (replicaTasks.length === 0) {
6743
7356
  return JSON.stringify({
6744
7357
  success: false,
@@ -6842,7 +7455,7 @@ function computeMagiCleanupTargets(replicaTasks) {
6842
7455
  function resolveMagiAutoCleanupMode(ctx, perCallOverride) {
6843
7456
  if (perCallOverride === true) return "stop_and_delete";
6844
7457
  if (perCallOverride === false) return "preserve";
6845
- return (0, import_daemon_core6.resolveMagiSessionCleanupMode)(ctx.mesh?.policy?.magiSessionCleanup);
7458
+ return (0, import_daemon_core7.resolveMagiSessionCleanupMode)(ctx.mesh?.policy?.magiSessionCleanup);
6846
7459
  }
6847
7460
  async function cleanupMagiAutoLaunchedSessions(ctx, args) {
6848
7461
  if (args.mode === "preserve") return null;
@@ -6921,7 +7534,7 @@ function classifyStaleReplicas(annotatedTasks, terminal = MAGI_TERMINAL_STATUSES
6921
7534
  }
6922
7535
  function persistMagiDispatched(ctx, args) {
6923
7536
  try {
6924
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
7537
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
6925
7538
  kind: "magi_dispatched",
6926
7539
  payload: {
6927
7540
  source: "magi",
@@ -6941,7 +7554,7 @@ function persistMagiDispatched(ctx, args) {
6941
7554
  }
6942
7555
  function recoverMagiTaskKind(ctx, consensusGroupId) {
6943
7556
  try {
6944
- const entries = (0, import_daemon_core5.readLedgerEntries)(ctx.mesh.id, { kind: ["magi_dispatched"], tail: 200 });
7557
+ const entries = (0, import_daemon_core6.readLedgerEntries)(ctx.mesh.id, { kind: ["magi_dispatched"], tail: 200 });
6945
7558
  for (let i = entries.length - 1; i >= 0; i -= 1) {
6946
7559
  const payload = entries[i]?.payload;
6947
7560
  if (!payload || typeof payload !== "object") continue;
@@ -6965,7 +7578,7 @@ function stripRawAnswers(synthesis) {
6965
7578
  }
6966
7579
  function persistMagiSynthesis(ctx, args) {
6967
7580
  try {
6968
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
7581
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
6969
7582
  kind: "magi_synthesis",
6970
7583
  payload: {
6971
7584
  source: "magi",
@@ -6985,9 +7598,9 @@ function closeMagiMissionIfTerminal(ctx, missionId, terminal) {
6985
7598
  const id = readString(missionId);
6986
7599
  if (!id) return;
6987
7600
  try {
6988
- const mission = (0, import_daemon_core5.getMeshMission)(ctx.mesh.id, id);
7601
+ const mission = (0, import_daemon_core6.getMeshMission)(ctx.mesh.id, id);
6989
7602
  if (!mission || mission.status !== "active") return;
6990
- (0, import_daemon_core5.upsertMeshMission)(ctx.mesh.id, {
7603
+ (0, import_daemon_core6.upsertMeshMission)(ctx.mesh.id, {
6991
7604
  id,
6992
7605
  title: mission.title,
6993
7606
  // Preserve goal: upsert defaults goal to the existing value when omitted.
@@ -7022,8 +7635,8 @@ async function collectMagiResponses(ctx, args) {
7022
7635
  const candidates = collectMagiCandidateTexts(payload);
7023
7636
  const raw = candidates.find((c) => c.trim().length > 0);
7024
7637
  if (!raw) return;
7025
- if (raw.length > import_daemon_core5.MAGI_RAW_ANSWER_CAP) {
7026
- source.rawAnswer = raw.slice(0, import_daemon_core5.MAGI_RAW_ANSWER_CAP);
7638
+ if (raw.length > import_daemon_core6.MAGI_RAW_ANSWER_CAP) {
7639
+ source.rawAnswer = raw.slice(0, import_daemon_core6.MAGI_RAW_ANSWER_CAP);
7027
7640
  source.rawAnswerTruncated = true;
7028
7641
  } else {
7029
7642
  source.rawAnswer = raw;
@@ -7033,7 +7646,7 @@ async function collectMagiResponses(ctx, args) {
7033
7646
  };
7034
7647
  const buildSource = (task) => {
7035
7648
  const sourceNodeId = task.assignedNodeId || task.targetNodeId || void 0;
7036
- const gitRef = extractNodeGitRef(sourceNodeId ? ctx.mesh.nodes.find((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, sourceNodeId)) : void 0);
7649
+ const gitRef = extractNodeGitRef(sourceNodeId ? ctx.mesh.nodes.find((n) => (0, import_daemon_core6.meshNodeIdMatches)(n, sourceNodeId)) : void 0);
7037
7650
  return {
7038
7651
  taskId: task.id,
7039
7652
  nodeId: sourceNodeId,
@@ -7043,7 +7656,7 @@ async function collectMagiResponses(ctx, args) {
7043
7656
  };
7044
7657
  };
7045
7658
  const sendKindRetry = async (task, failReason) => {
7046
- const node = ctx.mesh.nodes.find((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, task.assignedNodeId));
7659
+ const node = ctx.mesh.nodes.find((n) => (0, import_daemon_core6.meshNodeIdMatches)(n, task.assignedNodeId));
7047
7660
  if (!node || !task.assignedSessionId) return false;
7048
7661
  const why = failReason === "empty_evidence" ? "your previous answer had an empty evidence array" : failReason === "missing_required_fields" ? "your previous answer was missing required fields" : "your previous answer did not parse as the required JSON";
7049
7662
  const message = `Your previous MAGI answer could not be accepted (${why}). Respond NOW with ONLY a single JSON object (no prose, no code fence) matching EXACTLY this schema, with non-empty evidence:
@@ -7069,7 +7682,7 @@ ${magiOutputContractFor(kind)}`;
7069
7682
  }
7070
7683
  });
7071
7684
  try {
7072
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
7685
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
7073
7686
  kind: "magi_replica_retry",
7074
7687
  payload: { taskId: task.id, kind, failReason }
7075
7688
  });
@@ -7081,7 +7694,7 @@ ${magiOutputContractFor(kind)}`;
7081
7694
  }
7082
7695
  };
7083
7696
  const nudgeWedgedReplica = async (task) => {
7084
- const node = ctx.mesh.nodes.find((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, task.assignedNodeId));
7697
+ const node = ctx.mesh.nodes.find((n) => (0, import_daemon_core6.meshNodeIdMatches)(n, task.assignedNodeId));
7085
7698
  if (!node || !task.assignedSessionId) return;
7086
7699
  try {
7087
7700
  const read = await commandForNode(ctx, node, "read_chat", {
@@ -7103,7 +7716,7 @@ ${magiOutputContractFor(kind)}`;
7103
7716
  action: "approve"
7104
7717
  });
7105
7718
  try {
7106
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
7719
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
7107
7720
  kind: "magi_replica_auto_approved",
7108
7721
  payload: { taskId: task.id, nodeId: task.assignedNodeId, status }
7109
7722
  });
@@ -7147,7 +7760,7 @@ ${magiOutputContractFor(kind)}`;
7147
7760
  }
7148
7761
  let kindResult;
7149
7762
  try {
7150
- const node = ctx.mesh.nodes.find((n) => (0, import_daemon_core5.meshNodeIdMatches)(n, task.assignedNodeId));
7763
+ const node = ctx.mesh.nodes.find((n) => (0, import_daemon_core6.meshNodeIdMatches)(n, task.assignedNodeId));
7151
7764
  if (!node) throw new Error("assigned node not in mesh");
7152
7765
  const result = await commandForNode(ctx, node, "read_chat", {
7153
7766
  sessionId: task.assignedSessionId,
@@ -7200,7 +7813,7 @@ ${magiOutputContractFor(kind)}`;
7200
7813
  return false;
7201
7814
  };
7202
7815
  for (; ; ) {
7203
- const tasks = annotateQueueStaleness((0, import_daemon_core5.getQueue)(ctx.mesh.id).filter((t) => ids.has(t.id)), ctx.mesh);
7816
+ const tasks = annotateQueueStaleness((0, import_daemon_core6.getQueue)(ctx.mesh.id).filter((t) => ids.has(t.id)), ctx.mesh);
7204
7817
  const allPresent = tasks.length === ids.size;
7205
7818
  const { staleTaskIds: staleTaskIds2, staleReasons: staleReasons2 } = classifyStaleReplicas(tasks, TERMINAL);
7206
7819
  const pastDeadline = Date.now() >= deadline;
@@ -7214,7 +7827,7 @@ ${magiOutputContractFor(kind)}`;
7214
7827
  if (allPresent && outstanding.length > 0 && outstanding.every((t) => staleTaskIds2.has(t.id))) break;
7215
7828
  await sleep(Math.min(MAGI_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
7216
7829
  }
7217
- const finalTasks = annotateQueueStaleness((0, import_daemon_core5.getQueue)(ctx.mesh.id).filter((t) => ids.has(t.id)), ctx.mesh);
7830
+ const finalTasks = annotateQueueStaleness((0, import_daemon_core6.getQueue)(ctx.mesh.id).filter((t) => ids.has(t.id)), ctx.mesh);
7218
7831
  const { staleTaskIds, staleReasons } = classifyStaleReplicas(finalTasks, TERMINAL);
7219
7832
  const presentIds = new Set(finalTasks.map((t) => t.id));
7220
7833
  for (const task of finalTasks) {
@@ -7409,7 +8022,7 @@ async function meshNodeSlotsPropose(ctx, args = {}) {
7409
8022
  }
7410
8023
 
7411
8024
  // src/tools/mesh-tools-session.ts
7412
- var import_daemon_core7 = require("@adhdev/daemon-core");
8025
+ var import_daemon_core8 = require("@adhdev/daemon-core");
7413
8026
  function computeIdleDispatchAckRisk(sessionWasIdle, dispatchPreRecorded, sessionId) {
7414
8027
  if (!sessionWasIdle || dispatchPreRecorded) return {};
7415
8028
  return {
@@ -7423,11 +8036,11 @@ async function meshPruneStaleDirect(ctx, args = {}) {
7423
8036
  const execute = args.execute === true && args.dry_run !== true;
7424
8037
  const includeTerminal = args.include_terminal === true;
7425
8038
  const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
7426
- const ledgerEntries = (0, import_daemon_core5.readLedgerEntries)(ctx.mesh.id, { tail: 500 });
7427
- const directDispatches = (0, import_daemon_core5.getActiveDirectDispatches)(ctx.mesh.id);
7428
- const result = (0, import_daemon_core5.pruneStaleDirectDispatches)({
8039
+ const ledgerEntries = (0, import_daemon_core6.readLedgerEntries)(ctx.mesh.id, { tail: 500 });
8040
+ const directDispatches = (0, import_daemon_core6.getActiveDirectDispatches)(ctx.mesh.id);
8041
+ const result = (0, import_daemon_core6.pruneStaleDirectDispatches)({
7429
8042
  meshId: ctx.mesh.id,
7430
- queue: (0, import_daemon_core5.getQueue)(ctx.mesh.id),
8043
+ queue: (0, import_daemon_core6.getQueue)(ctx.mesh.id),
7431
8044
  ledgerEntries,
7432
8045
  directDispatches,
7433
8046
  nodes: liveNodes,
@@ -7478,7 +8091,7 @@ async function meshSendTask(ctx, args) {
7478
8091
  const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
7479
8092
  const readonly = args.readonly === true || args.read_only === true;
7480
8093
  const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
7481
- if (missionId && !(0, import_daemon_core5.getMeshMission)(ctx.mesh.id, missionId)) {
8094
+ if (missionId && !(0, import_daemon_core6.getMeshMission)(ctx.mesh.id, missionId)) {
7482
8095
  return JSON.stringify({
7483
8096
  success: false,
7484
8097
  code: "mission_not_found",
@@ -7496,7 +8109,7 @@ async function meshSendTask(ctx, args) {
7496
8109
  });
7497
8110
  }
7498
8111
  const difficulty = difficultyRaw;
7499
- const modeValidation = (0, import_daemon_core5.validateMeshTaskModeRequest)(requestedTaskMode, message, readonly);
8112
+ const modeValidation = (0, import_daemon_core6.validateMeshTaskModeRequest)(requestedTaskMode, message, readonly);
7500
8113
  if (!modeValidation.valid) {
7501
8114
  return JSON.stringify({
7502
8115
  success: false,
@@ -7507,7 +8120,7 @@ async function meshSendTask(ctx, args) {
7507
8120
  // can see what tripped the guard instead of rewording blind.
7508
8121
  ...modeValidation.violationDetails ? { violationDetails: modeValidation.violationDetails } : {},
7509
8122
  allowedOperations: modeValidation.allowedOperations,
7510
- error: (0, import_daemon_core5.buildMeshTaskModeViolationError)(modeValidation)
8123
+ error: (0, import_daemon_core6.buildMeshTaskModeViolationError)(modeValidation)
7511
8124
  });
7512
8125
  }
7513
8126
  const taskMode = modeValidation.taskMode;
@@ -7611,7 +8224,7 @@ async function meshSendTask(ctx, args) {
7611
8224
  const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
7612
8225
  try {
7613
8226
  const providerType = result2.providerType || cached?.providerType;
7614
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
8227
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
7615
8228
  kind: "task_dispatched",
7616
8229
  nodeId: args.node_id,
7617
8230
  sessionId: dispatchedSessionId,
@@ -7629,7 +8242,7 @@ async function meshSendTask(ctx, args) {
7629
8242
  ...coordinatorDaemonId ? { coordinatorDaemonId } : {}
7630
8243
  })
7631
8244
  });
7632
- (0, import_daemon_core5.insertDirectDispatch)(ctx.mesh.id, {
8245
+ (0, import_daemon_core6.insertDirectDispatch)(ctx.mesh.id, {
7633
8246
  taskId,
7634
8247
  nodeId: args.node_id,
7635
8248
  sessionId: dispatchedSessionId,
@@ -7639,7 +8252,7 @@ async function meshSendTask(ctx, args) {
7639
8252
  via: "p2p_direct",
7640
8253
  dispatchedAt
7641
8254
  });
7642
- (0, import_daemon_core5.recordDirectDispatchTask)(ctx.mesh.id, message, {
8255
+ (0, import_daemon_core6.recordDirectDispatchTask)(ctx.mesh.id, message, {
7643
8256
  id: taskId,
7644
8257
  ...missionId ? { missionId } : {},
7645
8258
  assignedNodeId: args.node_id,
@@ -7744,7 +8357,7 @@ async function meshSendTask(ctx, args) {
7744
8357
  const { resolveDeliveryDecision } = await import("@adhdev/daemon-core");
7745
8358
  const policyResult = resolveDeliveryDecision(sessionStatus, { kind: "task" });
7746
8359
  if (policyResult.decision === "queued") {
7747
- const queuedTask = (0, import_daemon_core5.enqueueTask)(ctx.mesh.id, message, {
8360
+ const queuedTask = (0, import_daemon_core6.enqueueTask)(ctx.mesh.id, message, {
7748
8361
  targetNodeId: args.node_id,
7749
8362
  targetSessionId: args.session_id,
7750
8363
  taskMode,
@@ -7774,7 +8387,7 @@ async function meshSendTask(ctx, args) {
7774
8387
  const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
7775
8388
  const coordinatorDaemonId = resolveCoordinatorDaemonId(ctx);
7776
8389
  try {
7777
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
8390
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
7778
8391
  kind: "task_dispatched",
7779
8392
  nodeId: args.node_id,
7780
8393
  sessionId: args.session_id,
@@ -7795,7 +8408,7 @@ async function meshSendTask(ctx, args) {
7795
8408
  });
7796
8409
  } catch {
7797
8410
  }
7798
- (0, import_daemon_core5.insertDirectDispatch)(ctx.mesh.id, {
8411
+ (0, import_daemon_core6.insertDirectDispatch)(ctx.mesh.id, {
7799
8412
  taskId,
7800
8413
  nodeId: args.node_id,
7801
8414
  sessionId: args.session_id,
@@ -7808,7 +8421,7 @@ async function meshSendTask(ctx, args) {
7808
8421
  });
7809
8422
  let dispatchPreRecorded = false;
7810
8423
  try {
7811
- dispatchPreRecorded = (0, import_daemon_core5.getActiveDirectDispatches)(ctx.mesh.id).some((d) => d.taskId === taskId);
8424
+ dispatchPreRecorded = (0, import_daemon_core6.getActiveDirectDispatches)(ctx.mesh.id).some((d) => d.taskId === taskId);
7812
8425
  } catch {
7813
8426
  }
7814
8427
  const dispatchResult = await commandForNode(ctx, node, "agent_command", {
@@ -7832,7 +8445,7 @@ async function meshSendTask(ctx, args) {
7832
8445
  const dispatchPayload = unwrapCommandPayload(dispatchResult);
7833
8446
  if (dispatchPayload?.success === false || dispatchResult?.success === false) {
7834
8447
  try {
7835
- (0, import_daemon_core5.deleteDirectDispatchesByTaskId)(ctx.mesh.id, [taskId]);
8448
+ (0, import_daemon_core6.deleteDirectDispatchesByTaskId)(ctx.mesh.id, [taskId]);
7836
8449
  } catch {
7837
8450
  }
7838
8451
  dispatchPreRecorded = false;
@@ -7846,7 +8459,7 @@ async function meshSendTask(ctx, args) {
7846
8459
  });
7847
8460
  }
7848
8461
  try {
7849
- (0, import_daemon_core5.recordDirectDispatchTask)(ctx.mesh.id, message, {
8462
+ (0, import_daemon_core6.recordDirectDispatchTask)(ctx.mesh.id, message, {
7850
8463
  id: taskId,
7851
8464
  ...missionId ? { missionId } : {},
7852
8465
  assignedNodeId: args.node_id,
@@ -7892,7 +8505,7 @@ async function meshSendTask(ctx, args) {
7892
8505
  ...buildMissionInactiveWarning(ctx, missionId) ?? {}
7893
8506
  });
7894
8507
  }
7895
- const task = (0, import_daemon_core5.enqueueTask)(ctx.mesh.id, message, {
8508
+ const task = (0, import_daemon_core6.enqueueTask)(ctx.mesh.id, message, {
7896
8509
  targetNodeId: args.node_id,
7897
8510
  targetSessionId: args.session_id,
7898
8511
  taskMode,
@@ -7902,7 +8515,7 @@ async function meshSendTask(ctx, args) {
7902
8515
  ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {}
7903
8516
  });
7904
8517
  const queueTrigger = await triggerMeshQueueAndReport(ctx);
7905
- const pendingEvents = (0, import_daemon_core5.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId);
8518
+ const pendingEvents = (0, import_daemon_core6.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId);
7906
8519
  const result = {
7907
8520
  success: true,
7908
8521
  source: "queue",
@@ -7948,7 +8561,7 @@ async function meshReadChat(ctx, args) {
7948
8561
  tailLimit: args.tail ?? 10
7949
8562
  });
7950
8563
  } catch (e) {
7951
- if (isLocalNode || !(0, import_daemon_core5.isP2pRelayTransportFailure)(e)) throw e;
8564
+ if (isLocalNode || !(0, import_daemon_core6.isP2pRelayTransportFailure)(e)) throw e;
7952
8565
  return buildMeshReadChatCacheFallback(ctx, args, node, e);
7953
8566
  }
7954
8567
  const payload = annotateRapidReadChatAdvisory(unwrapCommandPayload(result), {
@@ -8037,7 +8650,7 @@ async function meshSendKeys(ctx, args) {
8037
8650
  const auditKeys = requestedKeys.slice(0, 64);
8038
8651
  const recordAudit = (result2, extra = {}) => {
8039
8652
  try {
8040
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
8653
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
8041
8654
  kind: "key_injection",
8042
8655
  nodeId: args.node_id,
8043
8656
  sessionId: args.session_id,
@@ -8054,7 +8667,7 @@ async function meshSendKeys(ctx, args) {
8054
8667
  }
8055
8668
  };
8056
8669
  if (hasDestructive) {
8057
- const policyAllows = (0, import_daemon_core5.resolveAllowSendKeysDestructive)(ctx.mesh.policy, node.policy);
8670
+ const policyAllows = (0, import_daemon_core6.resolveAllowSendKeysDestructive)(ctx.mesh.policy, node.policy);
8058
8671
  if (args.confirm_destructive !== true || !policyAllows) {
8059
8672
  recordAudit("refused", { refused: "destructive_gate", policyAllows });
8060
8673
  return JSON.stringify({
@@ -8104,7 +8717,7 @@ async function meshLaunchSession(ctx, args) {
8104
8717
  supportedProviders: slotProviders
8105
8718
  }, null, 2);
8106
8719
  }
8107
- const explicitBlock = (0, import_daemon_core7.evaluateProviderQuotaGate)(node, requestedType, ctx.mesh.policy?.quotaRouting ?? null);
8720
+ const explicitBlock = (0, import_daemon_core8.evaluateProviderQuotaGate)(node, requestedType, ctx.mesh.policy?.quotaRouting ?? null);
8108
8721
  if (explicitBlock) {
8109
8722
  explicitTypeQuotaWarning = {
8110
8723
  quotaWarning: `Provider '${requestedType}' on node '${args.node_id}' is quota-gated (${explicitBlock.reason}; ${explicitBlock.window} window at ${explicitBlock.remainingPercent}% remaining, threshold ${explicitBlock.thresholdPercent}%). Launching anyway because the type was requested explicitly \u2014 the session may fail immediately if the provider rejects on quota.`,
@@ -8142,7 +8755,7 @@ async function meshLaunchSession(ctx, args) {
8142
8755
  failed.push(`${providerType}: ${detectedPayload?.error || "not detected"}`);
8143
8756
  }
8144
8757
  if (detectedCandidates.length) {
8145
- const ranked = (0, import_daemon_core7.rankProvidersByQuotaGate)(node, detectedCandidates, ctx.mesh.policy?.quotaRouting ?? null);
8758
+ const ranked = (0, import_daemon_core8.rankProvidersByQuotaGate)(node, detectedCandidates, ctx.mesh.policy?.quotaRouting ?? null);
8146
8759
  if (ranked.clear.length) {
8147
8760
  resolvedProviderType = ranked.clear[0];
8148
8761
  } else {
@@ -8173,14 +8786,14 @@ async function meshLaunchSession(ctx, args) {
8173
8786
  const coordinatorNode = resolveCoordinatorNode(ctx);
8174
8787
  const coordinatorDaemonId = resolveCoordinatorDaemonId(ctx);
8175
8788
  const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
8176
- const delegatedWorkerAutoApprove = (0, import_daemon_core5.resolveDelegatedWorkerAutoApprove)(ctx.mesh.policy, node.policy);
8789
+ const delegatedWorkerAutoApprove = (0, import_daemon_core6.resolveDelegatedWorkerAutoApprove)(ctx.mesh.policy, node.policy);
8177
8790
  let requestedAutoApproveMode;
8178
- const delegatedWorkerDangerousModeAllow = (0, import_daemon_core5.resolveDelegatedWorkerDangerousModeAllow)(ctx.mesh.policy, node.policy);
8791
+ const delegatedWorkerDangerousModeAllow = (0, import_daemon_core6.resolveDelegatedWorkerDangerousModeAllow)(ctx.mesh.policy, node.policy);
8179
8792
  if (delegatedWorkerAutoApprove !== false) {
8180
8793
  try {
8181
8794
  const ws = typeof node.workspace === "string" && node.workspace.trim() ? node.workspace.trim() : "";
8182
8795
  if (ws) {
8183
- const repo = (0, import_daemon_core5.loadRepoMeshJsonConfig)(ws);
8796
+ const repo = (0, import_daemon_core6.loadRepoMeshJsonConfig)(ws);
8184
8797
  const repoMode = repo.sourceType === "repo_file" ? repo.config?.providerDefaults?.autoApproveModes?.[resolvedProviderType] : void 0;
8185
8798
  if (typeof repoMode === "string" && repoMode.trim()) requestedAutoApproveMode = repoMode.trim();
8186
8799
  }
@@ -8277,7 +8890,7 @@ async function meshLaunchSession(ctx, args) {
8277
8890
  });
8278
8891
  }
8279
8892
  try {
8280
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
8893
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
8281
8894
  kind: "session_launched",
8282
8895
  nodeId: args.node_id,
8283
8896
  sessionId: runtimeSessionId || void 0,
@@ -8331,26 +8944,26 @@ async function meshAnswerQuestion(ctx, args) {
8331
8944
  return JSON.stringify(result, null, 2);
8332
8945
  }
8333
8946
  async function meshListPendingApprovals(ctx, _args = {}) {
8334
- (0, import_daemon_core5.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_list_pending_approvals" });
8947
+ (0, import_daemon_core6.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_list_pending_approvals" });
8335
8948
  await refreshMeshFromDaemon(ctx);
8336
8949
  const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
8337
- let ledgerEntries = (0, import_daemon_core5.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
8338
- let directDispatches = (0, import_daemon_core5.getActiveDirectDispatches)(ctx.mesh.id);
8950
+ let ledgerEntries = (0, import_daemon_core6.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
8951
+ let directDispatches = (0, import_daemon_core6.getActiveDirectDispatches)(ctx.mesh.id);
8339
8952
  const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries);
8340
8953
  if (directReconciliation.reconciled > 0) {
8341
- ledgerEntries = (0, import_daemon_core5.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
8342
- directDispatches = (0, import_daemon_core5.getActiveDirectDispatches)(ctx.mesh.id);
8954
+ ledgerEntries = (0, import_daemon_core6.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
8955
+ directDispatches = (0, import_daemon_core6.getActiveDirectDispatches)(ctx.mesh.id);
8343
8956
  }
8344
- (0, import_daemon_core5.markStaleDirectDispatches)(ctx.mesh.id);
8345
- directDispatches = (0, import_daemon_core5.getActiveDirectDispatches)(ctx.mesh.id);
8346
- const activeWorkEvidence = (0, import_daemon_core5.buildMeshActiveWork)({
8957
+ (0, import_daemon_core6.markStaleDirectDispatches)(ctx.mesh.id);
8958
+ directDispatches = (0, import_daemon_core6.getActiveDirectDispatches)(ctx.mesh.id);
8959
+ const activeWorkEvidence = (0, import_daemon_core6.buildMeshActiveWork)({
8347
8960
  meshId: ctx.mesh.id,
8348
- queue: (0, import_daemon_core5.getQueue)(ctx.mesh.id),
8961
+ queue: (0, import_daemon_core6.getQueue)(ctx.mesh.id),
8349
8962
  ledgerEntries,
8350
8963
  directDispatches,
8351
8964
  nodes: liveNodes
8352
8965
  });
8353
- const approvals = (0, import_daemon_core5.collectPendingApprovals)(activeWorkEvidence.activeWork);
8966
+ const approvals = (0, import_daemon_core6.collectPendingApprovals)(activeWorkEvidence.activeWork);
8354
8967
  return JSON.stringify({
8355
8968
  count: approvals.length,
8356
8969
  approvals,
@@ -8504,7 +9117,7 @@ async function meshRestartDaemon(ctx, args) {
8504
9117
  const targetDaemonId = typeof rawTarget.daemonId === "string" && rawTarget.daemonId.trim() ? rawTarget.daemonId.trim() : "unknown";
8505
9118
  const targetNpmTag = typeof rawTarget.npmTag === "string" && rawTarget.npmTag.trim() ? rawTarget.npmTag.trim() : typeof payload?.npmTag === "string" && payload.npmTag.trim() ? payload.npmTag.trim() : "unknown";
8506
9119
  const trackMismatch = meshAttachedTrack === "unknown" || targetTrack === "unknown" ? null : meshAttachedTrack !== targetTrack;
8507
- const daemonMismatch = meshAttachedDaemonId === "unknown" || targetDaemonId === "unknown" ? null : !(0, import_daemon_core5.daemonIdsEquivalent)(meshAttachedDaemonId, targetDaemonId);
9120
+ const daemonMismatch = meshAttachedDaemonId === "unknown" || targetDaemonId === "unknown" ? null : !(0, import_daemon_core6.daemonIdsEquivalent)(meshAttachedDaemonId, targetDaemonId);
8508
9121
  const routingMismatch = trackMismatch === true || daemonMismatch === true;
8509
9122
  return JSON.stringify({
8510
9123
  ...payload,
@@ -8558,7 +9171,7 @@ async function meshCheckpoint(ctx, args) {
8558
9171
  includeUntracked: true
8559
9172
  });
8560
9173
  try {
8561
- (0, import_daemon_core5.appendLedgerEntry)(ctx.mesh.id, {
9174
+ (0, import_daemon_core6.appendLedgerEntry)(ctx.mesh.id, {
8562
9175
  kind: "checkpoint_created",
8563
9176
  nodeId: args.node_id,
8564
9177
  payload: {
@@ -10256,6 +10869,15 @@ async function startMcpServer(opts) {
10256
10869
  case "mesh_view_queue":
10257
10870
  text = await meshViewQueue(meshCtx, a);
10258
10871
  break;
10872
+ case "mesh_graph_view":
10873
+ text = await meshGraphView(meshCtx, a);
10874
+ break;
10875
+ case "mesh_graph_gate_claim":
10876
+ text = await meshGraphGateClaim(meshCtx, a);
10877
+ break;
10878
+ case "mesh_graph_gate_release":
10879
+ text = await meshGraphGateRelease(meshCtx, a);
10880
+ break;
10259
10881
  case "mesh_queue_cancel":
10260
10882
  text = await meshQueueCancel(meshCtx, a);
10261
10883
  break;