@adhdev/daemon-standalone 0.9.82-rc.18 → 0.9.82-rc.180
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 +55142 -27038
- 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 +1198 -170
- 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) {
|
|
@@ -832,7 +1110,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
832
1110
|
error: `P2P dispatch failed: ${errorMessage}`
|
|
833
1111
|
};
|
|
834
1112
|
}
|
|
835
|
-
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
|
|
1113
|
+
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType, providerType: resolvedProviderType };
|
|
836
1114
|
} catch (e) {
|
|
837
1115
|
const errorMessage = e?.message || String(e);
|
|
838
1116
|
return {
|
|
@@ -862,34 +1140,199 @@ function resolveCoordinatorNode(ctx) {
|
|
|
862
1140
|
return void 0;
|
|
863
1141
|
}
|
|
864
1142
|
function readNodeMachineId(node) {
|
|
865
|
-
return readString(node.machineId) || readString(node.machine_id);
|
|
1143
|
+
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
1144
|
}
|
|
867
1145
|
function readNodeDaemonId(node) {
|
|
868
|
-
return readString(node.daemonId) || readString(node.daemon_id);
|
|
1146
|
+
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);
|
|
1147
|
+
}
|
|
1148
|
+
function normalizeHostname(value) {
|
|
1149
|
+
const hostname = readString(value);
|
|
1150
|
+
if (!hostname) return void 0;
|
|
1151
|
+
return hostname.toLowerCase().replace(/\.$/, "");
|
|
1152
|
+
}
|
|
1153
|
+
function readNodeHostname(node) {
|
|
1154
|
+
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);
|
|
1155
|
+
}
|
|
1156
|
+
function readNodeDisplayMachineName(node) {
|
|
1157
|
+
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);
|
|
1158
|
+
}
|
|
1159
|
+
function compactIdentityEvidence(value) {
|
|
1160
|
+
if (!value) return void 0;
|
|
1161
|
+
return value.length > 24 ? `${value.slice(0, 12)}\u2026${value.slice(-8)}` : value;
|
|
1162
|
+
}
|
|
1163
|
+
function pushIdentityEvidence(evidence, label, value) {
|
|
1164
|
+
const compact = compactIdentityEvidence(value);
|
|
1165
|
+
if (compact) evidence.push(`${label}:${compact}`);
|
|
1166
|
+
}
|
|
1167
|
+
function buildNodeMachineIdentity(ctx, node) {
|
|
1168
|
+
const machineId = readNodeMachineId(node);
|
|
1169
|
+
const daemonId = readNodeDaemonId(node);
|
|
1170
|
+
const hostname = readNodeHostname(node);
|
|
1171
|
+
const machineName = readNodeDisplayMachineName(node);
|
|
1172
|
+
const coordinatorHostname = readString(ctx.coordinatorHostname);
|
|
1173
|
+
const localControlPlaneReason = getLocalControlPlaneMatchReason(ctx, node);
|
|
1174
|
+
const directLocal = !!localControlPlaneReason;
|
|
1175
|
+
const hostnameMatches = Boolean(
|
|
1176
|
+
normalizeHostname(hostname) && normalizeHostname(coordinatorHostname) && normalizeHostname(hostname) === normalizeHostname(coordinatorHostname)
|
|
1177
|
+
);
|
|
1178
|
+
const sameMachine = directLocal || hostnameMatches;
|
|
1179
|
+
const evidence = [];
|
|
1180
|
+
pushIdentityEvidence(evidence, "machineName", machineName);
|
|
1181
|
+
pushIdentityEvidence(evidence, "hostname", hostname);
|
|
1182
|
+
pushIdentityEvidence(evidence, "machineId", machineId);
|
|
1183
|
+
pushIdentityEvidence(evidence, "daemonId", daemonId);
|
|
1184
|
+
if (localControlPlaneReason) {
|
|
1185
|
+
pushIdentityEvidence(evidence, "localMatch", localControlPlaneReason);
|
|
1186
|
+
pushIdentityEvidence(evidence, "localMachineId", ctx.localMachineId);
|
|
1187
|
+
pushIdentityEvidence(evidence, "localDaemonId", ctx.localDaemonId);
|
|
1188
|
+
}
|
|
1189
|
+
const locality = sameMachine ? "same_machine" : evidence.length > 0 ? "remote_known" : "remote_or_unknown";
|
|
1190
|
+
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";
|
|
1191
|
+
return {
|
|
1192
|
+
daemonId,
|
|
1193
|
+
machineId,
|
|
1194
|
+
hostname,
|
|
1195
|
+
machineName,
|
|
1196
|
+
displayName: machineName || hostname || daemonId || machineId,
|
|
1197
|
+
coordinatorHostname,
|
|
1198
|
+
sameMachine,
|
|
1199
|
+
locality,
|
|
1200
|
+
localityReason,
|
|
1201
|
+
identityEvidence: evidence
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
function nodeHasLocalDaemonEvidence(ctx, node) {
|
|
1205
|
+
const isLocal = (session) => {
|
|
1206
|
+
if (!session || typeof session !== "object") return false;
|
|
1207
|
+
if (ctx.localDaemonId && session.settings?.meshCoordinatorDaemonId === ctx.localDaemonId) return true;
|
|
1208
|
+
if (session.launchedByCoordinator === true) return true;
|
|
1209
|
+
if (ctx.localDaemonId && session.runtime?.owner === ctx.localDaemonId) return true;
|
|
1210
|
+
if (ctx.localDaemonId && session.daemonClient?.daemonId === ctx.localDaemonId) return true;
|
|
1211
|
+
return false;
|
|
1212
|
+
};
|
|
1213
|
+
const sessionArrays = [
|
|
1214
|
+
node?.sessions,
|
|
1215
|
+
node?.activeSessions,
|
|
1216
|
+
node?.active_sessions,
|
|
1217
|
+
node?.lastProbe?.sessions,
|
|
1218
|
+
node?.last_probe?.sessions,
|
|
1219
|
+
node?.lastProbe?.status?.sessions,
|
|
1220
|
+
node?.last_probe?.status?.sessions
|
|
1221
|
+
];
|
|
1222
|
+
for (const arr of sessionArrays) {
|
|
1223
|
+
if (Array.isArray(arr) && arr.some(isLocal)) return true;
|
|
1224
|
+
}
|
|
1225
|
+
const sessionRecords = [
|
|
1226
|
+
node?.activeSession,
|
|
1227
|
+
node?.active_session,
|
|
1228
|
+
node?.currentSession,
|
|
1229
|
+
node?.current_session,
|
|
1230
|
+
node?.runtimeSession,
|
|
1231
|
+
node?.runtime_session,
|
|
1232
|
+
node?.session,
|
|
1233
|
+
node?.lastProbe?.activeSession,
|
|
1234
|
+
node?.last_probe?.active_session,
|
|
1235
|
+
node?.lastProbe?.currentSession,
|
|
1236
|
+
node?.last_probe?.current_session,
|
|
1237
|
+
node?.lastProbe?.session,
|
|
1238
|
+
node?.last_probe?.session
|
|
1239
|
+
];
|
|
1240
|
+
for (const session of sessionRecords) {
|
|
1241
|
+
if (isLocal(session)) return true;
|
|
1242
|
+
}
|
|
1243
|
+
return false;
|
|
869
1244
|
}
|
|
870
1245
|
function isDirectLocalNode(ctx, node) {
|
|
871
1246
|
const machineId = readNodeMachineId(node);
|
|
872
1247
|
const daemonId = readNodeDaemonId(node);
|
|
873
1248
|
return Boolean(
|
|
874
|
-
ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId
|
|
1249
|
+
ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId || nodeHasLocalDaemonEvidence(ctx, node)
|
|
875
1250
|
);
|
|
876
1251
|
}
|
|
1252
|
+
function isConfiguredCoordinatorNode(ctx, node) {
|
|
1253
|
+
if (!ctx.localMachineId && !ctx.localDaemonId) return false;
|
|
1254
|
+
const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
|
|
1255
|
+
if (!nodeId) return false;
|
|
1256
|
+
const nodeDaemonId = readNodeDaemonId(node);
|
|
1257
|
+
const nodeMachineId = readNodeMachineId(node);
|
|
1258
|
+
if (nodeDaemonId && ctx.localDaemonId && nodeDaemonId !== ctx.localDaemonId) return false;
|
|
1259
|
+
if (nodeMachineId && ctx.localMachineId && nodeMachineId !== ctx.localMachineId) return false;
|
|
1260
|
+
const preferredNodeId = readString(ctx.mesh.coordinator?.preferredNodeId) || readString(ctx.mesh.coordinator?.preferred_node_id);
|
|
1261
|
+
if (preferredNodeId) return nodeId === preferredNodeId;
|
|
1262
|
+
const first = ctx.mesh.nodes?.[0];
|
|
1263
|
+
const firstNodeId = readString(first?.id) || readString(first?.nodeId) || readString(first?.node_id);
|
|
1264
|
+
return !!firstNodeId && nodeId === firstNodeId;
|
|
1265
|
+
}
|
|
1266
|
+
function getLocalControlPlaneMatchReason(ctx, node) {
|
|
1267
|
+
if (isDirectLocalNode(ctx, node)) return "matched coordinator daemon or machine id";
|
|
1268
|
+
if (isConfiguredCoordinatorNode(ctx, node)) return "matched configured coordinator node";
|
|
1269
|
+
if (node.isLocalWorktree === true) {
|
|
1270
|
+
const sourceNode = findClonedFromNode(ctx, node);
|
|
1271
|
+
if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return "matched local cloned-from node";
|
|
1272
|
+
if (sourceNode && isConfiguredCoordinatorNode(ctx, sourceNode)) return "matched configured coordinator source node";
|
|
1273
|
+
}
|
|
1274
|
+
return void 0;
|
|
1275
|
+
}
|
|
877
1276
|
function findClonedFromNode(ctx, node) {
|
|
878
1277
|
const clonedFromNodeId = readString(node.clonedFromNodeId) || readString(node.cloned_from_node_id);
|
|
879
1278
|
if (!clonedFromNodeId) return void 0;
|
|
880
1279
|
return ctx.mesh.nodes.find((n) => n.id === clonedFromNodeId || n.nodeId === clonedFromNodeId || n.node_id === clonedFromNodeId);
|
|
881
1280
|
}
|
|
882
1281
|
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;
|
|
1282
|
+
return !!getLocalControlPlaneMatchReason(ctx, node);
|
|
889
1283
|
}
|
|
890
1284
|
function meshSessionCacheKey(nodeId, runtimeSessionId) {
|
|
891
1285
|
return `${nodeId}:${runtimeSessionId}`;
|
|
892
1286
|
}
|
|
1287
|
+
function rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, metadata) {
|
|
1288
|
+
const keyNodeId = readString(nodeId);
|
|
1289
|
+
const keySessionId = readString(runtimeSessionId);
|
|
1290
|
+
if (!keyNodeId || !keySessionId) return;
|
|
1291
|
+
const providerType = readString(metadata.providerType);
|
|
1292
|
+
const providerSessionId = readString(metadata.providerSessionId);
|
|
1293
|
+
if (!providerType && !providerSessionId) return;
|
|
1294
|
+
const existing = getSessionMetadata(meshSessionCacheKey(keyNodeId, keySessionId)) || { providerType: "" };
|
|
1295
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(keyNodeId, keySessionId), {
|
|
1296
|
+
providerType: providerType || existing.providerType,
|
|
1297
|
+
providerSessionId: providerSessionId || existing.providerSessionId,
|
|
1298
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
function rememberMeshSessionProviderMetadataFromEvent(event) {
|
|
1302
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : event && typeof event === "object" ? event : {};
|
|
1303
|
+
const nodeId = readString(event?.nodeId) || readString(metadataEvent.nodeId) || readString(metadataEvent.meshNodeId);
|
|
1304
|
+
const sessionId = readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId) || readString(event?.sessionId);
|
|
1305
|
+
rememberMeshSessionProviderMetadata(nodeId, sessionId, {
|
|
1306
|
+
providerType: readString(metadataEvent.providerType) || readString(event?.providerType) || "",
|
|
1307
|
+
providerSessionId: readString(metadataEvent.providerSessionId) || readString(event?.providerSessionId)
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
|
|
1311
|
+
const entries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 50 });
|
|
1312
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1313
|
+
const entry = entries[i];
|
|
1314
|
+
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
1315
|
+
const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
|
|
1316
|
+
if (entryNodeId && entryNodeId !== nodeId) continue;
|
|
1317
|
+
const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
|
|
1318
|
+
if (entrySessionId !== runtimeSessionId) continue;
|
|
1319
|
+
const providerType = readString(entry.providerType) || readString(payload.providerType);
|
|
1320
|
+
const completionDiagnostic = payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? payload.completionDiagnostic : {};
|
|
1321
|
+
const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : {};
|
|
1322
|
+
const providerSessionId = readString(payload.providerSessionId) || readString(completionDiagnostic.providerSessionId) || readString(metadataEvent.providerSessionId);
|
|
1323
|
+
if (providerType || providerSessionId) {
|
|
1324
|
+
return { providerType: providerType || "", providerSessionId };
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
return void 0;
|
|
1328
|
+
}
|
|
1329
|
+
function resolveMeshSessionProviderMetadata(ctx, nodeId, runtimeSessionId) {
|
|
1330
|
+
const cached = getSessionMetadata(meshSessionCacheKey(nodeId, runtimeSessionId));
|
|
1331
|
+
if (cached?.providerType || cached?.providerSessionId) return cached;
|
|
1332
|
+
const fromLedger = resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId);
|
|
1333
|
+
if (fromLedger) rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, fromLedger);
|
|
1334
|
+
return fromLedger;
|
|
1335
|
+
}
|
|
893
1336
|
function countUncommittedChanges(status) {
|
|
894
1337
|
if (typeof status?.uncommittedChanges === "number") return status.uncommittedChanges;
|
|
895
1338
|
const keys = ["staged", "modified", "untracked", "deleted", "renamed"];
|
|
@@ -902,6 +1345,20 @@ function isGitStatusDirty(status) {
|
|
|
902
1345
|
if (typeof status?.dirty === "boolean") return status.dirty;
|
|
903
1346
|
return countUncommittedChanges(status) > 0;
|
|
904
1347
|
}
|
|
1348
|
+
function slimLedgerPayload(payload) {
|
|
1349
|
+
const slim = {};
|
|
1350
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
1351
|
+
if (k === "message" || k === "taskSummary") {
|
|
1352
|
+
slim[k] = typeof v === "string" && v.length > 200 ? v.slice(0, 200) + "\u2026" : v;
|
|
1353
|
+
} else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
|
|
1354
|
+
} else if (k === "finalSummary") {
|
|
1355
|
+
slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
|
|
1356
|
+
} else {
|
|
1357
|
+
slim[k] = v;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
return slim;
|
|
1361
|
+
}
|
|
905
1362
|
function readRelatedRepos(node) {
|
|
906
1363
|
const raw = Array.isArray(node.relatedRepos) ? node.relatedRepos : Array.isArray(node.policy?.relatedRepos) ? node.policy.relatedRepos : [];
|
|
907
1364
|
return raw.map((entry) => ({
|
|
@@ -960,6 +1417,16 @@ function missingProviderPriorityMessage(nodeId) {
|
|
|
960
1417
|
return `Node '${nodeId}' has no providerPriority policy; pass type explicitly or configure node.policy.providerPriority`;
|
|
961
1418
|
}
|
|
962
1419
|
function getNodeLaunchReadiness(node) {
|
|
1420
|
+
const bootstrap = node.worktreeBootstrap;
|
|
1421
|
+
if (node.isLocalWorktree && bootstrap?.status === "failed" && bootstrap?.required !== false) {
|
|
1422
|
+
return {
|
|
1423
|
+
providerPriority: readProviderPriority(node.policy),
|
|
1424
|
+
launchReady: false,
|
|
1425
|
+
launchBlockedReason: "worktree_bootstrap_failed",
|
|
1426
|
+
launchBlockedMessage: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : "Required worktree bootstrap failed; resolve it before launching an agent into this node.",
|
|
1427
|
+
worktreeBootstrap: bootstrap
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
963
1430
|
const providerPriority = readProviderPriority(node.policy);
|
|
964
1431
|
if (providerPriority.length) {
|
|
965
1432
|
return {
|
|
@@ -974,6 +1441,33 @@ function getNodeLaunchReadiness(node) {
|
|
|
974
1441
|
launchBlockedMessage: missingProviderPriorityMessage(node.id)
|
|
975
1442
|
};
|
|
976
1443
|
}
|
|
1444
|
+
function getWorktreeBootstrapLaunchBlock(node) {
|
|
1445
|
+
const bootstrap = node.worktreeBootstrap;
|
|
1446
|
+
if (!node.isLocalWorktree || bootstrap?.status !== "failed" || bootstrap?.required === false) return void 0;
|
|
1447
|
+
return {
|
|
1448
|
+
success: false,
|
|
1449
|
+
code: "worktree_bootstrap_failed",
|
|
1450
|
+
error: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : `Node '${node.id}' has a failed required worktree bootstrap.`,
|
|
1451
|
+
nodeId: node.id,
|
|
1452
|
+
worktreeBootstrap: bootstrap,
|
|
1453
|
+
recoveryHint: "Fix the configured worktree bootstrap command or remove/recreate the worktree node before launching an agent."
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
async function collectLiveStatusSessions(ctx, node) {
|
|
1457
|
+
try {
|
|
1458
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
1459
|
+
return extractStatusMetadataSessions(statusResult);
|
|
1460
|
+
} catch {
|
|
1461
|
+
return [];
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
async function collectMeshViewQueueNodesWithLiveSessions(ctx) {
|
|
1465
|
+
const nodes = await Promise.all(ctx.mesh.nodes.map(async (node) => {
|
|
1466
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
1467
|
+
return liveSessions.length > 0 ? { ...node, sessions: liveSessions } : node;
|
|
1468
|
+
}));
|
|
1469
|
+
return nodes;
|
|
1470
|
+
}
|
|
977
1471
|
function readNumeric(value, fallback = 0) {
|
|
978
1472
|
const parsed = Number(value);
|
|
979
1473
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
@@ -1109,7 +1603,8 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
1109
1603
|
if (isLocalTransport(ctx.transport)) {
|
|
1110
1604
|
return ctx.transport.command(command, args);
|
|
1111
1605
|
}
|
|
1112
|
-
|
|
1606
|
+
const identity = buildNodeMachineIdentity(ctx, node);
|
|
1607
|
+
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
1608
|
}
|
|
1114
1609
|
function normalizePendingMeshCoordinatorEvents(value) {
|
|
1115
1610
|
const payload = unwrapCommandPayload(value);
|
|
@@ -1127,6 +1622,14 @@ function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
|
1127
1622
|
providerType: readString(metadataEvent.providerType),
|
|
1128
1623
|
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
1129
1624
|
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
1625
|
+
jobId: readString(metadataEvent.jobId),
|
|
1626
|
+
interactionId: readString(metadataEvent.interactionId),
|
|
1627
|
+
status: readString(metadataEvent.status),
|
|
1628
|
+
targetDaemonId: readString(metadataEvent.targetDaemonId),
|
|
1629
|
+
startedAt: readString(metadataEvent.startedAt),
|
|
1630
|
+
completedAt: readString(metadataEvent.completedAt),
|
|
1631
|
+
retryOfJobId: readString(metadataEvent.retryOfJobId),
|
|
1632
|
+
...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
|
|
1130
1633
|
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
1131
1634
|
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
1132
1635
|
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
@@ -1143,8 +1646,9 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
|
1143
1646
|
const surfacedEvents = [];
|
|
1144
1647
|
try {
|
|
1145
1648
|
surfacedEvents.push(
|
|
1146
|
-
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", {})).filter(matchesCurrentMesh)
|
|
1649
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1147
1650
|
);
|
|
1651
|
+
surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1148
1652
|
} catch {
|
|
1149
1653
|
}
|
|
1150
1654
|
for (const node of ctx.mesh.nodes) {
|
|
@@ -1152,27 +1656,31 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
|
1152
1656
|
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
1153
1657
|
try {
|
|
1154
1658
|
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
1155
|
-
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", {})
|
|
1659
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", { meshId: ctx.mesh.id })
|
|
1156
1660
|
).filter(matchesCurrentMesh);
|
|
1157
1661
|
if (remoteEvents.length === 0) continue;
|
|
1158
1662
|
for (const event of remoteEvents) {
|
|
1159
1663
|
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1160
1664
|
if (!payload.event || !payload.meshId) continue;
|
|
1161
1665
|
await ctx.transport.command("mesh_forward_event", payload);
|
|
1666
|
+
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
1162
1667
|
}
|
|
1163
1668
|
} catch {
|
|
1164
1669
|
}
|
|
1165
1670
|
}
|
|
1166
1671
|
try {
|
|
1167
1672
|
surfacedEvents.push(
|
|
1168
|
-
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", {})).filter(matchesCurrentMesh)
|
|
1673
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", { meshId: ctx.mesh.id })).filter(matchesCurrentMesh)
|
|
1169
1674
|
);
|
|
1675
|
+
surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1170
1676
|
} catch {
|
|
1171
1677
|
}
|
|
1172
1678
|
return surfacedEvents;
|
|
1173
1679
|
}
|
|
1174
1680
|
if (isLocalTransport(ctx.transport)) {
|
|
1175
|
-
|
|
1681
|
+
const events = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
|
|
1682
|
+
events.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1683
|
+
return events;
|
|
1176
1684
|
}
|
|
1177
1685
|
return [];
|
|
1178
1686
|
}
|
|
@@ -1189,11 +1697,12 @@ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
|
|
|
1189
1697
|
}
|
|
1190
1698
|
var MESH_STATUS_TOOL = {
|
|
1191
1699
|
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.",
|
|
1700
|
+
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
1701
|
inputSchema: {
|
|
1194
1702
|
type: "object",
|
|
1195
1703
|
properties: {
|
|
1196
|
-
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
|
|
1704
|
+
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
|
|
1705
|
+
includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." }
|
|
1197
1706
|
}
|
|
1198
1707
|
}
|
|
1199
1708
|
};
|
|
@@ -1213,14 +1722,18 @@ var MESH_ENQUEUE_TASK_TOOL = {
|
|
|
1213
1722
|
inputSchema: {
|
|
1214
1723
|
type: "object",
|
|
1215
1724
|
properties: {
|
|
1216
|
-
message: { type: "string", description: "The task instruction for the agent." }
|
|
1725
|
+
message: { type: "string", description: "The task instruction for the agent." },
|
|
1726
|
+
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." },
|
|
1727
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
|
|
1728
|
+
requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
|
|
1729
|
+
required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." }
|
|
1217
1730
|
},
|
|
1218
1731
|
required: ["message"]
|
|
1219
1732
|
}
|
|
1220
1733
|
};
|
|
1221
1734
|
var MESH_VIEW_QUEUE_TOOL = {
|
|
1222
1735
|
name: "mesh_view_queue",
|
|
1223
|
-
description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records.",
|
|
1736
|
+
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
1737
|
inputSchema: {
|
|
1225
1738
|
type: "object",
|
|
1226
1739
|
properties: {
|
|
@@ -1273,7 +1786,9 @@ var MESH_SEND_TASK_TOOL = {
|
|
|
1273
1786
|
properties: {
|
|
1274
1787
|
node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
|
|
1275
1788
|
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." }
|
|
1789
|
+
message: { type: "string", description: "Natural-language task to send to the agent." },
|
|
1790
|
+
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." },
|
|
1791
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1277
1792
|
},
|
|
1278
1793
|
required: ["node_id", "session_id", "message"]
|
|
1279
1794
|
}
|
|
@@ -1331,6 +1846,21 @@ var MESH_GIT_STATUS_TOOL = {
|
|
|
1331
1846
|
required: ["node_id"]
|
|
1332
1847
|
}
|
|
1333
1848
|
};
|
|
1849
|
+
var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
1850
|
+
name: "mesh_fast_forward_node",
|
|
1851
|
+
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.",
|
|
1852
|
+
inputSchema: {
|
|
1853
|
+
type: "object",
|
|
1854
|
+
properties: {
|
|
1855
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1856
|
+
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
1857
|
+
execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
|
|
1858
|
+
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
1859
|
+
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." }
|
|
1860
|
+
},
|
|
1861
|
+
required: ["node_id"]
|
|
1862
|
+
}
|
|
1863
|
+
};
|
|
1334
1864
|
var MESH_CHECKPOINT_TOOL = {
|
|
1335
1865
|
name: "mesh_checkpoint",
|
|
1336
1866
|
description: "Create a git checkpoint (commit) on a mesh node workspace.",
|
|
@@ -1414,7 +1944,7 @@ var MESH_TASK_HISTORY_TOOL = {
|
|
|
1414
1944
|
type: "object",
|
|
1415
1945
|
properties: {
|
|
1416
1946
|
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." }
|
|
1947
|
+
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
1948
|
}
|
|
1419
1949
|
}
|
|
1420
1950
|
};
|
|
@@ -1434,7 +1964,7 @@ var MESH_RECONCILE_LEDGER_TOOL = {
|
|
|
1434
1964
|
};
|
|
1435
1965
|
var MESH_REFINE_NODE_TOOL = {
|
|
1436
1966
|
name: "mesh_refine_node",
|
|
1437
|
-
description: "The Refinery:
|
|
1967
|
+
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
1968
|
inputSchema: {
|
|
1439
1969
|
type: "object",
|
|
1440
1970
|
properties: {
|
|
@@ -1443,6 +1973,43 @@ var MESH_REFINE_NODE_TOOL = {
|
|
|
1443
1973
|
required: ["node_id"]
|
|
1444
1974
|
}
|
|
1445
1975
|
};
|
|
1976
|
+
var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
|
|
1977
|
+
name: "mesh_refine_config_schema",
|
|
1978
|
+
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.",
|
|
1979
|
+
inputSchema: { type: "object", properties: {} }
|
|
1980
|
+
};
|
|
1981
|
+
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
1982
|
+
name: "mesh_validate_refine_config",
|
|
1983
|
+
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
1984
|
+
inputSchema: {
|
|
1985
|
+
type: "object",
|
|
1986
|
+
properties: {
|
|
1987
|
+
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
1988
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
|
|
1993
|
+
name: "mesh_suggest_refine_config",
|
|
1994
|
+
description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
|
|
1995
|
+
inputSchema: {
|
|
1996
|
+
type: "object",
|
|
1997
|
+
properties: {
|
|
1998
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
};
|
|
2002
|
+
var MESH_REFINE_PLAN_TOOL = {
|
|
2003
|
+
name: "mesh_refine_plan",
|
|
2004
|
+
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.",
|
|
2005
|
+
inputSchema: {
|
|
2006
|
+
type: "object",
|
|
2007
|
+
properties: {
|
|
2008
|
+
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
2009
|
+
},
|
|
2010
|
+
required: ["node_id"]
|
|
2011
|
+
}
|
|
2012
|
+
};
|
|
1446
2013
|
var ALL_MESH_TOOLS = [
|
|
1447
2014
|
MESH_STATUS_TOOL,
|
|
1448
2015
|
MESH_LIST_NODES_TOOL,
|
|
@@ -1455,24 +2022,31 @@ var ALL_MESH_TOOLS = [
|
|
|
1455
2022
|
MESH_READ_DEBUG_TOOL,
|
|
1456
2023
|
MESH_LAUNCH_SESSION_TOOL,
|
|
1457
2024
|
MESH_GIT_STATUS_TOOL,
|
|
2025
|
+
MESH_FAST_FORWARD_NODE_TOOL,
|
|
1458
2026
|
MESH_CHECKPOINT_TOOL,
|
|
1459
2027
|
MESH_APPROVE_TOOL,
|
|
1460
2028
|
MESH_CLONE_NODE_TOOL,
|
|
1461
2029
|
MESH_REMOVE_NODE_TOOL,
|
|
1462
2030
|
MESH_REFINE_NODE_TOOL,
|
|
2031
|
+
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
2032
|
+
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
2033
|
+
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
2034
|
+
MESH_REFINE_PLAN_TOOL,
|
|
1463
2035
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1464
2036
|
MESH_TASK_HISTORY_TOOL,
|
|
1465
2037
|
MESH_RECONCILE_LEDGER_TOOL
|
|
1466
2038
|
];
|
|
1467
|
-
async function meshStatus(ctx) {
|
|
2039
|
+
async function meshStatus(ctx, args = {}) {
|
|
1468
2040
|
await refreshMeshFromDaemon(ctx);
|
|
1469
2041
|
const { mesh, transport } = ctx;
|
|
1470
|
-
const results = [];
|
|
1471
2042
|
const ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
1472
|
-
|
|
2043
|
+
const results = await Promise.all(mesh.nodes.map(async (node) => {
|
|
1473
2044
|
const entry = {
|
|
1474
2045
|
nodeId: node.id,
|
|
1475
2046
|
workspace: node.workspace,
|
|
2047
|
+
machine: buildNodeMachineIdentity(ctx, node),
|
|
2048
|
+
daemonId: readNodeDaemonId(node),
|
|
2049
|
+
machineId: readNodeMachineId(node),
|
|
1476
2050
|
...getNodeLaunchReadiness(node)
|
|
1477
2051
|
};
|
|
1478
2052
|
try {
|
|
@@ -1482,6 +2056,7 @@ async function meshStatus(ctx) {
|
|
|
1482
2056
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1483
2057
|
const dirty = isGitStatusDirty(status);
|
|
1484
2058
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
2059
|
+
assignFullGitSnapshot(entry, status);
|
|
1485
2060
|
entry.branch = status?.branch;
|
|
1486
2061
|
entry.isDirty = dirty;
|
|
1487
2062
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1503,6 +2078,7 @@ async function meshStatus(ctx) {
|
|
|
1503
2078
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1504
2079
|
const dirty = isGitStatusDirty(status);
|
|
1505
2080
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
2081
|
+
assignFullGitSnapshot(entry, status);
|
|
1506
2082
|
entry.branch = status?.branch;
|
|
1507
2083
|
entry.isDirty = dirty;
|
|
1508
2084
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1538,7 +2114,7 @@ async function meshStatus(ctx) {
|
|
|
1538
2114
|
if (recoveryContext.consecutiveNodeFailures > 0) {
|
|
1539
2115
|
entry.recoveryHints = {
|
|
1540
2116
|
consecutiveFailures: recoveryContext.consecutiveNodeFailures,
|
|
1541
|
-
lastTaskMessage: recoveryContext.lastTaskMessage,
|
|
2117
|
+
lastTaskMessage: typeof recoveryContext.lastTaskMessage === "string" ? recoveryContext.lastTaskMessage.slice(0, 100) + (recoveryContext.lastTaskMessage.length > 100 ? "\u2026" : "") : recoveryContext.lastTaskMessage,
|
|
1542
2118
|
advice: recoveryContext.advice,
|
|
1543
2119
|
retryRecommended: recoveryContext.retryRecommended
|
|
1544
2120
|
};
|
|
@@ -1578,7 +2154,47 @@ async function meshStatus(ctx) {
|
|
|
1578
2154
|
}
|
|
1579
2155
|
const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
|
|
1580
2156
|
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
1581
|
-
|
|
2157
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
2158
|
+
if (liveSessions.length > 0) {
|
|
2159
|
+
entry.sessions = liveSessions.map((s) => {
|
|
2160
|
+
const coordinatorMeshId = typeof s.coordinator?.meshId === "string" ? s.coordinator.meshId : void 0;
|
|
2161
|
+
const isSelfCoordinator = coordinatorMeshId === mesh.id;
|
|
2162
|
+
return {
|
|
2163
|
+
id: s.instanceId ?? s.id ?? s.sessionId,
|
|
2164
|
+
status: s.status ?? s.lifecycle ?? s.state,
|
|
2165
|
+
providerType: s.providerType ?? s.cliType ?? s.type,
|
|
2166
|
+
...s.activeChat?.status ? { chatStatus: s.activeChat.status } : {},
|
|
2167
|
+
...isSelfCoordinator ? { isSelfCoordinator: true, role: "coordinator" } : {}
|
|
2168
|
+
};
|
|
2169
|
+
}).filter((s) => s.id);
|
|
2170
|
+
}
|
|
2171
|
+
return entry;
|
|
2172
|
+
}));
|
|
2173
|
+
const ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
|
|
2174
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2175
|
+
meshId: mesh.id,
|
|
2176
|
+
queue: (0, import_daemon_core.getQueue)(mesh.id),
|
|
2177
|
+
ledgerEntries,
|
|
2178
|
+
nodes: results
|
|
2179
|
+
});
|
|
2180
|
+
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
2181
|
+
const staleDirectWorkSummary = (0, import_daemon_core.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
|
|
2182
|
+
note: activeWorkEvidence.staleDirectWorkNote,
|
|
2183
|
+
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."
|
|
2184
|
+
});
|
|
2185
|
+
const coordinatorSessions = [];
|
|
2186
|
+
for (const nodeEntry of results) {
|
|
2187
|
+
const sessions = Array.isArray(nodeEntry.sessions) ? nodeEntry.sessions : [];
|
|
2188
|
+
for (const s of sessions) {
|
|
2189
|
+
if (s?.isSelfCoordinator === true && s.id) {
|
|
2190
|
+
coordinatorSessions.push({
|
|
2191
|
+
nodeId: nodeEntry.nodeId,
|
|
2192
|
+
sessionId: s.id,
|
|
2193
|
+
providerType: s.providerType,
|
|
2194
|
+
status: s.status
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
1582
2198
|
}
|
|
1583
2199
|
const response = {
|
|
1584
2200
|
meshId: mesh.id,
|
|
@@ -1586,8 +2202,29 @@ async function meshStatus(ctx) {
|
|
|
1586
2202
|
repoIdentity: mesh.repoIdentity,
|
|
1587
2203
|
policy: mesh.policy,
|
|
1588
2204
|
refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2205
|
+
sourceOfTruth: {
|
|
2206
|
+
membership: "coordinator_daemon_live_mesh",
|
|
2207
|
+
currentStatus: "live_git_and_session_probes",
|
|
2208
|
+
activeWork: "mesh_queue_file_and_local_ledger",
|
|
2209
|
+
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
2210
|
+
},
|
|
1589
2211
|
nodes: results,
|
|
1590
|
-
|
|
2212
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2213
|
+
staleDirectWorkSummary,
|
|
2214
|
+
...args.includeStaleDirectWorkDetails === true ? { staleDirectWork: activeWorkEvidence.staleDirectWork } : {},
|
|
2215
|
+
// terminalDirectWork is historical (completed/failed direct dispatches) — opt-in only.
|
|
2216
|
+
...args.includeTerminalDirectWork === true ? { terminalDirectWork: activeWorkEvidence.terminalDirectWork } : {},
|
|
2217
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
2218
|
+
...pollingGuidance ? { pollingGuidance } : {},
|
|
2219
|
+
branchConvergenceSummary: summarizeBranchConvergence(results),
|
|
2220
|
+
...coordinatorSessions.length > 0 ? {
|
|
2221
|
+
coordinatorSessions,
|
|
2222
|
+
selfIdentification: {
|
|
2223
|
+
meshId: mesh.id,
|
|
2224
|
+
coordinatorSessions,
|
|
2225
|
+
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."
|
|
2226
|
+
}
|
|
2227
|
+
} : {}
|
|
1591
2228
|
};
|
|
1592
2229
|
try {
|
|
1593
2230
|
response.ledgerSummary = ledgerSummary;
|
|
@@ -1595,6 +2232,14 @@ async function meshStatus(ctx) {
|
|
|
1595
2232
|
}
|
|
1596
2233
|
try {
|
|
1597
2234
|
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
2235
|
+
const asyncRefineJobs = (0, import_daemon_core.buildMeshAsyncRefineJobs)({
|
|
2236
|
+
meshId: mesh.id,
|
|
2237
|
+
ledgerEntries,
|
|
2238
|
+
pendingEvents
|
|
2239
|
+
});
|
|
2240
|
+
if (asyncRefineJobs.length > 0) {
|
|
2241
|
+
response.asyncRefineJobs = asyncRefineJobs;
|
|
2242
|
+
}
|
|
1598
2243
|
if (pendingEvents.length > 0) {
|
|
1599
2244
|
response.pendingCoordinatorEvents = pendingEvents;
|
|
1600
2245
|
}
|
|
@@ -1604,12 +2249,21 @@ async function meshStatus(ctx) {
|
|
|
1604
2249
|
}
|
|
1605
2250
|
async function meshTaskHistory(ctx, args) {
|
|
1606
2251
|
const { mesh } = ctx;
|
|
1607
|
-
await drainCoordinatorPendingEvents(ctx);
|
|
2252
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1608
2253
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
1609
2254
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
1610
|
-
const
|
|
2255
|
+
const rawEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
2256
|
+
const entries = rawEntries.map((e) => ({
|
|
2257
|
+
...e,
|
|
2258
|
+
payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
|
|
2259
|
+
}));
|
|
1611
2260
|
const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
1612
|
-
return JSON.stringify({
|
|
2261
|
+
return JSON.stringify({
|
|
2262
|
+
meshId: mesh.id,
|
|
2263
|
+
entries,
|
|
2264
|
+
summary,
|
|
2265
|
+
...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
|
|
2266
|
+
}, null, 2);
|
|
1613
2267
|
}
|
|
1614
2268
|
async function meshReconcileLedger(ctx, args) {
|
|
1615
2269
|
await refreshMeshFromDaemon(ctx);
|
|
@@ -1699,6 +2353,9 @@ async function meshListNodes(ctx) {
|
|
|
1699
2353
|
nodeId: n.id,
|
|
1700
2354
|
workspace: n.workspace,
|
|
1701
2355
|
repoRoot: n.repoRoot,
|
|
2356
|
+
daemonId: readNodeDaemonId(n),
|
|
2357
|
+
machineId: readNodeMachineId(n),
|
|
2358
|
+
machine: buildNodeMachineIdentity(ctx, n),
|
|
1702
2359
|
isLocalWorktree: n.isLocalWorktree,
|
|
1703
2360
|
policy: n.policy,
|
|
1704
2361
|
relatedRepos: readRelatedRepos(n),
|
|
@@ -1708,12 +2365,14 @@ async function meshListNodes(ctx) {
|
|
|
1708
2365
|
}, null, 2);
|
|
1709
2366
|
}
|
|
1710
2367
|
async function meshEnqueueTask(ctx, args) {
|
|
2368
|
+
const taskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2369
|
+
const requiredTags = (0, import_daemon_core.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
|
|
1711
2370
|
try {
|
|
1712
|
-
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message);
|
|
2371
|
+
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags });
|
|
1713
2372
|
if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
|
|
1714
2373
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1715
2374
|
});
|
|
1716
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2375
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode, requiredTags: task.requiredTags });
|
|
1717
2376
|
}
|
|
1718
2377
|
if (ctx.transport instanceof IpcTransport) {
|
|
1719
2378
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
@@ -1722,43 +2381,97 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
1722
2381
|
for (const node of ctx.mesh.nodes) {
|
|
1723
2382
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1724
2383
|
if (isLocalNode || !node.daemonId) continue;
|
|
2384
|
+
if (!(0, import_daemon_core.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core.buildMeshNodeCapabilityTags)(node))) continue;
|
|
1725
2385
|
dispatchPromises.push(
|
|
1726
2386
|
ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
|
|
1727
2387
|
if (result.success) {
|
|
1728
2388
|
try {
|
|
2389
|
+
const providerType = result.providerType;
|
|
2390
|
+
const descriptor = summarizeTaskMessage(args.message);
|
|
1729
2391
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1730
2392
|
kind: "task_dispatched",
|
|
1731
2393
|
nodeId: node.id,
|
|
1732
2394
|
sessionId: result.sessionId,
|
|
1733
|
-
|
|
2395
|
+
providerType,
|
|
2396
|
+
payload: {
|
|
2397
|
+
source: "queue",
|
|
2398
|
+
via: "p2p_direct",
|
|
2399
|
+
taskId: task.id,
|
|
2400
|
+
message: args.message,
|
|
2401
|
+
taskTitle: descriptor.taskTitle,
|
|
2402
|
+
taskSummary: descriptor.taskSummary,
|
|
2403
|
+
...task.taskMode ? { taskMode: task.taskMode } : {},
|
|
2404
|
+
...providerType ? { providerType } : {},
|
|
2405
|
+
targetSessionId: result.sessionId
|
|
2406
|
+
}
|
|
1734
2407
|
});
|
|
1735
2408
|
} catch {
|
|
1736
2409
|
}
|
|
1737
2410
|
}
|
|
1738
|
-
}).catch(() => {
|
|
2411
|
+
}).catch((err) => {
|
|
2412
|
+
try {
|
|
2413
|
+
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2414
|
+
kind: "p2p_dispatch_failed",
|
|
2415
|
+
nodeId: node.id,
|
|
2416
|
+
payload: {
|
|
2417
|
+
source: "queue",
|
|
2418
|
+
via: "p2p_direct",
|
|
2419
|
+
taskId: task.id,
|
|
2420
|
+
error: err?.message || String(err),
|
|
2421
|
+
dispatchFailedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2422
|
+
}
|
|
2423
|
+
});
|
|
2424
|
+
} catch {
|
|
2425
|
+
}
|
|
1739
2426
|
})
|
|
1740
2427
|
);
|
|
1741
2428
|
}
|
|
1742
2429
|
Promise.all(dispatchPromises).catch(() => {
|
|
1743
2430
|
});
|
|
1744
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2431
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode, requiredTags: task.requiredTags });
|
|
1745
2432
|
}
|
|
1746
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2433
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode, requiredTags: task.requiredTags });
|
|
1747
2434
|
} catch (e) {
|
|
1748
|
-
|
|
2435
|
+
const message = e?.message || String(e);
|
|
2436
|
+
if (message.includes("live_debug_readonly_guardrail_violation")) {
|
|
2437
|
+
return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
|
|
2438
|
+
}
|
|
2439
|
+
return JSON.stringify({ success: false, error: message });
|
|
1749
2440
|
}
|
|
1750
2441
|
}
|
|
1751
2442
|
async function meshViewQueue(ctx, args) {
|
|
1752
2443
|
try {
|
|
2444
|
+
await refreshMeshFromDaemon(ctx);
|
|
1753
2445
|
const statusFilter = sanitizeQueueStatusFilter(args.status);
|
|
1754
2446
|
const view = normalizeQueueViewMode(args.view);
|
|
1755
|
-
const fullQueue = annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh);
|
|
2447
|
+
const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness((0, import_daemon_core.getQueue)(ctx.mesh.id), ctx.mesh));
|
|
1756
2448
|
const queue = filterQueueForView(fullQueue, view, statusFilter);
|
|
1757
2449
|
const summary = buildQueueStatusSummary(fullQueue);
|
|
1758
2450
|
const visibleSummary = buildQueueStatusSummary(queue);
|
|
1759
2451
|
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
2452
|
+
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
2453
|
+
(0, import_daemon_core.markStaleDirectDispatches)(ctx.mesh.id);
|
|
2454
|
+
const ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
2455
|
+
const directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
|
|
2456
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2457
|
+
meshId: ctx.mesh.id,
|
|
2458
|
+
queue: fullQueue,
|
|
2459
|
+
ledgerEntries,
|
|
2460
|
+
// Always pass BeadsDB records (may be empty). buildMeshActiveWork uses them for local
|
|
2461
|
+
// dispatches and falls through to ledger scan for remote P2P dispatches not in BeadsDB.
|
|
2462
|
+
directDispatches,
|
|
2463
|
+
nodes: liveNodes
|
|
2464
|
+
});
|
|
2465
|
+
const recentDispatchFailures = ledgerEntries.filter((e) => e.kind === "p2p_dispatch_failed").slice(-20).map((e) => ({
|
|
2466
|
+
nodeId: e.nodeId,
|
|
2467
|
+
taskId: e.payload?.taskId,
|
|
2468
|
+
error: e.payload?.error,
|
|
2469
|
+
via: e.payload?.via,
|
|
2470
|
+
failedAt: e.payload?.dispatchFailedAt || e.timestamp
|
|
2471
|
+
}));
|
|
1760
2472
|
const staleAssignedTasks = maintenance.staleAssignedTasks || [];
|
|
1761
2473
|
const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
2474
|
+
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
1762
2475
|
return JSON.stringify({
|
|
1763
2476
|
success: true,
|
|
1764
2477
|
sourceOfTruth: {
|
|
@@ -1773,21 +2486,20 @@ async function meshViewQueue(ctx, args) {
|
|
|
1773
2486
|
filtered: Boolean(statusFilter?.length) || view !== "all"
|
|
1774
2487
|
},
|
|
1775
2488
|
queue,
|
|
1776
|
-
|
|
1777
|
-
|
|
2489
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2490
|
+
staleDirectWork: activeWorkEvidence.staleDirectWork,
|
|
2491
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
2492
|
+
...pollingGuidance ? { pollingGuidance } : {},
|
|
1778
2493
|
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
2494
|
staleAssignedTasks,
|
|
1788
2495
|
staleAssignedCount: maintenance.staleAssignedCount,
|
|
1789
2496
|
queueMaintenance: maintenance,
|
|
1790
2497
|
cleanupDryRun: maintenance,
|
|
2498
|
+
...recentDispatchFailures.length > 0 ? {
|
|
2499
|
+
recentDispatchFailures,
|
|
2500
|
+
dispatchFailureCount: recentDispatchFailures.length,
|
|
2501
|
+
dispatchFailureNote: "Remote P2P dispatch attempts that failed. Affected tasks remain pending and may require mesh_queue_requeue if no idle session picks them up."
|
|
2502
|
+
} : {},
|
|
1791
2503
|
...view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status)) ? {
|
|
1792
2504
|
activeQueue: queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")))
|
|
1793
2505
|
} : {},
|
|
@@ -1807,6 +2519,10 @@ async function meshQueueCancel(ctx, args) {
|
|
|
1807
2519
|
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
1808
2520
|
const task = (0, import_daemon_core.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
|
|
1809
2521
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
2522
|
+
if (isLocalTransport(ctx.transport)) {
|
|
2523
|
+
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
1810
2526
|
return JSON.stringify({ success: true, task }, null, 2);
|
|
1811
2527
|
} catch (e) {
|
|
1812
2528
|
return JSON.stringify({ success: false, error: e.message });
|
|
@@ -1837,10 +2553,60 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
1837
2553
|
}
|
|
1838
2554
|
}
|
|
1839
2555
|
async function meshSendTask(ctx, args) {
|
|
2556
|
+
const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2557
|
+
const modeValidation = (0, import_daemon_core.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
|
|
2558
|
+
if (!modeValidation.valid) {
|
|
2559
|
+
return JSON.stringify({
|
|
2560
|
+
success: false,
|
|
2561
|
+
code: "live_debug_readonly_guardrail_violation",
|
|
2562
|
+
taskMode: modeValidation.taskMode || requestedTaskMode,
|
|
2563
|
+
violations: modeValidation.violations,
|
|
2564
|
+
allowedOperations: modeValidation.allowedOperations,
|
|
2565
|
+
error: `live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
const taskMode = modeValidation.taskMode;
|
|
1840
2569
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1841
2570
|
if (node.policy?.readOnly) {
|
|
1842
2571
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
1843
2572
|
}
|
|
2573
|
+
let explicitTargetSession;
|
|
2574
|
+
if (args.session_id && isWorkerTaskMode(taskMode) && (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport))) {
|
|
2575
|
+
try {
|
|
2576
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2577
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2578
|
+
explicitTargetSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2579
|
+
if (explicitTargetSession && isMeshCoordinatorSessionRecord(explicitTargetSession)) {
|
|
2580
|
+
return JSON.stringify({
|
|
2581
|
+
success: false,
|
|
2582
|
+
recoverable: true,
|
|
2583
|
+
code: "mesh_target_session_is_coordinator",
|
|
2584
|
+
reason: "mesh_target_session_is_coordinator",
|
|
2585
|
+
nodeId: args.node_id,
|
|
2586
|
+
sessionId: args.session_id,
|
|
2587
|
+
taskMode: taskMode || "unspecified",
|
|
2588
|
+
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.`,
|
|
2589
|
+
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.`
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
if (explicitTargetSession && isUnmanagedSessionRecord(explicitTargetSession)) {
|
|
2593
|
+
return JSON.stringify({
|
|
2594
|
+
success: false,
|
|
2595
|
+
recoverable: true,
|
|
2596
|
+
code: "mesh_target_session_unmanaged",
|
|
2597
|
+
reason: "mesh_target_session_unmanaged",
|
|
2598
|
+
nodeId: args.node_id,
|
|
2599
|
+
sessionId: args.session_id,
|
|
2600
|
+
taskMode: taskMode || "unspecified",
|
|
2601
|
+
unsafeTranscriptAlias: true,
|
|
2602
|
+
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.`,
|
|
2603
|
+
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.`
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
} catch {
|
|
2607
|
+
explicitTargetSession = void 0;
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
1844
2610
|
const duplicate = hasRecentDuplicateDispatch(ctx, args);
|
|
1845
2611
|
if (duplicate.duplicate) {
|
|
1846
2612
|
return JSON.stringify({
|
|
@@ -1864,75 +2630,212 @@ async function meshSendTask(ctx, args) {
|
|
|
1864
2630
|
const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
|
|
1865
2631
|
meshId: ctx.mesh.id,
|
|
1866
2632
|
message: args.message,
|
|
1867
|
-
targetNodeId: args.node_id
|
|
2633
|
+
targetNodeId: args.node_id,
|
|
2634
|
+
...taskMode ? { taskMode } : {}
|
|
1868
2635
|
});
|
|
1869
2636
|
return JSON.stringify(res);
|
|
1870
2637
|
}
|
|
1871
2638
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1872
2639
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1873
|
-
const cached =
|
|
2640
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id || ""));
|
|
2641
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1874
2642
|
const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
|
|
1875
2643
|
session_id: args.session_id,
|
|
1876
2644
|
message: args.message,
|
|
1877
|
-
providerType: cached?.providerType
|
|
2645
|
+
providerType: cached?.providerType,
|
|
2646
|
+
verifiedSession: explicitTargetSession
|
|
1878
2647
|
});
|
|
1879
2648
|
if (result2.success) {
|
|
1880
2649
|
const dispatchedSessionId = args.session_id || result2.sessionId;
|
|
2650
|
+
const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1881
2651
|
try {
|
|
2652
|
+
const providerType = result2.providerType || cached?.providerType;
|
|
1882
2653
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1883
2654
|
kind: "task_dispatched",
|
|
1884
2655
|
nodeId: args.node_id,
|
|
1885
2656
|
sessionId: dispatchedSessionId,
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2657
|
+
providerType,
|
|
2658
|
+
payload: buildDirectTaskPayload(args.message, "p2p_direct", {
|
|
2659
|
+
taskId,
|
|
2660
|
+
taskMode,
|
|
2661
|
+
providerType,
|
|
2662
|
+
targetSessionId: dispatchedSessionId
|
|
2663
|
+
})
|
|
2664
|
+
});
|
|
2665
|
+
(0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
|
|
2666
|
+
taskId,
|
|
2667
|
+
nodeId: args.node_id,
|
|
2668
|
+
sessionId: dispatchedSessionId,
|
|
2669
|
+
providerType: providerType || void 0,
|
|
2670
|
+
message: args.message,
|
|
2671
|
+
taskMode: taskMode || void 0,
|
|
2672
|
+
via: "p2p_direct",
|
|
2673
|
+
dispatchedAt
|
|
1891
2674
|
});
|
|
1892
2675
|
} catch {
|
|
1893
2676
|
}
|
|
1894
2677
|
}
|
|
1895
|
-
return JSON.stringify({
|
|
2678
|
+
return JSON.stringify({
|
|
2679
|
+
...result2,
|
|
2680
|
+
nodeId: args.node_id,
|
|
2681
|
+
sessionId: result2.success ? args.session_id || result2.sessionId : args.session_id,
|
|
2682
|
+
...result2.success ? { source: "direct", taskId } : {},
|
|
2683
|
+
taskMode,
|
|
2684
|
+
...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
|
|
2685
|
+
dispatched: result2.success === true
|
|
2686
|
+
});
|
|
1896
2687
|
}
|
|
1897
2688
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
1898
|
-
const cached =
|
|
2689
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
|
|
2690
|
+
let resolvedProviderType = cached?.providerType || "";
|
|
2691
|
+
if (!resolvedProviderType) {
|
|
2692
|
+
let explicitSession = explicitTargetSession;
|
|
2693
|
+
if (!explicitSession) {
|
|
2694
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2695
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2696
|
+
explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2697
|
+
}
|
|
2698
|
+
if (!explicitSession) {
|
|
2699
|
+
return JSON.stringify({
|
|
2700
|
+
success: false,
|
|
2701
|
+
recoverable: true,
|
|
2702
|
+
code: "mesh_target_session_not_found",
|
|
2703
|
+
reason: "mesh_target_session_not_found",
|
|
2704
|
+
transport: "local_ipc",
|
|
2705
|
+
retryRecommended: true,
|
|
2706
|
+
nodeId: args.node_id,
|
|
2707
|
+
sessionId: args.session_id,
|
|
2708
|
+
error: `Local session '${args.session_id}' is not present in live status for node '${args.node_id}'.`,
|
|
2709
|
+
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.`
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
if (isMeshCoordinatorSessionRecord(explicitSession)) {
|
|
2713
|
+
return JSON.stringify({
|
|
2714
|
+
success: false,
|
|
2715
|
+
recoverable: true,
|
|
2716
|
+
code: "mesh_target_session_is_coordinator",
|
|
2717
|
+
reason: "mesh_target_session_is_coordinator",
|
|
2718
|
+
nodeId: args.node_id,
|
|
2719
|
+
sessionId: args.session_id,
|
|
2720
|
+
taskMode: taskMode || "unspecified",
|
|
2721
|
+
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.`,
|
|
2722
|
+
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.`
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2725
|
+
if (isUnmanagedSessionRecord(explicitSession)) {
|
|
2726
|
+
return JSON.stringify({
|
|
2727
|
+
success: false,
|
|
2728
|
+
recoverable: true,
|
|
2729
|
+
code: "mesh_target_session_unmanaged",
|
|
2730
|
+
reason: "mesh_target_session_unmanaged",
|
|
2731
|
+
nodeId: args.node_id,
|
|
2732
|
+
sessionId: args.session_id,
|
|
2733
|
+
taskMode: taskMode || "unspecified",
|
|
2734
|
+
unsafeTranscriptAlias: true,
|
|
2735
|
+
unsafeDelegateTarget: true,
|
|
2736
|
+
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.`,
|
|
2737
|
+
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.`
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2741
|
+
if (resolvedProviderType) {
|
|
2742
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
|
|
2743
|
+
providerType: resolvedProviderType,
|
|
2744
|
+
providerSessionId: readString(explicitSession?.providerSessionId) || void 0,
|
|
2745
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
2746
|
+
});
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
if (!resolvedProviderType) {
|
|
2750
|
+
return JSON.stringify({
|
|
2751
|
+
success: false,
|
|
2752
|
+
recoverable: true,
|
|
2753
|
+
code: "mesh_target_session_provider_unknown",
|
|
2754
|
+
reason: "mesh_target_session_provider_unknown",
|
|
2755
|
+
transport: "local_ipc",
|
|
2756
|
+
retryRecommended: false,
|
|
2757
|
+
nodeId: args.node_id,
|
|
2758
|
+
sessionId: args.session_id,
|
|
2759
|
+
error: `Local session '${args.session_id}' is live but does not expose providerType/cliType, so agent_command cannot be routed safely.`,
|
|
2760
|
+
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.`
|
|
2761
|
+
});
|
|
2762
|
+
}
|
|
2763
|
+
const sessionWasIdle = explicitTargetSession ? isIdleSessionRecord(explicitTargetSession) : false;
|
|
1899
2764
|
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
1900
2765
|
targetSessionId: args.session_id,
|
|
1901
|
-
|
|
2766
|
+
agentType: resolvedProviderType,
|
|
2767
|
+
cliType: resolvedProviderType,
|
|
2768
|
+
providerType: resolvedProviderType,
|
|
1902
2769
|
action: "send_chat",
|
|
1903
2770
|
message: args.message
|
|
1904
2771
|
});
|
|
1905
2772
|
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
1906
2773
|
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
2774
|
+
const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
|
|
1907
2775
|
return JSON.stringify({
|
|
2776
|
+
...source && typeof source === "object" ? source : {},
|
|
1908
2777
|
success: false,
|
|
1909
2778
|
nodeId: args.node_id,
|
|
1910
2779
|
sessionId: args.session_id,
|
|
1911
2780
|
error: dispatchPayload?.error || dispatchResult?.error || "agent_command rejected the task"
|
|
1912
2781
|
});
|
|
1913
2782
|
}
|
|
2783
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
2784
|
+
const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1914
2785
|
try {
|
|
1915
2786
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1916
2787
|
kind: "task_dispatched",
|
|
1917
2788
|
nodeId: args.node_id,
|
|
1918
2789
|
sessionId: args.session_id,
|
|
1919
|
-
providerType:
|
|
1920
|
-
payload:
|
|
2790
|
+
providerType: resolvedProviderType,
|
|
2791
|
+
payload: buildDirectTaskPayload(args.message, "local_direct", {
|
|
2792
|
+
taskId,
|
|
2793
|
+
taskMode,
|
|
2794
|
+
providerType: resolvedProviderType,
|
|
2795
|
+
targetSessionId: args.session_id,
|
|
2796
|
+
dispatchedToIdleSession: sessionWasIdle
|
|
2797
|
+
})
|
|
1921
2798
|
});
|
|
1922
2799
|
} catch {
|
|
1923
2800
|
}
|
|
1924
|
-
|
|
2801
|
+
(0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
|
|
2802
|
+
taskId,
|
|
2803
|
+
nodeId: args.node_id,
|
|
2804
|
+
sessionId: args.session_id,
|
|
2805
|
+
providerType: resolvedProviderType || void 0,
|
|
2806
|
+
message: args.message,
|
|
2807
|
+
taskMode: taskMode || void 0,
|
|
2808
|
+
via: "local_direct",
|
|
2809
|
+
dispatchedToIdleSession: sessionWasIdle,
|
|
2810
|
+
dispatchedAt
|
|
2811
|
+
});
|
|
2812
|
+
return JSON.stringify({
|
|
2813
|
+
success: true,
|
|
2814
|
+
dispatched: true,
|
|
2815
|
+
source: "direct",
|
|
2816
|
+
taskId,
|
|
2817
|
+
taskMode,
|
|
2818
|
+
providerType: resolvedProviderType,
|
|
2819
|
+
nodeId: args.node_id,
|
|
2820
|
+
sessionId: args.session_id,
|
|
2821
|
+
...sessionWasIdle ? {
|
|
2822
|
+
dispatchAcknowledgementRisk: true,
|
|
2823
|
+
dispatchAcknowledgementRiskReason: "session_was_idle_at_dispatch",
|
|
2824
|
+
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.`
|
|
2825
|
+
} : {}
|
|
2826
|
+
});
|
|
1925
2827
|
}
|
|
1926
2828
|
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, {
|
|
1927
2829
|
targetNodeId: args.node_id,
|
|
1928
|
-
targetSessionId: args.session_id
|
|
2830
|
+
targetSessionId: args.session_id,
|
|
2831
|
+
taskMode
|
|
1929
2832
|
});
|
|
1930
2833
|
if (isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport) {
|
|
1931
2834
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1932
2835
|
});
|
|
1933
2836
|
}
|
|
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 };
|
|
2837
|
+
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId) : [];
|
|
2838
|
+
const result = { success: true, source: "queue", nodeId: args.node_id, taskId: task.id, status: task.status, taskMode: task.taskMode };
|
|
1936
2839
|
if (pendingEvents.length > 0) {
|
|
1937
2840
|
result.pendingCoordinatorEvents = pendingEvents;
|
|
1938
2841
|
}
|
|
@@ -1956,7 +2859,7 @@ async function meshReadChat(ctx, args) {
|
|
|
1956
2859
|
await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
|
|
1957
2860
|
}
|
|
1958
2861
|
if (isLocalTransport(ctx.transport)) {
|
|
1959
|
-
const cached =
|
|
2862
|
+
const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
|
|
1960
2863
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
1961
2864
|
const result = await commandForNode(ctx, node, "read_chat", {
|
|
1962
2865
|
sessionId: args.session_id,
|
|
@@ -1964,18 +2867,19 @@ async function meshReadChat(ctx, args) {
|
|
|
1964
2867
|
workspace: node.workspace,
|
|
1965
2868
|
...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
|
|
1966
2869
|
...providerSessionId ? { providerSessionId } : {},
|
|
1967
|
-
tailLimit: args.tail ??
|
|
2870
|
+
tailLimit: args.tail ?? 3
|
|
1968
2871
|
});
|
|
1969
2872
|
const payload = annotateRapidReadChatAdvisory(unwrapCommandPayload(result), {
|
|
1970
2873
|
key: `mesh:${args.node_id}:${args.session_id}`,
|
|
1971
2874
|
toolName: "mesh_read_chat",
|
|
1972
2875
|
completionCallbackExpected: true
|
|
1973
2876
|
});
|
|
1974
|
-
|
|
2877
|
+
const useCompact = args.compact !== false;
|
|
2878
|
+
if (useCompact) {
|
|
1975
2879
|
const compactPayload = compactChatPayload(payload, {
|
|
1976
2880
|
nodeId: args.node_id,
|
|
1977
2881
|
sessionId: args.session_id,
|
|
1978
|
-
limit: args.tail ??
|
|
2882
|
+
limit: args.tail ?? 3
|
|
1979
2883
|
});
|
|
1980
2884
|
return JSON.stringify(
|
|
1981
2885
|
payload.pollingAdvisory ? { ...compactPayload, pollingAdvisory: payload.pollingAdvisory } : compactPayload,
|
|
@@ -1988,7 +2892,7 @@ async function meshReadChat(ctx, args) {
|
|
|
1988
2892
|
try {
|
|
1989
2893
|
const targetId = `${node.daemonId}:session:${args.session_id}`;
|
|
1990
2894
|
const res = await ctx.transport.readChat(targetId, {
|
|
1991
|
-
limit: args.tail ??
|
|
2895
|
+
limit: args.tail ?? 3,
|
|
1992
2896
|
sessionId: args.session_id
|
|
1993
2897
|
});
|
|
1994
2898
|
return JSON.stringify(res, null, 2);
|
|
@@ -2002,7 +2906,7 @@ async function meshReadChat(ctx, args) {
|
|
|
2002
2906
|
async function meshReadDebug(ctx, args) {
|
|
2003
2907
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2004
2908
|
if (isLocalTransport(ctx.transport)) {
|
|
2005
|
-
const cached =
|
|
2909
|
+
const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
|
|
2006
2910
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
2007
2911
|
const delivery = args.delivery === "inline" ? void 0 : "daemon_file";
|
|
2008
2912
|
const result = await commandForNode(ctx, node, "get_chat_debug_bundle", {
|
|
@@ -2033,6 +2937,8 @@ async function meshReadDebug(ctx, args) {
|
|
|
2033
2937
|
}
|
|
2034
2938
|
async function meshLaunchSession(ctx, args) {
|
|
2035
2939
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2940
|
+
const bootstrapBlock = getWorktreeBootstrapLaunchBlock(node);
|
|
2941
|
+
if (bootstrapBlock) return JSON.stringify(bootstrapBlock, null, 2);
|
|
2036
2942
|
if (isLocalTransport(ctx.transport)) {
|
|
2037
2943
|
let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
|
|
2038
2944
|
if (!resolvedProviderType) {
|
|
@@ -2057,6 +2963,10 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2057
2963
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
2058
2964
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2059
2965
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2966
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2967
|
+
if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
|
|
2968
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2969
|
+
}
|
|
2060
2970
|
let result;
|
|
2061
2971
|
try {
|
|
2062
2972
|
result = await commandForNode(ctx, node, "launch_cli", {
|
|
@@ -2084,7 +2994,8 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2084
2994
|
if (runtimeSessionId) {
|
|
2085
2995
|
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, runtimeSessionId), {
|
|
2086
2996
|
providerType: resolvedProviderType,
|
|
2087
|
-
...providerSessionId ? { providerSessionId } : {}
|
|
2997
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
2998
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
2088
2999
|
});
|
|
2089
3000
|
}
|
|
2090
3001
|
try {
|
|
@@ -2097,7 +3008,6 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2097
3008
|
});
|
|
2098
3009
|
} catch {
|
|
2099
3010
|
}
|
|
2100
|
-
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2101
3011
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
2102
3012
|
ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2103
3013
|
});
|
|
@@ -2122,6 +3032,9 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2122
3032
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
2123
3033
|
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2124
3034
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
3035
|
+
if (!coordinatorDaemonId) {
|
|
3036
|
+
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
3037
|
+
}
|
|
2125
3038
|
try {
|
|
2126
3039
|
const res = await ctx.transport.launch(node.daemonId, {
|
|
2127
3040
|
type: resolvedProviderType,
|
|
@@ -2202,6 +3115,51 @@ async function meshGitStatus(ctx, args) {
|
|
|
2202
3115
|
}, null, 2);
|
|
2203
3116
|
}
|
|
2204
3117
|
}
|
|
3118
|
+
async function meshFastForwardNode(ctx, args) {
|
|
3119
|
+
await refreshMeshFromDaemon(ctx);
|
|
3120
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3121
|
+
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
3122
|
+
if (node.policy?.readOnly) {
|
|
3123
|
+
return JSON.stringify({
|
|
3124
|
+
success: false,
|
|
3125
|
+
code: "node_read_only",
|
|
3126
|
+
nodeId: args.node_id,
|
|
3127
|
+
workspace: node.workspace,
|
|
3128
|
+
allowed: false,
|
|
3129
|
+
willRun: false,
|
|
3130
|
+
executed: false,
|
|
3131
|
+
blockingReasons: ["node_read_only"]
|
|
3132
|
+
}, null, 2);
|
|
3133
|
+
}
|
|
3134
|
+
try {
|
|
3135
|
+
const dryRun = args.dry_run === true || args.execute !== true;
|
|
3136
|
+
const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
|
|
3137
|
+
meshId: ctx.mesh.id,
|
|
3138
|
+
nodeId: node.id,
|
|
3139
|
+
workspace: node.workspace,
|
|
3140
|
+
branch: typeof args.branch === "string" ? args.branch : void 0,
|
|
3141
|
+
execute: args.execute === true && args.dry_run !== true,
|
|
3142
|
+
dryRun,
|
|
3143
|
+
updateSubmodules: args.update_submodules === true,
|
|
3144
|
+
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
3145
|
+
});
|
|
3146
|
+
return JSON.stringify(unwrapCommandPayload(result), null, 2);
|
|
3147
|
+
} catch (e) {
|
|
3148
|
+
const failure = buildCoordinatorP2pRelayFailure(e, {
|
|
3149
|
+
command: "fast_forward_mesh_node",
|
|
3150
|
+
targetDaemonId: node.daemonId,
|
|
3151
|
+
nodeId: args.node_id
|
|
3152
|
+
});
|
|
3153
|
+
return JSON.stringify({
|
|
3154
|
+
...failure,
|
|
3155
|
+
workspace: node.workspace,
|
|
3156
|
+
allowed: false,
|
|
3157
|
+
willRun: false,
|
|
3158
|
+
executed: false,
|
|
3159
|
+
blockingReasons: [failure.code || "mesh_fast_forward_unavailable"]
|
|
3160
|
+
}, null, 2);
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
2205
3163
|
async function meshCheckpoint(ctx, args) {
|
|
2206
3164
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2207
3165
|
if (node.policy?.readOnly) {
|
|
@@ -2217,7 +3175,13 @@ async function meshCheckpoint(ctx, args) {
|
|
|
2217
3175
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2218
3176
|
kind: "checkpoint_created",
|
|
2219
3177
|
nodeId: args.node_id,
|
|
2220
|
-
payload: {
|
|
3178
|
+
payload: {
|
|
3179
|
+
message: args.message,
|
|
3180
|
+
commit: result?.checkpoint?.commit,
|
|
3181
|
+
outcome: result?.checkpoint?.status || (result?.checkpoint?.noop ? "skipped" : void 0),
|
|
3182
|
+
noop: result?.checkpoint?.noop === true,
|
|
3183
|
+
reason: result?.checkpoint?.reason
|
|
3184
|
+
}
|
|
2221
3185
|
});
|
|
2222
3186
|
} catch {
|
|
2223
3187
|
}
|
|
@@ -2233,7 +3197,13 @@ async function meshCheckpoint(ctx, args) {
|
|
|
2233
3197
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2234
3198
|
kind: "checkpoint_created",
|
|
2235
3199
|
nodeId: args.node_id,
|
|
2236
|
-
payload: {
|
|
3200
|
+
payload: {
|
|
3201
|
+
message: args.message,
|
|
3202
|
+
commit: res?.checkpoint?.commit,
|
|
3203
|
+
outcome: res?.checkpoint?.status || (res?.checkpoint?.noop ? "skipped" : void 0),
|
|
3204
|
+
noop: res?.checkpoint?.noop === true,
|
|
3205
|
+
reason: res?.checkpoint?.reason
|
|
3206
|
+
}
|
|
2237
3207
|
});
|
|
2238
3208
|
} catch {
|
|
2239
3209
|
}
|
|
@@ -2248,7 +3218,7 @@ async function meshCheckpoint(ctx, args) {
|
|
|
2248
3218
|
async function meshApprove(ctx, args) {
|
|
2249
3219
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2250
3220
|
if (isLocalTransport(ctx.transport)) {
|
|
2251
|
-
const cached =
|
|
3221
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
|
|
2252
3222
|
const providerSessionId = cached?.providerSessionId;
|
|
2253
3223
|
const result = await commandForNode(ctx, node, "resolve_action", {
|
|
2254
3224
|
sessionId: args.session_id,
|
|
@@ -2401,6 +3371,43 @@ async function meshRemoveNode(ctx, args) {
|
|
|
2401
3371
|
return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
|
|
2402
3372
|
}
|
|
2403
3373
|
}
|
|
3374
|
+
function resolveRefineConfigNode(ctx, nodeId) {
|
|
3375
|
+
if (nodeId) return findNode(ctx.mesh, nodeId);
|
|
3376
|
+
const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
|
|
3377
|
+
if (!node) throw new Error("No mesh node with a workspace is available");
|
|
3378
|
+
return node;
|
|
3379
|
+
}
|
|
3380
|
+
async function meshRefineConfigSchema(ctx) {
|
|
3381
|
+
const node = resolveRefineConfigNode(ctx);
|
|
3382
|
+
const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
|
|
3383
|
+
return JSON.stringify(result, null, 2);
|
|
3384
|
+
}
|
|
3385
|
+
async function meshValidateRefineConfig(ctx, args) {
|
|
3386
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
3387
|
+
const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
|
|
3388
|
+
workspace: node.workspace,
|
|
3389
|
+
inlineMesh: ctx.mesh,
|
|
3390
|
+
...args.config ? { config: args.config } : {}
|
|
3391
|
+
});
|
|
3392
|
+
return JSON.stringify(result, null, 2);
|
|
3393
|
+
}
|
|
3394
|
+
async function meshSuggestRefineConfig(ctx, args) {
|
|
3395
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
3396
|
+
const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
|
|
3397
|
+
workspace: node.workspace,
|
|
3398
|
+
inlineMesh: ctx.mesh
|
|
3399
|
+
});
|
|
3400
|
+
return JSON.stringify(result, null, 2);
|
|
3401
|
+
}
|
|
3402
|
+
async function meshRefinePlan(ctx, args) {
|
|
3403
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3404
|
+
const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
|
|
3405
|
+
meshId: ctx.mesh.id,
|
|
3406
|
+
nodeId: args.node_id,
|
|
3407
|
+
inlineMesh: ctx.mesh
|
|
3408
|
+
});
|
|
3409
|
+
return JSON.stringify(result, null, 2);
|
|
3410
|
+
}
|
|
2404
3411
|
async function meshRefineNode(ctx, args) {
|
|
2405
3412
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2406
3413
|
if (isLocalTransport(ctx.transport)) {
|
|
@@ -2409,7 +3416,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2409
3416
|
nodeId: args.node_id,
|
|
2410
3417
|
inlineMesh: ctx.mesh
|
|
2411
3418
|
});
|
|
2412
|
-
if (result?.success && result.removeResult?.removed !== false) {
|
|
3419
|
+
if (result?.success && result.async !== true && result.removeResult?.removed !== false) {
|
|
2413
3420
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2414
3421
|
if (idx >= 0) {
|
|
2415
3422
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2424,7 +3431,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2424
3431
|
nodeId: args.node_id,
|
|
2425
3432
|
inlineMesh: ctx.mesh
|
|
2426
3433
|
});
|
|
2427
|
-
if (res?.success && res.removeResult?.removed !== false) {
|
|
3434
|
+
if (res?.success && res.async !== true && res.removeResult?.removed !== false) {
|
|
2428
3435
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2429
3436
|
if (idx >= 0) {
|
|
2430
3437
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2461,13 +3468,13 @@ var STANDARD_TOOLS = [
|
|
|
2461
3468
|
function buildMcpHelpText() {
|
|
2462
3469
|
const meshTools = ALL_MESH_TOOLS.map((tool) => tool.name);
|
|
2463
3470
|
return `
|
|
2464
|
-
|
|
3471
|
+
ADHDev MCP Server
|
|
2465
3472
|
|
|
2466
3473
|
Usage:
|
|
2467
|
-
adhdev
|
|
2468
|
-
adhdev
|
|
2469
|
-
adhdev
|
|
2470
|
-
adhdev-mcp --
|
|
3474
|
+
adhdev mcp Local mode (requires standalone daemon)
|
|
3475
|
+
adhdev mcp --api-key <key> Cloud mode (ADHDev cloud API)
|
|
3476
|
+
adhdev mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
|
|
3477
|
+
adhdev-mcp --help Compatibility bin (same server, legacy package entrypoint)
|
|
2471
3478
|
|
|
2472
3479
|
Options:
|
|
2473
3480
|
--mode <mode> Transport: local, cloud, or ipc
|
|
@@ -2492,6 +3499,7 @@ Mesh tools: ${meshTools.join(", ")}
|
|
|
2492
3499
|
// src/server.ts
|
|
2493
3500
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
2494
3501
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
3502
|
+
var import_node_os = __toESM(require("os"));
|
|
2495
3503
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
2496
3504
|
|
|
2497
3505
|
// src/transports/local.ts
|
|
@@ -4025,6 +5033,7 @@ async function startMcpServer(opts) {
|
|
|
4025
5033
|
requirePreTaskCheckpoint: false,
|
|
4026
5034
|
requirePostTaskCheckpoint: true,
|
|
4027
5035
|
requireApprovalForPush: true,
|
|
5036
|
+
allowAutoPublishSubmoduleMainCommits: false,
|
|
4028
5037
|
requireApprovalForDestructiveGit: true,
|
|
4029
5038
|
dirtyWorkspaceBehavior: "warn",
|
|
4030
5039
|
maxParallelTasks: 2,
|
|
@@ -4081,11 +5090,13 @@ async function startMcpServer(opts) {
|
|
|
4081
5090
|
}
|
|
4082
5091
|
let localDaemonId;
|
|
4083
5092
|
let localMachineId;
|
|
5093
|
+
let coordinatorHostname = import_node_os.default.hostname();
|
|
4084
5094
|
if (transport instanceof LocalTransport || transport instanceof IpcTransport) {
|
|
4085
5095
|
try {
|
|
4086
5096
|
const { loadConfig } = await import("@adhdev/daemon-core");
|
|
4087
5097
|
const cfg = loadConfig();
|
|
4088
|
-
if (cfg.
|
|
5098
|
+
if (cfg.machineId) localMachineId = cfg.machineId;
|
|
5099
|
+
else if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
|
|
4089
5100
|
} catch {
|
|
4090
5101
|
}
|
|
4091
5102
|
}
|
|
@@ -4093,14 +5104,16 @@ async function startMcpServer(opts) {
|
|
|
4093
5104
|
try {
|
|
4094
5105
|
const statusResult = await transport.getStatus();
|
|
4095
5106
|
const instanceId = typeof statusResult?.status?.instanceId === "string" ? statusResult.status.instanceId.trim() : "";
|
|
5107
|
+
const hostname = typeof statusResult?.status?.hostname === "string" ? statusResult.status.hostname.trim() : typeof statusResult?.status?.machine?.hostname === "string" ? statusResult.status.machine.hostname.trim() : "";
|
|
4096
5108
|
if (instanceId) localDaemonId = instanceId;
|
|
5109
|
+
if (hostname) coordinatorHostname = hostname;
|
|
4097
5110
|
} catch {
|
|
4098
5111
|
}
|
|
4099
5112
|
}
|
|
4100
|
-
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {} };
|
|
5113
|
+
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {}, ...coordinatorHostname ? { coordinatorHostname } : {} };
|
|
4101
5114
|
const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
|
|
4102
5115
|
const server2 = new import_server.Server(
|
|
4103
|
-
{ name: "adhdev-mcp-server", version: "0.9.
|
|
5116
|
+
{ name: "adhdev-mcp-server", version: "0.9.82" },
|
|
4104
5117
|
{ capabilities: { tools: {}, resources: {} } }
|
|
4105
5118
|
);
|
|
4106
5119
|
const { ListResourcesRequestSchema, ReadResourceRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
|
|
@@ -4126,7 +5139,7 @@ async function startMcpServer(opts) {
|
|
|
4126
5139
|
let text;
|
|
4127
5140
|
switch (name) {
|
|
4128
5141
|
case "mesh_status":
|
|
4129
|
-
text = await meshStatus(meshCtx);
|
|
5142
|
+
text = await meshStatus(meshCtx, a);
|
|
4130
5143
|
break;
|
|
4131
5144
|
case "mesh_list_nodes":
|
|
4132
5145
|
text = await meshListNodes(meshCtx);
|
|
@@ -4158,6 +5171,9 @@ async function startMcpServer(opts) {
|
|
|
4158
5171
|
case "mesh_git_status":
|
|
4159
5172
|
text = await meshGitStatus(meshCtx, a);
|
|
4160
5173
|
break;
|
|
5174
|
+
case "mesh_fast_forward_node":
|
|
5175
|
+
text = await meshFastForwardNode(meshCtx, a);
|
|
5176
|
+
break;
|
|
4161
5177
|
case "mesh_checkpoint":
|
|
4162
5178
|
text = await meshCheckpoint(meshCtx, a);
|
|
4163
5179
|
break;
|
|
@@ -4173,6 +5189,18 @@ async function startMcpServer(opts) {
|
|
|
4173
5189
|
case "mesh_refine_node":
|
|
4174
5190
|
text = await meshRefineNode(meshCtx, a);
|
|
4175
5191
|
break;
|
|
5192
|
+
case "mesh_refine_config_schema":
|
|
5193
|
+
text = await meshRefineConfigSchema(meshCtx);
|
|
5194
|
+
break;
|
|
5195
|
+
case "mesh_validate_refine_config":
|
|
5196
|
+
text = await meshValidateRefineConfig(meshCtx, a);
|
|
5197
|
+
break;
|
|
5198
|
+
case "mesh_suggest_refine_config":
|
|
5199
|
+
text = await meshSuggestRefineConfig(meshCtx, a);
|
|
5200
|
+
break;
|
|
5201
|
+
case "mesh_refine_plan":
|
|
5202
|
+
text = await meshRefinePlan(meshCtx, a);
|
|
5203
|
+
break;
|
|
4176
5204
|
case "mesh_cleanup_sessions":
|
|
4177
5205
|
text = await meshCleanupSessions(meshCtx, a);
|
|
4178
5206
|
break;
|