@adhdev/daemon-standalone 0.9.82-rc.22 → 0.9.82-rc.221
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 +60949 -34121
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/public/assets/index-DJSLOQGR.js +113 -0
- package/public/assets/index-Xbq7Kygc.css +1 -0
- package/public/assets/{terminal-CfAvHQvJ.js → terminal-DN8I8rbd.js} +4 -4
- package/public/assets/vendor-BwuWgaJI.js +2768 -0
- package/public/index.html +3 -3
- package/public/snake.html +229 -0
- package/vendor/mcp-server/index.js +1607 -191
- 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-CsEmn20l.css +0 -1
- package/public/assets/index-Deb1Bk0M.js +0 -98
- 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
|
};
|
|
@@ -143,10 +230,6 @@ function isLocalTransport(transport) {
|
|
|
143
230
|
}
|
|
144
231
|
|
|
145
232
|
// src/tools/chat-compact.ts
|
|
146
|
-
function isAssistantLike(message) {
|
|
147
|
-
const role = String(message?.role ?? "").toLowerCase();
|
|
148
|
-
return role === "assistant" || role === "agent";
|
|
149
|
-
}
|
|
150
233
|
function messageContent(message) {
|
|
151
234
|
const content = message?.content;
|
|
152
235
|
if (typeof content === "string") return content;
|
|
@@ -165,11 +248,36 @@ function isCoordinatorVisibleMessage(message) {
|
|
|
165
248
|
if (meta?.internal === true || meta?.debug === true || meta?.control === true || meta?.userVisible === false || meta?.user_visible === false) return false;
|
|
166
249
|
return role === "user" || role === "assistant" || role === "agent";
|
|
167
250
|
}
|
|
251
|
+
function summarizeToolMessage(message) {
|
|
252
|
+
if (!message || typeof message !== "object") return null;
|
|
253
|
+
const kind = String(message.kind ?? message.type ?? message.messageKind ?? "").toLowerCase();
|
|
254
|
+
const role = String(message.role ?? "").toLowerCase();
|
|
255
|
+
if (kind === "terminal" || kind === "bash") {
|
|
256
|
+
const cmd = message.command ?? message.cmd ?? message.input ?? messageContent(message);
|
|
257
|
+
const exit = message.exitCode ?? message.exit_code ?? message.code;
|
|
258
|
+
const cmdShort = typeof cmd === "string" ? cmd.split("\n")[0].slice(0, 120) : null;
|
|
259
|
+
if (!cmdShort) return null;
|
|
260
|
+
return exit !== void 0 && exit !== null ? `[Bash] ${cmdShort} \u2192 exit ${exit}` : `[Bash] ${cmdShort}`;
|
|
261
|
+
}
|
|
262
|
+
if (kind === "tool_call" || kind === "tool" || role === "tool") {
|
|
263
|
+
const name = message.name ?? message.toolName ?? message.tool_name ?? message.function?.name;
|
|
264
|
+
if (typeof name === "string" && name.trim()) return `[Tool] ${name.trim()}`;
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
if (kind === "tool_result") {
|
|
268
|
+
const exit = message.exitCode ?? message.exit_code ?? message.code;
|
|
269
|
+
const name = message.name ?? message.toolName ?? message.tool_name;
|
|
270
|
+
const label = typeof name === "string" && name.trim() ? name.trim() : "tool";
|
|
271
|
+
return exit !== void 0 && exit !== null ? `[Tool result: ${label}] exit ${exit}` : null;
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
168
275
|
function buildCompactMessageTail(visibleMessages, opts) {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
276
|
+
const tail = visibleMessages.slice(-opts.limit);
|
|
277
|
+
if (opts.finalAssistant && !tail.includes(opts.finalAssistant)) {
|
|
278
|
+
return [opts.finalAssistant, ...tail];
|
|
279
|
+
}
|
|
280
|
+
return tail;
|
|
173
281
|
}
|
|
174
282
|
function compactChatPayload(payload, opts = {}) {
|
|
175
283
|
const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
|
|
@@ -181,6 +289,9 @@ function compactChatPayload(payload, opts = {}) {
|
|
|
181
289
|
});
|
|
182
290
|
const summary = typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : messageContent(finalAssistant).trim();
|
|
183
291
|
const messages = buildCompactMessageTail(visible, { summary, finalAssistant, limit });
|
|
292
|
+
const toolSummaries = rawMessages.filter((m) => !isCoordinatorVisibleMessage(m)).map(summarizeToolMessage).filter((s) => s !== null);
|
|
293
|
+
const omittedMessages = Math.max(0, rawMessages.length - messages.length);
|
|
294
|
+
const filteredMessages = Math.max(0, rawMessages.length - visible.length);
|
|
184
295
|
return {
|
|
185
296
|
success: payload?.success !== false,
|
|
186
297
|
compact: true,
|
|
@@ -190,8 +301,9 @@ function compactChatPayload(payload, opts = {}) {
|
|
|
190
301
|
providerSessionId: payload?.providerSessionId ?? null,
|
|
191
302
|
totalMessages: rawMessages.length,
|
|
192
303
|
visibleMessages: visible.length,
|
|
193
|
-
filteredMessages
|
|
194
|
-
omittedMessages
|
|
304
|
+
filteredMessages,
|
|
305
|
+
omittedMessages,
|
|
306
|
+
...toolSummaries.length > 0 ? { toolSummaries } : {},
|
|
195
307
|
summary,
|
|
196
308
|
...payload?.changedFiles !== void 0 ? { changedFiles: payload.changedFiles } : {},
|
|
197
309
|
...payload?.testsRun !== void 0 ? { testsRun: payload.testsRun } : {},
|
|
@@ -241,17 +353,64 @@ function annotateRapidReadChatAdvisory(payload, options) {
|
|
|
241
353
|
|
|
242
354
|
// src/tools/mesh-tools.ts
|
|
243
355
|
var import_daemon_core = require("@adhdev/daemon-core");
|
|
356
|
+
var SESSION_PROVIDER_METADATA_TTL_MS = 30 * 6e4;
|
|
244
357
|
var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
358
|
+
function getSessionMetadata(key) {
|
|
359
|
+
const entry = meshSessionProviderMetadata.get(key);
|
|
360
|
+
if (!entry) return void 0;
|
|
361
|
+
if (entry.expiresAt <= Date.now()) {
|
|
362
|
+
meshSessionProviderMetadata.delete(key);
|
|
363
|
+
return void 0;
|
|
364
|
+
}
|
|
365
|
+
return entry;
|
|
366
|
+
}
|
|
367
|
+
var ACTIVE_WORK_POLLING_BACKOFF_MS = 6e4;
|
|
368
|
+
function buildActiveWorkPollingGuidance(summary, now = Date.now()) {
|
|
369
|
+
if (!summary || summary.generatingCount <= 0) return void 0;
|
|
370
|
+
return {
|
|
371
|
+
activeGeneratingWork: true,
|
|
372
|
+
generatingCount: summary.generatingCount,
|
|
373
|
+
doNotPollBefore: new Date(now + ACTIVE_WORK_POLLING_BACKOFF_MS).toISOString(),
|
|
374
|
+
eventSurface: "pendingCoordinatorEvents",
|
|
375
|
+
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.",
|
|
376
|
+
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."
|
|
377
|
+
};
|
|
378
|
+
}
|
|
245
379
|
function readString(value) {
|
|
246
380
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
247
381
|
}
|
|
382
|
+
function summarizeTaskMessage(message) {
|
|
383
|
+
const taskSummary = message.replace(/\s+/g, " ").trim();
|
|
384
|
+
const taskTitle = taskSummary.length > 96 ? `${taskSummary.slice(0, 93)}...` : taskSummary;
|
|
385
|
+
return { taskTitle: taskTitle || "(untitled task)", taskSummary };
|
|
386
|
+
}
|
|
387
|
+
function buildDirectTaskPayload(message, via, opts) {
|
|
388
|
+
const descriptor = summarizeTaskMessage(message);
|
|
389
|
+
return {
|
|
390
|
+
source: "direct",
|
|
391
|
+
via,
|
|
392
|
+
taskId: opts.taskId,
|
|
393
|
+
message,
|
|
394
|
+
taskTitle: descriptor.taskTitle,
|
|
395
|
+
taskSummary: descriptor.taskSummary,
|
|
396
|
+
...opts.taskMode ? { taskMode: opts.taskMode } : {},
|
|
397
|
+
...opts.providerType ? { providerType: opts.providerType } : {},
|
|
398
|
+
...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {},
|
|
399
|
+
...opts.dispatchedToIdleSession !== void 0 ? { dispatchedToIdleSession: opts.dispatchedToIdleSession } : {}
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function findNode(mesh, nodeId) {
|
|
403
|
+
const node = mesh.nodes.find((n) => n.id === nodeId);
|
|
404
|
+
if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
|
|
405
|
+
return node;
|
|
406
|
+
}
|
|
248
407
|
var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
|
|
249
408
|
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
250
409
|
var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
251
410
|
var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
|
|
252
411
|
var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
253
412
|
async function refreshMeshFromDaemon(ctx) {
|
|
254
|
-
if (!(ctx.transport
|
|
413
|
+
if (!isLocalTransport(ctx.transport)) return;
|
|
255
414
|
try {
|
|
256
415
|
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
257
416
|
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
@@ -412,6 +571,25 @@ function extractStatusMetadataSessions(value) {
|
|
|
412
571
|
function resolveSessionProviderType(session) {
|
|
413
572
|
return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
|
|
414
573
|
}
|
|
574
|
+
function isMeshCoordinatorSessionRecord(session) {
|
|
575
|
+
return Boolean(
|
|
576
|
+
readString(session?.settings?.meshCoordinatorFor) || readString(session?.meta?.meshCoordinatorFor) || readString(session?.metadata?.meshCoordinatorFor) || readString(session?.meshCoordinatorFor)
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
function isUnmanagedSessionRecord(session) {
|
|
580
|
+
const hasMeshNodeFor = Boolean(
|
|
581
|
+
readString(session?.settings?.meshNodeFor) || readString(session?.meta?.meshNodeFor) || readString(session?.metadata?.meshNodeFor) || readString(session?.meshNodeFor)
|
|
582
|
+
);
|
|
583
|
+
if (hasMeshNodeFor) return false;
|
|
584
|
+
if (isMeshCoordinatorSessionRecord(session)) return false;
|
|
585
|
+
const launchedByCoordinator = Boolean(
|
|
586
|
+
session?.settings?.launchedByCoordinator === true || session?.meta?.launchedByCoordinator === true || session?.launchedByCoordinator === true
|
|
587
|
+
);
|
|
588
|
+
return !launchedByCoordinator;
|
|
589
|
+
}
|
|
590
|
+
function isWorkerTaskMode(taskMode) {
|
|
591
|
+
return taskMode !== "live_debug_readonly";
|
|
592
|
+
}
|
|
415
593
|
function addSessionRecord(target, session) {
|
|
416
594
|
if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
|
|
417
595
|
const sessionId = readSessionRecordId(session);
|
|
@@ -480,18 +658,26 @@ function queueAssignmentStaleReason(task, liveness) {
|
|
|
480
658
|
}
|
|
481
659
|
function buildQueueStatusSummary(queue) {
|
|
482
660
|
const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
|
|
661
|
+
let staleAssigned = 0;
|
|
483
662
|
for (const task of queue) {
|
|
484
663
|
const status = typeof task?.status === "string" ? task.status : void 0;
|
|
485
664
|
if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
|
|
486
665
|
counts[status] += 1;
|
|
487
666
|
}
|
|
667
|
+
if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
|
|
488
668
|
}
|
|
669
|
+
const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
|
|
489
670
|
return {
|
|
490
671
|
totalCount: queue.length,
|
|
491
|
-
activeCount: counts.pending +
|
|
672
|
+
activeCount: counts.pending + liveAssigned,
|
|
492
673
|
historicalCount: counts.completed + counts.failed + counts.cancelled,
|
|
493
674
|
counts,
|
|
494
675
|
activeCounts: {
|
|
676
|
+
pending: counts.pending,
|
|
677
|
+
assigned: liveAssigned
|
|
678
|
+
},
|
|
679
|
+
staleAssignedCount: staleAssigned,
|
|
680
|
+
rawActiveCounts: {
|
|
495
681
|
pending: counts.pending,
|
|
496
682
|
assigned: counts.assigned
|
|
497
683
|
},
|
|
@@ -519,6 +705,18 @@ function filterQueueForView(queue, view, statuses) {
|
|
|
519
705
|
if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
520
706
|
return queue;
|
|
521
707
|
}
|
|
708
|
+
function prioritizeActiveQueueRows(queue) {
|
|
709
|
+
const active = [];
|
|
710
|
+
const historical = [];
|
|
711
|
+
const other = [];
|
|
712
|
+
for (const task of queue) {
|
|
713
|
+
const status = String(task?.status || "");
|
|
714
|
+
if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
|
|
715
|
+
else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
|
|
716
|
+
else other.push(task);
|
|
717
|
+
}
|
|
718
|
+
return [...active, ...other, ...historical];
|
|
719
|
+
}
|
|
522
720
|
function slimQueueTask(task) {
|
|
523
721
|
return {
|
|
524
722
|
id: task?.id,
|
|
@@ -612,6 +810,172 @@ function unwrapCommandPayload(value) {
|
|
|
612
810
|
}
|
|
613
811
|
return current;
|
|
614
812
|
}
|
|
813
|
+
function isDirectDispatchLedgerEntry(entry) {
|
|
814
|
+
if (entry?.kind !== "task_dispatched") return false;
|
|
815
|
+
const payload = entry.payload || {};
|
|
816
|
+
const via = readString(payload.via);
|
|
817
|
+
return payload.source === "direct" || via === "p2p_direct" || via === "local_direct" || via === "mesh_send_task";
|
|
818
|
+
}
|
|
819
|
+
function readMessageTimestampIso(message) {
|
|
820
|
+
for (const value of [message?.timestamp, message?.createdAt, message?.created_at, message?.updatedAt, message?.time]) {
|
|
821
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
822
|
+
const ms = value > 1e10 ? value : value * 1e3;
|
|
823
|
+
return new Date(ms).toISOString();
|
|
824
|
+
}
|
|
825
|
+
if (typeof value === "string" && value.trim()) {
|
|
826
|
+
const ms = new Date(value.trim()).getTime();
|
|
827
|
+
if (Number.isFinite(ms)) return new Date(ms).toISOString();
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return void 0;
|
|
831
|
+
}
|
|
832
|
+
function readFinalAssistantTranscriptEvidence(payload) {
|
|
833
|
+
const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
|
|
834
|
+
const finalAssistant = [...rawMessages].reverse().filter(isCoordinatorVisibleMessage).find((message) => {
|
|
835
|
+
const role = String(message?.role ?? "").toLowerCase();
|
|
836
|
+
return (role === "assistant" || role === "agent") && messageContent(message).trim();
|
|
837
|
+
});
|
|
838
|
+
const finalSummary = messageContent(finalAssistant).trim() || (typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : void 0);
|
|
839
|
+
return {
|
|
840
|
+
finalSummary,
|
|
841
|
+
transcriptMessageAt: finalAssistant ? readMessageTimestampIso(finalAssistant) : void 0
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
function findNodeSession(nodes, nodeId, sessionId) {
|
|
845
|
+
if (!nodeId || !sessionId) return {};
|
|
846
|
+
const node = nodes.find((candidate) => readString(candidate?.id) === nodeId || readString(candidate?.nodeId) === nodeId);
|
|
847
|
+
if (!node) return {};
|
|
848
|
+
const sessions = Array.isArray(node.sessions) ? node.sessions : [];
|
|
849
|
+
const session = sessions.find((candidate) => readSessionRecordId(candidate) === sessionId);
|
|
850
|
+
return { node, session };
|
|
851
|
+
}
|
|
852
|
+
function buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries) {
|
|
853
|
+
const candidates = [];
|
|
854
|
+
const seenTaskIds = /* @__PURE__ */ new Set();
|
|
855
|
+
for (const dispatch of directDispatches || []) {
|
|
856
|
+
const taskId = readString(dispatch?.taskId);
|
|
857
|
+
if (!taskId || seenTaskIds.has(taskId)) continue;
|
|
858
|
+
seenTaskIds.add(taskId);
|
|
859
|
+
candidates.push(dispatch);
|
|
860
|
+
}
|
|
861
|
+
for (const entry of ledgerEntries || []) {
|
|
862
|
+
if (!isDirectDispatchLedgerEntry(entry)) continue;
|
|
863
|
+
const taskId = readString(entry.payload?.taskId);
|
|
864
|
+
if (!taskId || seenTaskIds.has(taskId)) continue;
|
|
865
|
+
seenTaskIds.add(taskId);
|
|
866
|
+
candidates.push({
|
|
867
|
+
taskId,
|
|
868
|
+
nodeId: entry.nodeId,
|
|
869
|
+
sessionId: entry.sessionId,
|
|
870
|
+
providerType: entry.providerType || readString(entry.payload?.providerType),
|
|
871
|
+
message: readString(entry.payload?.message),
|
|
872
|
+
dispatchedAt: entry.timestamp,
|
|
873
|
+
via: readString(entry.payload?.via)
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
return candidates;
|
|
877
|
+
}
|
|
878
|
+
async function reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries) {
|
|
879
|
+
let attempted = 0;
|
|
880
|
+
let reconciled = 0;
|
|
881
|
+
let skipped = 0;
|
|
882
|
+
const candidates = buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries);
|
|
883
|
+
for (const dispatch of candidates) {
|
|
884
|
+
const taskId = readString(dispatch?.taskId);
|
|
885
|
+
const nodeId = readString(dispatch?.nodeId);
|
|
886
|
+
const sessionId = readString(dispatch?.sessionId);
|
|
887
|
+
if (!taskId || !nodeId || !sessionId) {
|
|
888
|
+
skipped += 1;
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
const { session } = findNodeSession(liveNodes, nodeId, sessionId);
|
|
892
|
+
if (!session || !isIdleSessionRecord(session)) {
|
|
893
|
+
skipped += 1;
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
const node = await findOptionalNodeWithRefresh(ctx, nodeId).catch(() => null);
|
|
897
|
+
if (!node) {
|
|
898
|
+
skipped += 1;
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
const providerType = readString(dispatch?.providerType) || resolveSessionProviderType(session);
|
|
902
|
+
const providerSessionId = readString(session?.providerSessionId) || readString(session?.activeChat?.providerSessionId) || readString(session?.settings?.providerSessionId) || resolveMeshSessionProviderMetadata(ctx, nodeId, sessionId)?.providerSessionId;
|
|
903
|
+
attempted += 1;
|
|
904
|
+
try {
|
|
905
|
+
const readResult = await commandForNode(ctx, node, "read_chat", {
|
|
906
|
+
sessionId,
|
|
907
|
+
targetSessionId: sessionId,
|
|
908
|
+
workspace: node.workspace,
|
|
909
|
+
...providerType ? { agentType: providerType, providerType } : {},
|
|
910
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
911
|
+
tailLimit: 10
|
|
912
|
+
});
|
|
913
|
+
const payload = unwrapCommandPayload(readResult);
|
|
914
|
+
if (payload?.success === false) continue;
|
|
915
|
+
const evidence = readFinalAssistantTranscriptEvidence(payload);
|
|
916
|
+
if (!evidence.finalSummary) continue;
|
|
917
|
+
const result = (0, import_daemon_core.reconcileDirectDispatchCompletionFromTranscript)({
|
|
918
|
+
meshId: ctx.mesh.id,
|
|
919
|
+
nodeId,
|
|
920
|
+
sessionId,
|
|
921
|
+
providerType,
|
|
922
|
+
providerSessionId: readString(payload?.providerSessionId) || providerSessionId,
|
|
923
|
+
taskId,
|
|
924
|
+
finalSummary: evidence.finalSummary,
|
|
925
|
+
transcriptMessageAt: evidence.transcriptMessageAt,
|
|
926
|
+
targetCoordinatorDaemonId: ctx.localDaemonId,
|
|
927
|
+
source: "mcp_mesh_status_transcript_reconciliation"
|
|
928
|
+
});
|
|
929
|
+
if (result.reconciled) reconciled += 1;
|
|
930
|
+
} catch {
|
|
931
|
+
skipped += 1;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return { attempted, reconciled, skipped };
|
|
935
|
+
}
|
|
936
|
+
async function triggerMeshQueueAndReport(ctx, node, opts) {
|
|
937
|
+
if (!(isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport)) return void 0;
|
|
938
|
+
try {
|
|
939
|
+
let raw;
|
|
940
|
+
if (ctx.transport instanceof IpcTransport && node?.daemonId && opts?.localNode === false) {
|
|
941
|
+
raw = await ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id });
|
|
942
|
+
} else if (isLocalTransport(ctx.transport)) {
|
|
943
|
+
raw = await ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id });
|
|
944
|
+
} else {
|
|
945
|
+
return void 0;
|
|
946
|
+
}
|
|
947
|
+
const payload = unwrapCommandPayload(raw);
|
|
948
|
+
const trigger = payload?.trigger && typeof payload.trigger === "object" ? payload.trigger : payload;
|
|
949
|
+
return trigger && typeof trigger === "object" ? trigger : { success: true };
|
|
950
|
+
} catch (e) {
|
|
951
|
+
return {
|
|
952
|
+
success: false,
|
|
953
|
+
error: e?.message || String(e)
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function buildQueueTriggerGuidance(queueTrigger) {
|
|
958
|
+
if (!queueTrigger || queueTrigger.claimed === true) return void 0;
|
|
959
|
+
if (queueTrigger.success === false) {
|
|
960
|
+
return {
|
|
961
|
+
queueClaimed: false,
|
|
962
|
+
queueDispatchState: "trigger_failed",
|
|
963
|
+
nextAction: "Do not assume the queued task is running. Check mesh_view_queue and daemon connectivity before redispatching."
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
if (queueTrigger.noIdleMeshSessionAvailable === true) {
|
|
967
|
+
return {
|
|
968
|
+
queueClaimed: false,
|
|
969
|
+
queueDispatchState: "pending_no_idle_mesh_session",
|
|
970
|
+
nextAction: "The task is queued but not running. Launch a managed worker with mesh_launch_session, or wait for a delegated session to become ready and trigger the queue again."
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
return {
|
|
974
|
+
queueClaimed: false,
|
|
975
|
+
queueDispatchState: "pending_or_waiting_for_ready",
|
|
976
|
+
nextAction: "The task is queued but this trigger did not claim it. Use mesh_view_queue for the current active-work source of truth before retrying."
|
|
977
|
+
};
|
|
978
|
+
}
|
|
615
979
|
function isTerminalSessionRecord(session) {
|
|
616
980
|
const status = typeof session?.status === "string" ? session.status.toLowerCase() : "";
|
|
617
981
|
const lifecycle = typeof session?.lifecycle === "string" ? session.lifecycle.toLowerCase() : "";
|
|
@@ -627,16 +991,23 @@ function isIdleSessionRecord(session) {
|
|
|
627
991
|
function isMeshOwnedDelegateSession(session, meshId, nodeId) {
|
|
628
992
|
const settings = session?.settings;
|
|
629
993
|
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
630
|
-
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
631
994
|
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
632
|
-
if (sessionMeshId !== meshId
|
|
995
|
+
if (sessionMeshId !== meshId) return false;
|
|
633
996
|
return !sessionNodeId || sessionNodeId === nodeId;
|
|
634
997
|
}
|
|
998
|
+
function hasRemoteRelayMetadata(session) {
|
|
999
|
+
return Boolean(
|
|
1000
|
+
readString(session?.settings?.meshCoordinatorDaemonId) || readString(session?.meta?.meshCoordinatorDaemonId) || readString(session?.metadata?.meshCoordinatorDaemonId) || readString(session?.meshCoordinatorDaemonId)
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
function isRelaySafeRemoteDelegateSession(session, meshId, nodeId) {
|
|
1004
|
+
return isMeshOwnedDelegateSession(session, meshId, nodeId) && hasRemoteRelayMetadata(session);
|
|
1005
|
+
}
|
|
635
1006
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
636
1007
|
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
637
1008
|
const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
|
|
638
1009
|
const meshSessions = live.filter(
|
|
639
|
-
(session) =>
|
|
1010
|
+
(session) => isRelaySafeRemoteDelegateSession(session, meshId, nodeId)
|
|
640
1011
|
);
|
|
641
1012
|
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
|
|
642
1013
|
}
|
|
@@ -653,8 +1024,9 @@ function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType
|
|
|
653
1024
|
daemonId: node.daemonId,
|
|
654
1025
|
workspace: node.workspace,
|
|
655
1026
|
sessionId,
|
|
1027
|
+
unsafeTranscriptAlias: true,
|
|
656
1028
|
...providerType ? { resolvedProviderType: providerType } : {},
|
|
657
|
-
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.`,
|
|
1029
|
+
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).`,
|
|
658
1030
|
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.`,
|
|
659
1031
|
noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
|
|
660
1032
|
};
|
|
@@ -704,12 +1076,16 @@ function extractGitDiff(value) {
|
|
|
704
1076
|
}
|
|
705
1077
|
function extractSubmodules(value, ignorePaths) {
|
|
706
1078
|
const payload = unwrapCommandPayload(value);
|
|
707
|
-
const subs = payload?.submodules ?? value?.submodules;
|
|
1079
|
+
const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
|
|
708
1080
|
if (!Array.isArray(subs)) return void 0;
|
|
709
1081
|
if (ignorePaths.length === 0) return subs;
|
|
710
1082
|
const ignoreSet = new Set(ignorePaths);
|
|
711
1083
|
return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
|
|
712
1084
|
}
|
|
1085
|
+
function assignFullGitSnapshot(entry, status) {
|
|
1086
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return;
|
|
1087
|
+
entry.git = status;
|
|
1088
|
+
}
|
|
713
1089
|
function extractLaunchPayload(value) {
|
|
714
1090
|
return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
|
|
715
1091
|
}
|
|
@@ -834,7 +1210,20 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
834
1210
|
let sessionId = args.session_id?.trim() || "";
|
|
835
1211
|
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
836
1212
|
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
837
|
-
if (
|
|
1213
|
+
if (sessionId && args.verifiedSession) {
|
|
1214
|
+
const explicitSession = args.verifiedSession;
|
|
1215
|
+
if (!isRelaySafeRemoteDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
1216
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
1217
|
+
ctx,
|
|
1218
|
+
node,
|
|
1219
|
+
sessionId,
|
|
1220
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
if (!resolvedProviderType) {
|
|
1224
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
1225
|
+
}
|
|
1226
|
+
} else if (!sessionId || args.session_id) {
|
|
838
1227
|
try {
|
|
839
1228
|
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
840
1229
|
const sessions = extractStatusMetadataSessions(relayResult);
|
|
@@ -858,7 +1247,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
858
1247
|
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.`
|
|
859
1248
|
};
|
|
860
1249
|
}
|
|
861
|
-
if (!
|
|
1250
|
+
if (!isRelaySafeRemoteDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
862
1251
|
return buildRelayUnsafeRemoteSessionFailure(
|
|
863
1252
|
ctx,
|
|
864
1253
|
node,
|
|
@@ -902,7 +1291,8 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
902
1291
|
agentType: resolvedProviderType,
|
|
903
1292
|
cliType: resolvedProviderType,
|
|
904
1293
|
action: "send_chat",
|
|
905
|
-
message: args.message
|
|
1294
|
+
message: args.message,
|
|
1295
|
+
...args.meshContext ? { meshContext: args.meshContext } : {}
|
|
906
1296
|
});
|
|
907
1297
|
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
908
1298
|
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
@@ -920,7 +1310,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
920
1310
|
error: `P2P dispatch failed: ${errorMessage}`
|
|
921
1311
|
};
|
|
922
1312
|
}
|
|
923
|
-
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
|
|
1313
|
+
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType, providerType: resolvedProviderType };
|
|
924
1314
|
} catch (e) {
|
|
925
1315
|
const errorMessage = e?.message || String(e);
|
|
926
1316
|
return {
|
|
@@ -950,34 +1340,197 @@ function resolveCoordinatorNode(ctx) {
|
|
|
950
1340
|
return void 0;
|
|
951
1341
|
}
|
|
952
1342
|
function readNodeMachineId(node) {
|
|
953
|
-
return readString(node.machineId) || readString(node.machine_id);
|
|
1343
|
+
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);
|
|
954
1344
|
}
|
|
955
1345
|
function readNodeDaemonId(node) {
|
|
956
|
-
return readString(node.daemonId) || readString(node.daemon_id);
|
|
1346
|
+
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);
|
|
1347
|
+
}
|
|
1348
|
+
function normalizeHostname(value) {
|
|
1349
|
+
const hostname = readString(value);
|
|
1350
|
+
if (!hostname) return void 0;
|
|
1351
|
+
return hostname.toLowerCase().replace(/\.$/, "");
|
|
1352
|
+
}
|
|
1353
|
+
function readNodeHostname(node) {
|
|
1354
|
+
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);
|
|
1355
|
+
}
|
|
1356
|
+
function readNodeDisplayMachineName(node) {
|
|
1357
|
+
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);
|
|
1358
|
+
}
|
|
1359
|
+
function compactIdentityEvidence(value) {
|
|
1360
|
+
if (!value) return void 0;
|
|
1361
|
+
return value.length > 24 ? `${value.slice(0, 12)}\u2026${value.slice(-8)}` : value;
|
|
1362
|
+
}
|
|
1363
|
+
function pushIdentityEvidence(evidence, label, value) {
|
|
1364
|
+
const compact = compactIdentityEvidence(value);
|
|
1365
|
+
if (compact) evidence.push(`${label}:${compact}`);
|
|
1366
|
+
}
|
|
1367
|
+
function buildNodeMachineIdentity(ctx, node) {
|
|
1368
|
+
const machineId = readNodeMachineId(node);
|
|
1369
|
+
const daemonId = readNodeDaemonId(node);
|
|
1370
|
+
const hostname = readNodeHostname(node);
|
|
1371
|
+
const machineName = readNodeDisplayMachineName(node);
|
|
1372
|
+
const coordinatorHostname = readString(ctx.coordinatorHostname);
|
|
1373
|
+
const localControlPlaneReason = getLocalControlPlaneMatchReason(ctx, node);
|
|
1374
|
+
const directLocal = !!localControlPlaneReason;
|
|
1375
|
+
const hostnameMatches = Boolean(
|
|
1376
|
+
normalizeHostname(hostname) && normalizeHostname(coordinatorHostname) && normalizeHostname(hostname) === normalizeHostname(coordinatorHostname)
|
|
1377
|
+
);
|
|
1378
|
+
const sameMachine = directLocal || hostnameMatches;
|
|
1379
|
+
const evidence = [];
|
|
1380
|
+
pushIdentityEvidence(evidence, "machineName", machineName);
|
|
1381
|
+
pushIdentityEvidence(evidence, "hostname", hostname);
|
|
1382
|
+
pushIdentityEvidence(evidence, "machineId", machineId);
|
|
1383
|
+
pushIdentityEvidence(evidence, "daemonId", daemonId);
|
|
1384
|
+
if (localControlPlaneReason) {
|
|
1385
|
+
pushIdentityEvidence(evidence, "localMatch", localControlPlaneReason);
|
|
1386
|
+
pushIdentityEvidence(evidence, "localMachineId", ctx.localMachineId);
|
|
1387
|
+
pushIdentityEvidence(evidence, "localDaemonId", ctx.localDaemonId);
|
|
1388
|
+
}
|
|
1389
|
+
const locality = sameMachine ? "same_machine" : evidence.length > 0 ? "remote_known" : "remote_or_unknown";
|
|
1390
|
+
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";
|
|
1391
|
+
return {
|
|
1392
|
+
daemonId,
|
|
1393
|
+
machineId,
|
|
1394
|
+
hostname,
|
|
1395
|
+
machineName,
|
|
1396
|
+
displayName: machineName || hostname || daemonId || machineId,
|
|
1397
|
+
coordinatorHostname,
|
|
1398
|
+
sameMachine,
|
|
1399
|
+
locality,
|
|
1400
|
+
localityReason,
|
|
1401
|
+
identityEvidence: evidence
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
function nodeHasLocalDaemonEvidence(ctx, node) {
|
|
1405
|
+
const isLocal = (session) => {
|
|
1406
|
+
if (!session || typeof session !== "object") return false;
|
|
1407
|
+
if (ctx.localDaemonId && session.runtime?.owner === ctx.localDaemonId) return true;
|
|
1408
|
+
if (ctx.localDaemonId && session.daemonClient?.daemonId === ctx.localDaemonId) return true;
|
|
1409
|
+
return false;
|
|
1410
|
+
};
|
|
1411
|
+
const sessionArrays = [
|
|
1412
|
+
node?.sessions,
|
|
1413
|
+
node?.activeSessions,
|
|
1414
|
+
node?.active_sessions,
|
|
1415
|
+
node?.lastProbe?.sessions,
|
|
1416
|
+
node?.last_probe?.sessions,
|
|
1417
|
+
node?.lastProbe?.status?.sessions,
|
|
1418
|
+
node?.last_probe?.status?.sessions
|
|
1419
|
+
];
|
|
1420
|
+
for (const arr of sessionArrays) {
|
|
1421
|
+
if (Array.isArray(arr) && arr.some(isLocal)) return true;
|
|
1422
|
+
}
|
|
1423
|
+
const sessionRecords = [
|
|
1424
|
+
node?.activeSession,
|
|
1425
|
+
node?.active_session,
|
|
1426
|
+
node?.currentSession,
|
|
1427
|
+
node?.current_session,
|
|
1428
|
+
node?.runtimeSession,
|
|
1429
|
+
node?.runtime_session,
|
|
1430
|
+
node?.session,
|
|
1431
|
+
node?.lastProbe?.activeSession,
|
|
1432
|
+
node?.last_probe?.active_session,
|
|
1433
|
+
node?.lastProbe?.currentSession,
|
|
1434
|
+
node?.last_probe?.current_session,
|
|
1435
|
+
node?.lastProbe?.session,
|
|
1436
|
+
node?.last_probe?.session
|
|
1437
|
+
];
|
|
1438
|
+
for (const session of sessionRecords) {
|
|
1439
|
+
if (isLocal(session)) return true;
|
|
1440
|
+
}
|
|
1441
|
+
return false;
|
|
957
1442
|
}
|
|
958
1443
|
function isDirectLocalNode(ctx, node) {
|
|
959
1444
|
const machineId = readNodeMachineId(node);
|
|
960
1445
|
const daemonId = readNodeDaemonId(node);
|
|
961
1446
|
return Boolean(
|
|
962
|
-
ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId
|
|
1447
|
+
ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId || nodeHasLocalDaemonEvidence(ctx, node)
|
|
963
1448
|
);
|
|
964
1449
|
}
|
|
1450
|
+
function isConfiguredCoordinatorNode(ctx, node) {
|
|
1451
|
+
if (!ctx.localMachineId && !ctx.localDaemonId) return false;
|
|
1452
|
+
const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
|
|
1453
|
+
if (!nodeId) return false;
|
|
1454
|
+
const nodeDaemonId = readNodeDaemonId(node);
|
|
1455
|
+
const nodeMachineId = readNodeMachineId(node);
|
|
1456
|
+
if (nodeDaemonId && ctx.localDaemonId && nodeDaemonId !== ctx.localDaemonId) return false;
|
|
1457
|
+
if (nodeMachineId && ctx.localMachineId && nodeMachineId !== ctx.localMachineId) return false;
|
|
1458
|
+
const preferredNodeId = readString(ctx.mesh.coordinator?.preferredNodeId) || readString(ctx.mesh.coordinator?.preferred_node_id);
|
|
1459
|
+
if (preferredNodeId) return nodeId === preferredNodeId;
|
|
1460
|
+
const first = ctx.mesh.nodes?.[0];
|
|
1461
|
+
const firstNodeId = readString(first?.id) || readString(first?.nodeId) || readString(first?.node_id);
|
|
1462
|
+
return !!firstNodeId && nodeId === firstNodeId;
|
|
1463
|
+
}
|
|
1464
|
+
function getLocalControlPlaneMatchReason(ctx, node) {
|
|
1465
|
+
if (isDirectLocalNode(ctx, node)) return "matched coordinator daemon or machine id";
|
|
1466
|
+
if (isConfiguredCoordinatorNode(ctx, node)) return "matched configured coordinator node";
|
|
1467
|
+
if (node.isLocalWorktree === true) {
|
|
1468
|
+
const sourceNode = findClonedFromNode(ctx, node);
|
|
1469
|
+
if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return "matched local cloned-from node";
|
|
1470
|
+
if (sourceNode && isConfiguredCoordinatorNode(ctx, sourceNode)) return "matched configured coordinator source node";
|
|
1471
|
+
}
|
|
1472
|
+
return void 0;
|
|
1473
|
+
}
|
|
965
1474
|
function findClonedFromNode(ctx, node) {
|
|
966
1475
|
const clonedFromNodeId = readString(node.clonedFromNodeId) || readString(node.cloned_from_node_id);
|
|
967
1476
|
if (!clonedFromNodeId) return void 0;
|
|
968
1477
|
return ctx.mesh.nodes.find((n) => n.id === clonedFromNodeId || n.nodeId === clonedFromNodeId || n.node_id === clonedFromNodeId);
|
|
969
1478
|
}
|
|
970
1479
|
function isLocalControlPlaneNode(ctx, node) {
|
|
971
|
-
|
|
972
|
-
if (node.isLocalWorktree === true) {
|
|
973
|
-
const sourceNode = findClonedFromNode(ctx, node);
|
|
974
|
-
if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return true;
|
|
975
|
-
}
|
|
976
|
-
return false;
|
|
1480
|
+
return !!getLocalControlPlaneMatchReason(ctx, node);
|
|
977
1481
|
}
|
|
978
1482
|
function meshSessionCacheKey(nodeId, runtimeSessionId) {
|
|
979
1483
|
return `${nodeId}:${runtimeSessionId}`;
|
|
980
1484
|
}
|
|
1485
|
+
function rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, metadata) {
|
|
1486
|
+
const keyNodeId = readString(nodeId);
|
|
1487
|
+
const keySessionId = readString(runtimeSessionId);
|
|
1488
|
+
if (!keyNodeId || !keySessionId) return;
|
|
1489
|
+
const providerType = readString(metadata.providerType);
|
|
1490
|
+
const providerSessionId = readString(metadata.providerSessionId);
|
|
1491
|
+
if (!providerType && !providerSessionId) return;
|
|
1492
|
+
const existing = getSessionMetadata(meshSessionCacheKey(keyNodeId, keySessionId)) || { providerType: "" };
|
|
1493
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(keyNodeId, keySessionId), {
|
|
1494
|
+
providerType: providerType || existing.providerType,
|
|
1495
|
+
providerSessionId: providerSessionId || existing.providerSessionId,
|
|
1496
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
function rememberMeshSessionProviderMetadataFromEvent(event) {
|
|
1500
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : event && typeof event === "object" ? event : {};
|
|
1501
|
+
const nodeId = readString(event?.nodeId) || readString(metadataEvent.nodeId) || readString(metadataEvent.meshNodeId);
|
|
1502
|
+
const sessionId = readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId) || readString(event?.sessionId);
|
|
1503
|
+
rememberMeshSessionProviderMetadata(nodeId, sessionId, {
|
|
1504
|
+
providerType: readString(metadataEvent.providerType) || readString(event?.providerType) || "",
|
|
1505
|
+
providerSessionId: readString(metadataEvent.providerSessionId) || readString(event?.providerSessionId)
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
|
|
1509
|
+
const entries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 50 });
|
|
1510
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1511
|
+
const entry = entries[i];
|
|
1512
|
+
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
1513
|
+
const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
|
|
1514
|
+
if (entryNodeId && entryNodeId !== nodeId) continue;
|
|
1515
|
+
const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
|
|
1516
|
+
if (entrySessionId !== runtimeSessionId) continue;
|
|
1517
|
+
const providerType = readString(entry.providerType) || readString(payload.providerType);
|
|
1518
|
+
const completionDiagnostic = payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? payload.completionDiagnostic : {};
|
|
1519
|
+
const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : {};
|
|
1520
|
+
const providerSessionId = readString(payload.providerSessionId) || readString(completionDiagnostic.providerSessionId) || readString(metadataEvent.providerSessionId);
|
|
1521
|
+
if (providerType || providerSessionId) {
|
|
1522
|
+
return { providerType: providerType || "", providerSessionId };
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
return void 0;
|
|
1526
|
+
}
|
|
1527
|
+
function resolveMeshSessionProviderMetadata(ctx, nodeId, runtimeSessionId) {
|
|
1528
|
+
const cached = getSessionMetadata(meshSessionCacheKey(nodeId, runtimeSessionId));
|
|
1529
|
+
if (cached?.providerType || cached?.providerSessionId) return cached;
|
|
1530
|
+
const fromLedger = resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId);
|
|
1531
|
+
if (fromLedger) rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, fromLedger);
|
|
1532
|
+
return fromLedger;
|
|
1533
|
+
}
|
|
981
1534
|
function countUncommittedChanges(status) {
|
|
982
1535
|
if (typeof status?.uncommittedChanges === "number") return status.uncommittedChanges;
|
|
983
1536
|
const keys = ["staged", "modified", "untracked", "deleted", "renamed"];
|
|
@@ -988,8 +1541,23 @@ function countUncommittedChanges(status) {
|
|
|
988
1541
|
function isGitStatusDirty(status) {
|
|
989
1542
|
if (typeof status?.isDirty === "boolean") return status.isDirty;
|
|
990
1543
|
if (typeof status?.dirty === "boolean") return status.dirty;
|
|
1544
|
+
if (Array.isArray(status?.submodules) && status.submodules.some((submodule) => submodule?.dirty || submodule?.outOfSync || submodule?.error)) return true;
|
|
991
1545
|
return countUncommittedChanges(status) > 0;
|
|
992
1546
|
}
|
|
1547
|
+
function slimLedgerPayload(payload) {
|
|
1548
|
+
const slim = {};
|
|
1549
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
1550
|
+
if (k === "message" || k === "taskSummary") {
|
|
1551
|
+
slim[k] = typeof v === "string" && v.length > 200 ? v.slice(0, 200) + "\u2026" : v;
|
|
1552
|
+
} else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
|
|
1553
|
+
} else if (k === "finalSummary") {
|
|
1554
|
+
slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
|
|
1555
|
+
} else {
|
|
1556
|
+
slim[k] = v;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
return slim;
|
|
1560
|
+
}
|
|
993
1561
|
function readRelatedRepos(node) {
|
|
994
1562
|
const raw = Array.isArray(node.relatedRepos) ? node.relatedRepos : Array.isArray(node.policy?.relatedRepos) ? node.policy.relatedRepos : [];
|
|
995
1563
|
return raw.map((entry) => ({
|
|
@@ -1048,6 +1616,16 @@ function missingProviderPriorityMessage(nodeId) {
|
|
|
1048
1616
|
return `Node '${nodeId}' has no providerPriority policy; pass type explicitly or configure node.policy.providerPriority`;
|
|
1049
1617
|
}
|
|
1050
1618
|
function getNodeLaunchReadiness(node) {
|
|
1619
|
+
const bootstrap = node.worktreeBootstrap;
|
|
1620
|
+
if (node.isLocalWorktree && bootstrap?.status === "failed" && bootstrap?.required !== false) {
|
|
1621
|
+
return {
|
|
1622
|
+
providerPriority: readProviderPriority(node.policy),
|
|
1623
|
+
launchReady: false,
|
|
1624
|
+
launchBlockedReason: "worktree_bootstrap_failed",
|
|
1625
|
+
launchBlockedMessage: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : "Required worktree bootstrap failed; resolve it before launching an agent into this node.",
|
|
1626
|
+
worktreeBootstrap: bootstrap
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1051
1629
|
const providerPriority = readProviderPriority(node.policy);
|
|
1052
1630
|
if (providerPriority.length) {
|
|
1053
1631
|
return {
|
|
@@ -1062,6 +1640,45 @@ function getNodeLaunchReadiness(node) {
|
|
|
1062
1640
|
launchBlockedMessage: missingProviderPriorityMessage(node.id)
|
|
1063
1641
|
};
|
|
1064
1642
|
}
|
|
1643
|
+
function getWorktreeBootstrapLaunchBlock(node, meshPolicy) {
|
|
1644
|
+
if (!node.isLocalWorktree) return void 0;
|
|
1645
|
+
const bootstrap = node.worktreeBootstrap;
|
|
1646
|
+
const requireReady = !!(meshPolicy && typeof meshPolicy === "object" && meshPolicy.requireBootstrapBeforeLaunch === true);
|
|
1647
|
+
if (requireReady && bootstrap?.status !== "ready") {
|
|
1648
|
+
return {
|
|
1649
|
+
success: false,
|
|
1650
|
+
code: "bootstrap_not_ready",
|
|
1651
|
+
error: `Node '${node.id}' bootstrap state is '${bootstrap?.status ?? "unknown"}' and mesh policy requireBootstrapBeforeLaunch is enabled.`,
|
|
1652
|
+
nodeId: node.id,
|
|
1653
|
+
worktreeBootstrap: bootstrap ?? null,
|
|
1654
|
+
recoveryHint: "Run the worktree bootstrap (clone runOnClone or a refine with bootstrap inherit) until the node reports ready, or disable requireBootstrapBeforeLaunch."
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
if (bootstrap?.status !== "failed" || bootstrap?.required === false) return void 0;
|
|
1658
|
+
return {
|
|
1659
|
+
success: false,
|
|
1660
|
+
code: "worktree_bootstrap_failed",
|
|
1661
|
+
error: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : `Node '${node.id}' has a failed required worktree bootstrap.`,
|
|
1662
|
+
nodeId: node.id,
|
|
1663
|
+
worktreeBootstrap: bootstrap,
|
|
1664
|
+
recoveryHint: "Fix the configured worktree bootstrap command or remove/recreate the worktree node before launching an agent."
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
async function collectLiveStatusSessions(ctx, node) {
|
|
1668
|
+
try {
|
|
1669
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
1670
|
+
return extractStatusMetadataSessions(statusResult);
|
|
1671
|
+
} catch {
|
|
1672
|
+
return [];
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
async function collectMeshViewQueueNodesWithLiveSessions(ctx) {
|
|
1676
|
+
const nodes = await Promise.all(ctx.mesh.nodes.map(async (node) => {
|
|
1677
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
1678
|
+
return liveSessions.length > 0 ? { ...node, sessions: liveSessions } : node;
|
|
1679
|
+
}));
|
|
1680
|
+
return nodes;
|
|
1681
|
+
}
|
|
1065
1682
|
function readNumeric(value, fallback = 0) {
|
|
1066
1683
|
const parsed = Number(value);
|
|
1067
1684
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
@@ -1197,7 +1814,8 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
1197
1814
|
if (isLocalTransport(ctx.transport)) {
|
|
1198
1815
|
return ctx.transport.command(command, args);
|
|
1199
1816
|
}
|
|
1200
|
-
|
|
1817
|
+
const identity = buildNodeMachineIdentity(ctx, node);
|
|
1818
|
+
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})`);
|
|
1201
1819
|
}
|
|
1202
1820
|
function normalizePendingMeshCoordinatorEvents(value) {
|
|
1203
1821
|
const payload = unwrapCommandPayload(value);
|
|
@@ -1215,6 +1833,14 @@ function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
|
1215
1833
|
providerType: readString(metadataEvent.providerType),
|
|
1216
1834
|
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
1217
1835
|
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
1836
|
+
jobId: readString(metadataEvent.jobId),
|
|
1837
|
+
interactionId: readString(metadataEvent.interactionId),
|
|
1838
|
+
status: readString(metadataEvent.status),
|
|
1839
|
+
targetDaemonId: readString(metadataEvent.targetDaemonId),
|
|
1840
|
+
startedAt: readString(metadataEvent.startedAt),
|
|
1841
|
+
completedAt: readString(metadataEvent.completedAt),
|
|
1842
|
+
retryOfJobId: readString(metadataEvent.retryOfJobId),
|
|
1843
|
+
...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
|
|
1218
1844
|
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
1219
1845
|
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
1220
1846
|
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
@@ -1229,10 +1855,16 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
|
1229
1855
|
const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
|
|
1230
1856
|
if (ctx.transport instanceof IpcTransport) {
|
|
1231
1857
|
const surfacedEvents = [];
|
|
1858
|
+
const coordinatorDaemonId = readString(ctx.localDaemonId);
|
|
1859
|
+
const pendingEventArgs = {
|
|
1860
|
+
meshId: ctx.mesh.id,
|
|
1861
|
+
...coordinatorDaemonId ? { coordinatorDaemonId } : {}
|
|
1862
|
+
};
|
|
1232
1863
|
try {
|
|
1233
1864
|
surfacedEvents.push(
|
|
1234
|
-
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events",
|
|
1865
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh)
|
|
1235
1866
|
);
|
|
1867
|
+
surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1236
1868
|
} catch {
|
|
1237
1869
|
}
|
|
1238
1870
|
for (const node of ctx.mesh.nodes) {
|
|
@@ -1240,27 +1872,31 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
|
1240
1872
|
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
1241
1873
|
try {
|
|
1242
1874
|
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
1243
|
-
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events",
|
|
1875
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", pendingEventArgs)
|
|
1244
1876
|
).filter(matchesCurrentMesh);
|
|
1245
1877
|
if (remoteEvents.length === 0) continue;
|
|
1246
1878
|
for (const event of remoteEvents) {
|
|
1247
1879
|
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1248
1880
|
if (!payload.event || !payload.meshId) continue;
|
|
1249
1881
|
await ctx.transport.command("mesh_forward_event", payload);
|
|
1882
|
+
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
1250
1883
|
}
|
|
1251
1884
|
} catch {
|
|
1252
1885
|
}
|
|
1253
1886
|
}
|
|
1254
1887
|
try {
|
|
1255
1888
|
surfacedEvents.push(
|
|
1256
|
-
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events",
|
|
1889
|
+
...normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh)
|
|
1257
1890
|
);
|
|
1891
|
+
surfacedEvents.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1258
1892
|
} catch {
|
|
1259
1893
|
}
|
|
1260
1894
|
return surfacedEvents;
|
|
1261
1895
|
}
|
|
1262
1896
|
if (isLocalTransport(ctx.transport)) {
|
|
1263
|
-
|
|
1897
|
+
const events = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
|
|
1898
|
+
events.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1899
|
+
return events;
|
|
1264
1900
|
}
|
|
1265
1901
|
return [];
|
|
1266
1902
|
}
|
|
@@ -1277,11 +1913,12 @@ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
|
|
|
1277
1913
|
}
|
|
1278
1914
|
var MESH_STATUS_TOOL = {
|
|
1279
1915
|
name: "mesh_status",
|
|
1280
|
-
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.",
|
|
1916
|
+
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.",
|
|
1281
1917
|
inputSchema: {
|
|
1282
1918
|
type: "object",
|
|
1283
1919
|
properties: {
|
|
1284
|
-
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
|
|
1920
|
+
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
|
|
1921
|
+
includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." }
|
|
1285
1922
|
}
|
|
1286
1923
|
}
|
|
1287
1924
|
};
|
|
@@ -1301,14 +1938,22 @@ var MESH_ENQUEUE_TASK_TOOL = {
|
|
|
1301
1938
|
inputSchema: {
|
|
1302
1939
|
type: "object",
|
|
1303
1940
|
properties: {
|
|
1304
|
-
message: { type: "string", description: "The task instruction for the agent." }
|
|
1941
|
+
message: { type: "string", description: "The task instruction for the agent." },
|
|
1942
|
+
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." },
|
|
1943
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
|
|
1944
|
+
requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
|
|
1945
|
+
required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." },
|
|
1946
|
+
depends_on: { type: "array", items: { type: "string" }, description: "Task ids that must complete before this task becomes claimable. Cycles are rejected at enqueue." },
|
|
1947
|
+
dependsOn: { type: "array", items: { type: "string" }, description: "CamelCase alias for depends_on." },
|
|
1948
|
+
mission_id: { type: "string", description: "Mission this task belongs to (mesh_mission record id)." },
|
|
1949
|
+
missionId: { type: "string", description: "CamelCase alias for mission_id." }
|
|
1305
1950
|
},
|
|
1306
1951
|
required: ["message"]
|
|
1307
1952
|
}
|
|
1308
1953
|
};
|
|
1309
1954
|
var MESH_VIEW_QUEUE_TOOL = {
|
|
1310
1955
|
name: "mesh_view_queue",
|
|
1311
|
-
description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records.",
|
|
1956
|
+
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.",
|
|
1312
1957
|
inputSchema: {
|
|
1313
1958
|
type: "object",
|
|
1314
1959
|
properties: {
|
|
@@ -1339,7 +1984,7 @@ var MESH_QUEUE_CANCEL_TOOL = {
|
|
|
1339
1984
|
};
|
|
1340
1985
|
var MESH_QUEUE_REQUEUE_TOOL = {
|
|
1341
1986
|
name: "mesh_queue_requeue",
|
|
1342
|
-
description: "Return a mesh queue task to pending for retry. By default clears stale assigned owner and target session so another live session can claim it.",
|
|
1987
|
+
description: "Return a mesh queue task to pending for retry. By default clears stale assigned owner and target session so another live session can claim it. When the task has exceeded its retry cap it is auto-failed instead; use force=true to override.",
|
|
1343
1988
|
inputSchema: {
|
|
1344
1989
|
type: "object",
|
|
1345
1990
|
properties: {
|
|
@@ -1348,7 +1993,8 @@ var MESH_QUEUE_REQUEUE_TOOL = {
|
|
|
1348
1993
|
target_node_id: { type: "string", description: "Optional replacement target node ID." },
|
|
1349
1994
|
target_session_id: { type: "string", description: "Optional replacement target runtime session ID." },
|
|
1350
1995
|
clear_target_node: { type: "boolean", description: "When true, remove any existing target node constraint." },
|
|
1351
|
-
keep_target_session: { type: "boolean", description: "When true, preserve an existing target session if target_session_id is not provided. Defaults false to avoid stale session targets." }
|
|
1996
|
+
keep_target_session: { type: "boolean", description: "When true, preserve an existing target session if target_session_id is not provided. Defaults false to avoid stale session targets." },
|
|
1997
|
+
force: { type: "boolean", description: "When true, bypass the retry cap and requeue even if maxRetries has been exceeded. Use only for explicit operator recovery." }
|
|
1352
1998
|
},
|
|
1353
1999
|
required: ["task_id"]
|
|
1354
2000
|
}
|
|
@@ -1361,7 +2007,9 @@ var MESH_SEND_TASK_TOOL = {
|
|
|
1361
2007
|
properties: {
|
|
1362
2008
|
node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
|
|
1363
2009
|
session_id: { type: "string", description: "Agent session ID on the target node." },
|
|
1364
|
-
message: { type: "string", description: "Natural-language task to send to the agent." }
|
|
2010
|
+
message: { type: "string", description: "Natural-language task to send to the agent." },
|
|
2011
|
+
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." },
|
|
2012
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." }
|
|
1365
2013
|
},
|
|
1366
2014
|
required: ["node_id", "session_id", "message"]
|
|
1367
2015
|
}
|
|
@@ -1419,6 +2067,21 @@ var MESH_GIT_STATUS_TOOL = {
|
|
|
1419
2067
|
required: ["node_id"]
|
|
1420
2068
|
}
|
|
1421
2069
|
};
|
|
2070
|
+
var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
2071
|
+
name: "mesh_fast_forward_node",
|
|
2072
|
+
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.",
|
|
2073
|
+
inputSchema: {
|
|
2074
|
+
type: "object",
|
|
2075
|
+
properties: {
|
|
2076
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
2077
|
+
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
2078
|
+
execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
|
|
2079
|
+
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
2080
|
+
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." }
|
|
2081
|
+
},
|
|
2082
|
+
required: ["node_id"]
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
1422
2085
|
var MESH_CHECKPOINT_TOOL = {
|
|
1423
2086
|
name: "mesh_checkpoint",
|
|
1424
2087
|
description: "Create a git checkpoint (commit) on a mesh node workspace.",
|
|
@@ -1431,6 +2094,20 @@ var MESH_CHECKPOINT_TOOL = {
|
|
|
1431
2094
|
required: ["node_id", "message"]
|
|
1432
2095
|
}
|
|
1433
2096
|
};
|
|
2097
|
+
var MESH_MISSION_UPSERT_TOOL = {
|
|
2098
|
+
name: "mesh_mission_upsert",
|
|
2099
|
+
description: "Create or update a persistent mission record so the plan survives coordinator restarts. Create a mission before enqueueing a multi-task batch, attach tasks via mesh_enqueue_task mission_id, and update status to completed/abandoned when the outcome is decided. Progress is derived from task statuses \u2014 there is no separate progress field.",
|
|
2100
|
+
inputSchema: {
|
|
2101
|
+
type: "object",
|
|
2102
|
+
properties: {
|
|
2103
|
+
mission_id: { type: "string", description: "Mission id to update. Omit to create a new mission." },
|
|
2104
|
+
title: { type: "string", description: "Short mission title." },
|
|
2105
|
+
goal: { type: "string", description: "Free-text mission goal/definition of done." },
|
|
2106
|
+
status: { type: "string", enum: ["active", "paused", "completed", "abandoned"], description: "Mission lifecycle status. Defaults to active on create." }
|
|
2107
|
+
},
|
|
2108
|
+
required: ["title"]
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
1434
2111
|
var MESH_APPROVE_TOOL = {
|
|
1435
2112
|
name: "mesh_approve",
|
|
1436
2113
|
description: "Approve or reject a pending action on a delegated agent session.",
|
|
@@ -1502,7 +2179,7 @@ var MESH_TASK_HISTORY_TOOL = {
|
|
|
1502
2179
|
type: "object",
|
|
1503
2180
|
properties: {
|
|
1504
2181
|
tail: { type: "number", description: "Number of recent entries to return (default: 20)." },
|
|
1505
|
-
kind: { type: "string", description: "Filter by entry kind: task_dispatched, task_completed, task_failed, task_stalled, session_launched, checkpoint_created, node_cloned, node_removed." }
|
|
2182
|
+
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." }
|
|
1506
2183
|
}
|
|
1507
2184
|
}
|
|
1508
2185
|
};
|
|
@@ -1522,7 +2199,7 @@ var MESH_RECONCILE_LEDGER_TOOL = {
|
|
|
1522
2199
|
};
|
|
1523
2200
|
var MESH_REFINE_NODE_TOOL = {
|
|
1524
2201
|
name: "mesh_refine_node",
|
|
1525
|
-
description: "The Refinery:
|
|
2202
|
+
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.",
|
|
1526
2203
|
inputSchema: {
|
|
1527
2204
|
type: "object",
|
|
1528
2205
|
properties: {
|
|
@@ -1531,6 +2208,54 @@ var MESH_REFINE_NODE_TOOL = {
|
|
|
1531
2208
|
required: ["node_id"]
|
|
1532
2209
|
}
|
|
1533
2210
|
};
|
|
2211
|
+
var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
|
|
2212
|
+
name: "mesh_refine_config_schema",
|
|
2213
|
+
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.",
|
|
2214
|
+
inputSchema: { type: "object", properties: {} }
|
|
2215
|
+
};
|
|
2216
|
+
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
2217
|
+
name: "mesh_validate_refine_config",
|
|
2218
|
+
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
2219
|
+
inputSchema: {
|
|
2220
|
+
type: "object",
|
|
2221
|
+
properties: {
|
|
2222
|
+
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
2223
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
|
|
2228
|
+
name: "mesh_suggest_refine_config",
|
|
2229
|
+
description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
|
|
2230
|
+
inputSchema: {
|
|
2231
|
+
type: "object",
|
|
2232
|
+
properties: {
|
|
2233
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
var MESH_REFINE_PLAN_TOOL = {
|
|
2238
|
+
name: "mesh_refine_plan",
|
|
2239
|
+
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.",
|
|
2240
|
+
inputSchema: {
|
|
2241
|
+
type: "object",
|
|
2242
|
+
properties: {
|
|
2243
|
+
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
2244
|
+
},
|
|
2245
|
+
required: ["node_id"]
|
|
2246
|
+
}
|
|
2247
|
+
};
|
|
2248
|
+
var MESH_REVIEW_INBOX_TOOL = {
|
|
2249
|
+
name: "mesh_review_inbox",
|
|
2250
|
+
description: "List local worktree nodes that need human review: merge candidates (pushed feature branches ready to merge) and Refinery-blocked review results. Returns evidence summaries, diff stats vs. the default branch, and suggested actions (Refine / Requeue / Dismiss). Remote nodes are excluded in M4.0.",
|
|
2251
|
+
inputSchema: {
|
|
2252
|
+
type: "object",
|
|
2253
|
+
properties: {
|
|
2254
|
+
mesh_id: { type: "string", description: "Mesh ID (optional \u2014 inferred from active mesh if omitted)." }
|
|
2255
|
+
},
|
|
2256
|
+
required: []
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
1534
2259
|
var ALL_MESH_TOOLS = [
|
|
1535
2260
|
MESH_STATUS_TOOL,
|
|
1536
2261
|
MESH_LIST_NODES_TOOL,
|
|
@@ -1543,24 +2268,34 @@ var ALL_MESH_TOOLS = [
|
|
|
1543
2268
|
MESH_READ_DEBUG_TOOL,
|
|
1544
2269
|
MESH_LAUNCH_SESSION_TOOL,
|
|
1545
2270
|
MESH_GIT_STATUS_TOOL,
|
|
2271
|
+
MESH_FAST_FORWARD_NODE_TOOL,
|
|
1546
2272
|
MESH_CHECKPOINT_TOOL,
|
|
1547
2273
|
MESH_APPROVE_TOOL,
|
|
1548
2274
|
MESH_CLONE_NODE_TOOL,
|
|
1549
2275
|
MESH_REMOVE_NODE_TOOL,
|
|
1550
2276
|
MESH_REFINE_NODE_TOOL,
|
|
2277
|
+
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
2278
|
+
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
2279
|
+
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
2280
|
+
MESH_REFINE_PLAN_TOOL,
|
|
1551
2281
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1552
2282
|
MESH_TASK_HISTORY_TOOL,
|
|
1553
|
-
MESH_RECONCILE_LEDGER_TOOL
|
|
2283
|
+
MESH_RECONCILE_LEDGER_TOOL,
|
|
2284
|
+
MESH_MISSION_UPSERT_TOOL,
|
|
2285
|
+
MESH_REVIEW_INBOX_TOOL
|
|
1554
2286
|
];
|
|
1555
|
-
async function meshStatus(ctx) {
|
|
2287
|
+
async function meshStatus(ctx, args = {}) {
|
|
2288
|
+
const rateResult = (0, import_daemon_core.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
|
|
1556
2289
|
await refreshMeshFromDaemon(ctx);
|
|
1557
2290
|
const { mesh, transport } = ctx;
|
|
1558
|
-
|
|
1559
|
-
const
|
|
1560
|
-
for (const node of mesh.nodes) {
|
|
2291
|
+
let ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
2292
|
+
const results = await Promise.all(mesh.nodes.map(async (node) => {
|
|
1561
2293
|
const entry = {
|
|
1562
2294
|
nodeId: node.id,
|
|
1563
2295
|
workspace: node.workspace,
|
|
2296
|
+
machine: buildNodeMachineIdentity(ctx, node),
|
|
2297
|
+
daemonId: readNodeDaemonId(node),
|
|
2298
|
+
machineId: readNodeMachineId(node),
|
|
1564
2299
|
...getNodeLaunchReadiness(node)
|
|
1565
2300
|
};
|
|
1566
2301
|
try {
|
|
@@ -1570,6 +2305,7 @@ async function meshStatus(ctx) {
|
|
|
1570
2305
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1571
2306
|
const dirty = isGitStatusDirty(status);
|
|
1572
2307
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
2308
|
+
assignFullGitSnapshot(entry, status);
|
|
1573
2309
|
entry.branch = status?.branch;
|
|
1574
2310
|
entry.isDirty = dirty;
|
|
1575
2311
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1591,6 +2327,7 @@ async function meshStatus(ctx) {
|
|
|
1591
2327
|
const uncommittedChanges = countUncommittedChanges(status);
|
|
1592
2328
|
const dirty = isGitStatusDirty(status);
|
|
1593
2329
|
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
2330
|
+
assignFullGitSnapshot(entry, status);
|
|
1594
2331
|
entry.branch = status?.branch;
|
|
1595
2332
|
entry.isDirty = dirty;
|
|
1596
2333
|
entry.uncommittedChanges = uncommittedChanges;
|
|
@@ -1626,7 +2363,7 @@ async function meshStatus(ctx) {
|
|
|
1626
2363
|
if (recoveryContext.consecutiveNodeFailures > 0) {
|
|
1627
2364
|
entry.recoveryHints = {
|
|
1628
2365
|
consecutiveFailures: recoveryContext.consecutiveNodeFailures,
|
|
1629
|
-
lastTaskMessage: recoveryContext.lastTaskMessage,
|
|
2366
|
+
lastTaskMessage: typeof recoveryContext.lastTaskMessage === "string" ? recoveryContext.lastTaskMessage.slice(0, 100) + (recoveryContext.lastTaskMessage.length > 100 ? "\u2026" : "") : recoveryContext.lastTaskMessage,
|
|
1630
2367
|
advice: recoveryContext.advice,
|
|
1631
2368
|
retryRecommended: recoveryContext.retryRecommended
|
|
1632
2369
|
};
|
|
@@ -1661,12 +2398,60 @@ async function meshStatus(ctx) {
|
|
|
1661
2398
|
nextStepHints.push(`Consider reassigning work to a different node.`);
|
|
1662
2399
|
}
|
|
1663
2400
|
}
|
|
1664
|
-
if (nextStepHints.length > 0) {
|
|
1665
|
-
entry.nextStepHints = nextStepHints;
|
|
1666
|
-
}
|
|
1667
|
-
const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
|
|
1668
|
-
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
1669
|
-
|
|
2401
|
+
if (nextStepHints.length > 0) {
|
|
2402
|
+
entry.nextStepHints = nextStepHints;
|
|
2403
|
+
}
|
|
2404
|
+
const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
|
|
2405
|
+
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
2406
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
2407
|
+
if (liveSessions.length > 0) {
|
|
2408
|
+
entry.sessions = liveSessions.map((s) => {
|
|
2409
|
+
const coordinatorMeshId = typeof s.coordinator?.meshId === "string" ? s.coordinator.meshId : void 0;
|
|
2410
|
+
const isSelfCoordinator = coordinatorMeshId === mesh.id;
|
|
2411
|
+
return {
|
|
2412
|
+
id: s.instanceId ?? s.id ?? s.sessionId,
|
|
2413
|
+
status: s.status ?? s.lifecycle ?? s.state,
|
|
2414
|
+
providerType: s.providerType ?? s.cliType ?? s.type,
|
|
2415
|
+
...s.activeChat?.status ? { chatStatus: s.activeChat.status } : {},
|
|
2416
|
+
...isSelfCoordinator ? { isSelfCoordinator: true, role: "coordinator" } : {}
|
|
2417
|
+
};
|
|
2418
|
+
}).filter((s) => s.id);
|
|
2419
|
+
}
|
|
2420
|
+
return entry;
|
|
2421
|
+
}));
|
|
2422
|
+
let ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
|
|
2423
|
+
let directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(mesh.id);
|
|
2424
|
+
const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, results, directDispatches, ledgerEntries);
|
|
2425
|
+
if (directReconciliation.reconciled > 0) {
|
|
2426
|
+
ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
|
|
2427
|
+
directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(mesh.id);
|
|
2428
|
+
ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
2429
|
+
}
|
|
2430
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2431
|
+
meshId: mesh.id,
|
|
2432
|
+
queue: (0, import_daemon_core.getQueue)(mesh.id),
|
|
2433
|
+
ledgerEntries,
|
|
2434
|
+
directDispatches,
|
|
2435
|
+
nodes: results
|
|
2436
|
+
});
|
|
2437
|
+
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
2438
|
+
const staleDirectWorkSummary = (0, import_daemon_core.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
|
|
2439
|
+
note: activeWorkEvidence.staleDirectWorkNote,
|
|
2440
|
+
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."
|
|
2441
|
+
});
|
|
2442
|
+
const coordinatorSessions = [];
|
|
2443
|
+
for (const nodeEntry of results) {
|
|
2444
|
+
const sessions = Array.isArray(nodeEntry.sessions) ? nodeEntry.sessions : [];
|
|
2445
|
+
for (const s of sessions) {
|
|
2446
|
+
if (s?.isSelfCoordinator === true && s.id) {
|
|
2447
|
+
coordinatorSessions.push({
|
|
2448
|
+
nodeId: nodeEntry.nodeId,
|
|
2449
|
+
sessionId: s.id,
|
|
2450
|
+
providerType: s.providerType,
|
|
2451
|
+
status: s.status
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
1670
2455
|
}
|
|
1671
2456
|
const response = {
|
|
1672
2457
|
meshId: mesh.id,
|
|
@@ -1677,17 +2462,55 @@ async function meshStatus(ctx) {
|
|
|
1677
2462
|
sourceOfTruth: {
|
|
1678
2463
|
membership: "coordinator_daemon_live_mesh",
|
|
1679
2464
|
currentStatus: "live_git_and_session_probes",
|
|
2465
|
+
activeWork: "mesh_queue_file_and_local_ledger",
|
|
1680
2466
|
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
1681
2467
|
},
|
|
1682
2468
|
nodes: results,
|
|
1683
|
-
|
|
2469
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2470
|
+
staleDirectWorkSummary,
|
|
2471
|
+
...args.includeStaleDirectWorkDetails === true ? { staleDirectWork: activeWorkEvidence.staleDirectWork } : {},
|
|
2472
|
+
// terminalDirectWork is historical (completed/failed direct dispatches) — opt-in only.
|
|
2473
|
+
...args.includeTerminalDirectWork === true ? { terminalDirectWork: activeWorkEvidence.terminalDirectWork } : {},
|
|
2474
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
2475
|
+
...pollingGuidance ? { pollingGuidance } : {},
|
|
2476
|
+
...rateResult.rateLimitExceeded ? { pollingRateAdvisory: { type: "rate_limit_exceeded", tool: "mesh_status", callsInWindow: rateResult.callsInWindow, message: rateResult.advisory } } : {},
|
|
2477
|
+
branchConvergenceSummary: summarizeBranchConvergence(results),
|
|
2478
|
+
...coordinatorSessions.length > 0 ? {
|
|
2479
|
+
coordinatorSessions,
|
|
2480
|
+
selfIdentification: {
|
|
2481
|
+
meshId: mesh.id,
|
|
2482
|
+
coordinatorSessions,
|
|
2483
|
+
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."
|
|
2484
|
+
}
|
|
2485
|
+
} : {}
|
|
1684
2486
|
};
|
|
1685
2487
|
try {
|
|
1686
2488
|
response.ledgerSummary = ledgerSummary;
|
|
1687
2489
|
} catch {
|
|
1688
2490
|
}
|
|
2491
|
+
try {
|
|
2492
|
+
const missions = (0, import_daemon_core.getActiveMeshMissionSummaries)(mesh.id);
|
|
2493
|
+
if (missions.length > 0) {
|
|
2494
|
+
response.missions = missions.map((mission) => {
|
|
2495
|
+
try {
|
|
2496
|
+
return { ...mission, stats: (0, import_daemon_core.computeMeshMissionStats)(mesh.id, mission.id) };
|
|
2497
|
+
} catch {
|
|
2498
|
+
return mission;
|
|
2499
|
+
}
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
} catch {
|
|
2503
|
+
}
|
|
1689
2504
|
try {
|
|
1690
2505
|
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
2506
|
+
const asyncRefineJobs = (0, import_daemon_core.buildMeshAsyncRefineJobs)({
|
|
2507
|
+
meshId: mesh.id,
|
|
2508
|
+
ledgerEntries,
|
|
2509
|
+
pendingEvents
|
|
2510
|
+
});
|
|
2511
|
+
if (asyncRefineJobs.length > 0) {
|
|
2512
|
+
response.asyncRefineJobs = asyncRefineJobs;
|
|
2513
|
+
}
|
|
1691
2514
|
if (pendingEvents.length > 0) {
|
|
1692
2515
|
response.pendingCoordinatorEvents = pendingEvents;
|
|
1693
2516
|
}
|
|
@@ -1697,12 +2520,31 @@ async function meshStatus(ctx) {
|
|
|
1697
2520
|
}
|
|
1698
2521
|
async function meshTaskHistory(ctx, args) {
|
|
1699
2522
|
const { mesh } = ctx;
|
|
1700
|
-
await drainCoordinatorPendingEvents(ctx);
|
|
2523
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1701
2524
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
1702
2525
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
1703
|
-
const
|
|
2526
|
+
const rawEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
2527
|
+
const entries = rawEntries.map((e) => ({
|
|
2528
|
+
...e,
|
|
2529
|
+
payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
|
|
2530
|
+
}));
|
|
1704
2531
|
const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
1705
|
-
|
|
2532
|
+
let taskStats;
|
|
2533
|
+
try {
|
|
2534
|
+
const taskIds = [...new Set(rawEntries.map((e) => typeof e.payload?.taskId === "string" ? e.payload.taskId : "").filter(Boolean))];
|
|
2535
|
+
if (taskIds.length > 0) {
|
|
2536
|
+
const stats = (0, import_daemon_core.computeMeshTaskStats)(mesh.id, { taskIds });
|
|
2537
|
+
if (stats.length > 0) taskStats = stats;
|
|
2538
|
+
}
|
|
2539
|
+
} catch {
|
|
2540
|
+
}
|
|
2541
|
+
return JSON.stringify({
|
|
2542
|
+
meshId: mesh.id,
|
|
2543
|
+
entries,
|
|
2544
|
+
summary,
|
|
2545
|
+
...taskStats ? { taskStats } : {},
|
|
2546
|
+
...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
|
|
2547
|
+
}, null, 2);
|
|
1706
2548
|
}
|
|
1707
2549
|
async function meshReconcileLedger(ctx, args) {
|
|
1708
2550
|
await refreshMeshFromDaemon(ctx);
|
|
@@ -1719,7 +2561,7 @@ async function meshReconcileLedger(ctx, args) {
|
|
|
1719
2561
|
for (const node of nodes) {
|
|
1720
2562
|
try {
|
|
1721
2563
|
if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
|
|
1722
|
-
const slice2 = (0, import_daemon_core.
|
|
2564
|
+
const slice2 = (0, import_daemon_core.readLedgerSliceFromStore)(ctx.mesh.id, queryArgs);
|
|
1723
2565
|
replicas.push((0, import_daemon_core.buildMeshLedgerReplicaEvidence)({
|
|
1724
2566
|
nodeId: node.id,
|
|
1725
2567
|
daemonId: node.daemonId,
|
|
@@ -1792,6 +2634,9 @@ async function meshListNodes(ctx) {
|
|
|
1792
2634
|
nodeId: n.id,
|
|
1793
2635
|
workspace: n.workspace,
|
|
1794
2636
|
repoRoot: n.repoRoot,
|
|
2637
|
+
daemonId: readNodeDaemonId(n),
|
|
2638
|
+
machineId: readNodeMachineId(n),
|
|
2639
|
+
machine: buildNodeMachineIdentity(ctx, n),
|
|
1795
2640
|
isLocalWorktree: n.isLocalWorktree,
|
|
1796
2641
|
policy: n.policy,
|
|
1797
2642
|
relatedRepos: readRelatedRepos(n),
|
|
@@ -1800,58 +2645,168 @@ async function meshListNodes(ctx) {
|
|
|
1800
2645
|
}))
|
|
1801
2646
|
}, null, 2);
|
|
1802
2647
|
}
|
|
2648
|
+
async function meshMissionUpsert(ctx, args) {
|
|
2649
|
+
try {
|
|
2650
|
+
const mission = (0, import_daemon_core.upsertMeshMission)(ctx.mesh.id, {
|
|
2651
|
+
id: readString(args.mission_id) || readString(args.missionId) || void 0,
|
|
2652
|
+
title: args.title,
|
|
2653
|
+
goal: typeof args.goal === "string" ? args.goal : void 0,
|
|
2654
|
+
status: readString(args.status) || void 0
|
|
2655
|
+
});
|
|
2656
|
+
return JSON.stringify({
|
|
2657
|
+
success: true,
|
|
2658
|
+
mission,
|
|
2659
|
+
nextAction: "Attach tasks with mesh_enqueue_task mission_id and depends_on. mesh_status shows live task aggregates for this mission."
|
|
2660
|
+
});
|
|
2661
|
+
} catch (e) {
|
|
2662
|
+
const message = e?.message || String(e);
|
|
2663
|
+
const code = message.includes("mission_title_required") ? "mission_title_required" : message.includes("invalid_mission_status") ? "invalid_mission_status" : void 0;
|
|
2664
|
+
return JSON.stringify({ success: false, ...code ? { code } : {}, error: message });
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
1803
2667
|
async function meshEnqueueTask(ctx, args) {
|
|
2668
|
+
const taskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2669
|
+
const requiredTags = (0, import_daemon_core.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
|
|
2670
|
+
const dependsOn = Array.isArray(args.dependsOn) ? args.dependsOn : Array.isArray(args.depends_on) ? args.depends_on : void 0;
|
|
2671
|
+
const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
|
|
1804
2672
|
try {
|
|
1805
|
-
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message);
|
|
2673
|
+
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags, dependsOn, missionId });
|
|
1806
2674
|
if (isLocalTransport(ctx.transport) && !(ctx.transport instanceof IpcTransport)) {
|
|
1807
|
-
|
|
2675
|
+
const queueTrigger = await triggerMeshQueueAndReport(ctx);
|
|
2676
|
+
return JSON.stringify({
|
|
2677
|
+
success: true,
|
|
2678
|
+
source: "queue",
|
|
2679
|
+
taskId: task.id,
|
|
2680
|
+
status: task.status,
|
|
2681
|
+
taskMode: task.taskMode,
|
|
2682
|
+
requiredTags: task.requiredTags,
|
|
2683
|
+
queueTrigger,
|
|
2684
|
+
...buildQueueTriggerGuidance(queueTrigger)
|
|
1808
2685
|
});
|
|
1809
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
1810
2686
|
}
|
|
1811
2687
|
if (ctx.transport instanceof IpcTransport) {
|
|
1812
|
-
|
|
1813
|
-
});
|
|
2688
|
+
const queueTrigger = await triggerMeshQueueAndReport(ctx);
|
|
1814
2689
|
const dispatchPromises = [];
|
|
1815
2690
|
for (const node of ctx.mesh.nodes) {
|
|
1816
2691
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1817
2692
|
if (isLocalNode || !node.daemonId) continue;
|
|
2693
|
+
if (!(0, import_daemon_core.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core.buildMeshNodeCapabilityTags)(node))) continue;
|
|
1818
2694
|
dispatchPromises.push(
|
|
1819
2695
|
ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
|
|
1820
2696
|
if (result.success) {
|
|
1821
2697
|
try {
|
|
2698
|
+
const providerType = result.providerType;
|
|
2699
|
+
const descriptor = summarizeTaskMessage(args.message);
|
|
1822
2700
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1823
2701
|
kind: "task_dispatched",
|
|
1824
2702
|
nodeId: node.id,
|
|
1825
2703
|
sessionId: result.sessionId,
|
|
1826
|
-
|
|
2704
|
+
providerType,
|
|
2705
|
+
payload: {
|
|
2706
|
+
source: "queue",
|
|
2707
|
+
via: "p2p_direct",
|
|
2708
|
+
taskId: task.id,
|
|
2709
|
+
message: args.message,
|
|
2710
|
+
taskTitle: descriptor.taskTitle,
|
|
2711
|
+
taskSummary: descriptor.taskSummary,
|
|
2712
|
+
...task.taskMode ? { taskMode: task.taskMode } : {},
|
|
2713
|
+
...providerType ? { providerType } : {},
|
|
2714
|
+
targetSessionId: result.sessionId
|
|
2715
|
+
}
|
|
1827
2716
|
});
|
|
1828
2717
|
} catch {
|
|
1829
2718
|
}
|
|
1830
2719
|
}
|
|
1831
|
-
}).catch(() => {
|
|
2720
|
+
}).catch((err) => {
|
|
2721
|
+
try {
|
|
2722
|
+
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2723
|
+
kind: "p2p_dispatch_failed",
|
|
2724
|
+
nodeId: node.id,
|
|
2725
|
+
payload: {
|
|
2726
|
+
source: "queue",
|
|
2727
|
+
via: "p2p_direct",
|
|
2728
|
+
taskId: task.id,
|
|
2729
|
+
error: err?.message || String(err),
|
|
2730
|
+
dispatchFailedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2731
|
+
}
|
|
2732
|
+
});
|
|
2733
|
+
} catch {
|
|
2734
|
+
}
|
|
1832
2735
|
})
|
|
1833
2736
|
);
|
|
1834
2737
|
}
|
|
1835
2738
|
Promise.all(dispatchPromises).catch(() => {
|
|
1836
2739
|
});
|
|
1837
|
-
return JSON.stringify({
|
|
2740
|
+
return JSON.stringify({
|
|
2741
|
+
success: true,
|
|
2742
|
+
source: "queue",
|
|
2743
|
+
taskId: task.id,
|
|
2744
|
+
status: task.status,
|
|
2745
|
+
taskMode: task.taskMode,
|
|
2746
|
+
requiredTags: task.requiredTags,
|
|
2747
|
+
queueTrigger,
|
|
2748
|
+
...buildQueueTriggerGuidance(queueTrigger)
|
|
2749
|
+
});
|
|
1838
2750
|
}
|
|
1839
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
2751
|
+
return JSON.stringify({ success: true, source: "queue", taskId: task.id, status: task.status, taskMode: task.taskMode, requiredTags: task.requiredTags });
|
|
1840
2752
|
} catch (e) {
|
|
1841
|
-
|
|
2753
|
+
const message = e?.message || String(e);
|
|
2754
|
+
if (message.includes("live_debug_readonly_guardrail_violation")) {
|
|
2755
|
+
return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
|
|
2756
|
+
}
|
|
2757
|
+
if (message.includes("dependency_cycle_detected")) {
|
|
2758
|
+
return JSON.stringify({ success: false, code: "dependency_cycle_detected", dependsOn, error: message });
|
|
2759
|
+
}
|
|
2760
|
+
return JSON.stringify({ success: false, error: message });
|
|
1842
2761
|
}
|
|
1843
2762
|
}
|
|
1844
2763
|
async function meshViewQueue(ctx, args) {
|
|
2764
|
+
const rateResult = (0, import_daemon_core.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
|
|
1845
2765
|
try {
|
|
2766
|
+
await refreshMeshFromDaemon(ctx);
|
|
1846
2767
|
const statusFilter = sanitizeQueueStatusFilter(args.status);
|
|
1847
2768
|
const view = normalizeQueueViewMode(args.view);
|
|
1848
|
-
const
|
|
2769
|
+
const rawQueue = (0, import_daemon_core.getQueue)(ctx.mesh.id);
|
|
2770
|
+
const statusById = new Map(rawQueue.map((task) => [task.id, task.status]));
|
|
2771
|
+
const withDependencies = rawQueue.map((task) => {
|
|
2772
|
+
if (!Array.isArray(task.dependsOn) || task.dependsOn.length === 0) return task;
|
|
2773
|
+
const depState = (0, import_daemon_core.describeTaskDependencyState)(task, statusById);
|
|
2774
|
+
return { ...task, ...depState };
|
|
2775
|
+
});
|
|
2776
|
+
const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness(withDependencies, ctx.mesh));
|
|
1849
2777
|
const queue = filterQueueForView(fullQueue, view, statusFilter);
|
|
1850
2778
|
const summary = buildQueueStatusSummary(fullQueue);
|
|
1851
2779
|
const visibleSummary = buildQueueStatusSummary(queue);
|
|
1852
2780
|
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
2781
|
+
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
2782
|
+
let ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
2783
|
+
let directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
|
|
2784
|
+
const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries);
|
|
2785
|
+
if (directReconciliation.reconciled > 0) {
|
|
2786
|
+
ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
2787
|
+
directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
|
|
2788
|
+
}
|
|
2789
|
+
(0, import_daemon_core.markStaleDirectDispatches)(ctx.mesh.id);
|
|
2790
|
+
directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
|
|
2791
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2792
|
+
meshId: ctx.mesh.id,
|
|
2793
|
+
queue: fullQueue,
|
|
2794
|
+
ledgerEntries,
|
|
2795
|
+
// Always pass MeshRuntimeStore records (may be empty). buildMeshActiveWork uses them for local
|
|
2796
|
+
// dispatches and falls through to ledger scan for remote P2P dispatches not in MeshRuntimeStore.
|
|
2797
|
+
directDispatches,
|
|
2798
|
+
nodes: liveNodes
|
|
2799
|
+
});
|
|
2800
|
+
const recentDispatchFailures = ledgerEntries.filter((e) => e.kind === "p2p_dispatch_failed").slice(-20).map((e) => ({
|
|
2801
|
+
nodeId: e.nodeId,
|
|
2802
|
+
taskId: e.payload?.taskId,
|
|
2803
|
+
error: e.payload?.error,
|
|
2804
|
+
via: e.payload?.via,
|
|
2805
|
+
failedAt: e.payload?.dispatchFailedAt || e.timestamp
|
|
2806
|
+
}));
|
|
1853
2807
|
const staleAssignedTasks = maintenance.staleAssignedTasks || [];
|
|
1854
2808
|
const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
2809
|
+
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
1855
2810
|
return JSON.stringify({
|
|
1856
2811
|
success: true,
|
|
1857
2812
|
sourceOfTruth: {
|
|
@@ -1866,21 +2821,30 @@ async function meshViewQueue(ctx, args) {
|
|
|
1866
2821
|
filtered: Boolean(statusFilter?.length) || view !== "all"
|
|
1867
2822
|
},
|
|
1868
2823
|
queue,
|
|
1869
|
-
|
|
1870
|
-
|
|
2824
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2825
|
+
staleDirectWork: activeWorkEvidence.staleDirectWork,
|
|
2826
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
2827
|
+
...pollingGuidance ? { pollingGuidance } : {},
|
|
2828
|
+
...rateResult.rateLimitExceeded ? { pollingRateAdvisory: { type: "rate_limit_exceeded", tool: "mesh_view_queue", callsInWindow: rateResult.callsInWindow, message: rateResult.advisory } } : {},
|
|
1871
2829
|
summary,
|
|
2830
|
+
visibleSummary,
|
|
1872
2831
|
activeCounts: summary.activeCounts,
|
|
1873
2832
|
historicalCounts: summary.historicalCounts,
|
|
1874
|
-
activeCount: summary.activeCount,
|
|
1875
|
-
historicalCount: summary.historicalCount,
|
|
1876
2833
|
visibleActiveCounts: visibleSummary.activeCounts,
|
|
1877
2834
|
visibleHistoricalCounts: visibleSummary.historicalCounts,
|
|
2835
|
+
activeCount: summary.activeCount,
|
|
2836
|
+
historicalCount: summary.historicalCount,
|
|
1878
2837
|
visibleActiveCount: visibleSummary.activeCount,
|
|
1879
2838
|
visibleHistoricalCount: visibleSummary.historicalCount,
|
|
1880
2839
|
staleAssignedTasks,
|
|
1881
2840
|
staleAssignedCount: maintenance.staleAssignedCount,
|
|
1882
2841
|
queueMaintenance: maintenance,
|
|
1883
2842
|
cleanupDryRun: maintenance,
|
|
2843
|
+
...recentDispatchFailures.length > 0 ? {
|
|
2844
|
+
recentDispatchFailures,
|
|
2845
|
+
dispatchFailureCount: recentDispatchFailures.length,
|
|
2846
|
+
dispatchFailureNote: "Remote P2P dispatch attempts that failed. Affected tasks remain pending and may require mesh_queue_requeue if no idle session picks them up."
|
|
2847
|
+
} : {},
|
|
1884
2848
|
...view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status)) ? {
|
|
1885
2849
|
activeQueue: queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")))
|
|
1886
2850
|
} : {},
|
|
@@ -1900,6 +2864,10 @@ async function meshQueueCancel(ctx, args) {
|
|
|
1900
2864
|
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
1901
2865
|
const task = (0, import_daemon_core.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
|
|
1902
2866
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
2867
|
+
if (isLocalTransport(ctx.transport)) {
|
|
2868
|
+
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
1903
2871
|
return JSON.stringify({ success: true, task }, null, 2);
|
|
1904
2872
|
} catch (e) {
|
|
1905
2873
|
return JSON.stringify({ success: false, error: e.message });
|
|
@@ -1917,9 +2885,19 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
1917
2885
|
targetNodeId,
|
|
1918
2886
|
targetSessionId,
|
|
1919
2887
|
clearTargetNode: args.clear_target_node === true || args.clearTargetNode === true,
|
|
1920
|
-
clearTargetSession: targetSessionId ? false : !keepTargetSession
|
|
2888
|
+
clearTargetSession: targetSessionId ? false : !keepTargetSession,
|
|
2889
|
+
force: args.force === true
|
|
1921
2890
|
});
|
|
1922
2891
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
2892
|
+
if (task.status === "failed" && task.cancelReason?.startsWith("max_retries_exceeded")) {
|
|
2893
|
+
return JSON.stringify({
|
|
2894
|
+
success: false,
|
|
2895
|
+
code: "max_retries_exceeded",
|
|
2896
|
+
error: task.cancelReason,
|
|
2897
|
+
task,
|
|
2898
|
+
hint: "Use force=true to bypass the retry cap for explicit operator recovery."
|
|
2899
|
+
}, null, 2);
|
|
2900
|
+
}
|
|
1923
2901
|
if (isLocalTransport(ctx.transport)) {
|
|
1924
2902
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
1925
2903
|
});
|
|
@@ -1930,10 +2908,60 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
1930
2908
|
}
|
|
1931
2909
|
}
|
|
1932
2910
|
async function meshSendTask(ctx, args) {
|
|
2911
|
+
const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2912
|
+
const modeValidation = (0, import_daemon_core.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
|
|
2913
|
+
if (!modeValidation.valid) {
|
|
2914
|
+
return JSON.stringify({
|
|
2915
|
+
success: false,
|
|
2916
|
+
code: "live_debug_readonly_guardrail_violation",
|
|
2917
|
+
taskMode: modeValidation.taskMode || requestedTaskMode,
|
|
2918
|
+
violations: modeValidation.violations,
|
|
2919
|
+
allowedOperations: modeValidation.allowedOperations,
|
|
2920
|
+
error: `live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
const taskMode = modeValidation.taskMode;
|
|
1933
2924
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1934
2925
|
if (node.policy?.readOnly) {
|
|
1935
2926
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
1936
2927
|
}
|
|
2928
|
+
let explicitTargetSession;
|
|
2929
|
+
if (args.session_id && isWorkerTaskMode(taskMode) && (ctx.transport instanceof IpcTransport || isLocalTransport(ctx.transport))) {
|
|
2930
|
+
try {
|
|
2931
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2932
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
2933
|
+
explicitTargetSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
2934
|
+
if (explicitTargetSession && isMeshCoordinatorSessionRecord(explicitTargetSession)) {
|
|
2935
|
+
return JSON.stringify({
|
|
2936
|
+
success: false,
|
|
2937
|
+
recoverable: true,
|
|
2938
|
+
code: "mesh_target_session_is_coordinator",
|
|
2939
|
+
reason: "mesh_target_session_is_coordinator",
|
|
2940
|
+
nodeId: args.node_id,
|
|
2941
|
+
sessionId: args.session_id,
|
|
2942
|
+
taskMode: taskMode || "unspecified",
|
|
2943
|
+
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.`,
|
|
2944
|
+
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.`
|
|
2945
|
+
});
|
|
2946
|
+
}
|
|
2947
|
+
if (explicitTargetSession && isUnmanagedSessionRecord(explicitTargetSession)) {
|
|
2948
|
+
return JSON.stringify({
|
|
2949
|
+
success: false,
|
|
2950
|
+
recoverable: true,
|
|
2951
|
+
code: "mesh_target_session_unmanaged",
|
|
2952
|
+
reason: "mesh_target_session_unmanaged",
|
|
2953
|
+
nodeId: args.node_id,
|
|
2954
|
+
sessionId: args.session_id,
|
|
2955
|
+
taskMode: taskMode || "unspecified",
|
|
2956
|
+
unsafeTranscriptAlias: true,
|
|
2957
|
+
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.`,
|
|
2958
|
+
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.`
|
|
2959
|
+
});
|
|
2960
|
+
}
|
|
2961
|
+
} catch {
|
|
2962
|
+
explicitTargetSession = void 0;
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
1937
2965
|
const duplicate = hasRecentDuplicateDispatch(ctx, args);
|
|
1938
2966
|
if (duplicate.duplicate) {
|
|
1939
2967
|
return JSON.stringify({
|
|
@@ -1957,43 +2985,76 @@ async function meshSendTask(ctx, args) {
|
|
|
1957
2985
|
const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
|
|
1958
2986
|
meshId: ctx.mesh.id,
|
|
1959
2987
|
message: args.message,
|
|
1960
|
-
targetNodeId: args.node_id
|
|
2988
|
+
targetNodeId: args.node_id,
|
|
2989
|
+
...taskMode ? { taskMode } : {}
|
|
1961
2990
|
});
|
|
1962
2991
|
return JSON.stringify(res);
|
|
1963
2992
|
}
|
|
1964
2993
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1965
2994
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1966
|
-
const cached =
|
|
2995
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id || ""));
|
|
2996
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
1967
2997
|
const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
|
|
1968
2998
|
session_id: args.session_id,
|
|
1969
2999
|
message: args.message,
|
|
1970
|
-
providerType: cached?.providerType
|
|
3000
|
+
providerType: cached?.providerType,
|
|
3001
|
+
verifiedSession: explicitTargetSession,
|
|
3002
|
+
meshContext: {
|
|
3003
|
+
meshId: ctx.mesh.id,
|
|
3004
|
+
nodeId: args.node_id,
|
|
3005
|
+
taskId
|
|
3006
|
+
}
|
|
1971
3007
|
});
|
|
1972
3008
|
if (result2.success) {
|
|
1973
3009
|
const dispatchedSessionId = args.session_id || result2.sessionId;
|
|
3010
|
+
const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1974
3011
|
try {
|
|
3012
|
+
const providerType = result2.providerType || cached?.providerType;
|
|
1975
3013
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1976
3014
|
kind: "task_dispatched",
|
|
1977
3015
|
nodeId: args.node_id,
|
|
1978
3016
|
sessionId: dispatchedSessionId,
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
3017
|
+
providerType,
|
|
3018
|
+
payload: buildDirectTaskPayload(args.message, "p2p_direct", {
|
|
3019
|
+
taskId,
|
|
3020
|
+
taskMode,
|
|
3021
|
+
providerType,
|
|
3022
|
+
targetSessionId: dispatchedSessionId
|
|
3023
|
+
})
|
|
3024
|
+
});
|
|
3025
|
+
(0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
|
|
3026
|
+
taskId,
|
|
3027
|
+
nodeId: args.node_id,
|
|
3028
|
+
sessionId: dispatchedSessionId,
|
|
3029
|
+
providerType: providerType || void 0,
|
|
3030
|
+
message: args.message,
|
|
3031
|
+
taskMode: taskMode || void 0,
|
|
3032
|
+
via: "p2p_direct",
|
|
3033
|
+
dispatchedAt
|
|
1984
3034
|
});
|
|
1985
3035
|
} catch {
|
|
1986
3036
|
}
|
|
1987
3037
|
}
|
|
1988
|
-
return JSON.stringify({
|
|
3038
|
+
return JSON.stringify({
|
|
3039
|
+
...result2,
|
|
3040
|
+
nodeId: args.node_id,
|
|
3041
|
+
sessionId: result2.success ? args.session_id || result2.sessionId : args.session_id,
|
|
3042
|
+
...result2.success ? { source: "direct", taskId } : {},
|
|
3043
|
+
taskMode,
|
|
3044
|
+
...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
|
|
3045
|
+
dispatched: result2.success === true
|
|
3046
|
+
});
|
|
1989
3047
|
}
|
|
1990
3048
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
1991
|
-
const cached =
|
|
3049
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
|
|
1992
3050
|
let resolvedProviderType = cached?.providerType || "";
|
|
1993
3051
|
if (!resolvedProviderType) {
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
3052
|
+
let explicitSession = explicitTargetSession;
|
|
3053
|
+
if (!explicitSession) {
|
|
3054
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
3055
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
3056
|
+
explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
3057
|
+
}
|
|
1997
3058
|
if (!explicitSession) {
|
|
1998
3059
|
return JSON.stringify({
|
|
1999
3060
|
success: false,
|
|
@@ -2008,11 +3069,40 @@ async function meshSendTask(ctx, args) {
|
|
|
2008
3069
|
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.`
|
|
2009
3070
|
});
|
|
2010
3071
|
}
|
|
3072
|
+
if (isMeshCoordinatorSessionRecord(explicitSession)) {
|
|
3073
|
+
return JSON.stringify({
|
|
3074
|
+
success: false,
|
|
3075
|
+
recoverable: true,
|
|
3076
|
+
code: "mesh_target_session_is_coordinator",
|
|
3077
|
+
reason: "mesh_target_session_is_coordinator",
|
|
3078
|
+
nodeId: args.node_id,
|
|
3079
|
+
sessionId: args.session_id,
|
|
3080
|
+
taskMode: taskMode || "unspecified",
|
|
3081
|
+
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.`,
|
|
3082
|
+
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.`
|
|
3083
|
+
});
|
|
3084
|
+
}
|
|
3085
|
+
if (isUnmanagedSessionRecord(explicitSession)) {
|
|
3086
|
+
return JSON.stringify({
|
|
3087
|
+
success: false,
|
|
3088
|
+
recoverable: true,
|
|
3089
|
+
code: "mesh_target_session_unmanaged",
|
|
3090
|
+
reason: "mesh_target_session_unmanaged",
|
|
3091
|
+
nodeId: args.node_id,
|
|
3092
|
+
sessionId: args.session_id,
|
|
3093
|
+
taskMode: taskMode || "unspecified",
|
|
3094
|
+
unsafeTranscriptAlias: true,
|
|
3095
|
+
unsafeDelegateTarget: true,
|
|
3096
|
+
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.`,
|
|
3097
|
+
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.`
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
2011
3100
|
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2012
3101
|
if (resolvedProviderType) {
|
|
2013
3102
|
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
|
|
2014
3103
|
providerType: resolvedProviderType,
|
|
2015
|
-
providerSessionId: readString(explicitSession?.providerSessionId) || void 0
|
|
3104
|
+
providerSessionId: readString(explicitSession?.providerSessionId) || void 0,
|
|
3105
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
2016
3106
|
});
|
|
2017
3107
|
}
|
|
2018
3108
|
}
|
|
@@ -2030,17 +3120,56 @@ async function meshSendTask(ctx, args) {
|
|
|
2030
3120
|
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.`
|
|
2031
3121
|
});
|
|
2032
3122
|
}
|
|
3123
|
+
if (explicitTargetSession && !isIdleSessionRecord(explicitTargetSession) && !isTerminalSessionRecord(explicitTargetSession)) {
|
|
3124
|
+
const sessionStatus = typeof explicitTargetSession?.status === "string" ? explicitTargetSession.status : "unknown";
|
|
3125
|
+
const { createSessionDelivery: createDelivery, resolveDeliveryDecision } = await import("@adhdev/daemon-core");
|
|
3126
|
+
const policyResult = resolveDeliveryDecision(sessionStatus, { kind: "task" });
|
|
3127
|
+
if (policyResult.decision === "queued") {
|
|
3128
|
+
const delivery = createDelivery({
|
|
3129
|
+
meshId: ctx.mesh.id,
|
|
3130
|
+
nodeId: args.node_id,
|
|
3131
|
+
sessionId: args.session_id,
|
|
3132
|
+
providerType: resolvedProviderType,
|
|
3133
|
+
kind: "task",
|
|
3134
|
+
message: args.message,
|
|
3135
|
+
status: "queued"
|
|
3136
|
+
});
|
|
3137
|
+
return JSON.stringify({
|
|
3138
|
+
success: true,
|
|
3139
|
+
dispatched: false,
|
|
3140
|
+
decision: "queued_delivery",
|
|
3141
|
+
deliveryId: delivery.id,
|
|
3142
|
+
reason: policyResult.reason,
|
|
3143
|
+
nodeId: args.node_id,
|
|
3144
|
+
sessionId: args.session_id,
|
|
3145
|
+
sessionStatus,
|
|
3146
|
+
taskMode: taskMode || void 0,
|
|
3147
|
+
message: policyResult.message,
|
|
3148
|
+
nextAction: `Use mesh_status to watch for session idle transition, or use mesh_enqueue_task for queue-based assignment. Check deliveryId '${delivery.id}' to track queued delivery.`
|
|
3149
|
+
});
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
const sessionWasIdle = explicitTargetSession ? isIdleSessionRecord(explicitTargetSession) : false;
|
|
3153
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
3154
|
+
const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2033
3155
|
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
2034
3156
|
targetSessionId: args.session_id,
|
|
2035
3157
|
agentType: resolvedProviderType,
|
|
2036
3158
|
cliType: resolvedProviderType,
|
|
2037
3159
|
providerType: resolvedProviderType,
|
|
2038
3160
|
action: "send_chat",
|
|
2039
|
-
message: args.message
|
|
3161
|
+
message: args.message,
|
|
3162
|
+
meshContext: {
|
|
3163
|
+
meshId: ctx.mesh.id,
|
|
3164
|
+
nodeId: args.node_id,
|
|
3165
|
+
taskId
|
|
3166
|
+
}
|
|
2040
3167
|
});
|
|
2041
3168
|
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
2042
3169
|
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
3170
|
+
const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
|
|
2043
3171
|
return JSON.stringify({
|
|
3172
|
+
...source && typeof source === "object" ? source : {},
|
|
2044
3173
|
success: false,
|
|
2045
3174
|
nodeId: args.node_id,
|
|
2046
3175
|
sessionId: args.session_id,
|
|
@@ -2053,22 +3182,78 @@ async function meshSendTask(ctx, args) {
|
|
|
2053
3182
|
nodeId: args.node_id,
|
|
2054
3183
|
sessionId: args.session_id,
|
|
2055
3184
|
providerType: resolvedProviderType,
|
|
2056
|
-
payload:
|
|
3185
|
+
payload: buildDirectTaskPayload(args.message, "local_direct", {
|
|
3186
|
+
taskId,
|
|
3187
|
+
taskMode,
|
|
3188
|
+
providerType: resolvedProviderType,
|
|
3189
|
+
targetSessionId: args.session_id,
|
|
3190
|
+
dispatchedToIdleSession: sessionWasIdle
|
|
3191
|
+
})
|
|
3192
|
+
});
|
|
3193
|
+
} catch {
|
|
3194
|
+
}
|
|
3195
|
+
(0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
|
|
3196
|
+
taskId,
|
|
3197
|
+
nodeId: args.node_id,
|
|
3198
|
+
sessionId: args.session_id,
|
|
3199
|
+
providerType: resolvedProviderType || void 0,
|
|
3200
|
+
message: args.message,
|
|
3201
|
+
taskMode: taskMode || void 0,
|
|
3202
|
+
via: "local_direct",
|
|
3203
|
+
dispatchedToIdleSession: sessionWasIdle,
|
|
3204
|
+
dispatchedAt
|
|
3205
|
+
});
|
|
3206
|
+
let deliveryId;
|
|
3207
|
+
try {
|
|
3208
|
+
const { createSessionDelivery: createDelivery } = await import("@adhdev/daemon-core");
|
|
3209
|
+
const delivery = createDelivery({
|
|
3210
|
+
meshId: ctx.mesh.id,
|
|
3211
|
+
nodeId: args.node_id,
|
|
3212
|
+
sessionId: args.session_id,
|
|
3213
|
+
providerType: resolvedProviderType || void 0,
|
|
3214
|
+
taskId,
|
|
3215
|
+
kind: "task",
|
|
3216
|
+
message: args.message,
|
|
3217
|
+
status: sessionWasIdle ? "delivered" : "delivering"
|
|
2057
3218
|
});
|
|
3219
|
+
deliveryId = delivery.id;
|
|
2058
3220
|
} catch {
|
|
2059
3221
|
}
|
|
2060
|
-
return JSON.stringify({
|
|
3222
|
+
return JSON.stringify({
|
|
3223
|
+
success: true,
|
|
3224
|
+
dispatched: true,
|
|
3225
|
+
decision: "immediate",
|
|
3226
|
+
source: "direct",
|
|
3227
|
+
taskId,
|
|
3228
|
+
deliveryId,
|
|
3229
|
+
taskMode,
|
|
3230
|
+
providerType: resolvedProviderType,
|
|
3231
|
+
nodeId: args.node_id,
|
|
3232
|
+
sessionId: args.session_id,
|
|
3233
|
+
...sessionWasIdle ? {
|
|
3234
|
+
dispatchAcknowledgementRisk: true,
|
|
3235
|
+
dispatchAcknowledgementRiskReason: "session_was_idle_at_dispatch",
|
|
3236
|
+
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.`
|
|
3237
|
+
} : {}
|
|
3238
|
+
});
|
|
2061
3239
|
}
|
|
2062
3240
|
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, {
|
|
2063
3241
|
targetNodeId: args.node_id,
|
|
2064
|
-
targetSessionId: args.session_id
|
|
3242
|
+
targetSessionId: args.session_id,
|
|
3243
|
+
taskMode
|
|
2065
3244
|
});
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
3245
|
+
const queueTrigger = isLocalTransport(ctx.transport) || ctx.transport instanceof IpcTransport ? await triggerMeshQueueAndReport(ctx) : void 0;
|
|
3246
|
+
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId) : [];
|
|
3247
|
+
const result = {
|
|
3248
|
+
success: true,
|
|
3249
|
+
source: "queue",
|
|
3250
|
+
nodeId: args.node_id,
|
|
3251
|
+
taskId: task.id,
|
|
3252
|
+
status: task.status,
|
|
3253
|
+
taskMode: task.taskMode,
|
|
3254
|
+
queueTrigger,
|
|
3255
|
+
...buildQueueTriggerGuidance(queueTrigger)
|
|
3256
|
+
};
|
|
2072
3257
|
if (pendingEvents.length > 0) {
|
|
2073
3258
|
result.pendingCoordinatorEvents = pendingEvents;
|
|
2074
3259
|
}
|
|
@@ -2092,7 +3277,7 @@ async function meshReadChat(ctx, args) {
|
|
|
2092
3277
|
await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
|
|
2093
3278
|
}
|
|
2094
3279
|
if (isLocalTransport(ctx.transport)) {
|
|
2095
|
-
const cached =
|
|
3280
|
+
const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
|
|
2096
3281
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
2097
3282
|
const result = await commandForNode(ctx, node, "read_chat", {
|
|
2098
3283
|
sessionId: args.session_id,
|
|
@@ -2107,7 +3292,8 @@ async function meshReadChat(ctx, args) {
|
|
|
2107
3292
|
toolName: "mesh_read_chat",
|
|
2108
3293
|
completionCallbackExpected: true
|
|
2109
3294
|
});
|
|
2110
|
-
|
|
3295
|
+
const useCompact = args.compact !== false;
|
|
3296
|
+
if (useCompact) {
|
|
2111
3297
|
const compactPayload = compactChatPayload(payload, {
|
|
2112
3298
|
nodeId: args.node_id,
|
|
2113
3299
|
sessionId: args.session_id,
|
|
@@ -2138,7 +3324,7 @@ async function meshReadChat(ctx, args) {
|
|
|
2138
3324
|
async function meshReadDebug(ctx, args) {
|
|
2139
3325
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2140
3326
|
if (isLocalTransport(ctx.transport)) {
|
|
2141
|
-
const cached =
|
|
3327
|
+
const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
|
|
2142
3328
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
2143
3329
|
const delivery = args.delivery === "inline" ? void 0 : "daemon_file";
|
|
2144
3330
|
const result = await commandForNode(ctx, node, "get_chat_debug_bundle", {
|
|
@@ -2169,6 +3355,8 @@ async function meshReadDebug(ctx, args) {
|
|
|
2169
3355
|
}
|
|
2170
3356
|
async function meshLaunchSession(ctx, args) {
|
|
2171
3357
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3358
|
+
const bootstrapBlock = getWorktreeBootstrapLaunchBlock(node, ctx.mesh.policy);
|
|
3359
|
+
if (bootstrapBlock) return JSON.stringify(bootstrapBlock, null, 2);
|
|
2172
3360
|
if (isLocalTransport(ctx.transport)) {
|
|
2173
3361
|
let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
|
|
2174
3362
|
if (!resolvedProviderType) {
|
|
@@ -2203,6 +3391,9 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2203
3391
|
cliType: resolvedProviderType,
|
|
2204
3392
|
dir: node.workspace,
|
|
2205
3393
|
settings: {
|
|
3394
|
+
// Worker launch envelope (A5): structured metadata so worker sessions
|
|
3395
|
+
// know their role and can route completion events back correctly.
|
|
3396
|
+
role: "worker",
|
|
2206
3397
|
meshNodeFor: ctx.mesh.id,
|
|
2207
3398
|
meshNodeId: args.node_id,
|
|
2208
3399
|
spawnedSessionVisibility,
|
|
@@ -2224,7 +3415,8 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2224
3415
|
if (runtimeSessionId) {
|
|
2225
3416
|
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, runtimeSessionId), {
|
|
2226
3417
|
providerType: resolvedProviderType,
|
|
2227
|
-
...providerSessionId ? { providerSessionId } : {}
|
|
3418
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
3419
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
2228
3420
|
});
|
|
2229
3421
|
}
|
|
2230
3422
|
try {
|
|
@@ -2237,17 +3429,13 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2237
3429
|
});
|
|
2238
3430
|
} catch {
|
|
2239
3431
|
}
|
|
2240
|
-
|
|
2241
|
-
ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2242
|
-
});
|
|
2243
|
-
} else if (isLocalTransport(ctx.transport)) {
|
|
2244
|
-
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2245
|
-
});
|
|
2246
|
-
}
|
|
3432
|
+
const queueTrigger = await triggerMeshQueueAndReport(ctx, node, { localNode: isLocalNode });
|
|
2247
3433
|
return JSON.stringify({
|
|
2248
3434
|
...launchPayload,
|
|
2249
3435
|
resolvedProviderType,
|
|
2250
|
-
...providerSessionId ? { providerSessionId } : {}
|
|
3436
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
3437
|
+
queueTrigger,
|
|
3438
|
+
...buildQueueTriggerGuidance(queueTrigger)
|
|
2251
3439
|
}, null, 2);
|
|
2252
3440
|
} else if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
2253
3441
|
let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
|
|
@@ -2344,6 +3532,51 @@ async function meshGitStatus(ctx, args) {
|
|
|
2344
3532
|
}, null, 2);
|
|
2345
3533
|
}
|
|
2346
3534
|
}
|
|
3535
|
+
async function meshFastForwardNode(ctx, args) {
|
|
3536
|
+
await refreshMeshFromDaemon(ctx);
|
|
3537
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3538
|
+
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
3539
|
+
if (node.policy?.readOnly) {
|
|
3540
|
+
return JSON.stringify({
|
|
3541
|
+
success: false,
|
|
3542
|
+
code: "node_read_only",
|
|
3543
|
+
nodeId: args.node_id,
|
|
3544
|
+
workspace: node.workspace,
|
|
3545
|
+
allowed: false,
|
|
3546
|
+
willRun: false,
|
|
3547
|
+
executed: false,
|
|
3548
|
+
blockingReasons: ["node_read_only"]
|
|
3549
|
+
}, null, 2);
|
|
3550
|
+
}
|
|
3551
|
+
try {
|
|
3552
|
+
const dryRun = args.dry_run === true || args.execute !== true;
|
|
3553
|
+
const result = await commandForNode(ctx, node, "fast_forward_mesh_node", {
|
|
3554
|
+
meshId: ctx.mesh.id,
|
|
3555
|
+
nodeId: node.id,
|
|
3556
|
+
workspace: node.workspace,
|
|
3557
|
+
branch: typeof args.branch === "string" ? args.branch : void 0,
|
|
3558
|
+
execute: args.execute === true && args.dry_run !== true,
|
|
3559
|
+
dryRun,
|
|
3560
|
+
updateSubmodules: args.update_submodules === true,
|
|
3561
|
+
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
3562
|
+
});
|
|
3563
|
+
return JSON.stringify(unwrapCommandPayload(result), null, 2);
|
|
3564
|
+
} catch (e) {
|
|
3565
|
+
const failure = buildCoordinatorP2pRelayFailure(e, {
|
|
3566
|
+
command: "fast_forward_mesh_node",
|
|
3567
|
+
targetDaemonId: node.daemonId,
|
|
3568
|
+
nodeId: args.node_id
|
|
3569
|
+
});
|
|
3570
|
+
return JSON.stringify({
|
|
3571
|
+
...failure,
|
|
3572
|
+
workspace: node.workspace,
|
|
3573
|
+
allowed: false,
|
|
3574
|
+
willRun: false,
|
|
3575
|
+
executed: false,
|
|
3576
|
+
blockingReasons: [failure.code || "mesh_fast_forward_unavailable"]
|
|
3577
|
+
}, null, 2);
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
2347
3580
|
async function meshCheckpoint(ctx, args) {
|
|
2348
3581
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2349
3582
|
if (node.policy?.readOnly) {
|
|
@@ -2359,7 +3592,13 @@ async function meshCheckpoint(ctx, args) {
|
|
|
2359
3592
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2360
3593
|
kind: "checkpoint_created",
|
|
2361
3594
|
nodeId: args.node_id,
|
|
2362
|
-
payload: {
|
|
3595
|
+
payload: {
|
|
3596
|
+
message: args.message,
|
|
3597
|
+
commit: result?.checkpoint?.commit,
|
|
3598
|
+
outcome: result?.checkpoint?.status || (result?.checkpoint?.noop ? "skipped" : void 0),
|
|
3599
|
+
noop: result?.checkpoint?.noop === true,
|
|
3600
|
+
reason: result?.checkpoint?.reason
|
|
3601
|
+
}
|
|
2363
3602
|
});
|
|
2364
3603
|
} catch {
|
|
2365
3604
|
}
|
|
@@ -2375,7 +3614,13 @@ async function meshCheckpoint(ctx, args) {
|
|
|
2375
3614
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2376
3615
|
kind: "checkpoint_created",
|
|
2377
3616
|
nodeId: args.node_id,
|
|
2378
|
-
payload: {
|
|
3617
|
+
payload: {
|
|
3618
|
+
message: args.message,
|
|
3619
|
+
commit: res?.checkpoint?.commit,
|
|
3620
|
+
outcome: res?.checkpoint?.status || (res?.checkpoint?.noop ? "skipped" : void 0),
|
|
3621
|
+
noop: res?.checkpoint?.noop === true,
|
|
3622
|
+
reason: res?.checkpoint?.reason
|
|
3623
|
+
}
|
|
2379
3624
|
});
|
|
2380
3625
|
} catch {
|
|
2381
3626
|
}
|
|
@@ -2390,7 +3635,7 @@ async function meshCheckpoint(ctx, args) {
|
|
|
2390
3635
|
async function meshApprove(ctx, args) {
|
|
2391
3636
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2392
3637
|
if (isLocalTransport(ctx.transport)) {
|
|
2393
|
-
const cached =
|
|
3638
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
|
|
2394
3639
|
const providerSessionId = cached?.providerSessionId;
|
|
2395
3640
|
const result = await commandForNode(ctx, node, "resolve_action", {
|
|
2396
3641
|
sessionId: args.session_id,
|
|
@@ -2543,6 +3788,43 @@ async function meshRemoveNode(ctx, args) {
|
|
|
2543
3788
|
return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
|
|
2544
3789
|
}
|
|
2545
3790
|
}
|
|
3791
|
+
function resolveRefineConfigNode(ctx, nodeId) {
|
|
3792
|
+
if (nodeId) return findNode(ctx.mesh, nodeId);
|
|
3793
|
+
const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
|
|
3794
|
+
if (!node) throw new Error("No mesh node with a workspace is available");
|
|
3795
|
+
return node;
|
|
3796
|
+
}
|
|
3797
|
+
async function meshRefineConfigSchema(ctx) {
|
|
3798
|
+
const node = resolveRefineConfigNode(ctx);
|
|
3799
|
+
const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
|
|
3800
|
+
return JSON.stringify(result, null, 2);
|
|
3801
|
+
}
|
|
3802
|
+
async function meshValidateRefineConfig(ctx, args) {
|
|
3803
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
3804
|
+
const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
|
|
3805
|
+
workspace: node.workspace,
|
|
3806
|
+
inlineMesh: ctx.mesh,
|
|
3807
|
+
...args.config ? { config: args.config } : {}
|
|
3808
|
+
});
|
|
3809
|
+
return JSON.stringify(result, null, 2);
|
|
3810
|
+
}
|
|
3811
|
+
async function meshSuggestRefineConfig(ctx, args) {
|
|
3812
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
3813
|
+
const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
|
|
3814
|
+
workspace: node.workspace,
|
|
3815
|
+
inlineMesh: ctx.mesh
|
|
3816
|
+
});
|
|
3817
|
+
return JSON.stringify(result, null, 2);
|
|
3818
|
+
}
|
|
3819
|
+
async function meshRefinePlan(ctx, args) {
|
|
3820
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3821
|
+
const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
|
|
3822
|
+
meshId: ctx.mesh.id,
|
|
3823
|
+
nodeId: args.node_id,
|
|
3824
|
+
inlineMesh: ctx.mesh
|
|
3825
|
+
});
|
|
3826
|
+
return JSON.stringify(result, null, 2);
|
|
3827
|
+
}
|
|
2546
3828
|
async function meshRefineNode(ctx, args) {
|
|
2547
3829
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2548
3830
|
if (isLocalTransport(ctx.transport)) {
|
|
@@ -2551,7 +3833,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2551
3833
|
nodeId: args.node_id,
|
|
2552
3834
|
inlineMesh: ctx.mesh
|
|
2553
3835
|
});
|
|
2554
|
-
if (result?.success && result.removeResult?.removed !== false) {
|
|
3836
|
+
if (result?.success && result.async !== true && result.removeResult?.removed !== false) {
|
|
2555
3837
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2556
3838
|
if (idx >= 0) {
|
|
2557
3839
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2566,7 +3848,7 @@ async function meshRefineNode(ctx, args) {
|
|
|
2566
3848
|
nodeId: args.node_id,
|
|
2567
3849
|
inlineMesh: ctx.mesh
|
|
2568
3850
|
});
|
|
2569
|
-
if (res?.success && res.removeResult?.removed !== false) {
|
|
3851
|
+
if (res?.success && res.async !== true && res.removeResult?.removed !== false) {
|
|
2570
3852
|
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2571
3853
|
if (idx >= 0) {
|
|
2572
3854
|
ctx.mesh.nodes.splice(idx, 1);
|
|
@@ -2581,6 +3863,18 @@ async function meshRefineNode(ctx, args) {
|
|
|
2581
3863
|
return JSON.stringify({ error: "Cloud mesh refine_node requires node daemonId" });
|
|
2582
3864
|
}
|
|
2583
3865
|
}
|
|
3866
|
+
async function meshReviewInbox(ctx, args = {}) {
|
|
3867
|
+
if (!isLocalTransport(ctx.transport)) {
|
|
3868
|
+
return JSON.stringify({ error: "mesh_review_inbox requires a local daemon transport (M4.0 scope: local nodes only)" });
|
|
3869
|
+
}
|
|
3870
|
+
await refreshMeshFromDaemon(ctx);
|
|
3871
|
+
const meshId = (args.mesh_id ?? ctx.mesh.id).trim();
|
|
3872
|
+
const result = await commandForNode(ctx, ctx.mesh.nodes[0], "get_mesh_review_inbox", {
|
|
3873
|
+
meshId,
|
|
3874
|
+
inlineMesh: ctx.mesh
|
|
3875
|
+
});
|
|
3876
|
+
return JSON.stringify(result, null, 2);
|
|
3877
|
+
}
|
|
2584
3878
|
|
|
2585
3879
|
// src/help.ts
|
|
2586
3880
|
var STANDARD_TOOLS = [
|
|
@@ -2603,13 +3897,13 @@ var STANDARD_TOOLS = [
|
|
|
2603
3897
|
function buildMcpHelpText() {
|
|
2604
3898
|
const meshTools = ALL_MESH_TOOLS.map((tool) => tool.name);
|
|
2605
3899
|
return `
|
|
2606
|
-
|
|
3900
|
+
ADHDev MCP Server
|
|
2607
3901
|
|
|
2608
3902
|
Usage:
|
|
2609
|
-
adhdev
|
|
2610
|
-
adhdev
|
|
2611
|
-
adhdev
|
|
2612
|
-
adhdev-mcp --
|
|
3903
|
+
adhdev mcp Local mode (requires standalone daemon)
|
|
3904
|
+
adhdev mcp --api-key <key> Cloud mode (ADHDev cloud API)
|
|
3905
|
+
adhdev mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
|
|
3906
|
+
adhdev-mcp --help Compatibility bin (same server, legacy package entrypoint)
|
|
2613
3907
|
|
|
2614
3908
|
Options:
|
|
2615
3909
|
--mode <mode> Transport: local, cloud, or ipc
|
|
@@ -2634,6 +3928,7 @@ Mesh tools: ${meshTools.join(", ")}
|
|
|
2634
3928
|
// src/server.ts
|
|
2635
3929
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
2636
3930
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
3931
|
+
var import_node_os = __toESM(require("os"));
|
|
2637
3932
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
2638
3933
|
|
|
2639
3934
|
// src/transports/local.ts
|
|
@@ -3185,11 +4480,17 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
|
|
|
3185
4480
|
}, null, 2);
|
|
3186
4481
|
}
|
|
3187
4482
|
if ((format === "text" || format === void 0) && compact && compactPayload) {
|
|
3188
|
-
const
|
|
4483
|
+
const summaryText = typeof compactPayload.summary === "string" ? compactPayload.summary.trim() : "";
|
|
4484
|
+
const tail = outputMessages.slice(-limit);
|
|
4485
|
+
const lastIndex = tail.length - 1;
|
|
4486
|
+
const lines2 = tail.flatMap((m, idx) => {
|
|
3189
4487
|
const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
|
|
3190
4488
|
const content = messageContent(m);
|
|
4489
|
+
if (idx === lastIndex && (role === "Agent" || m.role === "agent") && summaryText && content.trim() === summaryText) {
|
|
4490
|
+
return [];
|
|
4491
|
+
}
|
|
3191
4492
|
const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
|
|
3192
|
-
return `[${role}] ${truncated}
|
|
4493
|
+
return [`[${role}] ${truncated}`];
|
|
3193
4494
|
});
|
|
3194
4495
|
if (compactPayload.summary) {
|
|
3195
4496
|
const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
|
|
@@ -3304,6 +4605,90 @@ function formatChatDebugResult(result, options) {
|
|
|
3304
4605
|
return JSON.stringify(result, null, 2);
|
|
3305
4606
|
}
|
|
3306
4607
|
|
|
4608
|
+
// src/tools/spec-debug.ts
|
|
4609
|
+
var SPEC_DEBUG_TOOL = {
|
|
4610
|
+
name: "spec_debug",
|
|
4611
|
+
description: "Get current spec state, sections, and state transition history for a spec-driven CLI session (claude-cli, antigravity-cli, etc.). Use to diagnose idle/busy detection issues, inspect section parsing, or verify idle_hold and busy_hold behavior.",
|
|
4612
|
+
inputSchema: {
|
|
4613
|
+
type: "object",
|
|
4614
|
+
properties: {
|
|
4615
|
+
session_id: {
|
|
4616
|
+
type: "string",
|
|
4617
|
+
description: "Target session ID (from list_sessions)."
|
|
4618
|
+
},
|
|
4619
|
+
daemon_id: {
|
|
4620
|
+
type: "string",
|
|
4621
|
+
description: "Daemon ID (cloud mode only). Omit for local mode."
|
|
4622
|
+
},
|
|
4623
|
+
...FORMAT_PROP
|
|
4624
|
+
},
|
|
4625
|
+
required: ["session_id"]
|
|
4626
|
+
}
|
|
4627
|
+
};
|
|
4628
|
+
async function specDebug(transport, args) {
|
|
4629
|
+
const sessionId = typeof args.session_id === "string" ? args.session_id.trim() : "";
|
|
4630
|
+
if (!sessionId) throw new Error("session_id is required");
|
|
4631
|
+
let result;
|
|
4632
|
+
if (isLocalTransport(transport)) {
|
|
4633
|
+
result = await transport.command("get_spec_debug", { targetSessionId: sessionId });
|
|
4634
|
+
} else {
|
|
4635
|
+
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
4636
|
+
const targetId = `${args.daemon_id}:session:${sessionId}`;
|
|
4637
|
+
result = await transport.sendCommand(targetId, "get_spec_debug", { targetSessionId: sessionId });
|
|
4638
|
+
}
|
|
4639
|
+
return formatSpecDebugResult(result, { sessionId, format: args.format });
|
|
4640
|
+
}
|
|
4641
|
+
function formatSpecDebugResult(result, options) {
|
|
4642
|
+
if (!result?.success) {
|
|
4643
|
+
const err = result?.error || "Unknown error";
|
|
4644
|
+
if (options.format === "json") return JSON.stringify({ success: false, error: err }, null, 2);
|
|
4645
|
+
return `Error: ${err}`;
|
|
4646
|
+
}
|
|
4647
|
+
if (options.format === "json") return JSON.stringify(result, null, 2);
|
|
4648
|
+
const snap = result.snapshot;
|
|
4649
|
+
if (!snap) {
|
|
4650
|
+
return [
|
|
4651
|
+
`session_id: ${options.sessionId}`,
|
|
4652
|
+
`provider_type: ${String(result.providerType || "")}`,
|
|
4653
|
+
"is_spec_provider: false",
|
|
4654
|
+
"No spec debug data available (not a spec-driven provider)."
|
|
4655
|
+
].join("\n");
|
|
4656
|
+
}
|
|
4657
|
+
const lines = [];
|
|
4658
|
+
lines.push(`session_id: ${options.sessionId}`);
|
|
4659
|
+
lines.push(`provider_type: ${String(result.providerType || snap.cliType || "")}`);
|
|
4660
|
+
lines.push(`spec_id: ${String(snap.spec_id || "")}`);
|
|
4661
|
+
lines.push(`spec_path: ${String(snap.specPath || "")}`);
|
|
4662
|
+
lines.push(`current_state: ${snap.current_state ? `${snap.current_state.id} (${snap.current_state.label})` : "none"}`);
|
|
4663
|
+
lines.push(`idle_hold_pending: ${String(snap.idleHoldPending ?? false)}`);
|
|
4664
|
+
lines.push(`last_busy_at: ${snap.lastBusyAt ? new Date(snap.lastBusyAt).toISOString() : "never"}`);
|
|
4665
|
+
lines.push(`exited: ${String(snap.exited ?? false)}`);
|
|
4666
|
+
if (snap.current_modal) {
|
|
4667
|
+
lines.push(`current_modal: ${JSON.stringify(snap.current_modal)}`);
|
|
4668
|
+
}
|
|
4669
|
+
if (snap.sections && typeof snap.sections === "object") {
|
|
4670
|
+
lines.push("");
|
|
4671
|
+
lines.push("\u2500\u2500 sections \u2500\u2500");
|
|
4672
|
+
for (const [id, text] of Object.entries(snap.sections)) {
|
|
4673
|
+
const preview = String(text || "").replace(/\n/g, "\u21B5").slice(0, 120);
|
|
4674
|
+
lines.push(` ${id}: ${preview}`);
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
const history = Array.isArray(snap.stateHistory) ? snap.stateHistory : [];
|
|
4678
|
+
if (history.length > 0) {
|
|
4679
|
+
lines.push("");
|
|
4680
|
+
lines.push("\u2500\u2500 state history (newest first) \u2500\u2500");
|
|
4681
|
+
const now = Date.now();
|
|
4682
|
+
for (const entry of [...history].reverse().slice(0, 20)) {
|
|
4683
|
+
const agoMs = now - entry.at;
|
|
4684
|
+
const ago = agoMs < 2e3 ? `${agoMs}ms ago` : `${(agoMs / 1e3).toFixed(1)}s ago`;
|
|
4685
|
+
const dur = entry.durationMs > 0 ? ` held ${entry.durationMs}ms` : "";
|
|
4686
|
+
lines.push(` ${String(entry.stateId).padEnd(18)} ${ago}${dur}`);
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
return lines.join("\n");
|
|
4690
|
+
}
|
|
4691
|
+
|
|
3307
4692
|
// src/tools/send-chat.ts
|
|
3308
4693
|
var SEND_CHAT_TOOL = {
|
|
3309
4694
|
name: "send_chat",
|
|
@@ -4167,6 +5552,7 @@ async function startMcpServer(opts) {
|
|
|
4167
5552
|
requirePreTaskCheckpoint: false,
|
|
4168
5553
|
requirePostTaskCheckpoint: true,
|
|
4169
5554
|
requireApprovalForPush: true,
|
|
5555
|
+
allowAutoPublishSubmoduleMainCommits: false,
|
|
4170
5556
|
requireApprovalForDestructiveGit: true,
|
|
4171
5557
|
dirtyWorkspaceBehavior: "warn",
|
|
4172
5558
|
maxParallelTasks: 2,
|
|
@@ -4223,11 +5609,13 @@ async function startMcpServer(opts) {
|
|
|
4223
5609
|
}
|
|
4224
5610
|
let localDaemonId;
|
|
4225
5611
|
let localMachineId;
|
|
5612
|
+
let coordinatorHostname = import_node_os.default.hostname();
|
|
4226
5613
|
if (transport instanceof LocalTransport || transport instanceof IpcTransport) {
|
|
4227
5614
|
try {
|
|
4228
5615
|
const { loadConfig } = await import("@adhdev/daemon-core");
|
|
4229
5616
|
const cfg = loadConfig();
|
|
4230
|
-
if (cfg.
|
|
5617
|
+
if (cfg.machineId) localMachineId = cfg.machineId;
|
|
5618
|
+
else if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
|
|
4231
5619
|
} catch {
|
|
4232
5620
|
}
|
|
4233
5621
|
}
|
|
@@ -4235,14 +5623,16 @@ async function startMcpServer(opts) {
|
|
|
4235
5623
|
try {
|
|
4236
5624
|
const statusResult = await transport.getStatus();
|
|
4237
5625
|
const instanceId = typeof statusResult?.status?.instanceId === "string" ? statusResult.status.instanceId.trim() : "";
|
|
5626
|
+
const hostname = typeof statusResult?.status?.hostname === "string" ? statusResult.status.hostname.trim() : typeof statusResult?.status?.machine?.hostname === "string" ? statusResult.status.machine.hostname.trim() : "";
|
|
4238
5627
|
if (instanceId) localDaemonId = instanceId;
|
|
5628
|
+
if (hostname) coordinatorHostname = hostname;
|
|
4239
5629
|
} catch {
|
|
4240
5630
|
}
|
|
4241
5631
|
}
|
|
4242
|
-
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {} };
|
|
5632
|
+
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {}, ...coordinatorHostname ? { coordinatorHostname } : {} };
|
|
4243
5633
|
const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
|
|
4244
5634
|
const server2 = new import_server.Server(
|
|
4245
|
-
{ name: "adhdev-mcp-server", version: "0.9.
|
|
5635
|
+
{ name: "adhdev-mcp-server", version: "0.9.82" },
|
|
4246
5636
|
{ capabilities: { tools: {}, resources: {} } }
|
|
4247
5637
|
);
|
|
4248
5638
|
const { ListResourcesRequestSchema, ReadResourceRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
|
|
@@ -4268,7 +5658,7 @@ async function startMcpServer(opts) {
|
|
|
4268
5658
|
let text;
|
|
4269
5659
|
switch (name) {
|
|
4270
5660
|
case "mesh_status":
|
|
4271
|
-
text = await meshStatus(meshCtx);
|
|
5661
|
+
text = await meshStatus(meshCtx, a);
|
|
4272
5662
|
break;
|
|
4273
5663
|
case "mesh_list_nodes":
|
|
4274
5664
|
text = await meshListNodes(meshCtx);
|
|
@@ -4300,6 +5690,9 @@ async function startMcpServer(opts) {
|
|
|
4300
5690
|
case "mesh_git_status":
|
|
4301
5691
|
text = await meshGitStatus(meshCtx, a);
|
|
4302
5692
|
break;
|
|
5693
|
+
case "mesh_fast_forward_node":
|
|
5694
|
+
text = await meshFastForwardNode(meshCtx, a);
|
|
5695
|
+
break;
|
|
4303
5696
|
case "mesh_checkpoint":
|
|
4304
5697
|
text = await meshCheckpoint(meshCtx, a);
|
|
4305
5698
|
break;
|
|
@@ -4315,6 +5708,18 @@ async function startMcpServer(opts) {
|
|
|
4315
5708
|
case "mesh_refine_node":
|
|
4316
5709
|
text = await meshRefineNode(meshCtx, a);
|
|
4317
5710
|
break;
|
|
5711
|
+
case "mesh_refine_config_schema":
|
|
5712
|
+
text = await meshRefineConfigSchema(meshCtx);
|
|
5713
|
+
break;
|
|
5714
|
+
case "mesh_validate_refine_config":
|
|
5715
|
+
text = await meshValidateRefineConfig(meshCtx, a);
|
|
5716
|
+
break;
|
|
5717
|
+
case "mesh_suggest_refine_config":
|
|
5718
|
+
text = await meshSuggestRefineConfig(meshCtx, a);
|
|
5719
|
+
break;
|
|
5720
|
+
case "mesh_refine_plan":
|
|
5721
|
+
text = await meshRefinePlan(meshCtx, a);
|
|
5722
|
+
break;
|
|
4318
5723
|
case "mesh_cleanup_sessions":
|
|
4319
5724
|
text = await meshCleanupSessions(meshCtx, a);
|
|
4320
5725
|
break;
|
|
@@ -4324,6 +5729,12 @@ async function startMcpServer(opts) {
|
|
|
4324
5729
|
case "mesh_reconcile_ledger":
|
|
4325
5730
|
text = await meshReconcileLedger(meshCtx, a);
|
|
4326
5731
|
break;
|
|
5732
|
+
case "mesh_mission_upsert":
|
|
5733
|
+
text = await meshMissionUpsert(meshCtx, a);
|
|
5734
|
+
break;
|
|
5735
|
+
case "mesh_review_inbox":
|
|
5736
|
+
text = await meshReviewInbox(meshCtx, a);
|
|
5737
|
+
break;
|
|
4327
5738
|
default:
|
|
4328
5739
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
4329
5740
|
}
|
|
@@ -4346,6 +5757,7 @@ async function startMcpServer(opts) {
|
|
|
4346
5757
|
CHECK_PENDING_TOOL,
|
|
4347
5758
|
READ_CHAT_TOOL,
|
|
4348
5759
|
READ_CHAT_DEBUG_TOOL,
|
|
5760
|
+
SPEC_DEBUG_TOOL,
|
|
4349
5761
|
SEND_CHAT_TOOL,
|
|
4350
5762
|
APPROVE_TOOL,
|
|
4351
5763
|
GIT_STATUS_TOOL,
|
|
@@ -4381,6 +5793,10 @@ async function startMcpServer(opts) {
|
|
|
4381
5793
|
const text = await readChatDebug(transport, a);
|
|
4382
5794
|
return { content: [{ type: "text", text }] };
|
|
4383
5795
|
}
|
|
5796
|
+
case "spec_debug": {
|
|
5797
|
+
const text = await specDebug(transport, a);
|
|
5798
|
+
return { content: [{ type: "text", text }] };
|
|
5799
|
+
}
|
|
4384
5800
|
case "send_chat": {
|
|
4385
5801
|
const text = await sendChat(transport, { message: a.message, session_id: a.session_id, daemon_id: a.daemon_id });
|
|
4386
5802
|
return { content: [{ type: "text", text }] };
|