@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.
- package/dist/index.js +6187 -889
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/vendor/mcp-server/index.js +832 -210
- package/vendor/mcp-server/index.js.map +1 -1
|
@@ -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] ??
|
|
81
|
-
IPC_COMMAND_TIMEOUTS_MS[nestedCommand] ??
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
1989
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
4102
|
+
const drainerIdentity = (0, import_daemon_core5.coordinatorIdentityFromEmitFields)({
|
|
3863
4103
|
daemonId: ctx.localDaemonId,
|
|
3864
4104
|
sessionId: ctx.coordinatorSessionId
|
|
3865
4105
|
});
|
|
3866
|
-
const events = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
3997
|
-
const schedulingRuntime = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
4167
|
-
let directDispatches = (0,
|
|
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,
|
|
4171
|
-
directDispatches = (0,
|
|
4172
|
-
ledgerSummary = (0,
|
|
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,
|
|
4414
|
+
const activeWorkEvidence = (0, import_daemon_core6.buildMeshActiveWork)({
|
|
4175
4415
|
meshId: mesh.id,
|
|
4176
|
-
queue: (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
4752
|
+
const magiActivity = (0, import_daemon_core6.buildMeshMagiActivity)({ meshId: mesh.id, ledgerEntries });
|
|
4513
4753
|
if (magiActivity.length > 0) {
|
|
4514
|
-
const fold = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
5322
|
+
(0, import_daemon_core6.getQueue)(ctx.mesh.id).map((t) => [t.id, t.status])
|
|
4812
5323
|
);
|
|
4813
|
-
const eagerPushDeferred = !(0,
|
|
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 >
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4980
|
-
|
|
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((
|
|
4985
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
5048
|
-
let directDispatches = (0,
|
|
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,
|
|
5052
|
-
directDispatches = (0,
|
|
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,
|
|
5055
|
-
directDispatches = (0,
|
|
5056
|
-
const activeWorkEvidence = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
5337
|
-
const summary = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
5442
|
-
return (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
5507
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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) => !
|
|
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: ${
|
|
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,
|
|
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
|
|
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,
|
|
6142
|
-
const requiredTags = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
6780
|
+
const launchableCandidates = candidateNodes.filter((n) => (0, import_daemon_core6.isMeshNodeHealthLaunchable)(n));
|
|
6168
6781
|
if (launchableCandidates.length === 0) {
|
|
6169
|
-
const health = (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,
|
|
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,
|
|
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,
|
|
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,
|
|
7053
|
+
const current = (0, import_daemon_core6.getMagiKindPanel)(kind, meshId) ?? [];
|
|
6441
7054
|
if (!write) {
|
|
6442
|
-
const preview = (0,
|
|
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,
|
|
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,
|
|
7088
|
+
const all = (0, import_daemon_core6.listMagiKindPanels)(meshId);
|
|
6476
7089
|
if (only) {
|
|
6477
|
-
const slots = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
7601
|
+
const mission = (0, import_daemon_core6.getMeshMission)(ctx.mesh.id, id);
|
|
6989
7602
|
if (!mission || mission.status !== "active") return;
|
|
6990
|
-
(0,
|
|
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 >
|
|
7026
|
-
source.rawAnswer = raw.slice(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
7427
|
-
const directDispatches = (0,
|
|
7428
|
-
const result = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
8789
|
+
const delegatedWorkerAutoApprove = (0, import_daemon_core6.resolveDelegatedWorkerAutoApprove)(ctx.mesh.policy, node.policy);
|
|
8177
8790
|
let requestedAutoApproveMode;
|
|
8178
|
-
const delegatedWorkerDangerousModeAllow = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
8338
|
-
let directDispatches = (0,
|
|
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,
|
|
8342
|
-
directDispatches = (0,
|
|
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,
|
|
8345
|
-
directDispatches = (0,
|
|
8346
|
-
const activeWorkEvidence = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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;
|