@adhdev/daemon-standalone 0.9.82-rc.18 → 0.9.82-rc.181
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 +53899 -25564
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/public/assets/index-BMe8-Qvw.js +99 -0
- package/public/assets/index-BSeeeP5M.css +1 -0
- package/public/assets/{terminal-CfAvHQvJ.js → terminal-C45SzZMr.js} +4 -4
- package/public/assets/vendor-DNk1FT1R.js +2768 -0
- package/public/index.html +3 -3
- package/public/snake.html +229 -0
- package/vendor/mcp-server/index.js +1211 -172
- package/vendor/mcp-server/index.js.map +1 -1
- package/vendor/mcp-server/package.json +1 -1
- package/vendor/session-host-daemon/index.js +78 -2
- package/vendor/session-host-daemon/index.js.map +1 -1
- package/vendor/session-host-daemon/index.mjs +78 -2
- package/vendor/session-host-daemon/index.mjs.map +1 -1
- package/public/assets/index-BfLqOvWH.js +0 -98
- package/public/assets/index-DYP5yBTS.css +0 -1
- package/public/assets/vendor-CgiI0UIA.js +0 -2745
|
@@ -35,9 +35,128 @@ __export(index_exports, {
|
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
|
|
38
|
+
// src/tools/mesh-tools.ts
|
|
39
|
+
var import_node_crypto = require("crypto");
|
|
40
|
+
|
|
38
41
|
// src/transports/ipc.ts
|
|
39
42
|
var DEFAULT_IPC_PORT = 19222;
|
|
40
43
|
var DEFAULT_IPC_PATH = "/ipc";
|
|
44
|
+
var DEFAULT_IPC_COMMAND_TIMEOUT_MS = 15e3;
|
|
45
|
+
var IPC_COMMAND_TIMEOUTS_MS = {
|
|
46
|
+
mesh_relay_command: 12e4,
|
|
47
|
+
agent_command: 3e4,
|
|
48
|
+
git_status: 45e3,
|
|
49
|
+
git_diff_summary: 45e3,
|
|
50
|
+
fast_forward_mesh_node: 12e4,
|
|
51
|
+
mesh_status: 12e4
|
|
52
|
+
};
|
|
53
|
+
var WS_CONNECTING = 0;
|
|
54
|
+
var WS_OPEN = 1;
|
|
55
|
+
var POOL_IDLE_EVICT_MS = 5 * 6e4;
|
|
56
|
+
var POOL_MAX_AGE_MS = 10 * 6e4;
|
|
57
|
+
var connectionPool = /* @__PURE__ */ new Map();
|
|
58
|
+
function buildRequestId() {
|
|
59
|
+
return `mcp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
60
|
+
}
|
|
61
|
+
function getTimeoutMs(type, nestedCommand) {
|
|
62
|
+
return Math.max(
|
|
63
|
+
IPC_COMMAND_TIMEOUTS_MS[type] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS,
|
|
64
|
+
IPC_COMMAND_TIMEOUTS_MS[nestedCommand] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
function getOrCreateConnection(WebSocketCtor, url) {
|
|
68
|
+
const existing = connectionPool.get(url);
|
|
69
|
+
if (existing) {
|
|
70
|
+
const { readyState } = existing.ws;
|
|
71
|
+
const now2 = Date.now();
|
|
72
|
+
const isAlive = readyState === WS_CONNECTING || readyState === WS_OPEN;
|
|
73
|
+
const isIdle = now2 - existing.lastUsedAt > POOL_IDLE_EVICT_MS && existing.pending.size === 0;
|
|
74
|
+
const isTooOld = now2 - existing.createdAt > POOL_MAX_AGE_MS && existing.pending.size === 0;
|
|
75
|
+
if (isAlive && !isIdle && !isTooOld) {
|
|
76
|
+
return existing;
|
|
77
|
+
}
|
|
78
|
+
if (isAlive && (isIdle || isTooOld)) {
|
|
79
|
+
try {
|
|
80
|
+
existing.ws.close();
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
connectionPool.delete(url);
|
|
84
|
+
}
|
|
85
|
+
connectionPool.delete(url);
|
|
86
|
+
}
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
const conn = {
|
|
89
|
+
ws: new WebSocketCtor(url),
|
|
90
|
+
ready: false,
|
|
91
|
+
commandQueue: [],
|
|
92
|
+
pending: /* @__PURE__ */ new Map(),
|
|
93
|
+
lastUsedAt: now,
|
|
94
|
+
createdAt: now
|
|
95
|
+
};
|
|
96
|
+
connectionPool.set(url, conn);
|
|
97
|
+
const drainQueue = () => {
|
|
98
|
+
conn.ready = true;
|
|
99
|
+
for (const { type, args, requestId } of conn.commandQueue) {
|
|
100
|
+
conn.ws.send(JSON.stringify({ type: "ext:command", payload: { command: type, args, requestId } }));
|
|
101
|
+
}
|
|
102
|
+
conn.commandQueue = [];
|
|
103
|
+
};
|
|
104
|
+
let tornDown = false;
|
|
105
|
+
const teardown = (error) => {
|
|
106
|
+
if (tornDown) return;
|
|
107
|
+
tornDown = true;
|
|
108
|
+
connectionPool.delete(url);
|
|
109
|
+
conn.ready = false;
|
|
110
|
+
for (const [, req] of conn.pending) {
|
|
111
|
+
clearTimeout(req.timer);
|
|
112
|
+
req.reject(error);
|
|
113
|
+
}
|
|
114
|
+
conn.pending.clear();
|
|
115
|
+
conn.commandQueue = [];
|
|
116
|
+
};
|
|
117
|
+
conn.ws.addEventListener("open", () => {
|
|
118
|
+
conn.ws.send(JSON.stringify({
|
|
119
|
+
type: "ext:register",
|
|
120
|
+
payload: {
|
|
121
|
+
ideType: "mcp-server",
|
|
122
|
+
ideVersion: "1.0.0",
|
|
123
|
+
extensionVersion: "1.0.0",
|
|
124
|
+
instanceId: `mcp-server-${process.pid}`,
|
|
125
|
+
machineId: "mcp-server",
|
|
126
|
+
workspaceFolders: []
|
|
127
|
+
}
|
|
128
|
+
}));
|
|
129
|
+
});
|
|
130
|
+
conn.ws.addEventListener("message", (event) => {
|
|
131
|
+
try {
|
|
132
|
+
const raw = typeof event.data === "string" ? event.data : String(event.data);
|
|
133
|
+
const msg = JSON.parse(raw);
|
|
134
|
+
if (msg?.type === "daemon:welcome") {
|
|
135
|
+
drainQueue();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (msg?.type !== "ext:command_result") return;
|
|
139
|
+
const req = conn.pending.get(msg?.payload?.requestId);
|
|
140
|
+
if (!req) return;
|
|
141
|
+
conn.pending.delete(msg.payload.requestId);
|
|
142
|
+
clearTimeout(req.timer);
|
|
143
|
+
const payload = msg.payload;
|
|
144
|
+
if (payload?.success === false) {
|
|
145
|
+
req.reject(new Error(payload.error || "Daemon IPC command failed"));
|
|
146
|
+
} else {
|
|
147
|
+
req.resolve(payload?.result ?? payload);
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
conn.ws.addEventListener("error", () => {
|
|
153
|
+
teardown(new Error(`Cannot connect to daemon IPC at ${url}`));
|
|
154
|
+
});
|
|
155
|
+
conn.ws.addEventListener("close", () => {
|
|
156
|
+
teardown(new Error(`Daemon IPC connection closed: ${url}`));
|
|
157
|
+
});
|
|
158
|
+
return conn;
|
|
159
|
+
}
|
|
41
160
|
var IpcTransport = class {
|
|
42
161
|
port;
|
|
43
162
|
path;
|
|
@@ -66,73 +185,41 @@ var IpcTransport = class {
|
|
|
66
185
|
args
|
|
67
186
|
});
|
|
68
187
|
}
|
|
69
|
-
|
|
188
|
+
sendIpcCommand(type, args) {
|
|
70
189
|
const WebSocketCtor = globalThis.WebSocket;
|
|
71
190
|
if (!WebSocketCtor) {
|
|
72
|
-
|
|
191
|
+
return Promise.reject(new Error("WebSocket is not available in this Node runtime; Node 20+ is required for daemon IPC mode"));
|
|
73
192
|
}
|
|
193
|
+
const requestId = buildRequestId();
|
|
194
|
+
const nestedCommand = typeof args?.command === "string" ? args.command : "";
|
|
195
|
+
const timeoutMs = getTimeoutMs(type, nestedCommand);
|
|
196
|
+
const targetDaemonId = typeof args?.targetDaemonId === "string" ? args.targetDaemonId : "";
|
|
197
|
+
const diagnosticParts = [
|
|
198
|
+
`command='${type}'`,
|
|
199
|
+
...nestedCommand ? [`relayedCommand='${nestedCommand}'`] : [],
|
|
200
|
+
...targetDaemonId ? [`targetDaemonId='${targetDaemonId.slice(0, 12)}'`] : [],
|
|
201
|
+
...typeof args?.nodeId === "string" ? [`nodeId='${args.nodeId}'`] : [],
|
|
202
|
+
...typeof args?.workspace === "string" ? [`workspace='${args.workspace}'`] : []
|
|
203
|
+
];
|
|
204
|
+
const url = `ws://127.0.0.1:${this.port}${this.path}`;
|
|
74
205
|
return new Promise((resolve, reject) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
} catch {
|
|
85
|
-
}
|
|
86
|
-
fn();
|
|
87
|
-
};
|
|
88
|
-
const timeoutMs = type === "mesh_relay_command" ? 6e4 : 15e3;
|
|
89
|
-
const timeout = setTimeout(() => {
|
|
90
|
-
finish(() => reject(new Error(`Daemon IPC command '${type}' timed out after ${Math.round(timeoutMs / 1e3)}s`)));
|
|
206
|
+
let conn;
|
|
207
|
+
try {
|
|
208
|
+
conn = getOrCreateConnection(WebSocketCtor, url);
|
|
209
|
+
} catch (e) {
|
|
210
|
+
return reject(new Error(`Failed to create IPC connection: ${e?.message || e}`));
|
|
211
|
+
}
|
|
212
|
+
const timer = setTimeout(() => {
|
|
213
|
+
conn.pending.delete(requestId);
|
|
214
|
+
reject(new Error(`Daemon IPC ${diagnosticParts.join(" ")} timed out after ${Math.round(timeoutMs / 1e3)}s (requestId=${requestId})`));
|
|
91
215
|
}, timeoutMs);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}));
|
|
100
|
-
};
|
|
101
|
-
ws.addEventListener("open", () => {
|
|
102
|
-
ws.send(JSON.stringify({
|
|
103
|
-
type: "ext:register",
|
|
104
|
-
payload: {
|
|
105
|
-
ideType: "mcp-server",
|
|
106
|
-
ideVersion: "1.0.0",
|
|
107
|
-
extensionVersion: "1.0.0",
|
|
108
|
-
instanceId: `mcp-server-${process.pid}`,
|
|
109
|
-
machineId: "mcp-server",
|
|
110
|
-
workspaceFolders: []
|
|
111
|
-
}
|
|
112
|
-
}));
|
|
113
|
-
});
|
|
114
|
-
ws.addEventListener("message", (event) => {
|
|
115
|
-
try {
|
|
116
|
-
const raw = typeof event.data === "string" ? event.data : String(event.data);
|
|
117
|
-
const msg = JSON.parse(raw);
|
|
118
|
-
if (msg?.type === "daemon:welcome") {
|
|
119
|
-
send();
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (msg?.type !== "ext:command_result") return;
|
|
123
|
-
if (msg?.payload?.requestId !== requestId) return;
|
|
124
|
-
const payload = msg.payload;
|
|
125
|
-
if (payload?.success === false) {
|
|
126
|
-
finish(() => reject(new Error(payload.error || `Daemon IPC command '${type}' failed`)));
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
finish(() => resolve(payload?.result ?? payload));
|
|
130
|
-
} catch {
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
ws.addEventListener("error", () => {
|
|
134
|
-
finish(() => reject(new Error(`Cannot connect to daemon IPC at ws://127.0.0.1:${this.port}${this.path}`)));
|
|
135
|
-
});
|
|
216
|
+
conn.pending.set(requestId, { resolve, reject, timer });
|
|
217
|
+
conn.lastUsedAt = Date.now();
|
|
218
|
+
if (conn.ready) {
|
|
219
|
+
conn.ws.send(JSON.stringify({ type: "ext:command", payload: { command: type, args, requestId } }));
|
|
220
|
+
} else {
|
|
221
|
+
conn.commandQueue.push({ type, args, requestId });
|
|
222
|
+
}
|
|
136
223
|
});
|
|
137
224
|
}
|
|
138
225
|
};
|
|
@@ -241,17 +328,64 @@ function annotateRapidReadChatAdvisory(payload, options) {
|
|
|
241
328
|
|
|
242
329
|
// src/tools/mesh-tools.ts
|
|
243
330
|
var import_daemon_core = require("@adhdev/daemon-core");
|
|
331
|
+
var SESSION_PROVIDER_METADATA_TTL_MS = 30 * 6e4;
|
|
244
332
|
var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
333
|
+
function getSessionMetadata(key) {
|
|
334
|
+
const entry = meshSessionProviderMetadata.get(key);
|
|
335
|
+
if (!entry) return void 0;
|
|
336
|
+
if (entry.expiresAt <= Date.now()) {
|
|
337
|
+
meshSessionProviderMetadata.delete(key);
|
|
338
|
+
return void 0;
|
|
339
|
+
}
|
|
340
|
+
return entry;
|
|
341
|
+
}
|
|
342
|
+
var ACTIVE_WORK_POLLING_BACKOFF_MS = 6e4;
|
|
343
|
+
function buildActiveWorkPollingGuidance(summary, now = Date.now()) {
|
|
344
|
+
if (!summary || summary.generatingCount <= 0) return void 0;
|
|
345
|
+
return {
|
|
346
|
+
activeGeneratingWork: true,
|
|
347
|
+
generatingCount: summary.generatingCount,
|
|
348
|
+
doNotPollBefore: new Date(now + ACTIVE_WORK_POLLING_BACKOFF_MS).toISOString(),
|
|
349
|
+
eventSurface: "pendingCoordinatorEvents",
|
|
350
|
+
nextRecommendedAction: "Wait for pendingCoordinatorEvents/completion events or an explicit user status request. If no terminal evidence appears and the user asks for status, make one bounded status check, then wait again.",
|
|
351
|
+
message: "Do not repeatedly poll mesh_status/mesh_view_queue/mesh_read_chat while delegated work is generating; terminal ledger or completion evidence will be surfaced through pendingCoordinatorEvents when available."
|
|
352
|
+
};
|
|
353
|
+
}
|
|
245
354
|
function readString(value) {
|
|
246
355
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
247
356
|
}
|
|
357
|
+
function summarizeTaskMessage(message) {
|
|
358
|
+
const taskSummary = message.replace(/\s+/g, " ").trim();
|
|
359
|
+
const taskTitle = taskSummary.length > 96 ? `${taskSummary.slice(0, 93)}...` : taskSummary;
|
|
360
|
+
return { taskTitle: taskTitle || "(untitled task)", taskSummary };
|
|
361
|
+
}
|
|
362
|
+
function buildDirectTaskPayload(message, via, opts) {
|
|
363
|
+
const descriptor = summarizeTaskMessage(message);
|
|
364
|
+
return {
|
|
365
|
+
source: "direct",
|
|
366
|
+
via,
|
|
367
|
+
taskId: opts.taskId,
|
|
368
|
+
message,
|
|
369
|
+
taskTitle: descriptor.taskTitle,
|
|
370
|
+
taskSummary: descriptor.taskSummary,
|
|
371
|
+
...opts.taskMode ? { taskMode: opts.taskMode } : {},
|
|
372
|
+
...opts.providerType ? { providerType: opts.providerType } : {},
|
|
373
|
+
...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {},
|
|
374
|
+
...opts.dispatchedToIdleSession !== void 0 ? { dispatchedToIdleSession: opts.dispatchedToIdleSession } : {}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function findNode(mesh, nodeId) {
|
|
378
|
+
const node = mesh.nodes.find((n) => n.id === nodeId);
|
|
379
|
+
if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
|
|
380
|
+
return node;
|
|
381
|
+
}
|
|
248
382
|
var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
|
|
249
383
|
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
250
384
|
var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
251
385
|
var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
|
|
252
386
|
var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
253
387
|
async function refreshMeshFromDaemon(ctx) {
|
|
254
|
-
if (!(ctx.transport
|
|
388
|
+
if (!isLocalTransport(ctx.transport)) return;
|
|
255
389
|
try {
|
|
256
390
|
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
257
391
|
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
@@ -404,6 +538,33 @@ function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
|
404
538
|
function readSessionRecordId(session) {
|
|
405
539
|
return readString(session?.id) || readString(session?.sessionId) || readString(session?.session_id) || readString(session?.runtimeSessionId) || readString(session?.runtime_session_id) || readString(session?.instanceId) || readString(session?.instance_id);
|
|
406
540
|
}
|
|
541
|
+
function extractStatusMetadataSessions(value) {
|
|
542
|
+
const payload = unwrapCommandPayload(value);
|
|
543
|
+
const status = payload?.status && typeof payload.status === "object" ? payload.status : payload;
|
|
544
|
+
return Array.isArray(status?.sessions) ? status.sessions : [];
|
|
545
|
+
}
|
|
546
|
+
function resolveSessionProviderType(session) {
|
|
547
|
+
return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
|
|
548
|
+
}
|
|
549
|
+
function isMeshCoordinatorSessionRecord(session) {
|
|
550
|
+
return Boolean(
|
|
551
|
+
readString(session?.settings?.meshCoordinatorFor) || readString(session?.meta?.meshCoordinatorFor) || readString(session?.metadata?.meshCoordinatorFor) || readString(session?.meshCoordinatorFor)
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
function isUnmanagedSessionRecord(session) {
|
|
555
|
+
const hasMeshNodeFor = Boolean(
|
|
556
|
+
readString(session?.settings?.meshNodeFor) || readString(session?.meta?.meshNodeFor) || readString(session?.metadata?.meshNodeFor) || readString(session?.meshNodeFor)
|
|
557
|
+
);
|
|
558
|
+
if (hasMeshNodeFor) return false;
|
|
559
|
+
if (isMeshCoordinatorSessionRecord(session)) return false;
|
|
560
|
+
const launchedByCoordinator = Boolean(
|
|
561
|
+
session?.settings?.launchedByCoordinator === true || session?.meta?.launchedByCoordinator === true || session?.launchedByCoordinator === true
|
|
562
|
+
);
|
|
563
|
+
return !launchedByCoordinator;
|
|
564
|
+
}
|
|
565
|
+
function isWorkerTaskMode(taskMode) {
|
|
566
|
+
return taskMode !== "live_debug_readonly";
|
|
567
|
+
}
|
|
407
568
|
function addSessionRecord(target, session) {
|
|
408
569
|
if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
|
|
409
570
|
const sessionId = readSessionRecordId(session);
|
|
@@ -472,18 +633,26 @@ function queueAssignmentStaleReason(task, liveness) {
|
|
|
472
633
|
}
|
|
473
634
|
function buildQueueStatusSummary(queue) {
|
|
474
635
|
const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
|
|
636
|
+
let staleAssigned = 0;
|
|
475
637
|
for (const task of queue) {
|
|
476
638
|
const status = typeof task?.status === "string" ? task.status : void 0;
|
|
477
639
|
if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
|
|
478
640
|
counts[status] += 1;
|
|
479
641
|
}
|
|
642
|
+
if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
|
|
480
643
|
}
|
|
644
|
+
const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
|
|
481
645
|
return {
|
|
482
646
|
totalCount: queue.length,
|
|
483
|
-
activeCount: counts.pending +
|
|
647
|
+
activeCount: counts.pending + liveAssigned,
|
|
484
648
|
historicalCount: counts.completed + counts.failed + counts.cancelled,
|
|
485
649
|
counts,
|
|
486
650
|
activeCounts: {
|
|
651
|
+
pending: counts.pending,
|
|
652
|
+
assigned: liveAssigned
|
|
653
|
+
},
|
|
654
|
+
staleAssignedCount: staleAssigned,
|
|
655
|
+
rawActiveCounts: {
|
|
487
656
|
pending: counts.pending,
|
|
488
657
|
assigned: counts.assigned
|
|
489
658
|
},
|
|
@@ -511,6 +680,18 @@ function filterQueueForView(queue, view, statuses) {
|
|
|
511
680
|
if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
512
681
|
return queue;
|
|
513
682
|
}
|
|
683
|
+
function prioritizeActiveQueueRows(queue) {
|
|
684
|
+
const active = [];
|
|
685
|
+
const historical = [];
|
|
686
|
+
const other = [];
|
|
687
|
+
for (const task of queue) {
|
|
688
|
+
const status = String(task?.status || "");
|
|
689
|
+
if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
|
|
690
|
+
else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
|
|
691
|
+
else other.push(task);
|
|
692
|
+
}
|
|
693
|
+
return [...active, ...other, ...historical];
|
|
694
|
+
}
|
|
514
695
|
function slimQueueTask(task) {
|
|
515
696
|
return {
|
|
516
697
|
id: task?.id,
|
|
@@ -616,22 +797,59 @@ function isIdleSessionRecord(session) {
|
|
|
616
797
|
const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
|
|
617
798
|
return status === "idle" || chatStatus === "waiting_input";
|
|
618
799
|
}
|
|
800
|
+
function isMeshOwnedDelegateSession(session, meshId, nodeId) {
|
|
801
|
+
const settings = session?.settings;
|
|
802
|
+
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
803
|
+
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
804
|
+
if (sessionMeshId !== meshId) return false;
|
|
805
|
+
return !sessionNodeId || sessionNodeId === nodeId;
|
|
806
|
+
}
|
|
619
807
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
620
808
|
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
621
809
|
const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
|
|
622
|
-
const isMeshOwnedDelegateSession = (session) => {
|
|
623
|
-
const settings = session?.settings;
|
|
624
|
-
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
625
|
-
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
626
|
-
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
627
|
-
if (sessionMeshId !== meshId || !coordinatorDaemonId) return false;
|
|
628
|
-
return !sessionNodeId || sessionNodeId === nodeId;
|
|
629
|
-
};
|
|
630
810
|
const meshSessions = live.filter(
|
|
631
|
-
(session) => isMeshOwnedDelegateSession(session)
|
|
811
|
+
(session) => isMeshOwnedDelegateSession(session, meshId, nodeId)
|
|
632
812
|
);
|
|
633
813
|
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
|
|
634
814
|
}
|
|
815
|
+
function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType) {
|
|
816
|
+
return {
|
|
817
|
+
success: false,
|
|
818
|
+
recoverable: true,
|
|
819
|
+
code: "mesh_delegate_session_missing_relay_metadata",
|
|
820
|
+
reason: "mesh_delegate_session_missing_relay_metadata",
|
|
821
|
+
transport: "mesh_transport",
|
|
822
|
+
retryRecommended: true,
|
|
823
|
+
meshId: ctx.mesh.id,
|
|
824
|
+
nodeId: node.id,
|
|
825
|
+
daemonId: node.daemonId,
|
|
826
|
+
workspace: node.workspace,
|
|
827
|
+
sessionId,
|
|
828
|
+
unsafeTranscriptAlias: true,
|
|
829
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
830
|
+
error: `Remote session '${sessionId}' is not relay-safe for mesh '${ctx.mesh.id}': missing meshNodeFor/meshCoordinatorDaemonId metadata, so completion events would not reach the coordinator ledger. This session may be the coordinator itself or an unrelated session (unsafe_transcript_alias risk).`,
|
|
831
|
+
nextAction: `Launch a fresh relay-safe session with mesh_launch_session(node_id: '${node.id}'${providerType ? `, type: '${providerType}'` : ""}) or dispatch without session_id so Repo Mesh can choose a valid delegate session.`,
|
|
832
|
+
noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
function buildMissingCoordinatorDaemonIdFailure(ctx, node, providerType) {
|
|
836
|
+
return {
|
|
837
|
+
success: false,
|
|
838
|
+
recoverable: true,
|
|
839
|
+
code: "mesh_coordinator_daemon_unknown",
|
|
840
|
+
reason: "mesh_coordinator_daemon_unknown",
|
|
841
|
+
transport: "mesh_transport",
|
|
842
|
+
retryRecommended: true,
|
|
843
|
+
meshId: ctx.mesh.id,
|
|
844
|
+
nodeId: node.id,
|
|
845
|
+
daemonId: node.daemonId,
|
|
846
|
+
workspace: node.workspace,
|
|
847
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
848
|
+
error: `Cannot launch a remote mesh delegate for node '${node.id}': coordinator daemon identity is unavailable, so the worker would be unable to relay completion events back to the coordinator.`,
|
|
849
|
+
nextAction: "Retry after the coordinator daemon identity is available (for example from an attached daemon-backed MCP session) so meshCoordinatorDaemonId can be stamped on the worker session.",
|
|
850
|
+
noFallbackReason: "Launching without meshCoordinatorDaemonId would create a worker session that can finish work but cannot emit task_completed / generating_completed back to the coordinator."
|
|
851
|
+
};
|
|
852
|
+
}
|
|
635
853
|
function findNestedPayload(value, predicate) {
|
|
636
854
|
const seen = /* @__PURE__ */ new Set();
|
|
637
855
|
const stack = [{ payload: value, depth: 0 }];
|
|
@@ -659,12 +877,16 @@ function extractGitDiff(value) {
|
|
|
659
877
|
}
|
|
660
878
|
function extractSubmodules(value, ignorePaths) {
|
|
661
879
|
const payload = unwrapCommandPayload(value);
|
|
662
|
-
const subs = payload?.submodules ?? value?.submodules;
|
|
880
|
+
const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
|
|
663
881
|
if (!Array.isArray(subs)) return void 0;
|
|
664
882
|
if (ignorePaths.length === 0) return subs;
|
|
665
883
|
const ignoreSet = new Set(ignorePaths);
|
|
666
884
|
return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
|
|
667
885
|
}
|
|
886
|
+
function assignFullGitSnapshot(entry, status) {
|
|
887
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return;
|
|
888
|
+
entry.git = status;
|
|
889
|
+
}
|
|
668
890
|
function extractLaunchPayload(value) {
|
|
669
891
|
return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
|
|
670
892
|
}
|
|
@@ -789,20 +1011,76 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
789
1011
|
let sessionId = args.session_id?.trim() || "";
|
|
790
1012
|
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
791
1013
|
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
792
|
-
if (
|
|
1014
|
+
if (sessionId && args.verifiedSession) {
|
|
1015
|
+
const explicitSession = args.verifiedSession;
|
|
1016
|
+
if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
1017
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
1018
|
+
ctx,
|
|
1019
|
+
node,
|
|
1020
|
+
sessionId,
|
|
1021
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
if (!resolvedProviderType) {
|
|
1025
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
1026
|
+
}
|
|
1027
|
+
} else if (!sessionId || args.session_id) {
|
|
793
1028
|
try {
|
|
794
1029
|
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1030
|
+
const sessions = extractStatusMetadataSessions(relayResult);
|
|
1031
|
+
if (sessionId) {
|
|
1032
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
|
|
1033
|
+
if (!explicitSession) {
|
|
1034
|
+
return {
|
|
1035
|
+
success: false,
|
|
1036
|
+
recoverable: true,
|
|
1037
|
+
code: "mesh_target_session_not_found",
|
|
1038
|
+
reason: "mesh_target_session_not_found",
|
|
1039
|
+
transport: "mesh_transport",
|
|
1040
|
+
retryRecommended: true,
|
|
1041
|
+
meshId: ctx.mesh.id,
|
|
1042
|
+
nodeId: node.id,
|
|
1043
|
+
daemonId,
|
|
1044
|
+
workspace: node.workspace,
|
|
1045
|
+
sessionId,
|
|
1046
|
+
...resolvedProviderType ? { resolvedProviderType } : {},
|
|
1047
|
+
error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
|
|
1048
|
+
nextAction: `Launch a fresh session with mesh_launch_session(node_id: '${node.id}'${resolvedProviderType ? `, type: '${resolvedProviderType}'` : ""}) or retry without session_id so Repo Mesh can target a live delegate session.`
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
if (!isMeshOwnedDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
1052
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
1053
|
+
ctx,
|
|
1054
|
+
node,
|
|
1055
|
+
sessionId,
|
|
1056
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
801
1059
|
if (!resolvedProviderType) {
|
|
802
|
-
resolvedProviderType =
|
|
1060
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
1061
|
+
}
|
|
1062
|
+
} else {
|
|
1063
|
+
const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id);
|
|
1064
|
+
if (targetSession?.id || targetSession?.sessionId) {
|
|
1065
|
+
sessionId = targetSession.id || targetSession.sessionId;
|
|
1066
|
+
if (!resolvedProviderType) {
|
|
1067
|
+
resolvedProviderType = resolveSessionProviderType(targetSession);
|
|
1068
|
+
}
|
|
803
1069
|
}
|
|
804
1070
|
}
|
|
805
1071
|
} catch (e) {
|
|
1072
|
+
if (sessionId) {
|
|
1073
|
+
return {
|
|
1074
|
+
...buildCoordinatorP2pRelayFailure(e, {
|
|
1075
|
+
command: "get_status_metadata",
|
|
1076
|
+
targetDaemonId: daemonId,
|
|
1077
|
+
nodeId: node.id,
|
|
1078
|
+
sessionId
|
|
1079
|
+
}),
|
|
1080
|
+
success: false,
|
|
1081
|
+
error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
806
1084
|
}
|
|
807
1085
|
}
|
|
808
1086
|
if (!resolvedProviderType) {
|
|
@@ -814,7 +1092,8 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
814
1092
|
agentType: resolvedProviderType,
|
|
815
1093
|
cliType: resolvedProviderType,
|
|
816
1094
|
action: "send_chat",
|
|
817
|
-
message: args.message
|
|
1095
|
+
message: args.message,
|
|
1096
|
+
...args.meshContext ? { meshContext: args.meshContext } : {}
|
|
818
1097
|
});
|
|
819
1098
|
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
820
1099
|
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
@@ -832,7 +1111,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
832
1111
|
error: `P2P dispatch failed: ${errorMessage}`
|
|
833
1112
|
};
|
|
834
1113
|
}
|
|
835
|
-
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
|
|
1114
|
+
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType, providerType: resolvedProviderType };
|
|
836
1115
|
} catch (e) {
|
|
837
1116
|
const errorMessage = e?.message || String(e);
|
|
838
1117
|
return {
|
|
@@ -862,34 +1141,199 @@ function resolveCoordinatorNode(ctx) {
|
|
|
862
1141
|
return void 0;
|
|
863
1142
|
}
|
|
864
1143
|
function readNodeMachineId(node) {
|
|
865
|
-
return readString(node.machineId) || readString(node.machine_id);
|
|
1144
|
+
return readString(node.machineId) || readString(node.machine_id) || readString(node.machine?.id) || readString(node.machine?.machineId) || readString(node.lastProbe?.machineId) || readString(node.last_probe?.machine_id) || readString(node.lastProbe?.machine?.id) || readString(node.lastProbe?.machine?.machineId) || readString(node.last_probe?.machine?.id) || readString(node.last_probe?.machine?.machine_id);
|
|
866
1145
|
}
|
|
867
1146
|
function readNodeDaemonId(node) {
|
|
868
|
-
return readString(node.daemonId) || readString(node.daemon_id);
|
|
1147
|
+
return readString(node.daemonId) || readString(node.daemon_id) || readString(node.machine?.daemonId) || readString(node.machine?.daemon_id) || readString(node.lastProbe?.daemonId) || readString(node.last_probe?.daemon_id) || readString(node.lastProbe?.machine?.daemonId) || readString(node.lastProbe?.machine?.daemon_id) || readString(node.last_probe?.machine?.daemonId) || readString(node.last_probe?.machine?.daemon_id);
|
|
1148
|
+
}
|
|
1149
|
+
function normalizeHostname(value) {
|
|
1150
|
+
const hostname = readString(value);
|
|
1151
|
+
if (!hostname) return void 0;
|
|
1152
|
+
return hostname.toLowerCase().replace(/\.$/, "");
|
|
1153
|
+
}
|
|
1154
|
+
function readNodeHostname(node) {
|
|
1155
|
+
return readString(node.hostname) || readString(node.host) || readString(node.machineHostname) || readString(node.machine_hostname) || readString(node.machine?.hostname) || readString(node.machine?.host) || readString(node.lastProbe?.hostname) || readString(node.last_probe?.hostname) || readString(node.lastProbe?.machine?.hostname) || readString(node.last_probe?.machine?.hostname);
|
|
1156
|
+
}
|
|
1157
|
+
function readNodeDisplayMachineName(node) {
|
|
1158
|
+
return readString(node.machineName) || readString(node.machine_name) || readString(node.machineLabel) || readString(node.machine_label) || readString(node.machineNickname) || readString(node.machine_nickname) || readString(node.alias) || readString(node.machine?.name) || readString(node.machine?.displayName) || readString(node.machine?.display_name) || readString(node.lastProbe?.machineName) || readString(node.last_probe?.machine_name) || readString(node.lastProbe?.machine?.name) || readString(node.last_probe?.machine?.name) || readNodeHostname(node);
|
|
1159
|
+
}
|
|
1160
|
+
function compactIdentityEvidence(value) {
|
|
1161
|
+
if (!value) return void 0;
|
|
1162
|
+
return value.length > 24 ? `${value.slice(0, 12)}\u2026${value.slice(-8)}` : value;
|
|
1163
|
+
}
|
|
1164
|
+
function pushIdentityEvidence(evidence, label, value) {
|
|
1165
|
+
const compact = compactIdentityEvidence(value);
|
|
1166
|
+
if (compact) evidence.push(`${label}:${compact}`);
|
|
1167
|
+
}
|
|
1168
|
+
function buildNodeMachineIdentity(ctx, node) {
|
|
1169
|
+
const machineId = readNodeMachineId(node);
|
|
1170
|
+
const daemonId = readNodeDaemonId(node);
|
|
1171
|
+
const hostname = readNodeHostname(node);
|
|
1172
|
+
const machineName = readNodeDisplayMachineName(node);
|
|
1173
|
+
const coordinatorHostname = readString(ctx.coordinatorHostname);
|
|
1174
|
+
const localControlPlaneReason = getLocalControlPlaneMatchReason(ctx, node);
|
|
1175
|
+
const directLocal = !!localControlPlaneReason;
|
|
1176
|
+
const hostnameMatches = Boolean(
|
|
1177
|
+
normalizeHostname(hostname) && normalizeHostname(coordinatorHostname) && normalizeHostname(hostname) === normalizeHostname(coordinatorHostname)
|
|
1178
|
+
);
|
|
1179
|
+
const sameMachine = directLocal || hostnameMatches;
|
|
1180
|
+
const evidence = [];
|
|
1181
|
+
pushIdentityEvidence(evidence, "machineName", machineName);
|
|
1182
|
+
pushIdentityEvidence(evidence, "hostname", hostname);
|
|
1183
|
+
pushIdentityEvidence(evidence, "machineId", machineId);
|
|
1184
|
+
pushIdentityEvidence(evidence, "daemonId", daemonId);
|
|
1185
|
+
if (localControlPlaneReason) {
|
|
1186
|
+
pushIdentityEvidence(evidence, "localMatch", localControlPlaneReason);
|
|
1187
|
+
pushIdentityEvidence(evidence, "localMachineId", ctx.localMachineId);
|
|
1188
|
+
pushIdentityEvidence(evidence, "localDaemonId", ctx.localDaemonId);
|
|
1189
|
+
}
|
|
1190
|
+
const locality = sameMachine ? "same_machine" : evidence.length > 0 ? "remote_known" : "remote_or_unknown";
|
|
1191
|
+
const localityReason = sameMachine ? localControlPlaneReason || "matched coordinator hostname" : evidence.length > 0 ? `known remote/other machine identity; no local coordinator match (${evidence.join(", ")})` : "no useful machine identity evidence available";
|
|
1192
|
+
return {
|
|
1193
|
+
daemonId,
|
|
1194
|
+
machineId,
|
|
1195
|
+
hostname,
|
|
1196
|
+
machineName,
|
|
1197
|
+
displayName: machineName || hostname || daemonId || machineId,
|
|
1198
|
+
coordinatorHostname,
|
|
1199
|
+
sameMachine,
|
|
1200
|
+
locality,
|
|
1201
|
+
localityReason,
|
|
1202
|
+
identityEvidence: evidence
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
function nodeHasLocalDaemonEvidence(ctx, node) {
|
|
1206
|
+
const isLocal = (session) => {
|
|
1207
|
+
if (!session || typeof session !== "object") return false;
|
|
1208
|
+
if (ctx.localDaemonId && session.settings?.meshCoordinatorDaemonId === ctx.localDaemonId) return true;
|
|
1209
|
+
if (session.launchedByCoordinator === true) return true;
|
|
1210
|
+
if (ctx.localDaemonId && session.runtime?.owner === ctx.localDaemonId) return true;
|
|
1211
|
+
if (ctx.localDaemonId && session.daemonClient?.daemonId === ctx.localDaemonId) return true;
|
|
1212
|
+
return false;
|
|
1213
|
+
};
|
|
1214
|
+
const sessionArrays = [
|
|
1215
|
+
node?.sessions,
|
|
1216
|
+
node?.activeSessions,
|
|
1217
|
+
node?.active_sessions,
|
|
1218
|
+
node?.lastProbe?.sessions,
|
|
1219
|
+
node?.last_probe?.sessions,
|
|
1220
|
+
node?.lastProbe?.status?.sessions,
|
|
1221
|
+
node?.last_probe?.status?.sessions
|
|
1222
|
+
];
|
|
1223
|
+
for (const arr of sessionArrays) {
|
|
1224
|
+
if (Array.isArray(arr) && arr.some(isLocal)) return true;
|
|
1225
|
+
}
|
|
1226
|
+
const sessionRecords = [
|
|
1227
|
+
node?.activeSession,
|
|
1228
|
+
node?.active_session,
|
|
1229
|
+
node?.currentSession,
|
|
1230
|
+
node?.current_session,
|
|
1231
|
+
node?.runtimeSession,
|
|
1232
|
+
node?.runtime_session,
|
|
1233
|
+
node?.session,
|
|
1234
|
+
node?.lastProbe?.activeSession,
|
|
1235
|
+
node?.last_probe?.active_session,
|
|
1236
|
+
node?.lastProbe?.currentSession,
|
|
1237
|
+
node?.last_probe?.current_session,
|
|
1238
|
+
node?.lastProbe?.session,
|
|
1239
|
+
node?.last_probe?.session
|
|
1240
|
+
];
|
|
1241
|
+
for (const session of sessionRecords) {
|
|
1242
|
+
if (isLocal(session)) return true;
|
|
1243
|
+
}
|
|
1244
|
+
return false;
|
|
869
1245
|
}
|
|
870
1246
|
function isDirectLocalNode(ctx, node) {
|
|
871
1247
|
const machineId = readNodeMachineId(node);
|
|
872
1248
|
const daemonId = readNodeDaemonId(node);
|
|
873
1249
|
return Boolean(
|
|
874
|
-
ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId
|
|
1250
|
+
ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId || nodeHasLocalDaemonEvidence(ctx, node)
|
|
875
1251
|
);
|
|
876
1252
|
}
|
|
1253
|
+
function isConfiguredCoordinatorNode(ctx, node) {
|
|
1254
|
+
if (!ctx.localMachineId && !ctx.localDaemonId) return false;
|
|
1255
|
+
const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
|
|
1256
|
+
if (!nodeId) return false;
|
|
1257
|
+
const nodeDaemonId = readNodeDaemonId(node);
|
|
1258
|
+
const nodeMachineId = readNodeMachineId(node);
|
|
1259
|
+
if (nodeDaemonId && ctx.localDaemonId && nodeDaemonId !== ctx.localDaemonId) return false;
|
|
1260
|
+
if (nodeMachineId && ctx.localMachineId && nodeMachineId !== ctx.localMachineId) return false;
|
|
1261
|
+
const preferredNodeId = readString(ctx.mesh.coordinator?.preferredNodeId) || readString(ctx.mesh.coordinator?.preferred_node_id);
|
|
1262
|
+
if (preferredNodeId) return nodeId === preferredNodeId;
|
|
1263
|
+
const first = ctx.mesh.nodes?.[0];
|
|
1264
|
+
const firstNodeId = readString(first?.id) || readString(first?.nodeId) || readString(first?.node_id);
|
|
1265
|
+
return !!firstNodeId && nodeId === firstNodeId;
|
|
1266
|
+
}
|
|
1267
|
+
function getLocalControlPlaneMatchReason(ctx, node) {
|
|
1268
|
+
if (isDirectLocalNode(ctx, node)) return "matched coordinator daemon or machine id";
|
|
1269
|
+
if (isConfiguredCoordinatorNode(ctx, node)) return "matched configured coordinator node";
|
|
1270
|
+
if (node.isLocalWorktree === true) {
|
|
1271
|
+
const sourceNode = findClonedFromNode(ctx, node);
|
|
1272
|
+
if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return "matched local cloned-from node";
|
|
1273
|
+
if (sourceNode && isConfiguredCoordinatorNode(ctx, sourceNode)) return "matched configured coordinator source node";
|
|
1274
|
+
}
|
|
1275
|
+
return void 0;
|
|
1276
|
+
}
|
|
877
1277
|
function findClonedFromNode(ctx, node) {
|
|
878
1278
|
const clonedFromNodeId = readString(node.clonedFromNodeId) || readString(node.cloned_from_node_id);
|
|
879
1279
|
if (!clonedFromNodeId) return void 0;
|
|
880
1280
|
return ctx.mesh.nodes.find((n) => n.id === clonedFromNodeId || n.nodeId === clonedFromNodeId || n.node_id === clonedFromNodeId);
|
|
881
1281
|
}
|
|
882
1282
|
function isLocalControlPlaneNode(ctx, node) {
|
|
883
|
-
|
|
884
|
-
if (node.isLocalWorktree === true) {
|
|
885
|
-
const sourceNode = findClonedFromNode(ctx, node);
|
|
886
|
-
if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return true;
|
|
887
|
-
}
|
|
888
|
-
return false;
|
|
1283
|
+
return !!getLocalControlPlaneMatchReason(ctx, node);
|
|
889
1284
|
}
|
|
890
1285
|
function meshSessionCacheKey(nodeId, runtimeSessionId) {
|
|
891
1286
|
return `${nodeId}:${runtimeSessionId}`;
|
|
892
1287
|
}
|
|
1288
|
+
function rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, metadata) {
|
|
1289
|
+
const keyNodeId = readString(nodeId);
|
|
1290
|
+
const keySessionId = readString(runtimeSessionId);
|
|
1291
|
+
if (!keyNodeId || !keySessionId) return;
|
|
1292
|
+
const providerType = readString(metadata.providerType);
|
|
1293
|
+
const providerSessionId = readString(metadata.providerSessionId);
|
|
1294
|
+
if (!providerType && !providerSessionId) return;
|
|
1295
|
+
const existing = getSessionMetadata(meshSessionCacheKey(keyNodeId, keySessionId)) || { providerType: "" };
|
|
1296
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(keyNodeId, keySessionId), {
|
|
1297
|
+
providerType: providerType || existing.providerType,
|
|
1298
|
+
providerSessionId: providerSessionId || existing.providerSessionId,
|
|
1299
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
function rememberMeshSessionProviderMetadataFromEvent(event) {
|
|
1303
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : event && typeof event === "object" ? event : {};
|
|
1304
|
+
const nodeId = readString(event?.nodeId) || readString(metadataEvent.nodeId) || readString(metadataEvent.meshNodeId);
|
|
1305
|
+
const sessionId = readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId) || readString(event?.sessionId);
|
|
1306
|
+
rememberMeshSessionProviderMetadata(nodeId, sessionId, {
|
|
1307
|
+
providerType: readString(metadataEvent.providerType) || readString(event?.providerType) || "",
|
|
1308
|
+
providerSessionId: readString(metadataEvent.providerSessionId) || readString(event?.providerSessionId)
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
|
|
1312
|
+
const entries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 50 });
|
|
1313
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1314
|
+
const entry = entries[i];
|
|
1315
|
+
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
1316
|
+
const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
|
|
1317
|
+
if (entryNodeId && entryNodeId !== nodeId) continue;
|
|
1318
|
+
const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
|
|
1319
|
+
if (entrySessionId !== runtimeSessionId) continue;
|
|
1320
|
+
const providerType = readString(entry.providerType) || readString(payload.providerType);
|
|
1321
|
+
const completionDiagnostic = payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? payload.completionDiagnostic : {};
|
|
1322
|
+
const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : {};
|
|
1323
|
+
const providerSessionId = readString(payload.providerSessionId) || readString(completionDiagnostic.providerSessionId) || readString(metadataEvent.providerSessionId);
|
|
1324
|
+
if (providerType || providerSessionId) {
|
|
1325
|
+
return { providerType: providerType || "", providerSessionId };
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return void 0;
|
|
1329
|
+
}
|
|
1330
|
+
function resolveMeshSessionProviderMetadata(ctx, nodeId, runtimeSessionId) {
|
|
1331
|
+
const cached = getSessionMetadata(meshSessionCacheKey(nodeId, runtimeSessionId));
|
|
1332
|
+
if (cached?.providerType || cached?.providerSessionId) return cached;
|
|
1333
|
+
const fromLedger = resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId);
|
|
1334
|
+
if (fromLedger) rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, fromLedger);
|
|
1335
|
+
return fromLedger;
|
|
1336
|
+
}
|
|
893
1337
|
function countUncommittedChanges(status) {
|
|
894
1338
|
if (typeof status?.uncommittedChanges === "number") return status.uncommittedChanges;
|
|
895
1339
|
const keys = ["staged", "modified", "untracked", "deleted", "renamed"];
|
|
@@ -902,6 +1346,20 @@ function isGitStatusDirty(status) {
|
|
|
902
1346
|
if (typeof status?.dirty === "boolean") return status.dirty;
|
|
903
1347
|
return countUncommittedChanges(status) > 0;
|
|
904
1348
|
}
|
|
1349
|
+
function slimLedgerPayload(payload) {
|
|
1350
|
+
const slim = {};
|
|
1351
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
1352
|
+
if (k === "message" || k === "taskSummary") {
|
|
1353
|
+
slim[k] = typeof v === "string" && v.length > 200 ? v.slice(0, 200) + "\u2026" : v;
|
|
1354
|
+
} else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
|
|
1355
|
+
} else if (k === "finalSummary") {
|
|
1356
|
+
slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
|
|
1357
|
+
} else {
|
|
1358
|
+
slim[k] = v;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
return slim;
|
|
1362
|
+
}
|
|
905
1363
|
function readRelatedRepos(node) {
|
|
906
1364
|
const raw = Array.isArray(node.relatedRepos) ? node.relatedRepos : Array.isArray(node.policy?.relatedRepos) ? node.policy.relatedRepos : [];
|
|
907
1365
|
return raw.map((entry) => ({
|
|
@@ -960,6 +1418,16 @@ function missingProviderPriorityMessage(nodeId) {
|
|
|
960
1418
|
return `Node '${nodeId}' has no providerPriority policy; pass type explicitly or configure node.policy.providerPriority`;
|
|
961
1419
|
}
|
|
962
1420
|
function getNodeLaunchReadiness(node) {
|
|
1421
|
+
const bootstrap = node.worktreeBootstrap;
|
|
1422
|
+
if (node.isLocalWorktree && bootstrap?.status === "failed" && bootstrap?.required !== false) {
|
|
1423
|
+
return {
|
|
1424
|
+
providerPriority: readProviderPriority(node.policy),
|
|
1425
|
+
launchReady: false,
|
|
1426
|
+
launchBlockedReason: "worktree_bootstrap_failed",
|
|
1427
|
+
launchBlockedMessage: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : "Required worktree bootstrap failed; resolve it before launching an agent into this node.",
|
|
1428
|
+
worktreeBootstrap: bootstrap
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
963
1431
|
const providerPriority = readProviderPriority(node.policy);
|
|
964
1432
|
if (providerPriority.length) {
|
|
965
1433
|
return {
|
|
@@ -974,6 +1442,33 @@ function getNodeLaunchReadiness(node) {
|
|
|
974
1442
|
launchBlockedMessage: missingProviderPriorityMessage(node.id)
|
|
975
1443
|
};
|
|
976
1444
|
}
|
|
1445
|
+
function getWorktreeBootstrapLaunchBlock(node) {
|
|
1446
|
+
const bootstrap = node.worktreeBootstrap;
|
|
1447
|
+
if (!node.isLocalWorktree || bootstrap?.status !== "failed" || bootstrap?.required === false) return void 0;
|
|
1448
|
+
return {
|
|
1449
|
+
success: false,
|
|
1450
|
+
code: "worktree_bootstrap_failed",
|
|
1451
|
+
error: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : `Node '${node.id}' has a failed required worktree bootstrap.`,
|
|
1452
|
+
nodeId: node.id,
|
|
1453
|
+
worktreeBootstrap: bootstrap,
|
|
1454
|
+
recoveryHint: "Fix the configured worktree bootstrap command or remove/recreate the worktree node before launching an agent."
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
async function collectLiveStatusSessions(ctx, node) {
|
|
1458
|
+
try {
|
|
1459
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
1460
|
+
return extractStatusMetadataSessions(statusResult);
|
|
1461
|
+
} catch {
|
|
1462
|
+
return [];
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
async function collectMeshViewQueueNodesWithLiveSessions(ctx) {
|
|
1466
|
+
const nodes = await Promise.all(ctx.mesh.nodes.map(async (node) => {
|
|
1467
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
1468
|
+
return liveSessions.length > 0 ? { ...node, sessions: liveSessions } : node;
|
|
1469
|
+
}));
|
|
1470
|
+
return nodes;
|
|
1471
|
+
}
|
|
977
1472
|
function readNumeric(value, fallback = 0) {
|
|
978
1473
|
const parsed = Number(value);
|
|
979
1474
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
@@ -1109,7 +1604,8 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
1109
1604
|
if (isLocalTransport(ctx.transport)) {
|
|
1110
1605
|
return ctx.transport.command(command, args);
|
|
1111
1606
|
}
|
|
1112
|
-
|
|
1607
|
+
const identity = buildNodeMachineIdentity(ctx, node);
|
|
1608
|
+
throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}' (hostname=${identity.hostname || "unknown"}, coordinatorHostname=${identity.coordinatorHostname || "unknown"}, sameMachine=${identity.sameMachine})`);
|
|
1113
1609
|
}
|
|
1114
1610
|
function normalizePendingMeshCoordinatorEvents(value) {
|
|
1115
1611
|
const payload = unwrapCommandPayload(value);
|
|
@@ -1127,6 +1623,14 @@ function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
|
1127
1623
|
providerType: readString(metadataEvent.providerType),
|
|
1128
1624
|
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
1129
1625
|
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
1626
|
+
jobId: readString(metadataEvent.jobId),
|
|
1627
|
+
interactionId: readString(metadataEvent.interactionId),
|
|
1628
|
+
status: readString(metadataEvent.status),
|
|
1629
|
+
targetDaemonId: readString(metadataEvent.targetDaemonId),
|
|
1630
|
+
startedAt: readString(metadataEvent.startedAt),
|
|
1631
|
+
completedAt: readString(metadataEvent.completedAt),
|
|
1632
|
+
retryOfJobId: readString(metadataEvent.retryOfJobId),
|
|
1633
|
+
...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
|
|
1130
1634
|
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
1131
1635
|
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
1132
1636
|
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
@@ -1143,8 +1647,9 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
|
1143
1647
|
const surfacedEvents = [];
|
|
1144
1648
|
try {
|
|
1145
1649
|
surfacedEvents.push(
|
|
1146
|
-
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", {})).filter(matchesCurrentMesh)
|
|
1650
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1147
1651
|
);
|
|
1652
|
+
surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1148
1653
|
} catch {
|
|
1149
1654
|
}
|
|
1150
1655
|
for (const node of ctx.mesh.nodes) {
|
|
@@ -1152,27 +1657,31 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
|
1152
1657
|
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
1153
1658
|
try {
|
|
1154
1659
|
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
1155
|
-
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", {})
|
|
1660
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", { meshId: ctx.mesh.id })
|
|
1156
1661
|
).filter(matchesCurrentMesh);
|
|
1157
1662
|
if (remoteEvents.length === 0) continue;
|
|
1158
1663
|
for (const event of remoteEvents) {
|
|
1159
1664
|
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1160
1665
|
if (!payload.event || !payload.meshId) continue;
|
|
1161
1666
|
await ctx.transport.command("mesh_forward_event", payload);
|
|
1667
|
+
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
1162
1668
|
}
|
|
1163
1669
|
} catch {
|
|
1164
1670
|
}
|
|
1165
1671
|
}
|
|
1166
1672
|
try {
|
|
1167
1673
|
surfacedEvents.push(
|
|
1168
|
-
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", {})).filter(matchesCurrentMesh)
|
|
1674
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1169
1675
|
);
|
|
1676
|
+
surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1170
1677
|
} catch {
|
|
1171
1678
|
}
|
|
1172
1679
|
return surfacedEvents;
|
|
1173
1680
|
}
|
|
1174
1681
|
if (isLocalTransport(ctx.transport)) {
|
|
1175
|
-
|
|
1682
|
+
const events = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
|
|
1683
|
+
events.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1684
|
+
return events;
|
|
1176
1685
|
}
|
|
1177
1686
|
return [];
|
|
1178
1687
|
}
|
|
@@ -1189,11 +1698,12 @@ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
|
|
|
1189
1698
|
}
|
|
1190
1699
|
var MESH_STATUS_TOOL = {
|
|
1191
1700
|
name: "mesh_status",
|
|
1192
|
-
description: "Get the current status of all nodes in the repo mesh \u2014 health, git state, active sessions, recovery hints, and recommended next steps. Use this to decide which node to send work to or how to recover from failures.",
|
|
1701
|
+
description: "Get the current status of all nodes in the repo mesh \u2014 health, git state, active sessions, recovery hints, and recommended next steps. Use this to decide which node to send work to or how to recover from failures. Do not repeatedly call this to wait for generating delegated work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
|
|
1193
1702
|
inputSchema: {
|
|
1194
1703
|
type: "object",
|
|
1195
1704
|
properties: {
|
|
1196
|
-
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
|
|
1705
|
+
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
|
|
1706
|
+
includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." }
|
|
1197
1707
|
}
|
|
1198
1708
|
}
|
|
1199
1709
|
};
|
|
@@ -1213,14 +1723,18 @@ var MESH_ENQUEUE_TASK_TOOL = {
|
|
|
1213
1723
|
inputSchema: {
|
|
1214
1724
|
type: "object",
|
|
1215
1725
|
properties: {
|
|
1216
|
-
message: { type: "string", description: "The task instruction for the agent." }
|
|
1726
|
+
message: { type: "string", description: "The task instruction for the agent." },
|
|
1727
|
+
task_mode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "Optional task-mode contract. live_debug_readonly rejects obvious write/commit/push/deploy/destructive instructions before dispatch." },
|
|
1728
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
|
|
1729
|
+
requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
|
|
1730
|
+
required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." }
|
|
1217
1731
|
},
|
|
1218
1732
|
required: ["message"]
|
|
1219
1733
|
}
|
|
1220
1734
|
};
|
|
1221
1735
|
var MESH_VIEW_QUEUE_TOOL = {
|
|
1222
1736
|
name: "mesh_view_queue",
|
|
1223
|
-
description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records.",
|
|
1737
|
+
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.",
|
|
1224
1738
|
inputSchema: {
|
|
1225
1739
|
type: "object",
|
|
1226
1740
|
properties: {
|
|
@@ -1273,7 +1787,9 @@ var MESH_SEND_TASK_TOOL = {
|
|
|
1273
1787
|
properties: {
|
|
1274
1788
|
node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
|
|
1275
1789
|
session_id: { type: "string", description: "Agent session ID on the target node." },
|
|
1276
|
-
message: { type: "string", description: "Natural-language task to send to the agent." }
|
|
1790
|
+
message: { type: "string", description: "Natural-language task to send to the agent." },
|
|
1791
|
+
task_mode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "Optional task-mode contract. live_debug_readonly rejects obvious write/commit/push/deploy/destructive instructions before local or remote direct dispatch." },
|
|
1792
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1277
1793
|
},
|
|
1278
1794
|
required: ["node_id", "session_id", "message"]
|
|
1279
1795
|
}
|
|
@@ -1331,6 +1847,21 @@ var MESH_GIT_STATUS_TOOL = {
|
|
|
1331
1847
|
required: ["node_id"]
|
|
1332
1848
|
}
|
|
1333
1849
|
};
|
|
1850
|
+
var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
1851
|
+
name: "mesh_fast_forward_node",
|
|
1852
|
+
description: "Safely dry-run or execute an obvious direct fast-forward for a mesh node without launching an agent session. Defaults to dry-run; execution requires execute=true. Never pushes, rebases, resets, cleans, or checks out arbitrary revisions.",
|
|
1853
|
+
inputSchema: {
|
|
1854
|
+
type: "object",
|
|
1855
|
+
properties: {
|
|
1856
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1857
|
+
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
1858
|
+
execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
|
|
1859
|
+
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
1860
|
+
update_submodules: { type: "boolean", description: "When true, if the root fast-forward changes gitlinks, run only git submodule update --init --recursive and verify submodules clean." }
|
|
1861
|
+
},
|
|
1862
|
+
required: ["node_id"]
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1334
1865
|
var MESH_CHECKPOINT_TOOL = {
|
|
1335
1866
|
name: "mesh_checkpoint",
|
|
1336
1867
|
description: "Create a git checkpoint (commit) on a mesh node workspace.",
|
|
@@ -1414,7 +1945,7 @@ var MESH_TASK_HISTORY_TOOL = {
|
|
|
1414
1945
|
type: "object",
|
|
1415
1946
|
properties: {
|
|
1416
1947
|
tail: { type: "number", description: "Number of recent entries to return (default: 20)." },
|
|
1417
|
-
kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed." }
|
|
1948
|
+
kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed, direct_fast_forward." }
|
|
1418
1949
|
}
|
|
1419
1950
|
}
|
|
1420
1951
|
};
|
|
@@ -1434,7 +1965,7 @@ var MESH_RECONCILE_LEDGER_TOOL = {
|
|
|
1434
1965
|
};
|
|
1435
1966
|
var MESH_REFINE_NODE_TOOL = {
|
|
1436
1967
|
name: "mesh_refine_node",
|
|
1437
|
-
description: "The Refinery:
|
|
1968
|
+
description: "The Refinery: Accept an async validation/merge/cleanup job for a completed worktree node. The immediate response includes async:true, status:'accepted', jobId, interactionId, target node, and startedAt; completion/failure evidence is delivered through pending mesh events and the mesh task ledger.",
|
|
1438
1969
|
inputSchema: {
|
|
1439
1970
|
type: "object",
|
|
1440
1971
|
properties: {
|
|
@@ -1443,6 +1974,43 @@ var MESH_REFINE_NODE_TOOL = {
|
|
|
1443
1974
|
required: ["node_id"]
|
|
1444
1975
|
}
|
|
1445
1976
|
};
|
|
1977
|
+
var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
|
|
1978
|
+
name: "mesh_refine_config_schema",
|
|
1979
|
+
description: "Return the Repo Mesh Refinery config JSON schema and supported repo-local config locations. This is the validation source of truth; heuristic command detection is suggestions-only.",
|
|
1980
|
+
inputSchema: { type: "object", properties: {} }
|
|
1981
|
+
};
|
|
1982
|
+
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
1983
|
+
name: "mesh_validate_refine_config",
|
|
1984
|
+
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
1985
|
+
inputSchema: {
|
|
1986
|
+
type: "object",
|
|
1987
|
+
properties: {
|
|
1988
|
+
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
1989
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
};
|
|
1993
|
+
var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
|
|
1994
|
+
name: "mesh_suggest_refine_config",
|
|
1995
|
+
description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
|
|
1996
|
+
inputSchema: {
|
|
1997
|
+
type: "object",
|
|
1998
|
+
properties: {
|
|
1999
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
};
|
|
2003
|
+
var MESH_REFINE_PLAN_TOOL = {
|
|
2004
|
+
name: "mesh_refine_plan",
|
|
2005
|
+
description: "Dry-run Refinery plan for a worktree node: reports config source, validation commands, suggestions/unavailable reason, and merge/cleanup intent without executing validation or git merge.",
|
|
2006
|
+
inputSchema: {
|
|
2007
|
+
type: "object",
|
|
2008
|
+
properties: {
|
|
2009
|
+
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
2010
|
+
},
|
|
2011
|
+
required: ["node_id"]
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
1446
2014
|
var ALL_MESH_TOOLS = [
|
|
1447
2015
|
MESH_STATUS_TOOL,
|
|
1448
2016
|
MESH_LIST_NODES_TOOL,
|
|
@@ -1455,24 +2023,31 @@ var ALL_MESH_TOOLS = [
|
|
|
1455
2023
|
MESH_READ_DEBUG_TOOL,
|
|
1456
2024
|
MESH_LAUNCH_SESSION_TOOL,
|
|
1457
2025
|
MESH_GIT_STATUS_TOOL,
|
|
2026
|
+
MESH_FAST_FORWARD_NODE_TOOL,
|
|
1458
2027
|
MESH_CHECKPOINT_TOOL,
|
|
1459
2028
|
MESH_APPROVE_TOOL,
|
|
1460
2029
|
MESH_CLONE_NODE_TOOL,
|
|
1461
2030
|
MESH_REMOVE_NODE_TOOL,
|
|
1462
2031
|
MESH_REFINE_NODE_TOOL,
|
|
2032
|
+
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
2033
|
+
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
2034
|
+
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
2035
|
+
MESH_REFINE_PLAN_TOOL,
|
|
1463
2036
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1464
2037
|
MESH_TASK_HISTORY_TOOL,
|
|
1465
2038
|
MESH_RECONCILE_LEDGER_TOOL
|
|
1466
2039
|
];
|
|
1467
|
-
async function meshStatus(ctx) {
|
|
2040
|
+
async function meshStatus(ctx, args = {}) {
|
|
1468
2041
|
await refreshMeshFromDaemon(ctx);
|
|
1469
2042
|
const { mesh, transport } = ctx;
|
|
1470
|
-
const results = [];
|
|
1471
2043
|
const ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
1472
|
-
|
|
2044
|
+
const results = await Promise.all(mesh.nodes.map(async (node) => {
|
|
1473
2045
|
const entry = {
|
|
1474
2046
|
nodeId: node.id,
|
|
1475
2047
|
workspace: node.workspace,
|
|
2048
|
+
machine: buildNodeMachineIdentity(ctx, node),
|
|
2049
|
+
daemonId: readNodeDaemonId(node),
|
|
2050
|
+
machineId: readNodeMachineId(node),
|
|
1476
2051
|
...getNodeLaunchReadiness(node)
|
|
1477
2052
|
};
|
|
1478
2053
|
try {
|
|
@@ -1482,6 +2057,7 @@ async function meshStatus(ctx) {
|
|
|
1482
2057
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1483
2058
|
const dirty = isGitStatusDirty(status);
|
|
1484
2059
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
2060
|
+
assignFullGitSnapshot(entry, status);
|
|
1485
2061
|
entry.branch = status?.branch;
|
|
1486
2062
|
entry.isDirty = dirty;
|
|
1487
2063
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1503,6 +2079,7 @@ async function meshStatus(ctx) {
|
|
|
1503
2079
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1504
2080
|
const dirty = isGitStatusDirty(status);
|
|
1505
2081
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
2082
|
+
assignFullGitSnapshot(entry, status);
|
|
1506
2083
|
entry.branch = status?.branch;
|
|
1507
2084
|
entry.isDirty = dirty;
|
|
1508
2085
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1538,7 +2115,7 @@ async function meshStatus(ctx) {
|
|
|
1538
2115
|
if (recoveryContext.consecutiveNodeFailures > 0) {
|
|
1539
2116
|
entry.recoveryHints = {
|
|
1540
2117
|
consecutiveFailures: recoveryContext.consecutiveNodeFailures,
|
|
1541
|
-
lastTaskMessage: recoveryContext.lastTaskMessage,
|
|
2118
|
+
lastTaskMessage: typeof recoveryContext.lastTaskMessage === "string" ? recoveryContext.lastTaskMessage.slice(0, 100) + (recoveryContext.lastTaskMessage.length > 100 ? "\u2026" : "") : recoveryContext.lastTaskMessage,
|
|
1542
2119
|
advice: recoveryContext.advice,
|
|
1543
2120
|
retryRecommended: recoveryContext.retryRecommended
|
|
1544
2121
|
};
|
|
@@ -1578,7 +2155,47 @@ async function meshStatus(ctx) {
|
|
|
1578
2155
|
}
|
|
1579
2156
|
const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
|
|
1580
2157
|
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
1581
|
-
|
|
2158
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
2159
|
+
if (liveSessions.length > 0) {
|
|
2160
|
+
entry.sessions = liveSessions.map((s) => {
|
|
2161
|
+
const coordinatorMeshId = typeof s.coordinator?.meshId === "string" ? s.coordinator.meshId : void 0;
|
|
2162
|
+
const isSelfCoordinator = coordinatorMeshId === mesh.id;
|
|
2163
|
+
return {
|
|
2164
|
+
id: s.instanceId ?? s.id ?? s.sessionId,
|
|
2165
|
+
status: s.status ?? s.lifecycle ?? s.state,
|
|
2166
|
+
providerType: s.providerType ?? s.cliType ?? s.type,
|
|
2167
|
+
...s.activeChat?.status ? { chatStatus: s.activeChat.status } : {},
|
|
2168
|
+
...isSelfCoordinator ? { isSelfCoordinator: true, role: "coordinator" } : {}
|
|
2169
|
+
};
|
|
2170
|
+
}).filter((s) => s.id);
|
|
2171
|
+
}
|
|
2172
|
+
return entry;
|
|
2173
|
+
}));
|
|
2174
|
+
const ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
|
|
2175
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2176
|
+
meshId: mesh.id,
|
|
2177
|
+
queue: (0, import_daemon_core.getQueue)(mesh.id),
|
|
2178
|
+
ledgerEntries,
|
|
2179
|
+
nodes: results
|
|
2180
|
+
});
|
|
2181
|
+
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
2182
|
+
const staleDirectWorkSummary = (0, import_daemon_core.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
|
|
2183
|
+
note: activeWorkEvidence.staleDirectWorkNote,
|
|
2184
|
+
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."
|
|
2185
|
+
});
|
|
2186
|
+
const coordinatorSessions = [];
|
|
2187
|
+
for (const nodeEntry of results) {
|
|
2188
|
+
const sessions = Array.isArray(nodeEntry.sessions) ? nodeEntry.sessions : [];
|
|
2189
|
+
for (const s of sessions) {
|
|
2190
|
+
if (s?.isSelfCoordinator === true && s.id) {
|
|
2191
|
+
coordinatorSessions.push({
|
|
2192
|
+
nodeId: nodeEntry.nodeId,
|
|
2193
|
+
sessionId: s.id,
|
|
2194
|
+
providerType: s.providerType,
|
|
2195
|
+
status: s.status
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
1582
2199
|
}
|
|
1583
2200
|
const response = {
|
|
1584
2201
|
meshId: mesh.id,
|
|
@@ -1586,8 +2203,29 @@ async function meshStatus(ctx) {
|
|
|
1586
2203
|
repoIdentity: mesh.repoIdentity,
|
|
1587
2204
|
policy: mesh.policy,
|
|
1588
2205
|
refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2206
|
+
sourceOfTruth: {
|
|
2207
|
+
membership: "coordinator_daemon_live_mesh",
|
|
2208
|
+
currentStatus: "live_git_and_session_probes",
|
|
2209
|
+
activeWork: "mesh_queue_file_and_local_ledger",
|
|
2210
|
+
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
2211
|
+
},
|
|
1589
2212
|
nodes: results,
|
|
1590
|
-
|
|
2213
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2214
|
+
staleDirectWorkSummary,
|
|
2215
|
+
...args.includeStaleDirectWorkDetails === true ? { staleDirectWork: activeWorkEvidence.staleDirectWork } : {},
|
|
2216
|
+
// terminalDirectWork is historical (completed/failed direct dispatches) — opt-in only.
|
|
2217
|
+
...args.includeTerminalDirectWork === true ? { terminalDirectWork: activeWorkEvidence.terminalDirectWork } : {},
|
|
2218
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
2219
|
+
...pollingGuidance ? { pollingGuidance } : {},
|
|
2220
|
+
branchConvergenceSummary: summarizeBranchConvergence(results),
|
|
2221
|
+
...coordinatorSessions.length > 0 ? {
|
|
2222
|
+
coordinatorSessions,
|
|
2223
|
+
selfIdentification: {
|
|
2224
|
+
meshId: mesh.id,
|
|
2225
|
+
coordinatorSessions,
|
|
2226
|
+
note: "Sessions listed here are coordinator sessions for this mesh. The calling coordinator IS one of these sessions \u2014 do not treat its own generating CLI session as a foreign delegated task. Per-session marker: sessions[].isSelfCoordinator === true."
|
|
2227
|
+
}
|
|
2228
|
+
} : {}
|
|
1591
2229
|
};
|
|
1592
2230
|
try {
|
|
1593
2231
|
response.ledgerSummary = ledgerSummary;
|
|
@@ -1595,6 +2233,14 @@ async function meshStatus(ctx) {
|
|
|
1595
2233
|
}
|
|
1596
2234
|
try {
|
|
1597
2235
|
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
2236
|
+
const asyncRefineJobs = (0, import_daemon_core.buildMeshAsyncRefineJobs)({
|
|
2237
|
+
meshId: mesh.id,
|
|
2238
|
+
ledgerEntries,
|
|
2239
|
+
pendingEvents
|
|
2240
|
+
});
|
|
2241
|
+
if (asyncRefineJobs.length > 0) {
|
|
2242
|
+
response.asyncRefineJobs = asyncRefineJobs;
|
|
2243
|
+
}
|
|
1598
2244
|
if (pendingEvents.length > 0) {
|
|
1599
2245
|
response.pendingCoordinatorEvents = pendingEvents;
|
|
1600
2246
|
}
|
|
@@ -1604,12 +2250,21 @@ async function meshStatus(ctx) {
|
|
|
1604
2250
|
}
|
|
1605
2251
|
async function meshTaskHistory(ctx, args) {
|
|
1606
2252
|
const { mesh } = ctx;
|
|
1607
|
-
await drainCoordinatorPendingEvents(ctx);
|
|
2253
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1608
2254
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
1609
2255
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
1610
|
-
const
|
|
2256
|
+
const rawEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
2257
|
+
const entries = rawEntries.map((e) => ({
|
|
2258
|
+
...e,
|
|
2259
|
+
payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
|
|
2260
|
+
}));
|
|
1611
2261
|
const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
1612
|
-
return JSON.stringify({
|
|
2262
|
+
return JSON.stringify({
|
|
2263
|
+
meshId: mesh.id,
|
|
2264
|
+
entries,
|
|
2265
|
+
summary,
|
|
2266
|
+
...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
|
|
2267
|
+
}, null, 2);
|
|
1613
2268
|
}
|
|
1614
2269
|
async function meshReconcileLedger(ctx, args) {
|
|
1615
2270
|
await refreshMeshFromDaemon(ctx);
|
|
@@ -1699,6 +2354,9 @@ async function meshListNodes(ctx) {
|
|
|
1699
2354
|
nodeId: n.id,
|
|
1700
2355
|
workspace: n.workspace,
|
|
1701
2356
|
repoRoot: n.repoRoot,
|
|
2357
|
+
daemonId: readNodeDaemonId(n),
|
|
2358
|
+
machineId: readNodeMachineId(n),
|
|
2359
|
+
machine: buildNodeMachineIdentity(ctx, n),
|
|
1702
2360
|
isLocalWorktree: n.isLocalWorktree,
|
|
1703
2361
|
policy: n.policy,
|
|
1704
2362
|
relatedRepos: readRelatedRepos(n),
|
|
@@ -1708,12 +2366,14 @@ async function meshListNodes(ctx) {
|
|
|
1708
2366
|
}, null, 2);
|
|
1709
2367
|
}
|
|
1710
2368
|
async function meshEnqueueTask(ctx, args) {
|
|
2369
|
+
const taskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2370
|
+
const requiredTags = (0, import_daemon_core.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
|
|
1711
2371
|
try {
|
|
1712
|
-
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message);
|
|
2372
|
+
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags });
|
|
1713
2373
|
if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
|
|
1714
2374
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1715
2375
|
});
|
|
1716
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2376
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode, requiredTags: task.requiredTags });
|
|
1717
2377
|
}
|
|
1718
2378
|
if (ctx.transport instanceof IpcTransport) {
|
|
1719
2379
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
@@ -1722,43 +2382,97 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
1722
2382
|
for (const node of ctx.mesh.nodes) {
|
|
1723
2383
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1724
2384
|
if (isLocalNode || !node.daemonId) continue;
|
|
2385
|
+
if (!(0, import_daemon_core.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core.buildMeshNodeCapabilityTags)(node))) continue;
|
|
1725
2386
|
dispatchPromises.push(
|
|
1726
2387
|
ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
|
|
1727
2388
|
if (result.success) {
|
|
1728
2389
|
try {
|
|
2390
|
+
const providerType = result.providerType;
|
|
2391
|
+
const descriptor = summarizeTaskMessage(args.message);
|
|
1729
2392
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1730
2393
|
kind: "task_dispatched",
|
|
1731
2394
|
nodeId: node.id,
|
|
1732
2395
|
sessionId: result.sessionId,
|
|
1733
|
-
|
|
2396
|
+
providerType,
|
|
2397
|
+
payload: {
|
|
2398
|
+
source: "queue",
|
|
2399
|
+
via: "p2p_direct",
|
|
2400
|
+
taskId: task.id,
|
|
2401
|
+
message: args.message,
|
|
2402
|
+
taskTitle: descriptor.taskTitle,
|
|
2403
|
+
taskSummary: descriptor.taskSummary,
|
|
2404
|
+
...task.taskMode ? { taskMode: task.taskMode } : {},
|
|
2405
|
+
...providerType ? { providerType } : {},
|
|
2406
|
+
targetSessionId: result.sessionId
|
|
2407
|
+
}
|
|
1734
2408
|
});
|
|
1735
2409
|
} catch {
|
|
1736
2410
|
}
|
|
1737
2411
|
}
|
|
1738
|
-
}).catch(() => {
|
|
2412
|
+
}).catch((err) => {
|
|
2413
|
+
try {
|
|
2414
|
+
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2415
|
+
kind: "p2p_dispatch_failed",
|
|
2416
|
+
nodeId: node.id,
|
|
2417
|
+
payload: {
|
|
2418
|
+
source: "queue",
|
|
2419
|
+
via: "p2p_direct",
|
|
2420
|
+
taskId: task.id,
|
|
2421
|
+
error: err?.message || String(err),
|
|
2422
|
+
dispatchFailedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2423
|
+
}
|
|
2424
|
+
});
|
|
2425
|
+
} catch {
|
|
2426
|
+
}
|
|
1739
2427
|
})
|
|
1740
2428
|
);
|
|
1741
2429
|
}
|
|
1742
2430
|
Promise.all(dispatchPromises).catch(() => {
|
|
1743
2431
|
});
|
|
1744
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2432
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode, requiredTags: task.requiredTags });
|
|
1745
2433
|
}
|
|
1746
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2434
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode, requiredTags: task.requiredTags });
|
|
1747
2435
|
} catch (e) {
|
|
1748
|
-
|
|
2436
|
+
const message = e?.message || String(e);
|
|
2437
|
+
if (message.includes("live_debug_readonly_guardrail_violation")) {
|
|
2438
|
+
return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
|
|
2439
|
+
}
|
|
2440
|
+
return JSON.stringify({ success: false, error: message });
|
|
1749
2441
|
}
|
|
1750
2442
|
}
|
|
1751
2443
|
async function meshViewQueue(ctx, args) {
|
|
1752
2444
|
try {
|
|
2445
|
+
await refreshMeshFromDaemon(ctx);
|
|
1753
2446
|
const statusFilter = sanitizeQueueStatusFilter(args.status);
|
|
1754
2447
|
const view = normalizeQueueViewMode(args.view);
|
|
1755
|
-
const fullQueue = annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh);
|
|
2448
|
+
const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh));
|
|
1756
2449
|
const queue = filterQueueForView(fullQueue, view, statusFilter);
|
|
1757
2450
|
const summary = buildQueueStatusSummary(fullQueue);
|
|
1758
2451
|
const visibleSummary = buildQueueStatusSummary(queue);
|
|
1759
2452
|
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
2453
|
+
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
2454
|
+
(0, import_daemon_core.markStaleDirectDispatches)(ctx.mesh.id);
|
|
2455
|
+
const ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
2456
|
+
const directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
|
|
2457
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2458
|
+
meshId: ctx.mesh.id,
|
|
2459
|
+
queue: fullQueue,
|
|
2460
|
+
ledgerEntries,
|
|
2461
|
+
// Always pass BeadsDB records (may be empty). buildMeshActiveWork uses them for local
|
|
2462
|
+
// dispatches and falls through to ledger scan for remote P2P dispatches not in BeadsDB.
|
|
2463
|
+
directDispatches,
|
|
2464
|
+
nodes: liveNodes
|
|
2465
|
+
});
|
|
2466
|
+
const recentDispatchFailures = ledgerEntries.filter((e) => e.kind === "p2p_dispatch_failed").slice(-20).map((e) => ({
|
|
2467
|
+
nodeId: e.nodeId,
|
|
2468
|
+
taskId: e.payload?.taskId,
|
|
2469
|
+
error: e.payload?.error,
|
|
2470
|
+
via: e.payload?.via,
|
|
2471
|
+
failedAt: e.payload?.dispatchFailedAt || e.timestamp
|
|
2472
|
+
}));
|
|
1760
2473
|
const staleAssignedTasks = maintenance.staleAssignedTasks || [];
|
|
1761
2474
|
const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
2475
|
+
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
1762
2476
|
return JSON.stringify({
|
|
1763
2477
|
success: true,
|
|
1764
2478
|
sourceOfTruth: {
|
|
@@ -1773,21 +2487,20 @@ async function meshViewQueue(ctx, args) {
|
|
|
1773
2487
|
filtered: Boolean(statusFilter?.length) || view !== "all"
|
|
1774
2488
|
},
|
|
1775
2489
|
queue,
|
|
1776
|
-
|
|
1777
|
-
|
|
2490
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2491
|
+
staleDirectWork: activeWorkEvidence.staleDirectWork,
|
|
2492
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
2493
|
+
...pollingGuidance ? { pollingGuidance } : {},
|
|
1778
2494
|
summary,
|
|
1779
|
-
activeCounts: summary.activeCounts,
|
|
1780
|
-
historicalCounts: summary.historicalCounts,
|
|
1781
|
-
activeCount: summary.activeCount,
|
|
1782
|
-
historicalCount: summary.historicalCount,
|
|
1783
|
-
visibleActiveCounts: visibleSummary.activeCounts,
|
|
1784
|
-
visibleHistoricalCounts: visibleSummary.historicalCounts,
|
|
1785
|
-
visibleActiveCount: visibleSummary.activeCount,
|
|
1786
|
-
visibleHistoricalCount: visibleSummary.historicalCount,
|
|
1787
2495
|
staleAssignedTasks,
|
|
1788
2496
|
staleAssignedCount: maintenance.staleAssignedCount,
|
|
1789
2497
|
queueMaintenance: maintenance,
|
|
1790
2498
|
cleanupDryRun: maintenance,
|
|
2499
|
+
...recentDispatchFailures.length > 0 ? {
|
|
2500
|
+
recentDispatchFailures,
|
|
2501
|
+
dispatchFailureCount: recentDispatchFailures.length,
|
|
2502
|
+
dispatchFailureNote: "Remote P2P dispatch attempts that failed. Affected tasks remain pending and may require mesh_queue_requeue if no idle session picks them up."
|
|
2503
|
+
} : {},
|
|
1791
2504
|
...view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status)) ? {
|
|
1792
2505
|
activeQueue: queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")))
|
|
1793
2506
|
} : {},
|
|
@@ -1807,6 +2520,10 @@ async function meshQueueCancel(ctx, args) {
|
|
|
1807
2520
|
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
1808
2521
|
const task = (0, import_daemon_core.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
|
|
1809
2522
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
2523
|
+
if (isLocalTransport(ctx.transport)) {
|
|
2524
|
+
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
1810
2527
|
return JSON.stringify({ success: true, task }, null, 2);
|
|
1811
2528
|
} catch (e) {
|
|
1812
2529
|
return JSON.stringify({ success: false, error: e.message });
|
|
@@ -1837,10 +2554,60 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
1837
2554
|
}
|
|
1838
2555
|
}
|
|
1839
2556
|
async function meshSendTask(ctx, args) {
|
|
2557
|
+
const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2558
|
+
const modeValidation = (0, import_daemon_core.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
|
|
2559
|
+
if (!modeValidation.valid) {
|
|
2560
|
+
return JSON.stringify({
|
|
2561
|
+
success: false,
|
|
2562
|
+
code: "live_debug_readonly_guardrail_violation",
|
|
2563
|
+
taskMode: modeValidation.taskMode || requestedTaskMode,
|
|
2564
|
+
violations: modeValidation.violations,
|
|
2565
|
+
allowedOperations: modeValidation.allowedOperations,
|
|
2566
|
+
error: `live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
const taskMode = modeValidation.taskMode;
|
|
1840
2570
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1841
2571
|
if (node.policy?.readOnly) {
|
|
1842
2572
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
1843
2573
|
}
|
|
2574
|
+
let explicitTargetSession;
|
|
2575
|
+
if (args.session_id && isWorkerTaskMode(taskMode) && (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport))) {
|
|
2576
|
+
try {
|
|
2577
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2578
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2579
|
+
explicitTargetSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2580
|
+
if (explicitTargetSession && isMeshCoordinatorSessionRecord(explicitTargetSession)) {
|
|
2581
|
+
return JSON.stringify({
|
|
2582
|
+
success: false,
|
|
2583
|
+
recoverable: true,
|
|
2584
|
+
code: "mesh_target_session_is_coordinator",
|
|
2585
|
+
reason: "mesh_target_session_is_coordinator",
|
|
2586
|
+
nodeId: args.node_id,
|
|
2587
|
+
sessionId: args.session_id,
|
|
2588
|
+
taskMode: taskMode || "unspecified",
|
|
2589
|
+
error: `Session '${args.session_id}' is a Repo Mesh coordinator session, not a visible worker session. Launch or use a visible worker session before dispatching this task.`,
|
|
2590
|
+
nextAction: `Call mesh_launch_session for node '${args.node_id}' and then retry mesh_send_task with that worker session_id, or use mesh_enqueue_task for queue-based worker assignment.`
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
if (explicitTargetSession && isUnmanagedSessionRecord(explicitTargetSession)) {
|
|
2594
|
+
return JSON.stringify({
|
|
2595
|
+
success: false,
|
|
2596
|
+
recoverable: true,
|
|
2597
|
+
code: "mesh_target_session_unmanaged",
|
|
2598
|
+
reason: "mesh_target_session_unmanaged",
|
|
2599
|
+
nodeId: args.node_id,
|
|
2600
|
+
sessionId: args.session_id,
|
|
2601
|
+
taskMode: taskMode || "unspecified",
|
|
2602
|
+
unsafeTranscriptAlias: true,
|
|
2603
|
+
error: `Session '${args.session_id}' on node '${args.node_id}' has no Repo Mesh delegation metadata (missing meshNodeFor/meshCoordinatorFor/launchedByCoordinator). It may be the coordinator's own session or an unrelated session \u2014 dispatching risks self-send and orphaned completion events that never reach the coordinator ledger.`,
|
|
2604
|
+
nextAction: `Call mesh_launch_session for node '${args.node_id}' to start a fresh managed worker session, then retry mesh_send_task with the returned session_id. Alternatively use mesh_enqueue_task for queue-based assignment without specifying session_id.`
|
|
2605
|
+
});
|
|
2606
|
+
}
|
|
2607
|
+
} catch {
|
|
2608
|
+
explicitTargetSession = void 0;
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
1844
2611
|
const duplicate = hasRecentDuplicateDispatch(ctx, args);
|
|
1845
2612
|
if (duplicate.duplicate) {
|
|
1846
2613
|
return JSON.stringify({
|
|
@@ -1864,47 +2631,162 @@ async function meshSendTask(ctx, args) {
|
|
|
1864
2631
|
const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
|
|
1865
2632
|
meshId: ctx.mesh.id,
|
|
1866
2633
|
message: args.message,
|
|
1867
|
-
targetNodeId: args.node_id
|
|
2634
|
+
targetNodeId: args.node_id,
|
|
2635
|
+
...taskMode ? { taskMode } : {}
|
|
1868
2636
|
});
|
|
1869
2637
|
return JSON.stringify(res);
|
|
1870
2638
|
}
|
|
1871
2639
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1872
2640
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1873
|
-
const cached =
|
|
2641
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id || ""));
|
|
2642
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1874
2643
|
const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
|
|
1875
2644
|
session_id: args.session_id,
|
|
1876
2645
|
message: args.message,
|
|
1877
|
-
providerType: cached?.providerType
|
|
2646
|
+
providerType: cached?.providerType,
|
|
2647
|
+
verifiedSession: explicitTargetSession,
|
|
2648
|
+
meshContext: {
|
|
2649
|
+
meshId: ctx.mesh.id,
|
|
2650
|
+
nodeId: args.node_id,
|
|
2651
|
+
taskId
|
|
2652
|
+
}
|
|
1878
2653
|
});
|
|
1879
2654
|
if (result2.success) {
|
|
1880
2655
|
const dispatchedSessionId = args.session_id || result2.sessionId;
|
|
2656
|
+
const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1881
2657
|
try {
|
|
2658
|
+
const providerType = result2.providerType || cached?.providerType;
|
|
1882
2659
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1883
2660
|
kind: "task_dispatched",
|
|
1884
2661
|
nodeId: args.node_id,
|
|
1885
2662
|
sessionId: dispatchedSessionId,
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2663
|
+
providerType,
|
|
2664
|
+
payload: buildDirectTaskPayload(args.message, "p2p_direct", {
|
|
2665
|
+
taskId,
|
|
2666
|
+
taskMode,
|
|
2667
|
+
providerType,
|
|
2668
|
+
targetSessionId: dispatchedSessionId
|
|
2669
|
+
})
|
|
2670
|
+
});
|
|
2671
|
+
(0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
|
|
2672
|
+
taskId,
|
|
2673
|
+
nodeId: args.node_id,
|
|
2674
|
+
sessionId: dispatchedSessionId,
|
|
2675
|
+
providerType: providerType || void 0,
|
|
2676
|
+
message: args.message,
|
|
2677
|
+
taskMode: taskMode || void 0,
|
|
2678
|
+
via: "p2p_direct",
|
|
2679
|
+
dispatchedAt
|
|
1891
2680
|
});
|
|
1892
2681
|
} catch {
|
|
1893
2682
|
}
|
|
1894
2683
|
}
|
|
1895
|
-
return JSON.stringify({
|
|
2684
|
+
return JSON.stringify({
|
|
2685
|
+
...result2,
|
|
2686
|
+
nodeId: args.node_id,
|
|
2687
|
+
sessionId: result2.success ? args.session_id || result2.sessionId : args.session_id,
|
|
2688
|
+
...result2.success ? { source: "direct", taskId } : {},
|
|
2689
|
+
taskMode,
|
|
2690
|
+
...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
|
|
2691
|
+
dispatched: result2.success === true
|
|
2692
|
+
});
|
|
1896
2693
|
}
|
|
1897
2694
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
1898
|
-
const cached =
|
|
2695
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
|
|
2696
|
+
let resolvedProviderType = cached?.providerType || "";
|
|
2697
|
+
if (!resolvedProviderType) {
|
|
2698
|
+
let explicitSession = explicitTargetSession;
|
|
2699
|
+
if (!explicitSession) {
|
|
2700
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2701
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2702
|
+
explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2703
|
+
}
|
|
2704
|
+
if (!explicitSession) {
|
|
2705
|
+
return JSON.stringify({
|
|
2706
|
+
success: false,
|
|
2707
|
+
recoverable: true,
|
|
2708
|
+
code: "mesh_target_session_not_found",
|
|
2709
|
+
reason: "mesh_target_session_not_found",
|
|
2710
|
+
transport: "local_ipc",
|
|
2711
|
+
retryRecommended: true,
|
|
2712
|
+
nodeId: args.node_id,
|
|
2713
|
+
sessionId: args.session_id,
|
|
2714
|
+
error: `Local session '${args.session_id}' is not present in live status for node '${args.node_id}'.`,
|
|
2715
|
+
nextAction: `Launch a fresh session with mesh_launch_session(node_id: '${args.node_id}') or retry without session_id so Repo Mesh can target a live delegate session.`
|
|
2716
|
+
});
|
|
2717
|
+
}
|
|
2718
|
+
if (isMeshCoordinatorSessionRecord(explicitSession)) {
|
|
2719
|
+
return JSON.stringify({
|
|
2720
|
+
success: false,
|
|
2721
|
+
recoverable: true,
|
|
2722
|
+
code: "mesh_target_session_is_coordinator",
|
|
2723
|
+
reason: "mesh_target_session_is_coordinator",
|
|
2724
|
+
nodeId: args.node_id,
|
|
2725
|
+
sessionId: args.session_id,
|
|
2726
|
+
taskMode: taskMode || "unspecified",
|
|
2727
|
+
error: `Session '${args.session_id}' is a Repo Mesh coordinator session, not a visible worker session. Launch or use a visible worker session before dispatching this task.`,
|
|
2728
|
+
nextAction: `Call mesh_launch_session for node '${args.node_id}' and then retry mesh_send_task with that worker session_id, or use mesh_enqueue_task for queue-based worker assignment.`
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
if (isUnmanagedSessionRecord(explicitSession)) {
|
|
2732
|
+
return JSON.stringify({
|
|
2733
|
+
success: false,
|
|
2734
|
+
recoverable: true,
|
|
2735
|
+
code: "mesh_target_session_unmanaged",
|
|
2736
|
+
reason: "mesh_target_session_unmanaged",
|
|
2737
|
+
nodeId: args.node_id,
|
|
2738
|
+
sessionId: args.session_id,
|
|
2739
|
+
taskMode: taskMode || "unspecified",
|
|
2740
|
+
unsafeTranscriptAlias: true,
|
|
2741
|
+
unsafeDelegateTarget: true,
|
|
2742
|
+
error: `Session '${args.session_id}' on node '${args.node_id}' has no Repo Mesh delegation metadata (missing meshNodeFor/meshCoordinatorFor/launchedByCoordinator). It may be the coordinator's own session or an unrelated session \u2014 dispatching risks self-send and orphaned completion events that never reach the coordinator ledger.`,
|
|
2743
|
+
nextAction: `Call mesh_launch_session for node '${args.node_id}' to start a fresh managed worker session, then retry mesh_send_task with the returned session_id. Alternatively use mesh_enqueue_task for queue-based assignment without specifying session_id.`
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2747
|
+
if (resolvedProviderType) {
|
|
2748
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
|
|
2749
|
+
providerType: resolvedProviderType,
|
|
2750
|
+
providerSessionId: readString(explicitSession?.providerSessionId) || void 0,
|
|
2751
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
2752
|
+
});
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
if (!resolvedProviderType) {
|
|
2756
|
+
return JSON.stringify({
|
|
2757
|
+
success: false,
|
|
2758
|
+
recoverable: true,
|
|
2759
|
+
code: "mesh_target_session_provider_unknown",
|
|
2760
|
+
reason: "mesh_target_session_provider_unknown",
|
|
2761
|
+
transport: "local_ipc",
|
|
2762
|
+
retryRecommended: false,
|
|
2763
|
+
nodeId: args.node_id,
|
|
2764
|
+
sessionId: args.session_id,
|
|
2765
|
+
error: `Local session '${args.session_id}' is live but does not expose providerType/cliType, so agent_command cannot be routed safely.`,
|
|
2766
|
+
nextAction: `Relaunch the target session on node '${args.node_id}' or retry without session_id so Repo Mesh can pick a session with provider metadata.`
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2769
|
+
const sessionWasIdle = explicitTargetSession ? isIdleSessionRecord(explicitTargetSession) : false;
|
|
2770
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
2771
|
+
const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1899
2772
|
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
1900
2773
|
targetSessionId: args.session_id,
|
|
1901
|
-
|
|
2774
|
+
agentType: resolvedProviderType,
|
|
2775
|
+
cliType: resolvedProviderType,
|
|
2776
|
+
providerType: resolvedProviderType,
|
|
1902
2777
|
action: "send_chat",
|
|
1903
|
-
message: args.message
|
|
2778
|
+
message: args.message,
|
|
2779
|
+
meshContext: {
|
|
2780
|
+
meshId: ctx.mesh.id,
|
|
2781
|
+
nodeId: args.node_id,
|
|
2782
|
+
taskId
|
|
2783
|
+
}
|
|
1904
2784
|
});
|
|
1905
2785
|
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
1906
2786
|
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
2787
|
+
const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
|
|
1907
2788
|
return JSON.stringify({
|
|
2789
|
+
...source && typeof source === "object" ? source : {},
|
|
1908
2790
|
success: false,
|
|
1909
2791
|
nodeId: args.node_id,
|
|
1910
2792
|
sessionId: args.session_id,
|
|
@@ -1916,23 +2798,55 @@ async function meshSendTask(ctx, args) {
|
|
|
1916
2798
|
kind: "task_dispatched",
|
|
1917
2799
|
nodeId: args.node_id,
|
|
1918
2800
|
sessionId: args.session_id,
|
|
1919
|
-
providerType:
|
|
1920
|
-
payload:
|
|
2801
|
+
providerType: resolvedProviderType,
|
|
2802
|
+
payload: buildDirectTaskPayload(args.message, "local_direct", {
|
|
2803
|
+
taskId,
|
|
2804
|
+
taskMode,
|
|
2805
|
+
providerType: resolvedProviderType,
|
|
2806
|
+
targetSessionId: args.session_id,
|
|
2807
|
+
dispatchedToIdleSession: sessionWasIdle
|
|
2808
|
+
})
|
|
1921
2809
|
});
|
|
1922
2810
|
} catch {
|
|
1923
2811
|
}
|
|
1924
|
-
|
|
2812
|
+
(0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
|
|
2813
|
+
taskId,
|
|
2814
|
+
nodeId: args.node_id,
|
|
2815
|
+
sessionId: args.session_id,
|
|
2816
|
+
providerType: resolvedProviderType || void 0,
|
|
2817
|
+
message: args.message,
|
|
2818
|
+
taskMode: taskMode || void 0,
|
|
2819
|
+
via: "local_direct",
|
|
2820
|
+
dispatchedToIdleSession: sessionWasIdle,
|
|
2821
|
+
dispatchedAt
|
|
2822
|
+
});
|
|
2823
|
+
return JSON.stringify({
|
|
2824
|
+
success: true,
|
|
2825
|
+
dispatched: true,
|
|
2826
|
+
source: "direct",
|
|
2827
|
+
taskId,
|
|
2828
|
+
taskMode,
|
|
2829
|
+
providerType: resolvedProviderType,
|
|
2830
|
+
nodeId: args.node_id,
|
|
2831
|
+
sessionId: args.session_id,
|
|
2832
|
+
...sessionWasIdle ? {
|
|
2833
|
+
dispatchAcknowledgementRisk: true,
|
|
2834
|
+
dispatchAcknowledgementRiskReason: "session_was_idle_at_dispatch",
|
|
2835
|
+
dispatchAcknowledgementNote: `Session '${args.session_id}' was idle at dispatch time. If it does not transition to generating, this direct task was not acknowledged. Use mesh_status to verify; if the session remains idle, it may appear as stale direct work \u2014 launch a fresh session and retry.`
|
|
2836
|
+
} : {}
|
|
2837
|
+
});
|
|
1925
2838
|
}
|
|
1926
2839
|
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, {
|
|
1927
2840
|
targetNodeId: args.node_id,
|
|
1928
|
-
targetSessionId: args.session_id
|
|
2841
|
+
targetSessionId: args.session_id,
|
|
2842
|
+
taskMode
|
|
1929
2843
|
});
|
|
1930
2844
|
if (isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport) {
|
|
1931
2845
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1932
2846
|
});
|
|
1933
2847
|
}
|
|
1934
|
-
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)() : [];
|
|
1935
|
-
const result = { success: true, nodeId: args.node_id, taskId: task.id, status: task.status };
|
|
2848
|
+
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId) : [];
|
|
2849
|
+
const result = { success: true, source: "queue", nodeId: args.node_id, taskId: task.id, status: task.status, taskMode: task.taskMode };
|
|
1936
2850
|
if (pendingEvents.length > 0) {
|
|
1937
2851
|
result.pendingCoordinatorEvents = pendingEvents;
|
|
1938
2852
|
}
|
|
@@ -1956,7 +2870,7 @@ async function meshReadChat(ctx, args) {
|
|
|
1956
2870
|
await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
|
|
1957
2871
|
}
|
|
1958
2872
|
if (isLocalTransport(ctx.transport)) {
|
|
1959
|
-
const cached =
|
|
2873
|
+
const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
|
|
1960
2874
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
1961
2875
|
const result = await commandForNode(ctx, node, "read_chat", {
|
|
1962
2876
|
sessionId: args.session_id,
|
|
@@ -1964,18 +2878,19 @@ async function meshReadChat(ctx, args) {
|
|
|
1964
2878
|
workspace: node.workspace,
|
|
1965
2879
|
...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
|
|
1966
2880
|
...providerSessionId ? { providerSessionId } : {},
|
|
1967
|
-
tailLimit: args.tail ??
|
|
2881
|
+
tailLimit: args.tail ?? 3
|
|
1968
2882
|
});
|
|
1969
2883
|
const payload = annotateRapidReadChatAdvisory(unwrapCommandPayload(result), {
|
|
1970
2884
|
key: `mesh:${args.node_id}:${args.session_id}`,
|
|
1971
2885
|
toolName: "mesh_read_chat",
|
|
1972
2886
|
completionCallbackExpected: true
|
|
1973
2887
|
});
|
|
1974
|
-
|
|
2888
|
+
const useCompact = args.compact !== false;
|
|
2889
|
+
if (useCompact) {
|
|
1975
2890
|
const compactPayload = compactChatPayload(payload, {
|
|
1976
2891
|
nodeId: args.node_id,
|
|
1977
2892
|
sessionId: args.session_id,
|
|
1978
|
-
limit: args.tail ??
|
|
2893
|
+
limit: args.tail ?? 3
|
|
1979
2894
|
});
|
|
1980
2895
|
return JSON.stringify(
|
|
1981
2896
|
payload.pollingAdvisory ? { ...compactPayload, pollingAdvisory: payload.pollingAdvisory } : compactPayload,
|
|
@@ -1988,7 +2903,7 @@ async function meshReadChat(ctx, args) {
|
|
|
1988
2903
|
try {
|
|
1989
2904
|
const targetId = `${node.daemonId}:session:${args.session_id}`;
|
|
1990
2905
|
const res = await ctx.transport.readChat(targetId, {
|
|
1991
|
-
limit: args.tail ??
|
|
2906
|
+
limit: args.tail ?? 3,
|
|
1992
2907
|
sessionId: args.session_id
|
|
1993
2908
|
});
|
|
1994
2909
|
return JSON.stringify(res, null, 2);
|
|
@@ -2002,7 +2917,7 @@ async function meshReadChat(ctx, args) {
|
|
|
2002
2917
|
async function meshReadDebug(ctx, args) {
|
|
2003
2918
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2004
2919
|
if (isLocalTransport(ctx.transport)) {
|
|
2005
|
-
const cached =
|
|
2920
|
+
const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
|
|
2006
2921
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
2007
2922
|
const delivery = args.delivery === "inline" ? void 0 : "daemon_file";
|
|
2008
2923
|
const result = await commandForNode(ctx, node, "get_chat_debug_bundle", {
|
|
@@ -2033,6 +2948,8 @@ async function meshReadDebug(ctx, args) {
|
|
|
2033
2948
|
}
|
|
2034
2949
|
async function meshLaunchSession(ctx, args) {
|
|
2035
2950
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2951
|
+
const bootstrapBlock = getWorktreeBootstrapLaunchBlock(node);
|
|
2952
|
+
if (bootstrapBlock) return JSON.stringify(bootstrapBlock, null, 2);
|
|
2036
2953
|
if (isLocalTransport(ctx.transport)) {
|
|
2037
2954
|
let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
|
|
2038
2955
|
if (!resolvedProviderType) {
|
|
@@ -2057,6 +2974,10 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2057
2974
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
2058
2975
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2059
2976
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2977
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2978
|
+
if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
|
|
2979
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2980
|
+
}
|
|
2060
2981
|
let result;
|
|
2061
2982
|
try {
|
|
2062
2983
|
result = await commandForNode(ctx, node, "launch_cli", {
|
|
@@ -2084,7 +3005,8 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2084
3005
|
if (runtimeSessionId) {
|
|
2085
3006
|
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, runtimeSessionId), {
|
|
2086
3007
|
providerType: resolvedProviderType,
|
|
2087
|
-
...providerSessionId ? { providerSessionId } : {}
|
|
3008
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
3009
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
2088
3010
|
});
|
|
2089
3011
|
}
|
|
2090
3012
|
try {
|
|
@@ -2097,7 +3019,6 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2097
3019
|
});
|
|
2098
3020
|
} catch {
|
|
2099
3021
|
}
|
|
2100
|
-
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2101
3022
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
2102
3023
|
ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2103
3024
|
});
|
|
@@ -2122,6 +3043,9 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2122
3043
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
2123
3044
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2124
3045
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
3046
|
+
if (!coordinatorDaemonId) {
|
|
3047
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
3048
|
+
}
|
|
2125
3049
|
try {
|
|
2126
3050
|
const res = await ctx.transport.launch(node.daemonId, {
|
|
2127
3051
|
type: resolvedProviderType,
|
|
@@ -2202,6 +3126,51 @@ async function meshGitStatus(ctx, args) {
|
|
|
2202
3126
|
}, null, 2);
|
|
2203
3127
|
}
|
|
2204
3128
|
}
|
|
3129
|
+
async function meshFastForwardNode(ctx, args) {
|
|
3130
|
+
await refreshMeshFromDaemon(ctx);
|
|
3131
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3132
|
+
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
3133
|
+
if (node.policy?.readOnly) {
|
|
3134
|
+
return JSON.stringify({
|
|
3135
|
+
success: false,
|
|
3136
|
+
code: "node_read_only",
|
|
3137
|
+
nodeId: args.node_id,
|
|
3138
|
+
workspace: node.workspace,
|
|
3139
|
+
allowed: false,
|
|
3140
|
+
willRun: false,
|
|
3141
|
+
executed: false,
|
|
3142
|
+
blockingReasons: ["node_read_only"]
|
|
3143
|
+
}, null, 2);
|
|
3144
|
+
}
|
|
3145
|
+
try {
|
|
3146
|
+
const dryRun = args.dry_run === true || args.execute !== true;
|
|
3147
|
+
const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
|
|
3148
|
+
meshId: ctx.mesh.id,
|
|
3149
|
+
nodeId: node.id,
|
|
3150
|
+
workspace: node.workspace,
|
|
3151
|
+
branch: typeof args.branch === "string" ? args.branch : void 0,
|
|
3152
|
+
execute: args.execute === true && args.dry_run !== true,
|
|
3153
|
+
dryRun,
|
|
3154
|
+
updateSubmodules: args.update_submodules === true,
|
|
3155
|
+
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
3156
|
+
});
|
|
3157
|
+
return JSON.stringify(unwrapCommandPayload(result), null, 2);
|
|
3158
|
+
} catch (e) {
|
|
3159
|
+
const failure = buildCoordinatorP2pRelayFailure(e, {
|
|
3160
|
+
command: "fast_forward_mesh_node",
|
|
3161
|
+
targetDaemonId: node.daemonId,
|
|
3162
|
+
nodeId: args.node_id
|
|
3163
|
+
});
|
|
3164
|
+
return JSON.stringify({
|
|
3165
|
+
...failure,
|
|
3166
|
+
workspace: node.workspace,
|
|
3167
|
+
allowed: false,
|
|
3168
|
+
willRun: false,
|
|
3169
|
+
executed: false,
|
|
3170
|
+
blockingReasons: [failure.code || "mesh_fast_forward_unavailable"]
|
|
3171
|
+
}, null, 2);
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
2205
3174
|
async function meshCheckpoint(ctx, args) {
|
|
2206
3175
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2207
3176
|
if (node.policy?.readOnly) {
|
|
@@ -2217,7 +3186,13 @@ async function meshCheckpoint(ctx, args) {
|
|
|
2217
3186
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2218
3187
|
kind: "checkpoint_created",
|
|
2219
3188
|
nodeId: args.node_id,
|
|
2220
|
-
payload: {
|
|
3189
|
+
payload: {
|
|
3190
|
+
message: args.message,
|
|
3191
|
+
commit: result?.checkpoint?.commit,
|
|
3192
|
+
outcome: result?.checkpoint?.status || (result?.checkpoint?.noop ? "skipped" : void 0),
|
|
3193
|
+
noop: result?.checkpoint?.noop === true,
|
|
3194
|
+
reason: result?.checkpoint?.reason
|
|
3195
|
+
}
|
|
2221
3196
|
});
|
|
2222
3197
|
} catch {
|
|
2223
3198
|
}
|
|
@@ -2233,7 +3208,13 @@ async function meshCheckpoint(ctx, args) {
|
|
|
2233
3208
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2234
3209
|
kind: "checkpoint_created",
|
|
2235
3210
|
nodeId: args.node_id,
|
|
2236
|
-
payload: {
|
|
3211
|
+
payload: {
|
|
3212
|
+
message: args.message,
|
|
3213
|
+
commit: res?.checkpoint?.commit,
|
|
3214
|
+
outcome: res?.checkpoint?.status || (res?.checkpoint?.noop ? "skipped" : void 0),
|
|
3215
|
+
noop: res?.checkpoint?.noop === true,
|
|
3216
|
+
reason: res?.checkpoint?.reason
|
|
3217
|
+
}
|
|
2237
3218
|
});
|
|
2238
3219
|
} catch {
|
|
2239
3220
|
}
|
|
@@ -2248,7 +3229,7 @@ async function meshCheckpoint(ctx, args) {
|
|
|
2248
3229
|
async function meshApprove(ctx, args) {
|
|
2249
3230
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2250
3231
|
if (isLocalTransport(ctx.transport)) {
|
|
2251
|
-
const cached =
|
|
3232
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
|
|
2252
3233
|
const providerSessionId = cached?.providerSessionId;
|
|
2253
3234
|
const result = await commandForNode(ctx, node, "resolve_action", {
|
|
2254
3235
|
sessionId: args.session_id,
|
|
@@ -2401,6 +3382,43 @@ async function meshRemoveNode(ctx, args) {
|
|
|
2401
3382
|
return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
|
|
2402
3383
|
}
|
|
2403
3384
|
}
|
|
3385
|
+
function resolveRefineConfigNode(ctx, nodeId) {
|
|
3386
|
+
if (nodeId) return findNode(ctx.mesh, nodeId);
|
|
3387
|
+
const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
|
|
3388
|
+
if (!node) throw new Error("No mesh node with a workspace is available");
|
|
3389
|
+
return node;
|
|
3390
|
+
}
|
|
3391
|
+
async function meshRefineConfigSchema(ctx) {
|
|
3392
|
+
const node = resolveRefineConfigNode(ctx);
|
|
3393
|
+
const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
|
|
3394
|
+
return JSON.stringify(result, null, 2);
|
|
3395
|
+
}
|
|
3396
|
+
async function meshValidateRefineConfig(ctx, args) {
|
|
3397
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
3398
|
+
const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
|
|
3399
|
+
workspace: node.workspace,
|
|
3400
|
+
inlineMesh: ctx.mesh,
|
|
3401
|
+
...args.config ? { config: args.config } : {}
|
|
3402
|
+
});
|
|
3403
|
+
return JSON.stringify(result, null, 2);
|
|
3404
|
+
}
|
|
3405
|
+
async function meshSuggestRefineConfig(ctx, args) {
|
|
3406
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
3407
|
+
const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
|
|
3408
|
+
workspace: node.workspace,
|
|
3409
|
+
inlineMesh: ctx.mesh
|
|
3410
|
+
});
|
|
3411
|
+
return JSON.stringify(result, null, 2);
|
|
3412
|
+
}
|
|
3413
|
+
async function meshRefinePlan(ctx, args) {
|
|
3414
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3415
|
+
const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
|
|
3416
|
+
meshId: ctx.mesh.id,
|
|
3417
|
+
nodeId: args.node_id,
|
|
3418
|
+
inlineMesh: ctx.mesh
|
|
3419
|
+
});
|
|
3420
|
+
return JSON.stringify(result, null, 2);
|
|
3421
|
+
}
|
|
2404
3422
|
async function meshRefineNode(ctx, args) {
|
|
2405
3423
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2406
3424
|
if (isLocalTransport(ctx.transport)) {
|
|
@@ -2409,7 +3427,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2409
3427
|
nodeId: args.node_id,
|
|
2410
3428
|
inlineMesh: ctx.mesh
|
|
2411
3429
|
});
|
|
2412
|
-
if (result?.success && result.removeResult?.removed !== false) {
|
|
3430
|
+
if (result?.success && result.async !== true && result.removeResult?.removed !== false) {
|
|
2413
3431
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2414
3432
|
if (idx >= 0) {
|
|
2415
3433
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2424,7 +3442,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2424
3442
|
nodeId: args.node_id,
|
|
2425
3443
|
inlineMesh: ctx.mesh
|
|
2426
3444
|
});
|
|
2427
|
-
if (res?.success && res.removeResult?.removed !== false) {
|
|
3445
|
+
if (res?.success && res.async !== true && res.removeResult?.removed !== false) {
|
|
2428
3446
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2429
3447
|
if (idx >= 0) {
|
|
2430
3448
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2461,13 +3479,13 @@ var STANDARD_TOOLS = [
|
|
|
2461
3479
|
function buildMcpHelpText() {
|
|
2462
3480
|
const meshTools = ALL_MESH_TOOLS.map((tool) => tool.name);
|
|
2463
3481
|
return `
|
|
2464
|
-
|
|
3482
|
+
ADHDev MCP Server
|
|
2465
3483
|
|
|
2466
3484
|
Usage:
|
|
2467
|
-
adhdev
|
|
2468
|
-
adhdev
|
|
2469
|
-
adhdev
|
|
2470
|
-
adhdev-mcp --
|
|
3485
|
+
adhdev mcp Local mode (requires standalone daemon)
|
|
3486
|
+
adhdev mcp --api-key <key> Cloud mode (ADHDev cloud API)
|
|
3487
|
+
adhdev mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
|
|
3488
|
+
adhdev-mcp --help Compatibility bin (same server, legacy package entrypoint)
|
|
2471
3489
|
|
|
2472
3490
|
Options:
|
|
2473
3491
|
--mode <mode> Transport: local, cloud, or ipc
|
|
@@ -2492,6 +3510,7 @@ Mesh tools: ${meshTools.join(", ")}
|
|
|
2492
3510
|
// src/server.ts
|
|
2493
3511
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
2494
3512
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
3513
|
+
var import_node_os = __toESM(require("os"));
|
|
2495
3514
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
2496
3515
|
|
|
2497
3516
|
// src/transports/local.ts
|
|
@@ -4025,6 +5044,7 @@ async function startMcpServer(opts) {
|
|
|
4025
5044
|
requirePreTaskCheckpoint: false,
|
|
4026
5045
|
requirePostTaskCheckpoint: true,
|
|
4027
5046
|
requireApprovalForPush: true,
|
|
5047
|
+
allowAutoPublishSubmoduleMainCommits: false,
|
|
4028
5048
|
requireApprovalForDestructiveGit: true,
|
|
4029
5049
|
dirtyWorkspaceBehavior: "warn",
|
|
4030
5050
|
maxParallelTasks: 2,
|
|
@@ -4081,11 +5101,13 @@ async function startMcpServer(opts) {
|
|
|
4081
5101
|
}
|
|
4082
5102
|
let localDaemonId;
|
|
4083
5103
|
let localMachineId;
|
|
5104
|
+
let coordinatorHostname = import_node_os.default.hostname();
|
|
4084
5105
|
if (transport instanceof LocalTransport || transport instanceof IpcTransport) {
|
|
4085
5106
|
try {
|
|
4086
5107
|
const { loadConfig } = await import("@adhdev/daemon-core");
|
|
4087
5108
|
const cfg = loadConfig();
|
|
4088
|
-
if (cfg.
|
|
5109
|
+
if (cfg.machineId) localMachineId = cfg.machineId;
|
|
5110
|
+
else if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
|
|
4089
5111
|
} catch {
|
|
4090
5112
|
}
|
|
4091
5113
|
}
|
|
@@ -4093,14 +5115,16 @@ async function startMcpServer(opts) {
|
|
|
4093
5115
|
try {
|
|
4094
5116
|
const statusResult = await transport.getStatus();
|
|
4095
5117
|
const instanceId = typeof statusResult?.status?.instanceId === "string" ? statusResult.status.instanceId.trim() : "";
|
|
5118
|
+
const hostname = typeof statusResult?.status?.hostname === "string" ? statusResult.status.hostname.trim() : typeof statusResult?.status?.machine?.hostname === "string" ? statusResult.status.machine.hostname.trim() : "";
|
|
4096
5119
|
if (instanceId) localDaemonId = instanceId;
|
|
5120
|
+
if (hostname) coordinatorHostname = hostname;
|
|
4097
5121
|
} catch {
|
|
4098
5122
|
}
|
|
4099
5123
|
}
|
|
4100
|
-
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {} };
|
|
5124
|
+
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {}, ...coordinatorHostname ? { coordinatorHostname } : {} };
|
|
4101
5125
|
const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
|
|
4102
5126
|
const server2 = new import_server.Server(
|
|
4103
|
-
{ name: "adhdev-mcp-server", version: "0.9.
|
|
5127
|
+
{ name: "adhdev-mcp-server", version: "0.9.82" },
|
|
4104
5128
|
{ capabilities: { tools: {}, resources: {} } }
|
|
4105
5129
|
);
|
|
4106
5130
|
const { ListResourcesRequestSchema, ReadResourceRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
|
|
@@ -4126,7 +5150,7 @@ async function startMcpServer(opts) {
|
|
|
4126
5150
|
let text;
|
|
4127
5151
|
switch (name) {
|
|
4128
5152
|
case "mesh_status":
|
|
4129
|
-
text = await meshStatus(meshCtx);
|
|
5153
|
+
text = await meshStatus(meshCtx, a);
|
|
4130
5154
|
break;
|
|
4131
5155
|
case "mesh_list_nodes":
|
|
4132
5156
|
text = await meshListNodes(meshCtx);
|
|
@@ -4158,6 +5182,9 @@ async function startMcpServer(opts) {
|
|
|
4158
5182
|
case "mesh_git_status":
|
|
4159
5183
|
text = await meshGitStatus(meshCtx, a);
|
|
4160
5184
|
break;
|
|
5185
|
+
case "mesh_fast_forward_node":
|
|
5186
|
+
text = await meshFastForwardNode(meshCtx, a);
|
|
5187
|
+
break;
|
|
4161
5188
|
case "mesh_checkpoint":
|
|
4162
5189
|
text = await meshCheckpoint(meshCtx, a);
|
|
4163
5190
|
break;
|
|
@@ -4173,6 +5200,18 @@ async function startMcpServer(opts) {
|
|
|
4173
5200
|
case "mesh_refine_node":
|
|
4174
5201
|
text = await meshRefineNode(meshCtx, a);
|
|
4175
5202
|
break;
|
|
5203
|
+
case "mesh_refine_config_schema":
|
|
5204
|
+
text = await meshRefineConfigSchema(meshCtx);
|
|
5205
|
+
break;
|
|
5206
|
+
case "mesh_validate_refine_config":
|
|
5207
|
+
text = await meshValidateRefineConfig(meshCtx, a);
|
|
5208
|
+
break;
|
|
5209
|
+
case "mesh_suggest_refine_config":
|
|
5210
|
+
text = await meshSuggestRefineConfig(meshCtx, a);
|
|
5211
|
+
break;
|
|
5212
|
+
case "mesh_refine_plan":
|
|
5213
|
+
text = await meshRefinePlan(meshCtx, a);
|
|
5214
|
+
break;
|
|
4176
5215
|
case "mesh_cleanup_sessions":
|
|
4177
5216
|
text = await meshCleanupSessions(meshCtx, a);
|
|
4178
5217
|
break;
|