@adhdev/daemon-standalone 0.9.82-rc.26 → 0.9.82-rc.261
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 +48515 -20726
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/public/assets/index-C5W61nZC.js +113 -0
- package/public/assets/index-DW-XJSIP.css +1 -0
- package/public/assets/{terminal-CfAvHQvJ.js → terminal-DsCRNxW_.js} +4 -4
- package/public/assets/vendor-BHUMCOj6.js +2768 -0
- package/public/index.html +3 -3
- package/public/snake.html +229 -0
- package/vendor/mcp-server/index.js +2022 -1377
- 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-Ce58Aw2L.js +0 -98
- package/public/assets/index-CiB8XPwO.css +0 -1
- package/public/assets/vendor-CgiI0UIA.js +0 -2745
|
@@ -35,9 +35,128 @@ __export(index_exports, {
|
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
|
|
38
|
+
// src/tools/mesh-tools.ts
|
|
39
|
+
var import_node_crypto = require("crypto");
|
|
40
|
+
|
|
38
41
|
// src/transports/ipc.ts
|
|
39
42
|
var DEFAULT_IPC_PORT = 19222;
|
|
40
43
|
var DEFAULT_IPC_PATH = "/ipc";
|
|
44
|
+
var DEFAULT_IPC_COMMAND_TIMEOUT_MS = 15e3;
|
|
45
|
+
var IPC_COMMAND_TIMEOUTS_MS = {
|
|
46
|
+
mesh_relay_command: 12e4,
|
|
47
|
+
agent_command: 3e4,
|
|
48
|
+
git_status: 45e3,
|
|
49
|
+
git_diff_summary: 45e3,
|
|
50
|
+
fast_forward_mesh_node: 12e4,
|
|
51
|
+
mesh_status: 12e4
|
|
52
|
+
};
|
|
53
|
+
var WS_CONNECTING = 0;
|
|
54
|
+
var WS_OPEN = 1;
|
|
55
|
+
var POOL_IDLE_EVICT_MS = 5 * 6e4;
|
|
56
|
+
var POOL_MAX_AGE_MS = 10 * 6e4;
|
|
57
|
+
var connectionPool = /* @__PURE__ */ new Map();
|
|
58
|
+
function buildRequestId() {
|
|
59
|
+
return `mcp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
60
|
+
}
|
|
61
|
+
function getTimeoutMs(type, nestedCommand) {
|
|
62
|
+
return Math.max(
|
|
63
|
+
IPC_COMMAND_TIMEOUTS_MS[type] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS,
|
|
64
|
+
IPC_COMMAND_TIMEOUTS_MS[nestedCommand] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
function getOrCreateConnection(WebSocketCtor, url) {
|
|
68
|
+
const existing = connectionPool.get(url);
|
|
69
|
+
if (existing) {
|
|
70
|
+
const { readyState } = existing.ws;
|
|
71
|
+
const now2 = Date.now();
|
|
72
|
+
const isAlive = readyState === WS_CONNECTING || readyState === WS_OPEN;
|
|
73
|
+
const isIdle = now2 - existing.lastUsedAt > POOL_IDLE_EVICT_MS && existing.pending.size === 0;
|
|
74
|
+
const isTooOld = now2 - existing.createdAt > POOL_MAX_AGE_MS && existing.pending.size === 0;
|
|
75
|
+
if (isAlive && !isIdle && !isTooOld) {
|
|
76
|
+
return existing;
|
|
77
|
+
}
|
|
78
|
+
if (isAlive && (isIdle || isTooOld)) {
|
|
79
|
+
try {
|
|
80
|
+
existing.ws.close();
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
connectionPool.delete(url);
|
|
84
|
+
}
|
|
85
|
+
connectionPool.delete(url);
|
|
86
|
+
}
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
const conn = {
|
|
89
|
+
ws: new WebSocketCtor(url),
|
|
90
|
+
ready: false,
|
|
91
|
+
commandQueue: [],
|
|
92
|
+
pending: /* @__PURE__ */ new Map(),
|
|
93
|
+
lastUsedAt: now,
|
|
94
|
+
createdAt: now
|
|
95
|
+
};
|
|
96
|
+
connectionPool.set(url, conn);
|
|
97
|
+
const drainQueue = () => {
|
|
98
|
+
conn.ready = true;
|
|
99
|
+
for (const { type, args, requestId } of conn.commandQueue) {
|
|
100
|
+
conn.ws.send(JSON.stringify({ type: "ext:command", payload: { command: type, args, requestId } }));
|
|
101
|
+
}
|
|
102
|
+
conn.commandQueue = [];
|
|
103
|
+
};
|
|
104
|
+
let tornDown = false;
|
|
105
|
+
const teardown = (error) => {
|
|
106
|
+
if (tornDown) return;
|
|
107
|
+
tornDown = true;
|
|
108
|
+
connectionPool.delete(url);
|
|
109
|
+
conn.ready = false;
|
|
110
|
+
for (const [, req] of conn.pending) {
|
|
111
|
+
clearTimeout(req.timer);
|
|
112
|
+
req.reject(error);
|
|
113
|
+
}
|
|
114
|
+
conn.pending.clear();
|
|
115
|
+
conn.commandQueue = [];
|
|
116
|
+
};
|
|
117
|
+
conn.ws.addEventListener("open", () => {
|
|
118
|
+
conn.ws.send(JSON.stringify({
|
|
119
|
+
type: "ext:register",
|
|
120
|
+
payload: {
|
|
121
|
+
ideType: "mcp-server",
|
|
122
|
+
ideVersion: "1.0.0",
|
|
123
|
+
extensionVersion: "1.0.0",
|
|
124
|
+
instanceId: `mcp-server-${process.pid}`,
|
|
125
|
+
machineId: "mcp-server",
|
|
126
|
+
workspaceFolders: []
|
|
127
|
+
}
|
|
128
|
+
}));
|
|
129
|
+
});
|
|
130
|
+
conn.ws.addEventListener("message", (event) => {
|
|
131
|
+
try {
|
|
132
|
+
const raw = typeof event.data === "string" ? event.data : String(event.data);
|
|
133
|
+
const msg = JSON.parse(raw);
|
|
134
|
+
if (msg?.type === "daemon:welcome") {
|
|
135
|
+
drainQueue();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (msg?.type !== "ext:command_result") return;
|
|
139
|
+
const req = conn.pending.get(msg?.payload?.requestId);
|
|
140
|
+
if (!req) return;
|
|
141
|
+
conn.pending.delete(msg.payload.requestId);
|
|
142
|
+
clearTimeout(req.timer);
|
|
143
|
+
const payload = msg.payload;
|
|
144
|
+
if (payload?.success === false) {
|
|
145
|
+
req.reject(new Error(payload.error || "Daemon IPC command failed"));
|
|
146
|
+
} else {
|
|
147
|
+
req.resolve(payload?.result ?? payload);
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
conn.ws.addEventListener("error", () => {
|
|
153
|
+
teardown(new Error(`Cannot connect to daemon IPC at ${url}`));
|
|
154
|
+
});
|
|
155
|
+
conn.ws.addEventListener("close", () => {
|
|
156
|
+
teardown(new Error(`Daemon IPC connection closed: ${url}`));
|
|
157
|
+
});
|
|
158
|
+
return conn;
|
|
159
|
+
}
|
|
41
160
|
var IpcTransport = class {
|
|
42
161
|
port;
|
|
43
162
|
path;
|
|
@@ -66,87 +185,46 @@ 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
|
};
|
|
139
226
|
|
|
140
|
-
// src/transports/mode.ts
|
|
141
|
-
function isLocalTransport(transport) {
|
|
142
|
-
return typeof transport.command === "function";
|
|
143
|
-
}
|
|
144
|
-
|
|
145
227
|
// src/tools/chat-compact.ts
|
|
146
|
-
function isAssistantLike(message) {
|
|
147
|
-
const role = String(message?.role ?? "").toLowerCase();
|
|
148
|
-
return role === "assistant" || role === "agent";
|
|
149
|
-
}
|
|
150
228
|
function messageContent(message) {
|
|
151
229
|
const content = message?.content;
|
|
152
230
|
if (typeof content === "string") return content;
|
|
@@ -165,11 +243,36 @@ function isCoordinatorVisibleMessage(message) {
|
|
|
165
243
|
if (meta?.internal === true || meta?.debug === true || meta?.control === true || meta?.userVisible === false || meta?.user_visible === false) return false;
|
|
166
244
|
return role === "user" || role === "assistant" || role === "agent";
|
|
167
245
|
}
|
|
246
|
+
function summarizeToolMessage(message) {
|
|
247
|
+
if (!message || typeof message !== "object") return null;
|
|
248
|
+
const kind = String(message.kind ?? message.type ?? message.messageKind ?? "").toLowerCase();
|
|
249
|
+
const role = String(message.role ?? "").toLowerCase();
|
|
250
|
+
if (kind === "terminal" || kind === "bash") {
|
|
251
|
+
const cmd = message.command ?? message.cmd ?? message.input ?? messageContent(message);
|
|
252
|
+
const exit = message.exitCode ?? message.exit_code ?? message.code;
|
|
253
|
+
const cmdShort = typeof cmd === "string" ? cmd.split("\n")[0].slice(0, 120) : null;
|
|
254
|
+
if (!cmdShort) return null;
|
|
255
|
+
return exit !== void 0 && exit !== null ? `[Bash] ${cmdShort} \u2192 exit ${exit}` : `[Bash] ${cmdShort}`;
|
|
256
|
+
}
|
|
257
|
+
if (kind === "tool_call" || kind === "tool" || role === "tool") {
|
|
258
|
+
const name = message.name ?? message.toolName ?? message.tool_name ?? message.function?.name;
|
|
259
|
+
if (typeof name === "string" && name.trim()) return `[Tool] ${name.trim()}`;
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
if (kind === "tool_result") {
|
|
263
|
+
const exit = message.exitCode ?? message.exit_code ?? message.code;
|
|
264
|
+
const name = message.name ?? message.toolName ?? message.tool_name;
|
|
265
|
+
const label = typeof name === "string" && name.trim() ? name.trim() : "tool";
|
|
266
|
+
return exit !== void 0 && exit !== null ? `[Tool result: ${label}] exit ${exit}` : null;
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
168
270
|
function buildCompactMessageTail(visibleMessages, opts) {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
271
|
+
const tail = visibleMessages.slice(-opts.limit);
|
|
272
|
+
if (opts.finalAssistant && !tail.includes(opts.finalAssistant)) {
|
|
273
|
+
return [opts.finalAssistant, ...tail];
|
|
274
|
+
}
|
|
275
|
+
return tail;
|
|
173
276
|
}
|
|
174
277
|
function compactChatPayload(payload, opts = {}) {
|
|
175
278
|
const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
|
|
@@ -181,6 +284,9 @@ function compactChatPayload(payload, opts = {}) {
|
|
|
181
284
|
});
|
|
182
285
|
const summary = typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : messageContent(finalAssistant).trim();
|
|
183
286
|
const messages = buildCompactMessageTail(visible, { summary, finalAssistant, limit });
|
|
287
|
+
const toolSummaries = rawMessages.filter((m) => !isCoordinatorVisibleMessage(m)).map(summarizeToolMessage).filter((s) => s !== null);
|
|
288
|
+
const omittedMessages = Math.max(0, rawMessages.length - messages.length);
|
|
289
|
+
const filteredMessages = Math.max(0, rawMessages.length - visible.length);
|
|
184
290
|
return {
|
|
185
291
|
success: payload?.success !== false,
|
|
186
292
|
compact: true,
|
|
@@ -190,8 +296,9 @@ function compactChatPayload(payload, opts = {}) {
|
|
|
190
296
|
providerSessionId: payload?.providerSessionId ?? null,
|
|
191
297
|
totalMessages: rawMessages.length,
|
|
192
298
|
visibleMessages: visible.length,
|
|
193
|
-
filteredMessages
|
|
194
|
-
omittedMessages
|
|
299
|
+
filteredMessages,
|
|
300
|
+
omittedMessages,
|
|
301
|
+
...toolSummaries.length > 0 ? { toolSummaries } : {},
|
|
195
302
|
summary,
|
|
196
303
|
...payload?.changedFiles !== void 0 ? { changedFiles: payload.changedFiles } : {},
|
|
197
304
|
...payload?.testsRun !== void 0 ? { testsRun: payload.testsRun } : {},
|
|
@@ -241,17 +348,63 @@ function annotateRapidReadChatAdvisory(payload, options) {
|
|
|
241
348
|
|
|
242
349
|
// src/tools/mesh-tools.ts
|
|
243
350
|
var import_daemon_core = require("@adhdev/daemon-core");
|
|
351
|
+
var SESSION_PROVIDER_METADATA_TTL_MS = 30 * 6e4;
|
|
244
352
|
var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
353
|
+
function getSessionMetadata(key) {
|
|
354
|
+
const entry = meshSessionProviderMetadata.get(key);
|
|
355
|
+
if (!entry) return void 0;
|
|
356
|
+
if (entry.expiresAt <= Date.now()) {
|
|
357
|
+
meshSessionProviderMetadata.delete(key);
|
|
358
|
+
return void 0;
|
|
359
|
+
}
|
|
360
|
+
return entry;
|
|
361
|
+
}
|
|
362
|
+
var ACTIVE_WORK_POLLING_BACKOFF_MS = 6e4;
|
|
363
|
+
function buildActiveWorkPollingGuidance(summary, now = Date.now()) {
|
|
364
|
+
if (!summary || summary.generatingCount <= 0) return void 0;
|
|
365
|
+
return {
|
|
366
|
+
activeGeneratingWork: true,
|
|
367
|
+
generatingCount: summary.generatingCount,
|
|
368
|
+
doNotPollBefore: new Date(now + ACTIVE_WORK_POLLING_BACKOFF_MS).toISOString(),
|
|
369
|
+
eventSurface: "pendingCoordinatorEvents",
|
|
370
|
+
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.",
|
|
371
|
+
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."
|
|
372
|
+
};
|
|
373
|
+
}
|
|
245
374
|
function readString(value) {
|
|
246
375
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
247
376
|
}
|
|
377
|
+
function summarizeTaskMessage(message) {
|
|
378
|
+
const taskSummary = message.replace(/\s+/g, " ").trim();
|
|
379
|
+
const taskTitle = taskSummary.length > 96 ? `${taskSummary.slice(0, 93)}...` : taskSummary;
|
|
380
|
+
return { taskTitle: taskTitle || "(untitled task)", taskSummary };
|
|
381
|
+
}
|
|
382
|
+
function buildDirectTaskPayload(message, via, opts) {
|
|
383
|
+
const descriptor = summarizeTaskMessage(message);
|
|
384
|
+
return {
|
|
385
|
+
source: "direct",
|
|
386
|
+
via,
|
|
387
|
+
taskId: opts.taskId,
|
|
388
|
+
message,
|
|
389
|
+
taskTitle: descriptor.taskTitle,
|
|
390
|
+
taskSummary: descriptor.taskSummary,
|
|
391
|
+
...opts.taskMode ? { taskMode: opts.taskMode } : {},
|
|
392
|
+
...opts.providerType ? { providerType: opts.providerType } : {},
|
|
393
|
+
...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {},
|
|
394
|
+
...opts.dispatchedToIdleSession !== void 0 ? { dispatchedToIdleSession: opts.dispatchedToIdleSession } : {}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function findNode(mesh, nodeId) {
|
|
398
|
+
const node = mesh.nodes.find((n) => n.id === nodeId);
|
|
399
|
+
if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
|
|
400
|
+
return node;
|
|
401
|
+
}
|
|
248
402
|
var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
|
|
249
403
|
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
250
404
|
var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
251
405
|
var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
|
|
252
406
|
var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
253
407
|
async function refreshMeshFromDaemon(ctx) {
|
|
254
|
-
if (!(ctx.transport instanceof IpcTransport)) return;
|
|
255
408
|
try {
|
|
256
409
|
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
257
410
|
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
@@ -412,6 +565,25 @@ function extractStatusMetadataSessions(value) {
|
|
|
412
565
|
function resolveSessionProviderType(session) {
|
|
413
566
|
return readString(session?.providerType) || readString(session?.cliType) || readString(session?.agentType) || "";
|
|
414
567
|
}
|
|
568
|
+
function isMeshCoordinatorSessionRecord(session) {
|
|
569
|
+
return Boolean(
|
|
570
|
+
readString(session?.settings?.meshCoordinatorFor) || readString(session?.meta?.meshCoordinatorFor) || readString(session?.metadata?.meshCoordinatorFor) || readString(session?.meshCoordinatorFor)
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
function isUnmanagedSessionRecord(session) {
|
|
574
|
+
const hasMeshNodeFor = Boolean(
|
|
575
|
+
readString(session?.settings?.meshNodeFor) || readString(session?.meta?.meshNodeFor) || readString(session?.metadata?.meshNodeFor) || readString(session?.meshNodeFor)
|
|
576
|
+
);
|
|
577
|
+
if (hasMeshNodeFor) return false;
|
|
578
|
+
if (isMeshCoordinatorSessionRecord(session)) return false;
|
|
579
|
+
const launchedByCoordinator = Boolean(
|
|
580
|
+
session?.settings?.launchedByCoordinator === true || session?.meta?.launchedByCoordinator === true || session?.launchedByCoordinator === true
|
|
581
|
+
);
|
|
582
|
+
return !launchedByCoordinator;
|
|
583
|
+
}
|
|
584
|
+
function isWorkerTaskMode(taskMode) {
|
|
585
|
+
return taskMode !== "live_debug_readonly";
|
|
586
|
+
}
|
|
415
587
|
function addSessionRecord(target, session) {
|
|
416
588
|
if (!session || typeof session !== "object" || isTerminalSessionRecord(session)) return;
|
|
417
589
|
const sessionId = readSessionRecordId(session);
|
|
@@ -480,18 +652,26 @@ function queueAssignmentStaleReason(task, liveness) {
|
|
|
480
652
|
}
|
|
481
653
|
function buildQueueStatusSummary(queue) {
|
|
482
654
|
const counts = { pending: 0, assigned: 0, completed: 0, failed: 0, cancelled: 0 };
|
|
655
|
+
let staleAssigned = 0;
|
|
483
656
|
for (const task of queue) {
|
|
484
657
|
const status = typeof task?.status === "string" ? task.status : void 0;
|
|
485
658
|
if (status && Object.prototype.hasOwnProperty.call(counts, status)) {
|
|
486
659
|
counts[status] += 1;
|
|
487
660
|
}
|
|
661
|
+
if (status === "assigned" && task?.staleAssigned === true) staleAssigned += 1;
|
|
488
662
|
}
|
|
663
|
+
const liveAssigned = Math.max(0, counts.assigned - staleAssigned);
|
|
489
664
|
return {
|
|
490
665
|
totalCount: queue.length,
|
|
491
|
-
activeCount: counts.pending +
|
|
666
|
+
activeCount: counts.pending + liveAssigned,
|
|
492
667
|
historicalCount: counts.completed + counts.failed + counts.cancelled,
|
|
493
668
|
counts,
|
|
494
669
|
activeCounts: {
|
|
670
|
+
pending: counts.pending,
|
|
671
|
+
assigned: liveAssigned
|
|
672
|
+
},
|
|
673
|
+
staleAssignedCount: staleAssigned,
|
|
674
|
+
rawActiveCounts: {
|
|
495
675
|
pending: counts.pending,
|
|
496
676
|
assigned: counts.assigned
|
|
497
677
|
},
|
|
@@ -519,6 +699,18 @@ function filterQueueForView(queue, view, statuses) {
|
|
|
519
699
|
if (view === "historical") return queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
520
700
|
return queue;
|
|
521
701
|
}
|
|
702
|
+
function prioritizeActiveQueueRows(queue) {
|
|
703
|
+
const active = [];
|
|
704
|
+
const historical = [];
|
|
705
|
+
const other = [];
|
|
706
|
+
for (const task of queue) {
|
|
707
|
+
const status = String(task?.status || "");
|
|
708
|
+
if (ACTIVE_QUEUE_STATUSES.has(status)) active.push(task);
|
|
709
|
+
else if (HISTORICAL_QUEUE_STATUSES.has(status)) historical.push(task);
|
|
710
|
+
else other.push(task);
|
|
711
|
+
}
|
|
712
|
+
return [...active, ...other, ...historical];
|
|
713
|
+
}
|
|
522
714
|
function slimQueueTask(task) {
|
|
523
715
|
return {
|
|
524
716
|
id: task?.id,
|
|
@@ -570,6 +762,28 @@ function buildQueueMaintenanceReport(queue) {
|
|
|
570
762
|
cleanupCandidateCount: cleanupCandidates.length
|
|
571
763
|
};
|
|
572
764
|
}
|
|
765
|
+
function buildCompactQueueMaintenanceReport(maintenance) {
|
|
766
|
+
const staleAssignedTasks = Array.isArray(maintenance.staleAssignedTasks) ? maintenance.staleAssignedTasks : [];
|
|
767
|
+
const cleanupCandidateCount = maintenance.cleanupCandidateCount ?? 0;
|
|
768
|
+
return {
|
|
769
|
+
readOnly: true,
|
|
770
|
+
mutationPerformed: false,
|
|
771
|
+
sourceOfTruth: "mesh_work_queue_file",
|
|
772
|
+
payloadMode: "compact",
|
|
773
|
+
staleAssignedDefinition: maintenance.staleAssignedDefinition,
|
|
774
|
+
historicalDefinition: maintenance.historicalDefinition,
|
|
775
|
+
// staleAssignedTasks are active assigned rows (not historical) — retain a
|
|
776
|
+
// bounded sample so coordinators can still see drift without the full array.
|
|
777
|
+
staleAssignedTasks: staleAssignedTasks.slice(0, 5),
|
|
778
|
+
staleAssignedSampleLimit: 5,
|
|
779
|
+
staleAssignedCount: maintenance.staleAssignedCount ?? staleAssignedTasks.length,
|
|
780
|
+
historicalRecordCount: maintenance.historicalRecordCount ?? 0,
|
|
781
|
+
oldHistoricalRecordCount: maintenance.oldHistoricalRecordCount ?? 0,
|
|
782
|
+
cleanupCandidateCount,
|
|
783
|
+
cleanupCandidatesOmitted: true,
|
|
784
|
+
cleanupCandidatesHint: "Per-row cleanup candidates are omitted in compact mode; call mesh_view_queue with verbose=true for the full maintenance/cleanupDryRun rows."
|
|
785
|
+
};
|
|
786
|
+
}
|
|
573
787
|
function annotateQueueStaleness(queue, mesh) {
|
|
574
788
|
const liveness = buildQueueLivenessIndex(mesh);
|
|
575
789
|
const now = Date.now();
|
|
@@ -612,6 +826,169 @@ function unwrapCommandPayload(value) {
|
|
|
612
826
|
}
|
|
613
827
|
return current;
|
|
614
828
|
}
|
|
829
|
+
function isDirectDispatchLedgerEntry(entry) {
|
|
830
|
+
if (entry?.kind !== "task_dispatched") return false;
|
|
831
|
+
const payload = entry.payload || {};
|
|
832
|
+
const via = readString(payload.via);
|
|
833
|
+
return payload.source === "direct" || via === "p2p_direct" || via === "local_direct" || via === "mesh_send_task";
|
|
834
|
+
}
|
|
835
|
+
function readMessageTimestampIso(message) {
|
|
836
|
+
for (const value of [message?.timestamp, message?.createdAt, message?.created_at, message?.updatedAt, message?.time]) {
|
|
837
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
838
|
+
const ms = value > 1e10 ? value : value * 1e3;
|
|
839
|
+
return new Date(ms).toISOString();
|
|
840
|
+
}
|
|
841
|
+
if (typeof value === "string" && value.trim()) {
|
|
842
|
+
const ms = new Date(value.trim()).getTime();
|
|
843
|
+
if (Number.isFinite(ms)) return new Date(ms).toISOString();
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return void 0;
|
|
847
|
+
}
|
|
848
|
+
function readFinalAssistantTranscriptEvidence(payload) {
|
|
849
|
+
const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
|
|
850
|
+
const finalAssistant = [...rawMessages].reverse().filter(isCoordinatorVisibleMessage).find((message) => {
|
|
851
|
+
const role = String(message?.role ?? "").toLowerCase();
|
|
852
|
+
return (role === "assistant" || role === "agent") && messageContent(message).trim();
|
|
853
|
+
});
|
|
854
|
+
const finalSummary = messageContent(finalAssistant).trim() || (typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : void 0);
|
|
855
|
+
return {
|
|
856
|
+
finalSummary,
|
|
857
|
+
transcriptMessageAt: finalAssistant ? readMessageTimestampIso(finalAssistant) : void 0
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
function findNodeSession(nodes, nodeId, sessionId) {
|
|
861
|
+
if (!nodeId || !sessionId) return {};
|
|
862
|
+
const node = nodes.find((candidate) => readString(candidate?.id) === nodeId || readString(candidate?.nodeId) === nodeId);
|
|
863
|
+
if (!node) return {};
|
|
864
|
+
const sessions = Array.isArray(node.sessions) ? node.sessions : [];
|
|
865
|
+
const session = sessions.find((candidate) => readSessionRecordId(candidate) === sessionId);
|
|
866
|
+
return { node, session };
|
|
867
|
+
}
|
|
868
|
+
function buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries) {
|
|
869
|
+
const candidates = [];
|
|
870
|
+
const seenTaskIds = /* @__PURE__ */ new Set();
|
|
871
|
+
for (const dispatch of directDispatches || []) {
|
|
872
|
+
const taskId = readString(dispatch?.taskId);
|
|
873
|
+
if (!taskId || seenTaskIds.has(taskId)) continue;
|
|
874
|
+
seenTaskIds.add(taskId);
|
|
875
|
+
candidates.push(dispatch);
|
|
876
|
+
}
|
|
877
|
+
for (const entry of ledgerEntries || []) {
|
|
878
|
+
if (!isDirectDispatchLedgerEntry(entry)) continue;
|
|
879
|
+
const taskId = readString(entry.payload?.taskId);
|
|
880
|
+
if (!taskId || seenTaskIds.has(taskId)) continue;
|
|
881
|
+
seenTaskIds.add(taskId);
|
|
882
|
+
candidates.push({
|
|
883
|
+
taskId,
|
|
884
|
+
nodeId: entry.nodeId,
|
|
885
|
+
sessionId: entry.sessionId,
|
|
886
|
+
providerType: entry.providerType || readString(entry.payload?.providerType),
|
|
887
|
+
message: readString(entry.payload?.message),
|
|
888
|
+
dispatchedAt: entry.timestamp,
|
|
889
|
+
via: readString(entry.payload?.via)
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
return candidates;
|
|
893
|
+
}
|
|
894
|
+
async function reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries) {
|
|
895
|
+
let attempted = 0;
|
|
896
|
+
let reconciled = 0;
|
|
897
|
+
let skipped = 0;
|
|
898
|
+
const candidates = buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries);
|
|
899
|
+
for (const dispatch of candidates) {
|
|
900
|
+
const taskId = readString(dispatch?.taskId);
|
|
901
|
+
const nodeId = readString(dispatch?.nodeId);
|
|
902
|
+
const sessionId = readString(dispatch?.sessionId);
|
|
903
|
+
if (!taskId || !nodeId || !sessionId) {
|
|
904
|
+
skipped += 1;
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
const { session } = findNodeSession(liveNodes, nodeId, sessionId);
|
|
908
|
+
if (!session || !isIdleSessionRecord(session)) {
|
|
909
|
+
skipped += 1;
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
const node = await findOptionalNodeWithRefresh(ctx, nodeId).catch(() => null);
|
|
913
|
+
if (!node) {
|
|
914
|
+
skipped += 1;
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
const providerType = readString(dispatch?.providerType) || resolveSessionProviderType(session);
|
|
918
|
+
const providerSessionId = readString(session?.providerSessionId) || readString(session?.activeChat?.providerSessionId) || readString(session?.settings?.providerSessionId) || resolveMeshSessionProviderMetadata(ctx, nodeId, sessionId)?.providerSessionId;
|
|
919
|
+
attempted += 1;
|
|
920
|
+
try {
|
|
921
|
+
const readResult = await commandForNode(ctx, node, "read_chat", {
|
|
922
|
+
sessionId,
|
|
923
|
+
targetSessionId: sessionId,
|
|
924
|
+
workspace: node.workspace,
|
|
925
|
+
...providerType ? { agentType: providerType, providerType } : {},
|
|
926
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
927
|
+
tailLimit: 10
|
|
928
|
+
});
|
|
929
|
+
const payload = unwrapCommandPayload(readResult);
|
|
930
|
+
if (payload?.success === false) continue;
|
|
931
|
+
const evidence = readFinalAssistantTranscriptEvidence(payload);
|
|
932
|
+
if (!evidence.finalSummary) continue;
|
|
933
|
+
const result = (0, import_daemon_core.reconcileDirectDispatchCompletionFromTranscript)({
|
|
934
|
+
meshId: ctx.mesh.id,
|
|
935
|
+
nodeId,
|
|
936
|
+
sessionId,
|
|
937
|
+
providerType,
|
|
938
|
+
providerSessionId: readString(payload?.providerSessionId) || providerSessionId,
|
|
939
|
+
taskId,
|
|
940
|
+
finalSummary: evidence.finalSummary,
|
|
941
|
+
transcriptMessageAt: evidence.transcriptMessageAt,
|
|
942
|
+
targetCoordinatorDaemonId: ctx.localDaemonId,
|
|
943
|
+
source: "mcp_mesh_status_transcript_reconciliation"
|
|
944
|
+
});
|
|
945
|
+
if (result.reconciled) reconciled += 1;
|
|
946
|
+
} catch {
|
|
947
|
+
skipped += 1;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return { attempted, reconciled, skipped };
|
|
951
|
+
}
|
|
952
|
+
async function triggerMeshQueueAndReport(ctx, node, opts) {
|
|
953
|
+
try {
|
|
954
|
+
let raw;
|
|
955
|
+
if (ctx.transport instanceof IpcTransport && node?.daemonId && opts?.localNode === false) {
|
|
956
|
+
raw = await ctx.transport.meshCommand(node.daemonId, "trigger_mesh_queue", { meshId: ctx.mesh.id });
|
|
957
|
+
} else {
|
|
958
|
+
raw = await ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id });
|
|
959
|
+
}
|
|
960
|
+
const payload = unwrapCommandPayload(raw);
|
|
961
|
+
const trigger = payload?.trigger && typeof payload.trigger === "object" ? payload.trigger : payload;
|
|
962
|
+
return trigger && typeof trigger === "object" ? trigger : { success: true };
|
|
963
|
+
} catch (e) {
|
|
964
|
+
return {
|
|
965
|
+
success: false,
|
|
966
|
+
error: e?.message || String(e)
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
function buildQueueTriggerGuidance(queueTrigger) {
|
|
971
|
+
if (!queueTrigger || queueTrigger.claimed === true) return void 0;
|
|
972
|
+
if (queueTrigger.success === false) {
|
|
973
|
+
return {
|
|
974
|
+
queueClaimed: false,
|
|
975
|
+
queueDispatchState: "trigger_failed",
|
|
976
|
+
nextAction: "Do not assume the queued task is running. Check mesh_view_queue and daemon connectivity before redispatching."
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
if (queueTrigger.noIdleMeshSessionAvailable === true) {
|
|
980
|
+
return {
|
|
981
|
+
queueClaimed: false,
|
|
982
|
+
queueDispatchState: "pending_no_idle_mesh_session",
|
|
983
|
+
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."
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
return {
|
|
987
|
+
queueClaimed: false,
|
|
988
|
+
queueDispatchState: "pending_or_waiting_for_ready",
|
|
989
|
+
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."
|
|
990
|
+
};
|
|
991
|
+
}
|
|
615
992
|
function isTerminalSessionRecord(session) {
|
|
616
993
|
const status = typeof session?.status === "string" ? session.status.toLowerCase() : "";
|
|
617
994
|
const lifecycle = typeof session?.lifecycle === "string" ? session.lifecycle.toLowerCase() : "";
|
|
@@ -627,16 +1004,23 @@ function isIdleSessionRecord(session) {
|
|
|
627
1004
|
function isMeshOwnedDelegateSession(session, meshId, nodeId) {
|
|
628
1005
|
const settings = session?.settings;
|
|
629
1006
|
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
630
|
-
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
631
1007
|
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
632
|
-
if (sessionMeshId !== meshId
|
|
1008
|
+
if (sessionMeshId !== meshId) return false;
|
|
633
1009
|
return !sessionNodeId || sessionNodeId === nodeId;
|
|
634
1010
|
}
|
|
1011
|
+
function hasRemoteRelayMetadata(session) {
|
|
1012
|
+
return Boolean(
|
|
1013
|
+
readString(session?.settings?.meshCoordinatorDaemonId) || readString(session?.meta?.meshCoordinatorDaemonId) || readString(session?.metadata?.meshCoordinatorDaemonId) || readString(session?.meshCoordinatorDaemonId)
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
function isRelaySafeRemoteDelegateSession(session, meshId, nodeId) {
|
|
1017
|
+
return isMeshOwnedDelegateSession(session, meshId, nodeId) && hasRemoteRelayMetadata(session);
|
|
1018
|
+
}
|
|
635
1019
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
636
1020
|
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
637
1021
|
const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
|
|
638
1022
|
const meshSessions = live.filter(
|
|
639
|
-
(session) =>
|
|
1023
|
+
(session) => isRelaySafeRemoteDelegateSession(session, meshId, nodeId)
|
|
640
1024
|
);
|
|
641
1025
|
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || meshSessions.find(matchingProvider) || void 0;
|
|
642
1026
|
}
|
|
@@ -653,8 +1037,9 @@ function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType
|
|
|
653
1037
|
daemonId: node.daemonId,
|
|
654
1038
|
workspace: node.workspace,
|
|
655
1039
|
sessionId,
|
|
1040
|
+
unsafeTranscriptAlias: true,
|
|
656
1041
|
...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.`,
|
|
1042
|
+
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
1043
|
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
1044
|
noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
|
|
660
1045
|
};
|
|
@@ -704,12 +1089,36 @@ function extractGitDiff(value) {
|
|
|
704
1089
|
}
|
|
705
1090
|
function extractSubmodules(value, ignorePaths) {
|
|
706
1091
|
const payload = unwrapCommandPayload(value);
|
|
707
|
-
const subs = payload?.submodules ?? value?.submodules;
|
|
1092
|
+
const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
|
|
708
1093
|
if (!Array.isArray(subs)) return void 0;
|
|
709
1094
|
if (ignorePaths.length === 0) return subs;
|
|
710
1095
|
const ignoreSet = new Set(ignorePaths);
|
|
711
1096
|
return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
|
|
712
1097
|
}
|
|
1098
|
+
function assignFullGitSnapshot(entry, status) {
|
|
1099
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return;
|
|
1100
|
+
entry.git = status;
|
|
1101
|
+
}
|
|
1102
|
+
function buildCompactGitSnapshot(status) {
|
|
1103
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return void 0;
|
|
1104
|
+
const slim = {};
|
|
1105
|
+
const carry = [
|
|
1106
|
+
"isGitRepo",
|
|
1107
|
+
"branch",
|
|
1108
|
+
"headCommit",
|
|
1109
|
+
"upstream",
|
|
1110
|
+
"upstreamStatus",
|
|
1111
|
+
"ahead",
|
|
1112
|
+
"behind",
|
|
1113
|
+
"dirty",
|
|
1114
|
+
"detached",
|
|
1115
|
+
"submodules"
|
|
1116
|
+
];
|
|
1117
|
+
for (const key of carry) {
|
|
1118
|
+
if (status[key] !== void 0) slim[key] = status[key];
|
|
1119
|
+
}
|
|
1120
|
+
return slim;
|
|
1121
|
+
}
|
|
713
1122
|
function extractLaunchPayload(value) {
|
|
714
1123
|
return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
|
|
715
1124
|
}
|
|
@@ -834,7 +1243,20 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
834
1243
|
let sessionId = args.session_id?.trim() || "";
|
|
835
1244
|
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
836
1245
|
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
837
|
-
if (
|
|
1246
|
+
if (sessionId && args.verifiedSession) {
|
|
1247
|
+
const explicitSession = args.verifiedSession;
|
|
1248
|
+
if (!isRelaySafeRemoteDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
1249
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
1250
|
+
ctx,
|
|
1251
|
+
node,
|
|
1252
|
+
sessionId,
|
|
1253
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
if (!resolvedProviderType) {
|
|
1257
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
1258
|
+
}
|
|
1259
|
+
} else if (!sessionId || args.session_id) {
|
|
838
1260
|
try {
|
|
839
1261
|
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
840
1262
|
const sessions = extractStatusMetadataSessions(relayResult);
|
|
@@ -858,7 +1280,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
858
1280
|
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
1281
|
};
|
|
860
1282
|
}
|
|
861
|
-
if (!
|
|
1283
|
+
if (!isRelaySafeRemoteDelegateSession(explicitSession, ctx.mesh.id, node.id)) {
|
|
862
1284
|
return buildRelayUnsafeRemoteSessionFailure(
|
|
863
1285
|
ctx,
|
|
864
1286
|
node,
|
|
@@ -902,7 +1324,8 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
902
1324
|
agentType: resolvedProviderType,
|
|
903
1325
|
cliType: resolvedProviderType,
|
|
904
1326
|
action: "send_chat",
|
|
905
|
-
message: args.message
|
|
1327
|
+
message: args.message,
|
|
1328
|
+
...args.meshContext ? { meshContext: args.meshContext } : {}
|
|
906
1329
|
});
|
|
907
1330
|
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
908
1331
|
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
@@ -920,7 +1343,7 @@ async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
|
920
1343
|
error: `P2P dispatch failed: ${errorMessage}`
|
|
921
1344
|
};
|
|
922
1345
|
}
|
|
923
|
-
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType };
|
|
1346
|
+
return { success: true, dispatched: true, sessionId: sessionId || resolvedProviderType, providerType: resolvedProviderType };
|
|
924
1347
|
} catch (e) {
|
|
925
1348
|
const errorMessage = e?.message || String(e);
|
|
926
1349
|
return {
|
|
@@ -950,34 +1373,197 @@ function resolveCoordinatorNode(ctx) {
|
|
|
950
1373
|
return void 0;
|
|
951
1374
|
}
|
|
952
1375
|
function readNodeMachineId(node) {
|
|
953
|
-
return readString(node.machineId) || readString(node.machine_id);
|
|
1376
|
+
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
1377
|
}
|
|
955
1378
|
function readNodeDaemonId(node) {
|
|
956
|
-
return readString(node.daemonId) || readString(node.daemon_id);
|
|
1379
|
+
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);
|
|
1380
|
+
}
|
|
1381
|
+
function normalizeHostname(value) {
|
|
1382
|
+
const hostname = readString(value);
|
|
1383
|
+
if (!hostname) return void 0;
|
|
1384
|
+
return hostname.toLowerCase().replace(/\.$/, "");
|
|
1385
|
+
}
|
|
1386
|
+
function readNodeHostname(node) {
|
|
1387
|
+
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);
|
|
1388
|
+
}
|
|
1389
|
+
function readNodeDisplayMachineName(node) {
|
|
1390
|
+
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);
|
|
1391
|
+
}
|
|
1392
|
+
function compactIdentityEvidence(value) {
|
|
1393
|
+
if (!value) return void 0;
|
|
1394
|
+
return value.length > 24 ? `${value.slice(0, 12)}\u2026${value.slice(-8)}` : value;
|
|
1395
|
+
}
|
|
1396
|
+
function pushIdentityEvidence(evidence, label, value) {
|
|
1397
|
+
const compact = compactIdentityEvidence(value);
|
|
1398
|
+
if (compact) evidence.push(`${label}:${compact}`);
|
|
1399
|
+
}
|
|
1400
|
+
function buildNodeMachineIdentity(ctx, node) {
|
|
1401
|
+
const machineId = readNodeMachineId(node);
|
|
1402
|
+
const daemonId = readNodeDaemonId(node);
|
|
1403
|
+
const hostname = readNodeHostname(node);
|
|
1404
|
+
const machineName = readNodeDisplayMachineName(node);
|
|
1405
|
+
const coordinatorHostname = readString(ctx.coordinatorHostname);
|
|
1406
|
+
const localControlPlaneReason = getLocalControlPlaneMatchReason(ctx, node);
|
|
1407
|
+
const directLocal = !!localControlPlaneReason;
|
|
1408
|
+
const hostnameMatches = Boolean(
|
|
1409
|
+
normalizeHostname(hostname) && normalizeHostname(coordinatorHostname) && normalizeHostname(hostname) === normalizeHostname(coordinatorHostname)
|
|
1410
|
+
);
|
|
1411
|
+
const sameMachine = directLocal || hostnameMatches;
|
|
1412
|
+
const evidence = [];
|
|
1413
|
+
pushIdentityEvidence(evidence, "machineName", machineName);
|
|
1414
|
+
pushIdentityEvidence(evidence, "hostname", hostname);
|
|
1415
|
+
pushIdentityEvidence(evidence, "machineId", machineId);
|
|
1416
|
+
pushIdentityEvidence(evidence, "daemonId", daemonId);
|
|
1417
|
+
if (localControlPlaneReason) {
|
|
1418
|
+
pushIdentityEvidence(evidence, "localMatch", localControlPlaneReason);
|
|
1419
|
+
pushIdentityEvidence(evidence, "localMachineId", ctx.localMachineId);
|
|
1420
|
+
pushIdentityEvidence(evidence, "localDaemonId", ctx.localDaemonId);
|
|
1421
|
+
}
|
|
1422
|
+
const locality = sameMachine ? "same_machine" : evidence.length > 0 ? "remote_known" : "remote_or_unknown";
|
|
1423
|
+
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";
|
|
1424
|
+
return {
|
|
1425
|
+
daemonId,
|
|
1426
|
+
machineId,
|
|
1427
|
+
hostname,
|
|
1428
|
+
machineName,
|
|
1429
|
+
displayName: machineName || hostname || daemonId || machineId,
|
|
1430
|
+
coordinatorHostname,
|
|
1431
|
+
sameMachine,
|
|
1432
|
+
locality,
|
|
1433
|
+
localityReason,
|
|
1434
|
+
identityEvidence: evidence
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
function nodeHasLocalDaemonEvidence(ctx, node) {
|
|
1438
|
+
const isLocal = (session) => {
|
|
1439
|
+
if (!session || typeof session !== "object") return false;
|
|
1440
|
+
if (ctx.localDaemonId && session.runtime?.owner === ctx.localDaemonId) return true;
|
|
1441
|
+
if (ctx.localDaemonId && session.daemonClient?.daemonId === ctx.localDaemonId) return true;
|
|
1442
|
+
return false;
|
|
1443
|
+
};
|
|
1444
|
+
const sessionArrays = [
|
|
1445
|
+
node?.sessions,
|
|
1446
|
+
node?.activeSessions,
|
|
1447
|
+
node?.active_sessions,
|
|
1448
|
+
node?.lastProbe?.sessions,
|
|
1449
|
+
node?.last_probe?.sessions,
|
|
1450
|
+
node?.lastProbe?.status?.sessions,
|
|
1451
|
+
node?.last_probe?.status?.sessions
|
|
1452
|
+
];
|
|
1453
|
+
for (const arr of sessionArrays) {
|
|
1454
|
+
if (Array.isArray(arr) && arr.some(isLocal)) return true;
|
|
1455
|
+
}
|
|
1456
|
+
const sessionRecords = [
|
|
1457
|
+
node?.activeSession,
|
|
1458
|
+
node?.active_session,
|
|
1459
|
+
node?.currentSession,
|
|
1460
|
+
node?.current_session,
|
|
1461
|
+
node?.runtimeSession,
|
|
1462
|
+
node?.runtime_session,
|
|
1463
|
+
node?.session,
|
|
1464
|
+
node?.lastProbe?.activeSession,
|
|
1465
|
+
node?.last_probe?.active_session,
|
|
1466
|
+
node?.lastProbe?.currentSession,
|
|
1467
|
+
node?.last_probe?.current_session,
|
|
1468
|
+
node?.lastProbe?.session,
|
|
1469
|
+
node?.last_probe?.session
|
|
1470
|
+
];
|
|
1471
|
+
for (const session of sessionRecords) {
|
|
1472
|
+
if (isLocal(session)) return true;
|
|
1473
|
+
}
|
|
1474
|
+
return false;
|
|
957
1475
|
}
|
|
958
1476
|
function isDirectLocalNode(ctx, node) {
|
|
959
1477
|
const machineId = readNodeMachineId(node);
|
|
960
1478
|
const daemonId = readNodeDaemonId(node);
|
|
961
1479
|
return Boolean(
|
|
962
|
-
ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId
|
|
1480
|
+
ctx.localMachineId && machineId === ctx.localMachineId || ctx.localDaemonId && daemonId === ctx.localDaemonId || nodeHasLocalDaemonEvidence(ctx, node)
|
|
963
1481
|
);
|
|
964
1482
|
}
|
|
1483
|
+
function isConfiguredCoordinatorNode(ctx, node) {
|
|
1484
|
+
if (!ctx.localMachineId && !ctx.localDaemonId) return false;
|
|
1485
|
+
const nodeId = readString(node.id) || readString(node.nodeId) || readString(node.node_id);
|
|
1486
|
+
if (!nodeId) return false;
|
|
1487
|
+
const nodeDaemonId = readNodeDaemonId(node);
|
|
1488
|
+
const nodeMachineId = readNodeMachineId(node);
|
|
1489
|
+
if (nodeDaemonId && ctx.localDaemonId && nodeDaemonId !== ctx.localDaemonId) return false;
|
|
1490
|
+
if (nodeMachineId && ctx.localMachineId && nodeMachineId !== ctx.localMachineId) return false;
|
|
1491
|
+
const preferredNodeId = readString(ctx.mesh.coordinator?.preferredNodeId) || readString(ctx.mesh.coordinator?.preferred_node_id);
|
|
1492
|
+
if (preferredNodeId) return nodeId === preferredNodeId;
|
|
1493
|
+
const first = ctx.mesh.nodes?.[0];
|
|
1494
|
+
const firstNodeId = readString(first?.id) || readString(first?.nodeId) || readString(first?.node_id);
|
|
1495
|
+
return !!firstNodeId && nodeId === firstNodeId;
|
|
1496
|
+
}
|
|
1497
|
+
function getLocalControlPlaneMatchReason(ctx, node) {
|
|
1498
|
+
if (isDirectLocalNode(ctx, node)) return "matched coordinator daemon or machine id";
|
|
1499
|
+
if (isConfiguredCoordinatorNode(ctx, node)) return "matched configured coordinator node";
|
|
1500
|
+
if (node.isLocalWorktree === true) {
|
|
1501
|
+
const sourceNode = findClonedFromNode(ctx, node);
|
|
1502
|
+
if (sourceNode && isDirectLocalNode(ctx, sourceNode)) return "matched local cloned-from node";
|
|
1503
|
+
if (sourceNode && isConfiguredCoordinatorNode(ctx, sourceNode)) return "matched configured coordinator source node";
|
|
1504
|
+
}
|
|
1505
|
+
return void 0;
|
|
1506
|
+
}
|
|
965
1507
|
function findClonedFromNode(ctx, node) {
|
|
966
1508
|
const clonedFromNodeId = readString(node.clonedFromNodeId) || readString(node.cloned_from_node_id);
|
|
967
1509
|
if (!clonedFromNodeId) return void 0;
|
|
968
1510
|
return ctx.mesh.nodes.find((n) => n.id === clonedFromNodeId || n.nodeId === clonedFromNodeId || n.node_id === clonedFromNodeId);
|
|
969
1511
|
}
|
|
970
1512
|
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;
|
|
1513
|
+
return !!getLocalControlPlaneMatchReason(ctx, node);
|
|
977
1514
|
}
|
|
978
1515
|
function meshSessionCacheKey(nodeId, runtimeSessionId) {
|
|
979
1516
|
return `${nodeId}:${runtimeSessionId}`;
|
|
980
1517
|
}
|
|
1518
|
+
function rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, metadata) {
|
|
1519
|
+
const keyNodeId = readString(nodeId);
|
|
1520
|
+
const keySessionId = readString(runtimeSessionId);
|
|
1521
|
+
if (!keyNodeId || !keySessionId) return;
|
|
1522
|
+
const providerType = readString(metadata.providerType);
|
|
1523
|
+
const providerSessionId = readString(metadata.providerSessionId);
|
|
1524
|
+
if (!providerType && !providerSessionId) return;
|
|
1525
|
+
const existing = getSessionMetadata(meshSessionCacheKey(keyNodeId, keySessionId)) || { providerType: "" };
|
|
1526
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(keyNodeId, keySessionId), {
|
|
1527
|
+
providerType: providerType || existing.providerType,
|
|
1528
|
+
providerSessionId: providerSessionId || existing.providerSessionId,
|
|
1529
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
function rememberMeshSessionProviderMetadataFromEvent(event) {
|
|
1533
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : event && typeof event === "object" ? event : {};
|
|
1534
|
+
const nodeId = readString(event?.nodeId) || readString(metadataEvent.nodeId) || readString(metadataEvent.meshNodeId);
|
|
1535
|
+
const sessionId = readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId) || readString(event?.sessionId);
|
|
1536
|
+
rememberMeshSessionProviderMetadata(nodeId, sessionId, {
|
|
1537
|
+
providerType: readString(metadataEvent.providerType) || readString(event?.providerType) || "",
|
|
1538
|
+
providerSessionId: readString(metadataEvent.providerSessionId) || readString(event?.providerSessionId)
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
|
|
1542
|
+
const entries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 50 });
|
|
1543
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1544
|
+
const entry = entries[i];
|
|
1545
|
+
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
1546
|
+
const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
|
|
1547
|
+
if (entryNodeId && entryNodeId !== nodeId) continue;
|
|
1548
|
+
const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
|
|
1549
|
+
if (entrySessionId !== runtimeSessionId) continue;
|
|
1550
|
+
const providerType = readString(entry.providerType) || readString(payload.providerType);
|
|
1551
|
+
const completionDiagnostic = payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? payload.completionDiagnostic : {};
|
|
1552
|
+
const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : {};
|
|
1553
|
+
const providerSessionId = readString(payload.providerSessionId) || readString(completionDiagnostic.providerSessionId) || readString(metadataEvent.providerSessionId);
|
|
1554
|
+
if (providerType || providerSessionId) {
|
|
1555
|
+
return { providerType: providerType || "", providerSessionId };
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return void 0;
|
|
1559
|
+
}
|
|
1560
|
+
function resolveMeshSessionProviderMetadata(ctx, nodeId, runtimeSessionId) {
|
|
1561
|
+
const cached = getSessionMetadata(meshSessionCacheKey(nodeId, runtimeSessionId));
|
|
1562
|
+
if (cached?.providerType || cached?.providerSessionId) return cached;
|
|
1563
|
+
const fromLedger = resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId);
|
|
1564
|
+
if (fromLedger) rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, fromLedger);
|
|
1565
|
+
return fromLedger;
|
|
1566
|
+
}
|
|
981
1567
|
function countUncommittedChanges(status) {
|
|
982
1568
|
if (typeof status?.uncommittedChanges === "number") return status.uncommittedChanges;
|
|
983
1569
|
const keys = ["staged", "modified", "untracked", "deleted", "renamed"];
|
|
@@ -988,8 +1574,23 @@ function countUncommittedChanges(status) {
|
|
|
988
1574
|
function isGitStatusDirty(status) {
|
|
989
1575
|
if (typeof status?.isDirty === "boolean") return status.isDirty;
|
|
990
1576
|
if (typeof status?.dirty === "boolean") return status.dirty;
|
|
1577
|
+
if (Array.isArray(status?.submodules) && status.submodules.some((submodule) => submodule?.dirty || submodule?.outOfSync || submodule?.error)) return true;
|
|
991
1578
|
return countUncommittedChanges(status) > 0;
|
|
992
1579
|
}
|
|
1580
|
+
function slimLedgerPayload(payload) {
|
|
1581
|
+
const slim = {};
|
|
1582
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
1583
|
+
if (k === "message" || k === "taskSummary") {
|
|
1584
|
+
slim[k] = typeof v === "string" && v.length > 200 ? v.slice(0, 200) + "\u2026" : v;
|
|
1585
|
+
} else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
|
|
1586
|
+
} else if (k === "finalSummary") {
|
|
1587
|
+
slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
|
|
1588
|
+
} else {
|
|
1589
|
+
slim[k] = v;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
return slim;
|
|
1593
|
+
}
|
|
993
1594
|
function readRelatedRepos(node) {
|
|
994
1595
|
const raw = Array.isArray(node.relatedRepos) ? node.relatedRepos : Array.isArray(node.policy?.relatedRepos) ? node.policy.relatedRepos : [];
|
|
995
1596
|
return raw.map((entry) => ({
|
|
@@ -1024,7 +1625,7 @@ async function collectRelatedRepoStatuses(ctx, node) {
|
|
|
1024
1625
|
const results = [];
|
|
1025
1626
|
for (const repo of relatedRepos) {
|
|
1026
1627
|
try {
|
|
1027
|
-
const statusResult =
|
|
1628
|
+
const statusResult = await commandForNode(ctx, node, "git_status", { workspace: repo.workspace, refreshUpstream: true });
|
|
1028
1629
|
const status = extractGitStatus(statusResult);
|
|
1029
1630
|
results.push(summarizeRelatedRepoStatus(repo, status));
|
|
1030
1631
|
} catch (e) {
|
|
@@ -1048,6 +1649,16 @@ function missingProviderPriorityMessage(nodeId) {
|
|
|
1048
1649
|
return `Node '${nodeId}' has no providerPriority policy; pass type explicitly or configure node.policy.providerPriority`;
|
|
1049
1650
|
}
|
|
1050
1651
|
function getNodeLaunchReadiness(node) {
|
|
1652
|
+
const bootstrap = node.worktreeBootstrap;
|
|
1653
|
+
if (node.isLocalWorktree && bootstrap?.status === "failed" && bootstrap?.required !== false) {
|
|
1654
|
+
return {
|
|
1655
|
+
providerPriority: readProviderPriority(node.policy),
|
|
1656
|
+
launchReady: false,
|
|
1657
|
+
launchBlockedReason: "worktree_bootstrap_failed",
|
|
1658
|
+
launchBlockedMessage: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : "Required worktree bootstrap failed; resolve it before launching an agent into this node.",
|
|
1659
|
+
worktreeBootstrap: bootstrap
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1051
1662
|
const providerPriority = readProviderPriority(node.policy);
|
|
1052
1663
|
if (providerPriority.length) {
|
|
1053
1664
|
return {
|
|
@@ -1062,6 +1673,45 @@ function getNodeLaunchReadiness(node) {
|
|
|
1062
1673
|
launchBlockedMessage: missingProviderPriorityMessage(node.id)
|
|
1063
1674
|
};
|
|
1064
1675
|
}
|
|
1676
|
+
function getWorktreeBootstrapLaunchBlock(node, meshPolicy) {
|
|
1677
|
+
if (!node.isLocalWorktree) return void 0;
|
|
1678
|
+
const bootstrap = node.worktreeBootstrap;
|
|
1679
|
+
const requireReady = !!(meshPolicy && typeof meshPolicy === "object" && meshPolicy.requireBootstrapBeforeLaunch === true);
|
|
1680
|
+
if (requireReady && bootstrap?.status !== "ready") {
|
|
1681
|
+
return {
|
|
1682
|
+
success: false,
|
|
1683
|
+
code: "bootstrap_not_ready",
|
|
1684
|
+
error: `Node '${node.id}' bootstrap state is '${bootstrap?.status ?? "unknown"}' and mesh policy requireBootstrapBeforeLaunch is enabled.`,
|
|
1685
|
+
nodeId: node.id,
|
|
1686
|
+
worktreeBootstrap: bootstrap ?? null,
|
|
1687
|
+
recoveryHint: "Run the worktree bootstrap (clone runOnClone or a refine with bootstrap inherit) until the node reports ready, or disable requireBootstrapBeforeLaunch."
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
if (bootstrap?.status !== "failed" || bootstrap?.required === false) return void 0;
|
|
1691
|
+
return {
|
|
1692
|
+
success: false,
|
|
1693
|
+
code: "worktree_bootstrap_failed",
|
|
1694
|
+
error: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : `Node '${node.id}' has a failed required worktree bootstrap.`,
|
|
1695
|
+
nodeId: node.id,
|
|
1696
|
+
worktreeBootstrap: bootstrap,
|
|
1697
|
+
recoveryHint: "Fix the configured worktree bootstrap command or remove/recreate the worktree node before launching an agent."
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
async function collectLiveStatusSessions(ctx, node) {
|
|
1701
|
+
try {
|
|
1702
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
1703
|
+
return extractStatusMetadataSessions(statusResult);
|
|
1704
|
+
} catch {
|
|
1705
|
+
return [];
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
async function collectMeshViewQueueNodesWithLiveSessions(ctx) {
|
|
1709
|
+
const nodes = await Promise.all(ctx.mesh.nodes.map(async (node) => {
|
|
1710
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
1711
|
+
return liveSessions.length > 0 ? { ...node, sessions: liveSessions } : node;
|
|
1712
|
+
}));
|
|
1713
|
+
return nodes;
|
|
1714
|
+
}
|
|
1065
1715
|
function readNumeric(value, fallback = 0) {
|
|
1066
1716
|
const parsed = Number(value);
|
|
1067
1717
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
@@ -1194,10 +1844,7 @@ async function commandForNode(ctx, node, command, args = {}) {
|
|
|
1194
1844
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1195
1845
|
return ctx.transport.meshCommand(node.daemonId, command, args);
|
|
1196
1846
|
}
|
|
1197
|
-
|
|
1198
|
-
return ctx.transport.command(command, args);
|
|
1199
|
-
}
|
|
1200
|
-
throw new Error(`Command '${command}' requires daemon IPC/local transport for node '${node.id}'`);
|
|
1847
|
+
return ctx.transport.command(command, args);
|
|
1201
1848
|
}
|
|
1202
1849
|
function normalizePendingMeshCoordinatorEvents(value) {
|
|
1203
1850
|
const payload = unwrapCommandPayload(value);
|
|
@@ -1215,6 +1862,14 @@ function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
|
1215
1862
|
providerType: readString(metadataEvent.providerType),
|
|
1216
1863
|
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
1217
1864
|
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
1865
|
+
jobId: readString(metadataEvent.jobId),
|
|
1866
|
+
interactionId: readString(metadataEvent.interactionId),
|
|
1867
|
+
status: readString(metadataEvent.status),
|
|
1868
|
+
targetDaemonId: readString(metadataEvent.targetDaemonId),
|
|
1869
|
+
startedAt: readString(metadataEvent.startedAt),
|
|
1870
|
+
completedAt: readString(metadataEvent.completedAt),
|
|
1871
|
+
retryOfJobId: readString(metadataEvent.retryOfJobId),
|
|
1872
|
+
...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
|
|
1218
1873
|
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
1219
1874
|
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
1220
1875
|
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
@@ -1229,10 +1884,25 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
|
1229
1884
|
const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
|
|
1230
1885
|
if (ctx.transport instanceof IpcTransport) {
|
|
1231
1886
|
const surfacedEvents = [];
|
|
1887
|
+
const coordinatorDaemonId = readString(ctx.localDaemonId);
|
|
1888
|
+
const pendingEventArgs = {
|
|
1889
|
+
meshId: ctx.mesh.id,
|
|
1890
|
+
...coordinatorDaemonId ? { coordinatorDaemonId } : {}
|
|
1891
|
+
};
|
|
1232
1892
|
try {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1893
|
+
const localEvents = normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh);
|
|
1894
|
+
for (const event of localEvents) {
|
|
1895
|
+
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1896
|
+
if (!payload.event || !payload.meshId) continue;
|
|
1897
|
+
let injected = false;
|
|
1898
|
+
try {
|
|
1899
|
+
await ctx.transport.command("mesh_forward_event", payload);
|
|
1900
|
+
injected = true;
|
|
1901
|
+
} catch {
|
|
1902
|
+
}
|
|
1903
|
+
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
1904
|
+
if (!injected) surfacedEvents.push(event);
|
|
1905
|
+
}
|
|
1236
1906
|
} catch {
|
|
1237
1907
|
}
|
|
1238
1908
|
for (const node of ctx.mesh.nodes) {
|
|
@@ -1240,29 +1910,39 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
|
1240
1910
|
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
1241
1911
|
try {
|
|
1242
1912
|
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
1243
|
-
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events",
|
|
1913
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", pendingEventArgs)
|
|
1244
1914
|
).filter(matchesCurrentMesh);
|
|
1245
1915
|
if (remoteEvents.length === 0) continue;
|
|
1246
1916
|
for (const event of remoteEvents) {
|
|
1247
1917
|
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1248
1918
|
if (!payload.event || !payload.meshId) continue;
|
|
1249
1919
|
await ctx.transport.command("mesh_forward_event", payload);
|
|
1920
|
+
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
1250
1921
|
}
|
|
1251
1922
|
} catch {
|
|
1252
1923
|
}
|
|
1253
1924
|
}
|
|
1254
1925
|
try {
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1926
|
+
const localEvents = normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh);
|
|
1927
|
+
for (const event of localEvents) {
|
|
1928
|
+
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
1929
|
+
if (!payload.event || !payload.meshId) continue;
|
|
1930
|
+
let injected = false;
|
|
1931
|
+
try {
|
|
1932
|
+
await ctx.transport.command("mesh_forward_event", payload);
|
|
1933
|
+
injected = true;
|
|
1934
|
+
} catch {
|
|
1935
|
+
}
|
|
1936
|
+
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
1937
|
+
if (!injected) surfacedEvents.push(event);
|
|
1938
|
+
}
|
|
1258
1939
|
} catch {
|
|
1259
1940
|
}
|
|
1260
1941
|
return surfacedEvents;
|
|
1261
1942
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
return [];
|
|
1943
|
+
const events = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
|
|
1944
|
+
events.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1945
|
+
return events;
|
|
1266
1946
|
}
|
|
1267
1947
|
function isP2pTransportUnavailableError(error) {
|
|
1268
1948
|
return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
|
|
@@ -1277,11 +1957,14 @@ function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode) {
|
|
|
1277
1957
|
}
|
|
1278
1958
|
var MESH_STATUS_TOOL = {
|
|
1279
1959
|
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.",
|
|
1960
|
+
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
1961
|
inputSchema: {
|
|
1282
1962
|
type: "object",
|
|
1283
1963
|
properties: {
|
|
1284
|
-
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
|
|
1964
|
+
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
|
|
1965
|
+
includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." },
|
|
1966
|
+
compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Set false (or verbose=true) for the full dashboard-grade payload." },
|
|
1967
|
+
verbose: { type: "boolean", description: "Force the full payload; overrides compact." }
|
|
1285
1968
|
}
|
|
1286
1969
|
}
|
|
1287
1970
|
};
|
|
@@ -1301,14 +1984,22 @@ var MESH_ENQUEUE_TASK_TOOL = {
|
|
|
1301
1984
|
inputSchema: {
|
|
1302
1985
|
type: "object",
|
|
1303
1986
|
properties: {
|
|
1304
|
-
message: { type: "string", description: "The task instruction for the agent." }
|
|
1987
|
+
message: { type: "string", description: "The task instruction for the agent." },
|
|
1988
|
+
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." },
|
|
1989
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
|
|
1990
|
+
requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
|
|
1991
|
+
required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." },
|
|
1992
|
+
depends_on: { type: "array", items: { type: "string" }, description: "Task ids that must complete before this task becomes claimable. Cycles are rejected at enqueue." },
|
|
1993
|
+
dependsOn: { type: "array", items: { type: "string" }, description: "CamelCase alias for depends_on." },
|
|
1994
|
+
mission_id: { type: "string", description: "Mission this task belongs to (mesh_mission record id)." },
|
|
1995
|
+
missionId: { type: "string", description: "CamelCase alias for mission_id." }
|
|
1305
1996
|
},
|
|
1306
1997
|
required: ["message"]
|
|
1307
1998
|
}
|
|
1308
1999
|
};
|
|
1309
2000
|
var MESH_VIEW_QUEUE_TOOL = {
|
|
1310
2001
|
name: "mesh_view_queue",
|
|
1311
|
-
description: "View the mesh work queue with source-of-truth active counts separated from historical completed/failed/cancelled records.",
|
|
2002
|
+
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
2003
|
inputSchema: {
|
|
1313
2004
|
type: "object",
|
|
1314
2005
|
properties: {
|
|
@@ -1321,7 +2012,9 @@ var MESH_VIEW_QUEUE_TOOL = {
|
|
|
1321
2012
|
type: "string",
|
|
1322
2013
|
enum: ["all", "active", "historical"],
|
|
1323
2014
|
description: "Optional row view. active returns pending/assigned rows, historical returns completed/failed/cancelled rows, all returns every persisted queue row. Defaults to all for compatibility."
|
|
1324
|
-
}
|
|
2015
|
+
},
|
|
2016
|
+
compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Drops large historical (completed/failed/cancelled) queue row arrays, the full staleDirectWork orphan array (kept as staleDirectWorkSummary counts), and per-row maintenance cleanupCandidates in favor of counts; pending/assigned active rows are retained. Set false (or verbose=true) for the full dashboard-grade payload." },
|
|
2017
|
+
verbose: { type: "boolean", description: "Force the full payload; overrides compact." }
|
|
1325
2018
|
}
|
|
1326
2019
|
}
|
|
1327
2020
|
};
|
|
@@ -1339,7 +2032,7 @@ var MESH_QUEUE_CANCEL_TOOL = {
|
|
|
1339
2032
|
};
|
|
1340
2033
|
var MESH_QUEUE_REQUEUE_TOOL = {
|
|
1341
2034
|
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.",
|
|
2035
|
+
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
2036
|
inputSchema: {
|
|
1344
2037
|
type: "object",
|
|
1345
2038
|
properties: {
|
|
@@ -1348,7 +2041,8 @@ var MESH_QUEUE_REQUEUE_TOOL = {
|
|
|
1348
2041
|
target_node_id: { type: "string", description: "Optional replacement target node ID." },
|
|
1349
2042
|
target_session_id: { type: "string", description: "Optional replacement target runtime session ID." },
|
|
1350
2043
|
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." }
|
|
2044
|
+
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." },
|
|
2045
|
+
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
2046
|
},
|
|
1353
2047
|
required: ["task_id"]
|
|
1354
2048
|
}
|
|
@@ -1361,7 +2055,11 @@ var MESH_SEND_TASK_TOOL = {
|
|
|
1361
2055
|
properties: {
|
|
1362
2056
|
node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
|
|
1363
2057
|
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." }
|
|
2058
|
+
message: { type: "string", description: "Natural-language task to send to the agent." },
|
|
2059
|
+
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." },
|
|
2060
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
|
|
2061
|
+
mission_id: { type: "string", description: "Mission this task belongs to (mesh_mission record id). When set, the directly dispatched task is attributed to the mission task aggregates exactly like mesh_enqueue_task, including terminal completion. Omit for an unattributed direct dispatch." },
|
|
2062
|
+
missionId: { type: "string", description: "CamelCase alias for mission_id." }
|
|
1365
2063
|
},
|
|
1366
2064
|
required: ["node_id", "session_id", "message"]
|
|
1367
2065
|
}
|
|
@@ -1419,7 +2117,22 @@ var MESH_GIT_STATUS_TOOL = {
|
|
|
1419
2117
|
required: ["node_id"]
|
|
1420
2118
|
}
|
|
1421
2119
|
};
|
|
1422
|
-
var
|
|
2120
|
+
var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
2121
|
+
name: "mesh_fast_forward_node",
|
|
2122
|
+
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.",
|
|
2123
|
+
inputSchema: {
|
|
2124
|
+
type: "object",
|
|
2125
|
+
properties: {
|
|
2126
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
2127
|
+
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
2128
|
+
execute: { type: "boolean", description: "When true, apply the fast-forward if all safety gates pass. Defaults false/dry-run." },
|
|
2129
|
+
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
2130
|
+
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." }
|
|
2131
|
+
},
|
|
2132
|
+
required: ["node_id"]
|
|
2133
|
+
}
|
|
2134
|
+
};
|
|
2135
|
+
var MESH_CHECKPOINT_TOOL = {
|
|
1423
2136
|
name: "mesh_checkpoint",
|
|
1424
2137
|
description: "Create a git checkpoint (commit) on a mesh node workspace.",
|
|
1425
2138
|
inputSchema: {
|
|
@@ -1431,6 +2144,20 @@ var MESH_CHECKPOINT_TOOL = {
|
|
|
1431
2144
|
required: ["node_id", "message"]
|
|
1432
2145
|
}
|
|
1433
2146
|
};
|
|
2147
|
+
var MESH_MISSION_UPSERT_TOOL = {
|
|
2148
|
+
name: "mesh_mission_upsert",
|
|
2149
|
+
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.",
|
|
2150
|
+
inputSchema: {
|
|
2151
|
+
type: "object",
|
|
2152
|
+
properties: {
|
|
2153
|
+
mission_id: { type: "string", description: "Mission id to update. Omit to create a new mission." },
|
|
2154
|
+
title: { type: "string", description: "Short mission title." },
|
|
2155
|
+
goal: { type: "string", description: "Free-text mission goal/definition of done." },
|
|
2156
|
+
status: { type: "string", enum: ["active", "paused", "completed", "abandoned"], description: "Mission lifecycle status. Defaults to active on create." }
|
|
2157
|
+
},
|
|
2158
|
+
required: ["title"]
|
|
2159
|
+
}
|
|
2160
|
+
};
|
|
1434
2161
|
var MESH_APPROVE_TOOL = {
|
|
1435
2162
|
name: "mesh_approve",
|
|
1436
2163
|
description: "Approve or reject a pending action on a delegated agent session.",
|
|
@@ -1502,7 +2229,7 @@ var MESH_TASK_HISTORY_TOOL = {
|
|
|
1502
2229
|
type: "object",
|
|
1503
2230
|
properties: {
|
|
1504
2231
|
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." }
|
|
2232
|
+
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
2233
|
}
|
|
1507
2234
|
}
|
|
1508
2235
|
};
|
|
@@ -1522,7 +2249,7 @@ var MESH_RECONCILE_LEDGER_TOOL = {
|
|
|
1522
2249
|
};
|
|
1523
2250
|
var MESH_REFINE_NODE_TOOL = {
|
|
1524
2251
|
name: "mesh_refine_node",
|
|
1525
|
-
description: "The Refinery:
|
|
2252
|
+
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
2253
|
inputSchema: {
|
|
1527
2254
|
type: "object",
|
|
1528
2255
|
properties: {
|
|
@@ -1531,6 +2258,71 @@ var MESH_REFINE_NODE_TOOL = {
|
|
|
1531
2258
|
required: ["node_id"]
|
|
1532
2259
|
}
|
|
1533
2260
|
};
|
|
2261
|
+
var MESH_REFINE_BATCH_TOOL = {
|
|
2262
|
+
name: "mesh_refine_batch",
|
|
2263
|
+
description: "Batch Refinery: converge multiple sibling worktree nodes onto the base branch in one conflict-aware sequential pipeline. Orders nodes by change-area (non-submodule nodes first, submodule-touching nodes serialized last) so each merged sibling advances the base and the next node auto-rebases + re-checks patch-equivalence before its own merge. Each node runs the same validation/patch-equivalence/submodule-reachability/merge/cleanup gates as mesh_refine_node. Conflicting or blocked nodes are isolated as blocked_review while the rest of the batch proceeds. Defaults to dry-run (plan only); set execute=true to converge. Never force-pushes or resets.",
|
|
2264
|
+
inputSchema: {
|
|
2265
|
+
type: "object",
|
|
2266
|
+
properties: {
|
|
2267
|
+
node_ids: {
|
|
2268
|
+
type: "array",
|
|
2269
|
+
items: { type: "string" },
|
|
2270
|
+
description: "Optional explicit node IDs to converge, in any order (the tool computes the safe merge order). When omitted, all local worktree nodes that need convergence are auto-collected."
|
|
2271
|
+
},
|
|
2272
|
+
execute: { type: "boolean", description: "When true, run validation/rebase/merge for each node in order. Defaults false/dry-run." },
|
|
2273
|
+
dry_run: { type: "boolean", description: "Preview the ordering + per-node validation plan without executing. Defaults true unless execute=true; dry_run=true overrides execute." }
|
|
2274
|
+
},
|
|
2275
|
+
required: []
|
|
2276
|
+
}
|
|
2277
|
+
};
|
|
2278
|
+
var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
|
|
2279
|
+
name: "mesh_refine_config_schema",
|
|
2280
|
+
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.",
|
|
2281
|
+
inputSchema: { type: "object", properties: {} }
|
|
2282
|
+
};
|
|
2283
|
+
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
2284
|
+
name: "mesh_validate_refine_config",
|
|
2285
|
+
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
2286
|
+
inputSchema: {
|
|
2287
|
+
type: "object",
|
|
2288
|
+
properties: {
|
|
2289
|
+
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
2290
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
};
|
|
2294
|
+
var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
|
|
2295
|
+
name: "mesh_suggest_refine_config",
|
|
2296
|
+
description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
|
|
2297
|
+
inputSchema: {
|
|
2298
|
+
type: "object",
|
|
2299
|
+
properties: {
|
|
2300
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
};
|
|
2304
|
+
var MESH_REFINE_PLAN_TOOL = {
|
|
2305
|
+
name: "mesh_refine_plan",
|
|
2306
|
+
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.",
|
|
2307
|
+
inputSchema: {
|
|
2308
|
+
type: "object",
|
|
2309
|
+
properties: {
|
|
2310
|
+
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
2311
|
+
},
|
|
2312
|
+
required: ["node_id"]
|
|
2313
|
+
}
|
|
2314
|
+
};
|
|
2315
|
+
var MESH_REVIEW_INBOX_TOOL = {
|
|
2316
|
+
name: "mesh_review_inbox",
|
|
2317
|
+
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.",
|
|
2318
|
+
inputSchema: {
|
|
2319
|
+
type: "object",
|
|
2320
|
+
properties: {
|
|
2321
|
+
mesh_id: { type: "string", description: "Mesh ID (optional \u2014 inferred from active mesh if omitted)." }
|
|
2322
|
+
},
|
|
2323
|
+
required: []
|
|
2324
|
+
}
|
|
2325
|
+
};
|
|
1534
2326
|
var ALL_MESH_TOOLS = [
|
|
1535
2327
|
MESH_STATUS_TOOL,
|
|
1536
2328
|
MESH_LIST_NODES_TOOL,
|
|
@@ -1543,66 +2335,59 @@ var ALL_MESH_TOOLS = [
|
|
|
1543
2335
|
MESH_READ_DEBUG_TOOL,
|
|
1544
2336
|
MESH_LAUNCH_SESSION_TOOL,
|
|
1545
2337
|
MESH_GIT_STATUS_TOOL,
|
|
2338
|
+
MESH_FAST_FORWARD_NODE_TOOL,
|
|
1546
2339
|
MESH_CHECKPOINT_TOOL,
|
|
1547
2340
|
MESH_APPROVE_TOOL,
|
|
1548
2341
|
MESH_CLONE_NODE_TOOL,
|
|
1549
2342
|
MESH_REMOVE_NODE_TOOL,
|
|
1550
2343
|
MESH_REFINE_NODE_TOOL,
|
|
2344
|
+
MESH_REFINE_BATCH_TOOL,
|
|
2345
|
+
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
2346
|
+
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
2347
|
+
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
2348
|
+
MESH_REFINE_PLAN_TOOL,
|
|
1551
2349
|
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1552
2350
|
MESH_TASK_HISTORY_TOOL,
|
|
1553
|
-
MESH_RECONCILE_LEDGER_TOOL
|
|
2351
|
+
MESH_RECONCILE_LEDGER_TOOL,
|
|
2352
|
+
MESH_MISSION_UPSERT_TOOL,
|
|
2353
|
+
MESH_REVIEW_INBOX_TOOL
|
|
1554
2354
|
];
|
|
1555
|
-
async function meshStatus(ctx) {
|
|
2355
|
+
async function meshStatus(ctx, args = {}) {
|
|
2356
|
+
const rateResult = (0, import_daemon_core.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
|
|
2357
|
+
const compact = args.verbose === true ? false : args.compact ?? true;
|
|
1556
2358
|
await refreshMeshFromDaemon(ctx);
|
|
1557
2359
|
const { mesh, transport } = ctx;
|
|
1558
|
-
|
|
1559
|
-
const
|
|
1560
|
-
for (const node of mesh.nodes) {
|
|
2360
|
+
let ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
2361
|
+
const results = await Promise.all(mesh.nodes.map(async (node) => {
|
|
1561
2362
|
const entry = {
|
|
1562
2363
|
nodeId: node.id,
|
|
1563
2364
|
workspace: node.workspace,
|
|
2365
|
+
machine: buildNodeMachineIdentity(ctx, node),
|
|
2366
|
+
daemonId: readNodeDaemonId(node),
|
|
2367
|
+
machineId: readNodeMachineId(node),
|
|
1564
2368
|
...getNodeLaunchReadiness(node)
|
|
1565
2369
|
};
|
|
1566
2370
|
try {
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
includeSubmodules: autoDiscover,
|
|
1588
|
-
submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
|
|
1589
|
-
});
|
|
1590
|
-
const status = extractGitStatus(statusResult);
|
|
1591
|
-
const uncommittedChanges = countUncommittedChanges(status);
|
|
1592
|
-
const dirty = isGitStatusDirty(status);
|
|
1593
|
-
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
1594
|
-
entry.branch = status?.branch;
|
|
1595
|
-
entry.isDirty = dirty;
|
|
1596
|
-
entry.uncommittedChanges = uncommittedChanges;
|
|
1597
|
-
entry.branchConvergence = buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges);
|
|
1598
|
-
const submodules = extractSubmodules(statusResult, node.policy?.submoduleIgnorePaths || []);
|
|
1599
|
-
if (submodules && submodules.some((s) => s?.outOfSync)) {
|
|
1600
|
-
entry.submoduleWarning = "One or more submodules are out of sync with the parent repo. Run `git submodule update` or check deployment readiness.";
|
|
1601
|
-
entry.outOfSyncSubmodules = submodules.filter((s) => s?.outOfSync).map((s) => s.path);
|
|
1602
|
-
}
|
|
1603
|
-
} else {
|
|
1604
|
-
entry.health = "unknown";
|
|
1605
|
-
entry.note = "No daemonId available for cloud status probe";
|
|
2371
|
+
const autoDiscover = node.policy?.autoDiscoverSubmodules !== false;
|
|
2372
|
+
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
2373
|
+
workspace: node.workspace,
|
|
2374
|
+
refreshUpstream: true,
|
|
2375
|
+
includeSubmodules: autoDiscover,
|
|
2376
|
+
submoduleIgnorePaths: node.policy?.submoduleIgnorePaths || void 0
|
|
2377
|
+
});
|
|
2378
|
+
const status = extractGitStatus(statusResult);
|
|
2379
|
+
const uncommittedChanges = countUncommittedChanges(status);
|
|
2380
|
+
const dirty = isGitStatusDirty(status);
|
|
2381
|
+
entry.health = status?.isGitRepo ? dirty ? "dirty" : "online" : "degraded";
|
|
2382
|
+
assignFullGitSnapshot(entry, status);
|
|
2383
|
+
entry.branch = status?.branch;
|
|
2384
|
+
entry.isDirty = dirty;
|
|
2385
|
+
entry.uncommittedChanges = uncommittedChanges;
|
|
2386
|
+
entry.branchConvergence = buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges);
|
|
2387
|
+
const submodules = extractSubmodules(statusResult, node.policy?.submoduleIgnorePaths || []);
|
|
2388
|
+
if (submodules && submodules.some((s) => s?.outOfSync)) {
|
|
2389
|
+
entry.submoduleWarning = "One or more submodules are out of sync with the parent repo. Run `git submodule update` or check deployment readiness.";
|
|
2390
|
+
entry.outOfSyncSubmodules = submodules.filter((s) => s?.outOfSync).map((s) => s.path);
|
|
1606
2391
|
}
|
|
1607
2392
|
} catch (e) {
|
|
1608
2393
|
const failure = buildCoordinatorP2pRelayFailure(e, {
|
|
@@ -1626,7 +2411,7 @@ async function meshStatus(ctx) {
|
|
|
1626
2411
|
if (recoveryContext.consecutiveNodeFailures > 0) {
|
|
1627
2412
|
entry.recoveryHints = {
|
|
1628
2413
|
consecutiveFailures: recoveryContext.consecutiveNodeFailures,
|
|
1629
|
-
lastTaskMessage: recoveryContext.lastTaskMessage,
|
|
2414
|
+
lastTaskMessage: typeof recoveryContext.lastTaskMessage === "string" ? recoveryContext.lastTaskMessage.slice(0, 100) + (recoveryContext.lastTaskMessage.length > 100 ? "\u2026" : "") : recoveryContext.lastTaskMessage,
|
|
1630
2415
|
advice: recoveryContext.advice,
|
|
1631
2416
|
retryRecommended: recoveryContext.retryRecommended
|
|
1632
2417
|
};
|
|
@@ -1666,28 +2451,130 @@ async function meshStatus(ctx) {
|
|
|
1666
2451
|
}
|
|
1667
2452
|
const relatedRepos = await collectRelatedRepoStatuses(ctx, node);
|
|
1668
2453
|
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
1669
|
-
|
|
2454
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
2455
|
+
if (liveSessions.length > 0) {
|
|
2456
|
+
entry.sessions = liveSessions.map((s) => {
|
|
2457
|
+
const coordinatorMeshId = typeof s.coordinator?.meshId === "string" ? s.coordinator.meshId : void 0;
|
|
2458
|
+
const isSelfCoordinator = coordinatorMeshId === mesh.id;
|
|
2459
|
+
return {
|
|
2460
|
+
id: s.instanceId ?? s.id ?? s.sessionId,
|
|
2461
|
+
status: s.status ?? s.lifecycle ?? s.state,
|
|
2462
|
+
providerType: s.providerType ?? s.cliType ?? s.type,
|
|
2463
|
+
...s.activeChat?.status ? { chatStatus: s.activeChat.status } : {},
|
|
2464
|
+
...isSelfCoordinator ? { isSelfCoordinator: true, role: "coordinator" } : {}
|
|
2465
|
+
};
|
|
2466
|
+
}).filter((s) => s.id);
|
|
2467
|
+
}
|
|
2468
|
+
return entry;
|
|
2469
|
+
}));
|
|
2470
|
+
let ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
|
|
2471
|
+
let directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(mesh.id);
|
|
2472
|
+
const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, results, directDispatches, ledgerEntries);
|
|
2473
|
+
if (directReconciliation.reconciled > 0) {
|
|
2474
|
+
ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
|
|
2475
|
+
directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(mesh.id);
|
|
2476
|
+
ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
2477
|
+
}
|
|
2478
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2479
|
+
meshId: mesh.id,
|
|
2480
|
+
queue: (0, import_daemon_core.getQueue)(mesh.id),
|
|
2481
|
+
ledgerEntries,
|
|
2482
|
+
directDispatches,
|
|
2483
|
+
nodes: results
|
|
2484
|
+
});
|
|
2485
|
+
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
2486
|
+
const staleDirectWorkSummary = (0, import_daemon_core.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
|
|
2487
|
+
note: activeWorkEvidence.staleDirectWorkNote,
|
|
2488
|
+
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."
|
|
2489
|
+
});
|
|
2490
|
+
const coordinatorSessions = [];
|
|
2491
|
+
for (const nodeEntry of results) {
|
|
2492
|
+
const sessions = Array.isArray(nodeEntry.sessions) ? nodeEntry.sessions : [];
|
|
2493
|
+
for (const s of sessions) {
|
|
2494
|
+
if (s?.isSelfCoordinator === true && s.id) {
|
|
2495
|
+
coordinatorSessions.push({
|
|
2496
|
+
nodeId: nodeEntry.nodeId,
|
|
2497
|
+
sessionId: s.id,
|
|
2498
|
+
providerType: s.providerType,
|
|
2499
|
+
status: s.status
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
1670
2503
|
}
|
|
2504
|
+
const nodesForResponse = compact ? results.map((entry) => {
|
|
2505
|
+
if (!entry || typeof entry !== "object" || entry.git === void 0) return entry;
|
|
2506
|
+
const slimGit = buildCompactGitSnapshot(entry.git);
|
|
2507
|
+
return slimGit ? { ...entry, git: slimGit } : entry;
|
|
2508
|
+
}) : results;
|
|
1671
2509
|
const response = {
|
|
1672
2510
|
meshId: mesh.id,
|
|
1673
2511
|
meshName: mesh.name,
|
|
1674
2512
|
repoIdentity: mesh.repoIdentity,
|
|
1675
2513
|
policy: mesh.policy,
|
|
2514
|
+
payloadMode: compact ? "compact" : "full",
|
|
1676
2515
|
refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1677
2516
|
sourceOfTruth: {
|
|
1678
2517
|
membership: "coordinator_daemon_live_mesh",
|
|
1679
2518
|
currentStatus: "live_git_and_session_probes",
|
|
2519
|
+
activeWork: "mesh_queue_file_and_local_ledger",
|
|
1680
2520
|
historicalEvidenceOnly: ["recoveryHints", "ledgerSummary"]
|
|
1681
2521
|
},
|
|
1682
|
-
nodes:
|
|
1683
|
-
|
|
2522
|
+
nodes: nodesForResponse,
|
|
2523
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2524
|
+
staleDirectWorkSummary,
|
|
2525
|
+
...args.includeStaleDirectWorkDetails === true ? { staleDirectWork: activeWorkEvidence.staleDirectWork } : {},
|
|
2526
|
+
// terminalDirectWork is historical (completed/failed direct dispatches) — opt-in only.
|
|
2527
|
+
...args.includeTerminalDirectWork === true ? { terminalDirectWork: activeWorkEvidence.terminalDirectWork } : {},
|
|
2528
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
2529
|
+
...pollingGuidance ? { pollingGuidance } : {},
|
|
2530
|
+
...rateResult.rateLimitExceeded ? { pollingRateAdvisory: { type: "rate_limit_exceeded", tool: "mesh_status", callsInWindow: rateResult.callsInWindow, message: rateResult.advisory } } : {},
|
|
2531
|
+
branchConvergenceSummary: summarizeBranchConvergence(results),
|
|
2532
|
+
...coordinatorSessions.length > 0 ? {
|
|
2533
|
+
coordinatorSessions,
|
|
2534
|
+
selfIdentification: {
|
|
2535
|
+
meshId: mesh.id,
|
|
2536
|
+
coordinatorSessions,
|
|
2537
|
+
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."
|
|
2538
|
+
}
|
|
2539
|
+
} : {}
|
|
1684
2540
|
};
|
|
1685
2541
|
try {
|
|
1686
2542
|
response.ledgerSummary = ledgerSummary;
|
|
1687
2543
|
} catch {
|
|
1688
2544
|
}
|
|
2545
|
+
try {
|
|
2546
|
+
const missions = (0, import_daemon_core.getActiveMeshMissionSummaries)(mesh.id);
|
|
2547
|
+
if (missions.length > 0) {
|
|
2548
|
+
response.missions = missions.map((mission) => {
|
|
2549
|
+
try {
|
|
2550
|
+
return { ...mission, stats: (0, import_daemon_core.computeMeshMissionStats)(mesh.id, mission.id) };
|
|
2551
|
+
} catch {
|
|
2552
|
+
return mission;
|
|
2553
|
+
}
|
|
2554
|
+
});
|
|
2555
|
+
}
|
|
2556
|
+
} catch {
|
|
2557
|
+
}
|
|
1689
2558
|
try {
|
|
1690
2559
|
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
2560
|
+
const asyncRefineJobs = (0, import_daemon_core.buildMeshAsyncRefineJobs)({
|
|
2561
|
+
meshId: mesh.id,
|
|
2562
|
+
ledgerEntries,
|
|
2563
|
+
pendingEvents
|
|
2564
|
+
});
|
|
2565
|
+
if (asyncRefineJobs.length > 0) {
|
|
2566
|
+
if (compact) {
|
|
2567
|
+
const summary = (0, import_daemon_core.summarizeMeshAsyncRefineJobs)(asyncRefineJobs);
|
|
2568
|
+
if (summary.activeJobs.length > 0) response.asyncRefineJobs = summary.activeJobs;
|
|
2569
|
+
response.asyncRefineJobsSummary = {
|
|
2570
|
+
total: summary.total,
|
|
2571
|
+
byStatus: summary.byStatus,
|
|
2572
|
+
...summary.staleTerminal > 0 ? { staleTerminal: summary.staleTerminal } : {}
|
|
2573
|
+
};
|
|
2574
|
+
} else {
|
|
2575
|
+
response.asyncRefineJobs = asyncRefineJobs;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
1691
2578
|
if (pendingEvents.length > 0) {
|
|
1692
2579
|
response.pendingCoordinatorEvents = pendingEvents;
|
|
1693
2580
|
}
|
|
@@ -1697,12 +2584,31 @@ async function meshStatus(ctx) {
|
|
|
1697
2584
|
}
|
|
1698
2585
|
async function meshTaskHistory(ctx, args) {
|
|
1699
2586
|
const { mesh } = ctx;
|
|
1700
|
-
await drainCoordinatorPendingEvents(ctx);
|
|
2587
|
+
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
1701
2588
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
1702
2589
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
1703
|
-
const
|
|
2590
|
+
const rawEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
2591
|
+
const entries = rawEntries.map((e) => ({
|
|
2592
|
+
...e,
|
|
2593
|
+
payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
|
|
2594
|
+
}));
|
|
1704
2595
|
const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
1705
|
-
|
|
2596
|
+
let taskStats;
|
|
2597
|
+
try {
|
|
2598
|
+
const taskIds = [...new Set(rawEntries.map((e) => typeof e.payload?.taskId === "string" ? e.payload.taskId : "").filter(Boolean))];
|
|
2599
|
+
if (taskIds.length > 0) {
|
|
2600
|
+
const stats = (0, import_daemon_core.computeMeshTaskStats)(mesh.id, { taskIds });
|
|
2601
|
+
if (stats.length > 0) taskStats = stats;
|
|
2602
|
+
}
|
|
2603
|
+
} catch {
|
|
2604
|
+
}
|
|
2605
|
+
return JSON.stringify({
|
|
2606
|
+
meshId: mesh.id,
|
|
2607
|
+
entries,
|
|
2608
|
+
summary,
|
|
2609
|
+
...taskStats ? { taskStats } : {},
|
|
2610
|
+
...pendingEvents.length > 0 ? { pendingCoordinatorEvents: pendingEvents } : {}
|
|
2611
|
+
}, null, 2);
|
|
1706
2612
|
}
|
|
1707
2613
|
async function meshReconcileLedger(ctx, args) {
|
|
1708
2614
|
await refreshMeshFromDaemon(ctx);
|
|
@@ -1719,7 +2625,7 @@ async function meshReconcileLedger(ctx, args) {
|
|
|
1719
2625
|
for (const node of nodes) {
|
|
1720
2626
|
try {
|
|
1721
2627
|
if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
|
|
1722
|
-
const slice2 = (0, import_daemon_core.
|
|
2628
|
+
const slice2 = (0, import_daemon_core.readLedgerSliceFromStore)(ctx.mesh.id, queryArgs);
|
|
1723
2629
|
replicas.push((0, import_daemon_core.buildMeshLedgerReplicaEvidence)({
|
|
1724
2630
|
nodeId: node.id,
|
|
1725
2631
|
daemonId: node.daemonId,
|
|
@@ -1792,6 +2698,9 @@ async function meshListNodes(ctx) {
|
|
|
1792
2698
|
nodeId: n.id,
|
|
1793
2699
|
workspace: n.workspace,
|
|
1794
2700
|
repoRoot: n.repoRoot,
|
|
2701
|
+
daemonId: readNodeDaemonId(n),
|
|
2702
|
+
machineId: readNodeMachineId(n),
|
|
2703
|
+
machine: buildNodeMachineIdentity(ctx, n),
|
|
1795
2704
|
isLocalWorktree: n.isLocalWorktree,
|
|
1796
2705
|
policy: n.policy,
|
|
1797
2706
|
relatedRepos: readRelatedRepos(n),
|
|
@@ -1800,60 +2709,179 @@ async function meshListNodes(ctx) {
|
|
|
1800
2709
|
}))
|
|
1801
2710
|
}, null, 2);
|
|
1802
2711
|
}
|
|
2712
|
+
async function meshMissionUpsert(ctx, args) {
|
|
2713
|
+
try {
|
|
2714
|
+
const mission = (0, import_daemon_core.upsertMeshMission)(ctx.mesh.id, {
|
|
2715
|
+
id: readString(args.mission_id) || readString(args.missionId) || void 0,
|
|
2716
|
+
title: args.title,
|
|
2717
|
+
goal: typeof args.goal === "string" ? args.goal : void 0,
|
|
2718
|
+
status: readString(args.status) || void 0
|
|
2719
|
+
});
|
|
2720
|
+
return JSON.stringify({
|
|
2721
|
+
success: true,
|
|
2722
|
+
mission,
|
|
2723
|
+
nextAction: "Attach tasks with mesh_enqueue_task mission_id and depends_on. mesh_status shows live task aggregates for this mission."
|
|
2724
|
+
});
|
|
2725
|
+
} catch (e) {
|
|
2726
|
+
const message = e?.message || String(e);
|
|
2727
|
+
const code = message.includes("mission_title_required") ? "mission_title_required" : message.includes("invalid_mission_status") ? "invalid_mission_status" : void 0;
|
|
2728
|
+
return JSON.stringify({ success: false, ...code ? { code } : {}, error: message });
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
1803
2731
|
async function meshEnqueueTask(ctx, args) {
|
|
2732
|
+
const taskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2733
|
+
const requiredTags = (0, import_daemon_core.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
|
|
2734
|
+
const dependsOn = Array.isArray(args.dependsOn) ? args.dependsOn : Array.isArray(args.depends_on) ? args.depends_on : void 0;
|
|
2735
|
+
const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
|
|
1804
2736
|
try {
|
|
1805
|
-
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message);
|
|
1806
|
-
if (
|
|
1807
|
-
|
|
2737
|
+
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags, dependsOn, missionId });
|
|
2738
|
+
if (!(ctx.transport instanceof IpcTransport)) {
|
|
2739
|
+
const queueTrigger = await triggerMeshQueueAndReport(ctx);
|
|
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)
|
|
1808
2749
|
});
|
|
1809
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
1810
2750
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
});
|
|
2751
|
+
{
|
|
2752
|
+
const queueTrigger = await triggerMeshQueueAndReport(ctx);
|
|
1814
2753
|
const dispatchPromises = [];
|
|
1815
2754
|
for (const node of ctx.mesh.nodes) {
|
|
1816
2755
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1817
2756
|
if (isLocalNode || !node.daemonId) continue;
|
|
2757
|
+
if (!(0, import_daemon_core.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core.buildMeshNodeCapabilityTags)(node))) continue;
|
|
1818
2758
|
dispatchPromises.push(
|
|
1819
2759
|
ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
|
|
1820
2760
|
if (result.success) {
|
|
1821
2761
|
try {
|
|
2762
|
+
const providerType = result.providerType;
|
|
2763
|
+
const descriptor = summarizeTaskMessage(args.message);
|
|
1822
2764
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1823
2765
|
kind: "task_dispatched",
|
|
1824
2766
|
nodeId: node.id,
|
|
1825
2767
|
sessionId: result.sessionId,
|
|
1826
|
-
|
|
2768
|
+
providerType,
|
|
2769
|
+
payload: {
|
|
2770
|
+
source: "queue",
|
|
2771
|
+
via: "p2p_direct",
|
|
2772
|
+
taskId: task.id,
|
|
2773
|
+
message: args.message,
|
|
2774
|
+
taskTitle: descriptor.taskTitle,
|
|
2775
|
+
taskSummary: descriptor.taskSummary,
|
|
2776
|
+
...task.taskMode ? { taskMode: task.taskMode } : {},
|
|
2777
|
+
...providerType ? { providerType } : {},
|
|
2778
|
+
targetSessionId: result.sessionId
|
|
2779
|
+
}
|
|
1827
2780
|
});
|
|
1828
2781
|
} catch {
|
|
1829
2782
|
}
|
|
1830
2783
|
}
|
|
1831
|
-
}).catch(() => {
|
|
2784
|
+
}).catch((err) => {
|
|
2785
|
+
try {
|
|
2786
|
+
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2787
|
+
kind: "p2p_dispatch_failed",
|
|
2788
|
+
nodeId: node.id,
|
|
2789
|
+
payload: {
|
|
2790
|
+
source: "queue",
|
|
2791
|
+
via: "p2p_direct",
|
|
2792
|
+
taskId: task.id,
|
|
2793
|
+
error: err?.message || String(err),
|
|
2794
|
+
dispatchFailedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2795
|
+
}
|
|
2796
|
+
});
|
|
2797
|
+
} catch {
|
|
2798
|
+
}
|
|
1832
2799
|
})
|
|
1833
2800
|
);
|
|
1834
2801
|
}
|
|
1835
2802
|
Promise.all(dispatchPromises).catch(() => {
|
|
1836
2803
|
});
|
|
1837
|
-
return JSON.stringify({
|
|
2804
|
+
return JSON.stringify({
|
|
2805
|
+
success: true,
|
|
2806
|
+
source: "queue",
|
|
2807
|
+
taskId: task.id,
|
|
2808
|
+
status: task.status,
|
|
2809
|
+
taskMode: task.taskMode,
|
|
2810
|
+
requiredTags: task.requiredTags,
|
|
2811
|
+
queueTrigger,
|
|
2812
|
+
...buildQueueTriggerGuidance(queueTrigger)
|
|
2813
|
+
});
|
|
1838
2814
|
}
|
|
1839
|
-
return JSON.stringify({ success: true, taskId: task.id, status: task.status });
|
|
1840
2815
|
} catch (e) {
|
|
1841
|
-
|
|
2816
|
+
const message = e?.message || String(e);
|
|
2817
|
+
if (message.includes("live_debug_readonly_guardrail_violation")) {
|
|
2818
|
+
return JSON.stringify({ success: false, code: "live_debug_readonly_guardrail_violation", taskMode, error: message });
|
|
2819
|
+
}
|
|
2820
|
+
if (message.includes("dependency_cycle_detected")) {
|
|
2821
|
+
return JSON.stringify({ success: false, code: "dependency_cycle_detected", dependsOn, error: message });
|
|
2822
|
+
}
|
|
2823
|
+
return JSON.stringify({ success: false, error: message });
|
|
1842
2824
|
}
|
|
1843
2825
|
}
|
|
1844
2826
|
async function meshViewQueue(ctx, args) {
|
|
2827
|
+
const rateResult = (0, import_daemon_core.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
|
|
2828
|
+
const compact = args.verbose === true ? false : args.compact ?? true;
|
|
1845
2829
|
try {
|
|
2830
|
+
await refreshMeshFromDaemon(ctx);
|
|
1846
2831
|
const statusFilter = sanitizeQueueStatusFilter(args.status);
|
|
1847
2832
|
const view = normalizeQueueViewMode(args.view);
|
|
1848
|
-
const
|
|
2833
|
+
const rawQueue = (0, import_daemon_core.getQueue)(ctx.mesh.id);
|
|
2834
|
+
const statusById = new Map(rawQueue.map((task) => [task.id, task.status]));
|
|
2835
|
+
const withDependencies = rawQueue.map((task) => {
|
|
2836
|
+
if (!Array.isArray(task.dependsOn) || task.dependsOn.length === 0) return task;
|
|
2837
|
+
const depState = (0, import_daemon_core.describeTaskDependencyState)(task, statusById);
|
|
2838
|
+
return { ...task, ...depState };
|
|
2839
|
+
});
|
|
2840
|
+
const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness(withDependencies, ctx.mesh));
|
|
1849
2841
|
const queue = filterQueueForView(fullQueue, view, statusFilter);
|
|
1850
2842
|
const summary = buildQueueStatusSummary(fullQueue);
|
|
1851
2843
|
const visibleSummary = buildQueueStatusSummary(queue);
|
|
1852
2844
|
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
2845
|
+
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
2846
|
+
let ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
2847
|
+
let directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
|
|
2848
|
+
const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries);
|
|
2849
|
+
if (directReconciliation.reconciled > 0) {
|
|
2850
|
+
ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
2851
|
+
directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
|
|
2852
|
+
}
|
|
2853
|
+
(0, import_daemon_core.markStaleDirectDispatches)(ctx.mesh.id);
|
|
2854
|
+
directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
|
|
2855
|
+
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2856
|
+
meshId: ctx.mesh.id,
|
|
2857
|
+
queue: fullQueue,
|
|
2858
|
+
ledgerEntries,
|
|
2859
|
+
// Always pass MeshRuntimeStore records (may be empty). buildMeshActiveWork uses them for local
|
|
2860
|
+
// dispatches and falls through to ledger scan for remote P2P dispatches not in MeshRuntimeStore.
|
|
2861
|
+
directDispatches,
|
|
2862
|
+
nodes: liveNodes
|
|
2863
|
+
});
|
|
2864
|
+
const recentDispatchFailures = ledgerEntries.filter((e) => e.kind === "p2p_dispatch_failed").slice(-20).map((e) => ({
|
|
2865
|
+
nodeId: e.nodeId,
|
|
2866
|
+
taskId: e.payload?.taskId,
|
|
2867
|
+
error: e.payload?.error,
|
|
2868
|
+
via: e.payload?.via,
|
|
2869
|
+
failedAt: e.payload?.dispatchFailedAt || e.timestamp
|
|
2870
|
+
}));
|
|
1853
2871
|
const staleAssignedTasks = maintenance.staleAssignedTasks || [];
|
|
1854
2872
|
const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
2873
|
+
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
2874
|
+
const visibleQueue = compact ? queue.filter((task) => !HISTORICAL_QUEUE_STATUSES.has(String(task?.status || ""))) : queue;
|
|
2875
|
+
const wantActiveQueueArray = view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status));
|
|
2876
|
+
const wantHistoricalQueueArray = !compact && (view === "historical" || requestedHistoricalRows);
|
|
2877
|
+
const staleDirectWorkSummary = (0, import_daemon_core.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
|
|
2878
|
+
note: activeWorkEvidence.staleDirectWorkNote,
|
|
2879
|
+
detailHint: "Full stale direct entries are omitted from mesh_view_queue in compact mode. Call mesh_view_queue with verbose=true, or inspect mesh_task_history for ledger detail."
|
|
2880
|
+
});
|
|
2881
|
+
const maintenanceForResponse = compact ? buildCompactQueueMaintenanceReport(maintenance) : maintenance;
|
|
1855
2882
|
return JSON.stringify({
|
|
1856
2883
|
success: true,
|
|
2884
|
+
payloadMode: compact ? "compact" : "full",
|
|
1857
2885
|
sourceOfTruth: {
|
|
1858
2886
|
kind: "mesh_work_queue_file",
|
|
1859
2887
|
activeStatuses: ["pending", "assigned"],
|
|
@@ -1865,26 +2893,37 @@ async function meshViewQueue(ctx, args) {
|
|
|
1865
2893
|
statuses: statusFilter,
|
|
1866
2894
|
filtered: Boolean(statusFilter?.length) || view !== "all"
|
|
1867
2895
|
},
|
|
1868
|
-
queue,
|
|
1869
|
-
|
|
1870
|
-
|
|
2896
|
+
queue: visibleQueue,
|
|
2897
|
+
...compact ? { historicalRowsOmitted: true, historicalRowsHint: "Completed/failed/cancelled rows are omitted in compact mode; see historicalCounts. Call mesh_view_queue with verbose=true (or view=historical, compact=false) for full rows." } : {},
|
|
2898
|
+
activeWork: activeWorkEvidence.activeWork,
|
|
2899
|
+
staleDirectWorkSummary,
|
|
2900
|
+
...compact ? {} : { staleDirectWork: activeWorkEvidence.staleDirectWork },
|
|
2901
|
+
activeWorkSummary: activeWorkEvidence.summary,
|
|
2902
|
+
...pollingGuidance ? { pollingGuidance } : {},
|
|
2903
|
+
...rateResult.rateLimitExceeded ? { pollingRateAdvisory: { type: "rate_limit_exceeded", tool: "mesh_view_queue", callsInWindow: rateResult.callsInWindow, message: rateResult.advisory } } : {},
|
|
1871
2904
|
summary,
|
|
2905
|
+
visibleSummary,
|
|
1872
2906
|
activeCounts: summary.activeCounts,
|
|
1873
2907
|
historicalCounts: summary.historicalCounts,
|
|
1874
|
-
activeCount: summary.activeCount,
|
|
1875
|
-
historicalCount: summary.historicalCount,
|
|
1876
2908
|
visibleActiveCounts: visibleSummary.activeCounts,
|
|
1877
2909
|
visibleHistoricalCounts: visibleSummary.historicalCounts,
|
|
2910
|
+
activeCount: summary.activeCount,
|
|
2911
|
+
historicalCount: summary.historicalCount,
|
|
1878
2912
|
visibleActiveCount: visibleSummary.activeCount,
|
|
1879
2913
|
visibleHistoricalCount: visibleSummary.historicalCount,
|
|
1880
2914
|
staleAssignedTasks,
|
|
1881
2915
|
staleAssignedCount: maintenance.staleAssignedCount,
|
|
1882
|
-
queueMaintenance:
|
|
1883
|
-
cleanupDryRun:
|
|
1884
|
-
...
|
|
2916
|
+
queueMaintenance: maintenanceForResponse,
|
|
2917
|
+
cleanupDryRun: maintenanceForResponse,
|
|
2918
|
+
...recentDispatchFailures.length > 0 ? {
|
|
2919
|
+
recentDispatchFailures,
|
|
2920
|
+
dispatchFailureCount: recentDispatchFailures.length,
|
|
2921
|
+
dispatchFailureNote: "Remote P2P dispatch attempts that failed. Affected tasks remain pending and may require mesh_queue_requeue if no idle session picks them up."
|
|
2922
|
+
} : {},
|
|
2923
|
+
...wantActiveQueueArray ? {
|
|
1885
2924
|
activeQueue: queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")))
|
|
1886
2925
|
} : {},
|
|
1887
|
-
...
|
|
2926
|
+
...wantHistoricalQueueArray ? {
|
|
1888
2927
|
historicalQueue: queue.filter((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")))
|
|
1889
2928
|
} : {},
|
|
1890
2929
|
// Back-compat alias for callers already reading the first hardening payload.
|
|
@@ -1900,6 +2939,8 @@ async function meshQueueCancel(ctx, args) {
|
|
|
1900
2939
|
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
1901
2940
|
const task = (0, import_daemon_core.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
|
|
1902
2941
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
2942
|
+
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2943
|
+
});
|
|
1903
2944
|
return JSON.stringify({ success: true, task }, null, 2);
|
|
1904
2945
|
} catch (e) {
|
|
1905
2946
|
return JSON.stringify({ success: false, error: e.message });
|
|
@@ -1917,23 +2958,82 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
1917
2958
|
targetNodeId,
|
|
1918
2959
|
targetSessionId,
|
|
1919
2960
|
clearTargetNode: args.clear_target_node === true || args.clearTargetNode === true,
|
|
1920
|
-
clearTargetSession: targetSessionId ? false : !keepTargetSession
|
|
2961
|
+
clearTargetSession: targetSessionId ? false : !keepTargetSession,
|
|
2962
|
+
force: args.force === true
|
|
1921
2963
|
});
|
|
1922
2964
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
1923
|
-
if (
|
|
1924
|
-
|
|
1925
|
-
|
|
2965
|
+
if (task.status === "failed" && task.cancelReason?.startsWith("max_retries_exceeded")) {
|
|
2966
|
+
return JSON.stringify({
|
|
2967
|
+
success: false,
|
|
2968
|
+
code: "max_retries_exceeded",
|
|
2969
|
+
error: task.cancelReason,
|
|
2970
|
+
task,
|
|
2971
|
+
hint: "Use force=true to bypass the retry cap for explicit operator recovery."
|
|
2972
|
+
}, null, 2);
|
|
1926
2973
|
}
|
|
2974
|
+
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2975
|
+
});
|
|
1927
2976
|
return JSON.stringify({ success: true, task }, null, 2);
|
|
1928
2977
|
} catch (e) {
|
|
1929
2978
|
return JSON.stringify({ success: false, error: e.message });
|
|
1930
2979
|
}
|
|
1931
2980
|
}
|
|
1932
2981
|
async function meshSendTask(ctx, args) {
|
|
2982
|
+
const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
2983
|
+
const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
|
|
2984
|
+
const modeValidation = (0, import_daemon_core.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
|
|
2985
|
+
if (!modeValidation.valid) {
|
|
2986
|
+
return JSON.stringify({
|
|
2987
|
+
success: false,
|
|
2988
|
+
code: "live_debug_readonly_guardrail_violation",
|
|
2989
|
+
taskMode: modeValidation.taskMode || requestedTaskMode,
|
|
2990
|
+
violations: modeValidation.violations,
|
|
2991
|
+
allowedOperations: modeValidation.allowedOperations,
|
|
2992
|
+
error: `live_debug_readonly_guardrail_violation: forbidden operations (${modeValidation.violations.join(", ")})`
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2995
|
+
const taskMode = modeValidation.taskMode;
|
|
1933
2996
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
1934
2997
|
if (node.policy?.readOnly) {
|
|
1935
2998
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
1936
2999
|
}
|
|
3000
|
+
let explicitTargetSession;
|
|
3001
|
+
if (args.session_id && isWorkerTaskMode(taskMode)) {
|
|
3002
|
+
try {
|
|
3003
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
3004
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
3005
|
+
explicitTargetSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
3006
|
+
if (explicitTargetSession && isMeshCoordinatorSessionRecord(explicitTargetSession)) {
|
|
3007
|
+
return JSON.stringify({
|
|
3008
|
+
success: false,
|
|
3009
|
+
recoverable: true,
|
|
3010
|
+
code: "mesh_target_session_is_coordinator",
|
|
3011
|
+
reason: "mesh_target_session_is_coordinator",
|
|
3012
|
+
nodeId: args.node_id,
|
|
3013
|
+
sessionId: args.session_id,
|
|
3014
|
+
taskMode: taskMode || "unspecified",
|
|
3015
|
+
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.`,
|
|
3016
|
+
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.`
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
if (explicitTargetSession && isUnmanagedSessionRecord(explicitTargetSession)) {
|
|
3020
|
+
return JSON.stringify({
|
|
3021
|
+
success: false,
|
|
3022
|
+
recoverable: true,
|
|
3023
|
+
code: "mesh_target_session_unmanaged",
|
|
3024
|
+
reason: "mesh_target_session_unmanaged",
|
|
3025
|
+
nodeId: args.node_id,
|
|
3026
|
+
sessionId: args.session_id,
|
|
3027
|
+
taskMode: taskMode || "unspecified",
|
|
3028
|
+
unsafeTranscriptAlias: true,
|
|
3029
|
+
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.`,
|
|
3030
|
+
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.`
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
} catch {
|
|
3034
|
+
explicitTargetSession = void 0;
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
1937
3037
|
const duplicate = hasRecentDuplicateDispatch(ctx, args);
|
|
1938
3038
|
if (duplicate.duplicate) {
|
|
1939
3039
|
return JSON.stringify({
|
|
@@ -1953,47 +3053,83 @@ async function meshSendTask(ctx, args) {
|
|
|
1953
3053
|
});
|
|
1954
3054
|
}
|
|
1955
3055
|
try {
|
|
1956
|
-
if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
1957
|
-
const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
|
|
1958
|
-
meshId: ctx.mesh.id,
|
|
1959
|
-
message: args.message,
|
|
1960
|
-
targetNodeId: args.node_id
|
|
1961
|
-
});
|
|
1962
|
-
return JSON.stringify(res);
|
|
1963
|
-
}
|
|
1964
3056
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
1965
3057
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
1966
|
-
const cached =
|
|
3058
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id || ""));
|
|
3059
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
3060
|
+
const coordinatorDaemonId = resolveCoordinatorNode(ctx)?.daemonId || ctx.localDaemonId;
|
|
1967
3061
|
const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
|
|
1968
3062
|
session_id: args.session_id,
|
|
1969
3063
|
message: args.message,
|
|
1970
|
-
providerType: cached?.providerType
|
|
3064
|
+
providerType: cached?.providerType,
|
|
3065
|
+
verifiedSession: explicitTargetSession,
|
|
3066
|
+
meshContext: {
|
|
3067
|
+
meshId: ctx.mesh.id,
|
|
3068
|
+
nodeId: args.node_id,
|
|
3069
|
+
taskId,
|
|
3070
|
+
...coordinatorDaemonId ? { coordinatorDaemonId } : {}
|
|
3071
|
+
}
|
|
1971
3072
|
});
|
|
1972
3073
|
if (result2.success) {
|
|
1973
3074
|
const dispatchedSessionId = args.session_id || result2.sessionId;
|
|
3075
|
+
const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1974
3076
|
try {
|
|
3077
|
+
const providerType = result2.providerType || cached?.providerType;
|
|
1975
3078
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1976
3079
|
kind: "task_dispatched",
|
|
1977
3080
|
nodeId: args.node_id,
|
|
1978
3081
|
sessionId: dispatchedSessionId,
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
3082
|
+
providerType,
|
|
3083
|
+
payload: buildDirectTaskPayload(args.message, "p2p_direct", {
|
|
3084
|
+
taskId,
|
|
3085
|
+
taskMode,
|
|
3086
|
+
providerType,
|
|
3087
|
+
targetSessionId: dispatchedSessionId
|
|
3088
|
+
})
|
|
3089
|
+
});
|
|
3090
|
+
(0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
|
|
3091
|
+
taskId,
|
|
3092
|
+
nodeId: args.node_id,
|
|
3093
|
+
sessionId: dispatchedSessionId,
|
|
3094
|
+
providerType: providerType || void 0,
|
|
3095
|
+
message: args.message,
|
|
3096
|
+
taskMode: taskMode || void 0,
|
|
3097
|
+
via: "p2p_direct",
|
|
3098
|
+
dispatchedAt
|
|
1984
3099
|
});
|
|
3100
|
+
if (missionId) {
|
|
3101
|
+
(0, import_daemon_core.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
|
|
3102
|
+
id: taskId,
|
|
3103
|
+
missionId,
|
|
3104
|
+
assignedNodeId: args.node_id,
|
|
3105
|
+
assignedSessionId: dispatchedSessionId,
|
|
3106
|
+
taskMode,
|
|
3107
|
+
dispatchedAt
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
1985
3110
|
} catch {
|
|
1986
3111
|
}
|
|
1987
3112
|
}
|
|
1988
|
-
return JSON.stringify({
|
|
3113
|
+
return JSON.stringify({
|
|
3114
|
+
...result2,
|
|
3115
|
+
nodeId: args.node_id,
|
|
3116
|
+
sessionId: result2.success ? args.session_id || result2.sessionId : args.session_id,
|
|
3117
|
+
...result2.success ? { source: "direct", taskId } : {},
|
|
3118
|
+
taskMode,
|
|
3119
|
+
...result2.success && result2.providerType ? { providerType: result2.providerType } : {},
|
|
3120
|
+
dispatched: result2.success === true
|
|
3121
|
+
});
|
|
1989
3122
|
}
|
|
1990
|
-
if (args.session_id
|
|
1991
|
-
const cached =
|
|
3123
|
+
if (args.session_id) {
|
|
3124
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
|
|
1992
3125
|
let resolvedProviderType = cached?.providerType || "";
|
|
1993
3126
|
if (!resolvedProviderType) {
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
3127
|
+
let explicitSession = explicitTargetSession;
|
|
3128
|
+
if (!explicitSession) {
|
|
3129
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
3130
|
+
const sessions = extractStatusMetadataSessions(statusResult);
|
|
3131
|
+
explicitSession = sessions.find((session) => readSessionRecordId(session) === args.session_id);
|
|
3132
|
+
}
|
|
1997
3133
|
if (!explicitSession) {
|
|
1998
3134
|
return JSON.stringify({
|
|
1999
3135
|
success: false,
|
|
@@ -2008,11 +3144,40 @@ async function meshSendTask(ctx, args) {
|
|
|
2008
3144
|
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
3145
|
});
|
|
2010
3146
|
}
|
|
3147
|
+
if (isMeshCoordinatorSessionRecord(explicitSession)) {
|
|
3148
|
+
return JSON.stringify({
|
|
3149
|
+
success: false,
|
|
3150
|
+
recoverable: true,
|
|
3151
|
+
code: "mesh_target_session_is_coordinator",
|
|
3152
|
+
reason: "mesh_target_session_is_coordinator",
|
|
3153
|
+
nodeId: args.node_id,
|
|
3154
|
+
sessionId: args.session_id,
|
|
3155
|
+
taskMode: taskMode || "unspecified",
|
|
3156
|
+
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.`,
|
|
3157
|
+
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.`
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
3160
|
+
if (isUnmanagedSessionRecord(explicitSession)) {
|
|
3161
|
+
return JSON.stringify({
|
|
3162
|
+
success: false,
|
|
3163
|
+
recoverable: true,
|
|
3164
|
+
code: "mesh_target_session_unmanaged",
|
|
3165
|
+
reason: "mesh_target_session_unmanaged",
|
|
3166
|
+
nodeId: args.node_id,
|
|
3167
|
+
sessionId: args.session_id,
|
|
3168
|
+
taskMode: taskMode || "unspecified",
|
|
3169
|
+
unsafeTranscriptAlias: true,
|
|
3170
|
+
unsafeDelegateTarget: true,
|
|
3171
|
+
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.`,
|
|
3172
|
+
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.`
|
|
3173
|
+
});
|
|
3174
|
+
}
|
|
2011
3175
|
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2012
3176
|
if (resolvedProviderType) {
|
|
2013
3177
|
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
|
|
2014
3178
|
providerType: resolvedProviderType,
|
|
2015
|
-
providerSessionId: readString(explicitSession?.providerSessionId) || void 0
|
|
3179
|
+
providerSessionId: readString(explicitSession?.providerSessionId) || void 0,
|
|
3180
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
2016
3181
|
});
|
|
2017
3182
|
}
|
|
2018
3183
|
}
|
|
@@ -2030,17 +3195,58 @@ async function meshSendTask(ctx, args) {
|
|
|
2030
3195
|
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
3196
|
});
|
|
2032
3197
|
}
|
|
3198
|
+
if (explicitTargetSession && !isIdleSessionRecord(explicitTargetSession) && !isTerminalSessionRecord(explicitTargetSession)) {
|
|
3199
|
+
const sessionStatus = typeof explicitTargetSession?.status === "string" ? explicitTargetSession.status : "unknown";
|
|
3200
|
+
const { createSessionDelivery: createDelivery, resolveDeliveryDecision } = await import("@adhdev/daemon-core");
|
|
3201
|
+
const policyResult = resolveDeliveryDecision(sessionStatus, { kind: "task" });
|
|
3202
|
+
if (policyResult.decision === "queued") {
|
|
3203
|
+
const delivery = createDelivery({
|
|
3204
|
+
meshId: ctx.mesh.id,
|
|
3205
|
+
nodeId: args.node_id,
|
|
3206
|
+
sessionId: args.session_id,
|
|
3207
|
+
providerType: resolvedProviderType,
|
|
3208
|
+
kind: "task",
|
|
3209
|
+
message: args.message,
|
|
3210
|
+
status: "queued"
|
|
3211
|
+
});
|
|
3212
|
+
return JSON.stringify({
|
|
3213
|
+
success: true,
|
|
3214
|
+
dispatched: false,
|
|
3215
|
+
decision: "queued_delivery",
|
|
3216
|
+
deliveryId: delivery.id,
|
|
3217
|
+
reason: policyResult.reason,
|
|
3218
|
+
nodeId: args.node_id,
|
|
3219
|
+
sessionId: args.session_id,
|
|
3220
|
+
sessionStatus,
|
|
3221
|
+
taskMode: taskMode || void 0,
|
|
3222
|
+
message: policyResult.message,
|
|
3223
|
+
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.`
|
|
3224
|
+
});
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
const sessionWasIdle = explicitTargetSession ? isIdleSessionRecord(explicitTargetSession) : false;
|
|
3228
|
+
const taskId = (0, import_node_crypto.randomUUID)();
|
|
3229
|
+
const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3230
|
+
const coordinatorDaemonId = resolveCoordinatorNode(ctx)?.daemonId || ctx.localDaemonId;
|
|
2033
3231
|
const dispatchResult = await commandForNode(ctx, node, "agent_command", {
|
|
2034
3232
|
targetSessionId: args.session_id,
|
|
2035
3233
|
agentType: resolvedProviderType,
|
|
2036
3234
|
cliType: resolvedProviderType,
|
|
2037
3235
|
providerType: resolvedProviderType,
|
|
2038
3236
|
action: "send_chat",
|
|
2039
|
-
message: args.message
|
|
3237
|
+
message: args.message,
|
|
3238
|
+
meshContext: {
|
|
3239
|
+
meshId: ctx.mesh.id,
|
|
3240
|
+
nodeId: args.node_id,
|
|
3241
|
+
taskId,
|
|
3242
|
+
...coordinatorDaemonId ? { coordinatorDaemonId } : {}
|
|
3243
|
+
}
|
|
2040
3244
|
});
|
|
2041
3245
|
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
2042
3246
|
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
3247
|
+
const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
|
|
2043
3248
|
return JSON.stringify({
|
|
3249
|
+
...source && typeof source === "object" ? source : {},
|
|
2044
3250
|
success: false,
|
|
2045
3251
|
nodeId: args.node_id,
|
|
2046
3252
|
sessionId: args.session_id,
|
|
@@ -2053,22 +3259,92 @@ async function meshSendTask(ctx, args) {
|
|
|
2053
3259
|
nodeId: args.node_id,
|
|
2054
3260
|
sessionId: args.session_id,
|
|
2055
3261
|
providerType: resolvedProviderType,
|
|
2056
|
-
payload:
|
|
3262
|
+
payload: buildDirectTaskPayload(args.message, "local_direct", {
|
|
3263
|
+
taskId,
|
|
3264
|
+
taskMode,
|
|
3265
|
+
providerType: resolvedProviderType,
|
|
3266
|
+
targetSessionId: args.session_id,
|
|
3267
|
+
dispatchedToIdleSession: sessionWasIdle
|
|
3268
|
+
})
|
|
3269
|
+
});
|
|
3270
|
+
} catch {
|
|
3271
|
+
}
|
|
3272
|
+
(0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
|
|
3273
|
+
taskId,
|
|
3274
|
+
nodeId: args.node_id,
|
|
3275
|
+
sessionId: args.session_id,
|
|
3276
|
+
providerType: resolvedProviderType || void 0,
|
|
3277
|
+
message: args.message,
|
|
3278
|
+
taskMode: taskMode || void 0,
|
|
3279
|
+
via: "local_direct",
|
|
3280
|
+
dispatchedToIdleSession: sessionWasIdle,
|
|
3281
|
+
dispatchedAt
|
|
3282
|
+
});
|
|
3283
|
+
if (missionId) {
|
|
3284
|
+
try {
|
|
3285
|
+
(0, import_daemon_core.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
|
|
3286
|
+
id: taskId,
|
|
3287
|
+
missionId,
|
|
3288
|
+
assignedNodeId: args.node_id,
|
|
3289
|
+
assignedSessionId: args.session_id,
|
|
3290
|
+
taskMode,
|
|
3291
|
+
dispatchedAt
|
|
3292
|
+
});
|
|
3293
|
+
} catch {
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
let deliveryId;
|
|
3297
|
+
try {
|
|
3298
|
+
const { createSessionDelivery: createDelivery } = await import("@adhdev/daemon-core");
|
|
3299
|
+
const delivery = createDelivery({
|
|
3300
|
+
meshId: ctx.mesh.id,
|
|
3301
|
+
nodeId: args.node_id,
|
|
3302
|
+
sessionId: args.session_id,
|
|
3303
|
+
providerType: resolvedProviderType || void 0,
|
|
3304
|
+
taskId,
|
|
3305
|
+
kind: "task",
|
|
3306
|
+
message: args.message,
|
|
3307
|
+
status: sessionWasIdle ? "delivered" : "delivering"
|
|
2057
3308
|
});
|
|
3309
|
+
deliveryId = delivery.id;
|
|
2058
3310
|
} catch {
|
|
2059
3311
|
}
|
|
2060
|
-
return JSON.stringify({
|
|
3312
|
+
return JSON.stringify({
|
|
3313
|
+
success: true,
|
|
3314
|
+
dispatched: true,
|
|
3315
|
+
decision: "immediate",
|
|
3316
|
+
source: "direct",
|
|
3317
|
+
taskId,
|
|
3318
|
+
deliveryId,
|
|
3319
|
+
taskMode,
|
|
3320
|
+
providerType: resolvedProviderType,
|
|
3321
|
+
nodeId: args.node_id,
|
|
3322
|
+
sessionId: args.session_id,
|
|
3323
|
+
...sessionWasIdle ? {
|
|
3324
|
+
dispatchAcknowledgementRisk: true,
|
|
3325
|
+
dispatchAcknowledgementRiskReason: "session_was_idle_at_dispatch",
|
|
3326
|
+
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.`
|
|
3327
|
+
} : {}
|
|
3328
|
+
});
|
|
2061
3329
|
}
|
|
2062
3330
|
const task = (0, import_daemon_core.enqueueTask)(ctx.mesh.id, args.message, {
|
|
2063
3331
|
targetNodeId: args.node_id,
|
|
2064
|
-
targetSessionId: args.session_id
|
|
3332
|
+
targetSessionId: args.session_id,
|
|
3333
|
+
taskMode,
|
|
3334
|
+
...missionId ? { missionId } : {}
|
|
2065
3335
|
});
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
3336
|
+
const queueTrigger = await triggerMeshQueueAndReport(ctx);
|
|
3337
|
+
const pendingEvents = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId);
|
|
3338
|
+
const result = {
|
|
3339
|
+
success: true,
|
|
3340
|
+
source: "queue",
|
|
3341
|
+
nodeId: args.node_id,
|
|
3342
|
+
taskId: task.id,
|
|
3343
|
+
status: task.status,
|
|
3344
|
+
taskMode: task.taskMode,
|
|
3345
|
+
queueTrigger,
|
|
3346
|
+
...buildQueueTriggerGuidance(queueTrigger)
|
|
3347
|
+
};
|
|
2072
3348
|
if (pendingEvents.length > 0) {
|
|
2073
3349
|
result.pendingCoordinatorEvents = pendingEvents;
|
|
2074
3350
|
}
|
|
@@ -2088,88 +3364,59 @@ async function meshReadChat(ctx, args) {
|
|
|
2088
3364
|
if (!node) {
|
|
2089
3365
|
return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
|
|
2090
3366
|
}
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
3367
|
+
await drainCoordinatorPendingEvents(ctx, { nodeIds: [args.node_id] });
|
|
3368
|
+
const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
|
|
3369
|
+
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
3370
|
+
const result = await commandForNode(ctx, node, "read_chat", {
|
|
3371
|
+
sessionId: args.session_id,
|
|
3372
|
+
targetSessionId: args.session_id,
|
|
3373
|
+
workspace: node.workspace,
|
|
3374
|
+
...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
|
|
3375
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
3376
|
+
tailLimit: args.tail ?? 10
|
|
3377
|
+
});
|
|
3378
|
+
const payload = annotateRapidReadChatAdvisory(unwrapCommandPayload(result), {
|
|
3379
|
+
key: `mesh:${args.node_id}:${args.session_id}`,
|
|
3380
|
+
toolName: "mesh_read_chat",
|
|
3381
|
+
completionCallbackExpected: true
|
|
3382
|
+
});
|
|
3383
|
+
const useCompact = args.compact !== false;
|
|
3384
|
+
if (useCompact) {
|
|
3385
|
+
const compactPayload = compactChatPayload(payload, {
|
|
3386
|
+
nodeId: args.node_id,
|
|
2098
3387
|
sessionId: args.session_id,
|
|
2099
|
-
|
|
2100
|
-
workspace: node.workspace,
|
|
2101
|
-
...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
|
|
2102
|
-
...providerSessionId ? { providerSessionId } : {},
|
|
2103
|
-
tailLimit: args.tail ?? 10
|
|
3388
|
+
limit: args.tail ?? 10
|
|
2104
3389
|
});
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
if (args.compact) {
|
|
2111
|
-
const compactPayload = compactChatPayload(payload, {
|
|
2112
|
-
nodeId: args.node_id,
|
|
2113
|
-
sessionId: args.session_id,
|
|
2114
|
-
limit: args.tail ?? 10
|
|
2115
|
-
});
|
|
2116
|
-
return JSON.stringify(
|
|
2117
|
-
payload.pollingAdvisory ? { ...compactPayload, pollingAdvisory: payload.pollingAdvisory } : compactPayload,
|
|
2118
|
-
null,
|
|
2119
|
-
2
|
|
2120
|
-
);
|
|
2121
|
-
}
|
|
2122
|
-
return JSON.stringify(payload, null, 2);
|
|
2123
|
-
} else if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
2124
|
-
try {
|
|
2125
|
-
const targetId = `${node.daemonId}:session:${args.session_id}`;
|
|
2126
|
-
const res = await ctx.transport.readChat(targetId, {
|
|
2127
|
-
limit: args.tail ?? 10,
|
|
2128
|
-
sessionId: args.session_id
|
|
2129
|
-
});
|
|
2130
|
-
return JSON.stringify(res, null, 2);
|
|
2131
|
-
} catch (e) {
|
|
2132
|
-
return JSON.stringify({ success: false, error: e.message });
|
|
2133
|
-
}
|
|
2134
|
-
} else {
|
|
2135
|
-
return JSON.stringify({ error: "Cloud mesh read_chat requires node daemonId" });
|
|
3390
|
+
return JSON.stringify(
|
|
3391
|
+
payload.pollingAdvisory ? { ...compactPayload, pollingAdvisory: payload.pollingAdvisory } : compactPayload,
|
|
3392
|
+
null,
|
|
3393
|
+
2
|
|
3394
|
+
);
|
|
2136
3395
|
}
|
|
3396
|
+
return JSON.stringify(payload, null, 2);
|
|
2137
3397
|
}
|
|
2138
3398
|
async function meshReadDebug(ctx, args) {
|
|
2139
3399
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
return JSON.stringify(payload, null, 2);
|
|
2155
|
-
} else if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
2156
|
-
try {
|
|
2157
|
-
const targetId = `${node.daemonId}:session:${args.session_id}`;
|
|
2158
|
-
const res = await ctx.transport.getChatDebugBundle(targetId, {
|
|
2159
|
-
sessionId: args.session_id,
|
|
2160
|
-
tailLimit: args.tail ?? 40,
|
|
2161
|
-
delivery: args.delivery
|
|
2162
|
-
});
|
|
2163
|
-
return JSON.stringify(res, null, 2);
|
|
2164
|
-
} catch (e) {
|
|
2165
|
-
return JSON.stringify({ success: false, error: e.message });
|
|
2166
|
-
}
|
|
2167
|
-
}
|
|
2168
|
-
return JSON.stringify({ error: "Cloud mesh read_debug requires node daemonId" });
|
|
3400
|
+
const cached = resolveMeshSessionProviderMetadata(ctx, args.node_id, args.session_id);
|
|
3401
|
+
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached?.providerSessionId;
|
|
3402
|
+
const delivery = args.delivery === "inline" ? void 0 : "daemon_file";
|
|
3403
|
+
const result = await commandForNode(ctx, node, "get_chat_debug_bundle", {
|
|
3404
|
+
sessionId: args.session_id,
|
|
3405
|
+
targetSessionId: args.session_id,
|
|
3406
|
+
workspace: node.workspace,
|
|
3407
|
+
...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
|
|
3408
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
3409
|
+
tailLimit: args.tail ?? 40,
|
|
3410
|
+
...delivery ? { delivery } : {}
|
|
3411
|
+
});
|
|
3412
|
+
const payload = unwrapCommandPayload(result);
|
|
3413
|
+
return JSON.stringify(payload, null, 2);
|
|
2169
3414
|
}
|
|
2170
3415
|
async function meshLaunchSession(ctx, args) {
|
|
2171
3416
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2172
|
-
|
|
3417
|
+
const bootstrapBlock = getWorktreeBootstrapLaunchBlock(node, ctx.mesh.policy);
|
|
3418
|
+
if (bootstrapBlock) return JSON.stringify(bootstrapBlock, null, 2);
|
|
3419
|
+
{
|
|
2173
3420
|
let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
|
|
2174
3421
|
if (!resolvedProviderType) {
|
|
2175
3422
|
const providerPriority = readProviderPriority(node.policy);
|
|
@@ -2203,6 +3450,9 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2203
3450
|
cliType: resolvedProviderType,
|
|
2204
3451
|
dir: node.workspace,
|
|
2205
3452
|
settings: {
|
|
3453
|
+
// Worker launch envelope (A5): structured metadata so worker sessions
|
|
3454
|
+
// know their role and can route completion events back correctly.
|
|
3455
|
+
role: "worker",
|
|
2206
3456
|
meshNodeFor: ctx.mesh.id,
|
|
2207
3457
|
meshNodeId: args.node_id,
|
|
2208
3458
|
spawnedSessionVisibility,
|
|
@@ -2224,7 +3474,8 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2224
3474
|
if (runtimeSessionId) {
|
|
2225
3475
|
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, runtimeSessionId), {
|
|
2226
3476
|
providerType: resolvedProviderType,
|
|
2227
|
-
...providerSessionId ? { providerSessionId } : {}
|
|
3477
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
3478
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
2228
3479
|
});
|
|
2229
3480
|
}
|
|
2230
3481
|
try {
|
|
@@ -2237,63 +3488,14 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2237
3488
|
});
|
|
2238
3489
|
} catch {
|
|
2239
3490
|
}
|
|
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
|
-
}
|
|
3491
|
+
const queueTrigger = await triggerMeshQueueAndReport(ctx, node, { localNode: isLocalNode });
|
|
2247
3492
|
return JSON.stringify({
|
|
2248
3493
|
...launchPayload,
|
|
2249
3494
|
resolvedProviderType,
|
|
2250
|
-
...providerSessionId ? { providerSessionId } : {}
|
|
3495
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
3496
|
+
queueTrigger,
|
|
3497
|
+
...buildQueueTriggerGuidance(queueTrigger)
|
|
2251
3498
|
}, null, 2);
|
|
2252
|
-
} else if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
2253
|
-
let resolvedProviderType = typeof args.type === "string" && args.type.trim() ? args.type : "";
|
|
2254
|
-
if (!resolvedProviderType) {
|
|
2255
|
-
const providerPriority = readProviderPriority(node.policy);
|
|
2256
|
-
if (!providerPriority.length) {
|
|
2257
|
-
return JSON.stringify({ success: false, error: missingProviderPriorityMessage(args.node_id) });
|
|
2258
|
-
}
|
|
2259
|
-
resolvedProviderType = providerPriority[0];
|
|
2260
|
-
}
|
|
2261
|
-
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
2262
|
-
const coordinatorDaemonId = coordinatorNode?.daemonId || ctx.localDaemonId;
|
|
2263
|
-
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
2264
|
-
if (!coordinatorDaemonId) {
|
|
2265
|
-
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
2266
|
-
}
|
|
2267
|
-
try {
|
|
2268
|
-
const res = await ctx.transport.launch(node.daemonId, {
|
|
2269
|
-
type: resolvedProviderType,
|
|
2270
|
-
dir: node.workspace,
|
|
2271
|
-
settings: {
|
|
2272
|
-
meshNodeFor: ctx.mesh.id,
|
|
2273
|
-
meshNodeId: args.node_id,
|
|
2274
|
-
spawnedSessionVisibility,
|
|
2275
|
-
...coordinatorDaemonId ? { meshCoordinatorDaemonId: coordinatorDaemonId } : {},
|
|
2276
|
-
...coordinatorNode?.id ? { meshCoordinatorNodeId: coordinatorNode.id } : {},
|
|
2277
|
-
launchedByCoordinator: true
|
|
2278
|
-
}
|
|
2279
|
-
});
|
|
2280
|
-
const runtimeSessionId = typeof res?.sessionId === "string" ? res.sessionId : typeof res?.id === "string" ? res.id : "";
|
|
2281
|
-
try {
|
|
2282
|
-
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2283
|
-
kind: "session_launched",
|
|
2284
|
-
nodeId: args.node_id,
|
|
2285
|
-
sessionId: runtimeSessionId || void 0,
|
|
2286
|
-
providerType: resolvedProviderType,
|
|
2287
|
-
payload: {}
|
|
2288
|
-
});
|
|
2289
|
-
} catch {
|
|
2290
|
-
}
|
|
2291
|
-
return JSON.stringify({ ...res, resolvedProviderType }, null, 2);
|
|
2292
|
-
} catch (e) {
|
|
2293
|
-
return JSON.stringify(recordRecoverableLaunchFailure(ctx, node, resolvedProviderType, e), null, 2);
|
|
2294
|
-
}
|
|
2295
|
-
} else {
|
|
2296
|
-
return JSON.stringify({ error: "Cloud mesh launch_session requires node daemonId" });
|
|
2297
3499
|
}
|
|
2298
3500
|
}
|
|
2299
3501
|
async function meshGitStatus(ctx, args) {
|
|
@@ -2301,37 +3503,23 @@ async function meshGitStatus(ctx, args) {
|
|
|
2301
3503
|
const autoDiscoverSubmodules = node.policy?.autoDiscoverSubmodules !== false;
|
|
2302
3504
|
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2303
3505
|
try {
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
const diffResult = await commandForNode(ctx, node, "git_diff_summary", {
|
|
2322
|
-
workspace: node.workspace
|
|
2323
|
-
});
|
|
2324
|
-
return JSON.stringify({
|
|
2325
|
-
nodeId: args.node_id,
|
|
2326
|
-
workspace: node.workspace,
|
|
2327
|
-
status: extractGitStatus(statusResult),
|
|
2328
|
-
diff: extractGitDiff(diffResult),
|
|
2329
|
-
submodules: autoDiscoverSubmodules ? extractSubmodules(statusResult, submoduleIgnorePaths) : void 0,
|
|
2330
|
-
relatedRepos: await collectRelatedRepoStatuses(ctx, node)
|
|
2331
|
-
}, null, 2);
|
|
2332
|
-
} else {
|
|
2333
|
-
return JSON.stringify({ error: "No daemonId available for cloud git_status probe" });
|
|
2334
|
-
}
|
|
3506
|
+
const statusResult = await commandForNode(ctx, node, "git_status", {
|
|
3507
|
+
workspace: node.workspace,
|
|
3508
|
+
refreshUpstream: true,
|
|
3509
|
+
includeSubmodules: autoDiscoverSubmodules,
|
|
3510
|
+
submoduleIgnorePaths: submoduleIgnorePaths.length > 0 ? submoduleIgnorePaths : void 0
|
|
3511
|
+
});
|
|
3512
|
+
const diffResult = await commandForNode(ctx, node, "git_diff_summary", {
|
|
3513
|
+
workspace: node.workspace
|
|
3514
|
+
});
|
|
3515
|
+
return JSON.stringify({
|
|
3516
|
+
nodeId: args.node_id,
|
|
3517
|
+
workspace: node.workspace,
|
|
3518
|
+
status: extractGitStatus(statusResult),
|
|
3519
|
+
diff: extractGitDiff(diffResult),
|
|
3520
|
+
submodules: autoDiscoverSubmodules ? extractSubmodules(statusResult, submoduleIgnorePaths) : void 0,
|
|
3521
|
+
relatedRepos: await collectRelatedRepoStatuses(ctx, node)
|
|
3522
|
+
}, null, 2);
|
|
2335
3523
|
} catch (e) {
|
|
2336
3524
|
const failure = buildCoordinatorP2pRelayFailure(e, {
|
|
2337
3525
|
command: "git_status",
|
|
@@ -2344,242 +3532,238 @@ async function meshGitStatus(ctx, args) {
|
|
|
2344
3532
|
}, null, 2);
|
|
2345
3533
|
}
|
|
2346
3534
|
}
|
|
2347
|
-
async function
|
|
3535
|
+
async function meshFastForwardNode(ctx, args) {
|
|
3536
|
+
await refreshMeshFromDaemon(ctx);
|
|
2348
3537
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3538
|
+
const submoduleIgnorePaths = node.policy?.submoduleIgnorePaths || [];
|
|
2349
3539
|
if (node.policy?.readOnly) {
|
|
2350
|
-
return JSON.stringify({
|
|
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);
|
|
2351
3550
|
}
|
|
2352
|
-
|
|
2353
|
-
const
|
|
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,
|
|
2354
3556
|
workspace: node.workspace,
|
|
2355
|
-
|
|
2356
|
-
|
|
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
|
|
2357
3562
|
});
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
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
|
+
}
|
|
3580
|
+
async function meshCheckpoint(ctx, args) {
|
|
3581
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3582
|
+
if (node.policy?.readOnly) {
|
|
3583
|
+
return JSON.stringify({ error: `Node '${args.node_id}' is read-only \u2014 cannot checkpoint` });
|
|
3584
|
+
}
|
|
3585
|
+
const result = await commandForNode(ctx, node, "git_checkpoint", {
|
|
3586
|
+
workspace: node.workspace,
|
|
3587
|
+
message: args.message,
|
|
3588
|
+
includeUntracked: true
|
|
3589
|
+
});
|
|
3590
|
+
try {
|
|
3591
|
+
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
3592
|
+
kind: "checkpoint_created",
|
|
3593
|
+
nodeId: args.node_id,
|
|
3594
|
+
payload: {
|
|
2371
3595
|
message: args.message,
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
kind: "checkpoint_created",
|
|
2377
|
-
nodeId: args.node_id,
|
|
2378
|
-
payload: { message: args.message, commit: res?.checkpoint?.commit }
|
|
2379
|
-
});
|
|
2380
|
-
} catch {
|
|
3596
|
+
commit: result?.checkpoint?.commit,
|
|
3597
|
+
outcome: result?.checkpoint?.status || (result?.checkpoint?.noop ? "skipped" : void 0),
|
|
3598
|
+
noop: result?.checkpoint?.noop === true,
|
|
3599
|
+
reason: result?.checkpoint?.reason
|
|
2381
3600
|
}
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
return JSON.stringify({ success: false, error: e.message });
|
|
2385
|
-
}
|
|
2386
|
-
} else {
|
|
2387
|
-
return JSON.stringify({ error: "Cloud mesh checkpoint requires node daemonId" });
|
|
3601
|
+
});
|
|
3602
|
+
} catch {
|
|
2388
3603
|
}
|
|
3604
|
+
return JSON.stringify(result, null, 2);
|
|
2389
3605
|
}
|
|
2390
3606
|
async function meshApprove(ctx, args) {
|
|
2391
3607
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
return JSON.stringify(result, null, 2);
|
|
2404
|
-
} else if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
2405
|
-
try {
|
|
2406
|
-
const targetId = `${node.daemonId}:session:${args.session_id}`;
|
|
2407
|
-
const res = await ctx.transport.approve(targetId, args.action === "reject" ? "reject" : "approve");
|
|
2408
|
-
return JSON.stringify(res, null, 2);
|
|
2409
|
-
} catch (e) {
|
|
2410
|
-
return JSON.stringify({ success: false, error: e.message });
|
|
2411
|
-
}
|
|
2412
|
-
} else {
|
|
2413
|
-
return JSON.stringify({ error: "Cloud mesh approve requires node daemonId" });
|
|
2414
|
-
}
|
|
3608
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
|
|
3609
|
+
const providerSessionId = cached?.providerSessionId;
|
|
3610
|
+
const result = await commandForNode(ctx, node, "resolve_action", {
|
|
3611
|
+
sessionId: args.session_id,
|
|
3612
|
+
targetSessionId: args.session_id,
|
|
3613
|
+
workspace: node.workspace,
|
|
3614
|
+
...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
|
|
3615
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
3616
|
+
action: args.action === "reject" ? "reject" : "approve"
|
|
3617
|
+
});
|
|
3618
|
+
return JSON.stringify(result, null, 2);
|
|
2415
3619
|
}
|
|
2416
3620
|
async function meshCloneNode(ctx, args) {
|
|
2417
3621
|
const sourceNode = await findNodeWithRefresh(ctx, args.source_node_id);
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2433
|
-
}
|
|
2434
|
-
return JSON.stringify(result, null, 2);
|
|
2435
|
-
} else if (!isLocalTransport(ctx.transport) && sourceNode.daemonId) {
|
|
2436
|
-
try {
|
|
2437
|
-
const res = await ctx.transport.meshCloneNode(sourceNode.daemonId, {
|
|
2438
|
-
meshId: ctx.mesh.id,
|
|
2439
|
-
sourceNodeId: args.source_node_id,
|
|
2440
|
-
branch: args.branch,
|
|
2441
|
-
baseBranch: args.base_branch,
|
|
2442
|
-
inlineMesh: ctx.mesh
|
|
2443
|
-
});
|
|
2444
|
-
const clonePayload = extractCloneNodePayload(res);
|
|
2445
|
-
if (clonePayload?.success && clonePayload.node?.id) {
|
|
2446
|
-
const existingIndex = ctx.mesh.nodes.findIndex((n) => n.id === clonePayload.node.id);
|
|
2447
|
-
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
2448
|
-
else ctx.mesh.nodes.push(clonePayload.node);
|
|
2449
|
-
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2450
|
-
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2451
|
-
}
|
|
2452
|
-
return JSON.stringify(res, null, 2);
|
|
2453
|
-
} catch (e) {
|
|
2454
|
-
return JSON.stringify({ success: false, error: e.message });
|
|
2455
|
-
}
|
|
2456
|
-
} else {
|
|
2457
|
-
return JSON.stringify({ error: "Cloud mesh clone_node requires source node daemonId" });
|
|
3622
|
+
const result = await commandForNode(ctx, sourceNode, "clone_mesh_node", {
|
|
3623
|
+
meshId: ctx.mesh.id,
|
|
3624
|
+
sourceNodeId: args.source_node_id,
|
|
3625
|
+
branch: args.branch,
|
|
3626
|
+
baseBranch: args.base_branch,
|
|
3627
|
+
inlineMesh: ctx.mesh
|
|
3628
|
+
});
|
|
3629
|
+
const clonePayload = extractCloneNodePayload(result);
|
|
3630
|
+
if (clonePayload?.success && clonePayload.node?.id) {
|
|
3631
|
+
const existingIndex = ctx.mesh.nodes.findIndex((n) => n.id === clonePayload.node.id);
|
|
3632
|
+
if (existingIndex >= 0) ctx.mesh.nodes[existingIndex] = clonePayload.node;
|
|
3633
|
+
else ctx.mesh.nodes.push(clonePayload.node);
|
|
3634
|
+
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3635
|
+
await syncCoordinatorDaemonMeshCache(ctx);
|
|
2458
3636
|
}
|
|
3637
|
+
return JSON.stringify(result, null, 2);
|
|
2459
3638
|
}
|
|
2460
3639
|
async function meshCleanupSessions(ctx, args) {
|
|
2461
3640
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
return JSON.stringify(result, null, 2);
|
|
2472
|
-
} else if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
2473
|
-
try {
|
|
2474
|
-
const res = await ctx.transport.meshCleanupSessions(node.daemonId, {
|
|
2475
|
-
meshId: ctx.mesh.id,
|
|
2476
|
-
nodeId: args.node_id,
|
|
2477
|
-
mode: args.mode,
|
|
2478
|
-
sessionIds: args.session_ids,
|
|
2479
|
-
dryRun: args.dry_run === true,
|
|
2480
|
-
inlineMesh: ctx.mesh
|
|
2481
|
-
});
|
|
2482
|
-
return JSON.stringify(res, null, 2);
|
|
2483
|
-
} catch (e) {
|
|
2484
|
-
return JSON.stringify({ success: false, error: e.message });
|
|
2485
|
-
}
|
|
2486
|
-
} else {
|
|
2487
|
-
return JSON.stringify({ error: "Cloud mesh cleanup_sessions requires node daemonId" });
|
|
2488
|
-
}
|
|
3641
|
+
const result = await commandForNode(ctx, node, "cleanup_mesh_sessions", {
|
|
3642
|
+
meshId: ctx.mesh.id,
|
|
3643
|
+
nodeId: args.node_id,
|
|
3644
|
+
mode: args.mode,
|
|
3645
|
+
sessionIds: args.session_ids,
|
|
3646
|
+
dryRun: args.dry_run === true,
|
|
3647
|
+
inlineMesh: ctx.mesh
|
|
3648
|
+
});
|
|
3649
|
+
return JSON.stringify(result, null, 2);
|
|
2489
3650
|
}
|
|
2490
3651
|
async function meshRemoveNode(ctx, args) {
|
|
2491
3652
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
}, null, 2);
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
if (result?.success && result.removed !== false) {
|
|
2516
|
-
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2517
|
-
if (idx >= 0) {
|
|
2518
|
-
ctx.mesh.nodes.splice(idx, 1);
|
|
2519
|
-
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2520
|
-
}
|
|
3653
|
+
const removeArgs = buildRemoveNodeArgs(ctx, args.node_id, args.session_cleanup_mode);
|
|
3654
|
+
let result;
|
|
3655
|
+
let transportFallback;
|
|
3656
|
+
try {
|
|
3657
|
+
result = await commandForNode(ctx, node, "remove_mesh_node", removeArgs);
|
|
3658
|
+
} catch (e) {
|
|
3659
|
+
if (ctx.transport instanceof IpcTransport && node.isLocalWorktree && isP2pTransportUnavailableError(e)) {
|
|
3660
|
+
result = await ctx.transport.command("remove_mesh_node", removeArgs);
|
|
3661
|
+
transportFallback = {
|
|
3662
|
+
from: "p2p_mesh_relay",
|
|
3663
|
+
to: "local_control_plane",
|
|
3664
|
+
reason: e?.message || String(e)
|
|
3665
|
+
};
|
|
3666
|
+
} else {
|
|
3667
|
+
return JSON.stringify({
|
|
3668
|
+
success: false,
|
|
3669
|
+
code: isP2pTransportUnavailableError(e) ? "p2p_unavailable" : "mesh_remove_node_failed",
|
|
3670
|
+
error: e?.message || String(e),
|
|
3671
|
+
recoveryHint: isP2pTransportUnavailableError(e) ? "If this is an ADHDev-managed local worktree, retry from a coordinator connected to the daemon that owns the worktree; dashboard command/data-plane traffic still requires P2P." : "Inspect mesh_status and retry after resolving the reported failure."
|
|
3672
|
+
}, null, 2);
|
|
2521
3673
|
}
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
...args.session_cleanup_mode ? { sessionCleanupMode: args.session_cleanup_mode } : {},
|
|
2529
|
-
inlineMesh: ctx.mesh
|
|
2530
|
-
});
|
|
2531
|
-
if (res?.success && res.removed !== false) {
|
|
2532
|
-
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
2533
|
-
if (idx >= 0) {
|
|
2534
|
-
ctx.mesh.nodes.splice(idx, 1);
|
|
2535
|
-
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
return JSON.stringify(res, null, 2);
|
|
2539
|
-
} catch (e) {
|
|
2540
|
-
return JSON.stringify({ success: false, error: e.message });
|
|
3674
|
+
}
|
|
3675
|
+
if (result?.success && result.removed !== false) {
|
|
3676
|
+
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
3677
|
+
if (idx >= 0) {
|
|
3678
|
+
ctx.mesh.nodes.splice(idx, 1);
|
|
3679
|
+
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2541
3680
|
}
|
|
2542
|
-
} else {
|
|
2543
|
-
return JSON.stringify({ error: "Cloud mesh remove_node requires node daemonId" });
|
|
2544
3681
|
}
|
|
3682
|
+
return JSON.stringify({ ...result || {}, ...transportFallback ? { transportFallback } : {} }, null, 2);
|
|
3683
|
+
}
|
|
3684
|
+
function resolveRefineConfigNode(ctx, nodeId) {
|
|
3685
|
+
if (nodeId) return findNode(ctx.mesh, nodeId);
|
|
3686
|
+
const node = ctx.mesh.nodes.find((entry) => !!entry.workspace);
|
|
3687
|
+
if (!node) throw new Error("No mesh node with a workspace is available");
|
|
3688
|
+
return node;
|
|
3689
|
+
}
|
|
3690
|
+
async function meshRefineConfigSchema(ctx) {
|
|
3691
|
+
const node = resolveRefineConfigNode(ctx);
|
|
3692
|
+
const result = await commandForNode(ctx, node, "get_mesh_refine_config_schema", {});
|
|
3693
|
+
return JSON.stringify(result, null, 2);
|
|
3694
|
+
}
|
|
3695
|
+
async function meshValidateRefineConfig(ctx, args) {
|
|
3696
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
3697
|
+
const result = await commandForNode(ctx, node, "validate_mesh_refine_config", {
|
|
3698
|
+
workspace: node.workspace,
|
|
3699
|
+
inlineMesh: ctx.mesh,
|
|
3700
|
+
...args.config ? { config: args.config } : {}
|
|
3701
|
+
});
|
|
3702
|
+
return JSON.stringify(result, null, 2);
|
|
3703
|
+
}
|
|
3704
|
+
async function meshSuggestRefineConfig(ctx, args) {
|
|
3705
|
+
const node = resolveRefineConfigNode(ctx, args.node_id);
|
|
3706
|
+
const result = await commandForNode(ctx, node, "suggest_mesh_refine_config", {
|
|
3707
|
+
workspace: node.workspace,
|
|
3708
|
+
inlineMesh: ctx.mesh
|
|
3709
|
+
});
|
|
3710
|
+
return JSON.stringify(result, null, 2);
|
|
3711
|
+
}
|
|
3712
|
+
async function meshRefinePlan(ctx, args) {
|
|
3713
|
+
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3714
|
+
const result = await commandForNode(ctx, node, "plan_mesh_refine_node", {
|
|
3715
|
+
meshId: ctx.mesh.id,
|
|
3716
|
+
nodeId: args.node_id,
|
|
3717
|
+
inlineMesh: ctx.mesh
|
|
3718
|
+
});
|
|
3719
|
+
return JSON.stringify(result, null, 2);
|
|
2545
3720
|
}
|
|
2546
3721
|
async function meshRefineNode(ctx, args) {
|
|
2547
3722
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2559
|
-
}
|
|
3723
|
+
const result = await commandForNode(ctx, node, "refine_mesh_node", {
|
|
3724
|
+
meshId: ctx.mesh.id,
|
|
3725
|
+
nodeId: args.node_id,
|
|
3726
|
+
inlineMesh: ctx.mesh
|
|
3727
|
+
});
|
|
3728
|
+
if (result?.success && result.async !== true && result.removeResult?.removed !== false) {
|
|
3729
|
+
const idx = ctx.mesh.nodes.findIndex((n) => n.id === args.node_id);
|
|
3730
|
+
if (idx >= 0) {
|
|
3731
|
+
ctx.mesh.nodes.splice(idx, 1);
|
|
3732
|
+
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2560
3733
|
}
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
3734
|
+
}
|
|
3735
|
+
return JSON.stringify(result, null, 2);
|
|
3736
|
+
}
|
|
3737
|
+
async function meshRefineBatch(ctx, args = {}) {
|
|
3738
|
+
await refreshMeshFromDaemon(ctx);
|
|
3739
|
+
const nodeIds = Array.isArray(args.node_ids) ? args.node_ids.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : void 0;
|
|
3740
|
+
const result = await ctx.transport.command("batch_refine_mesh_nodes", {
|
|
3741
|
+
meshId: ctx.mesh.id,
|
|
3742
|
+
...nodeIds ? { nodeIds } : {},
|
|
3743
|
+
...args.execute !== void 0 ? { execute: args.execute } : {},
|
|
3744
|
+
...args.dry_run !== void 0 ? { dryRun: args.dry_run } : {},
|
|
3745
|
+
inlineMesh: ctx.mesh
|
|
3746
|
+
});
|
|
3747
|
+
const payload = unwrapCommandPayload(result) ?? result;
|
|
3748
|
+
if (payload?.batch && payload?.dryRun === false && Array.isArray(payload?.results)) {
|
|
3749
|
+
for (const outcome of payload.results) {
|
|
3750
|
+
if (outcome?.convergence === "merged_to_main" || outcome?.convergence === "skipped_patch_equivalent") {
|
|
3751
|
+
const idx = ctx.mesh.nodes.findIndex((n) => n.id === outcome.nodeId);
|
|
3752
|
+
if (idx >= 0) ctx.mesh.nodes.splice(idx, 1);
|
|
2575
3753
|
}
|
|
2576
|
-
return JSON.stringify(res, null, 2);
|
|
2577
|
-
} catch (e) {
|
|
2578
|
-
return JSON.stringify({ success: false, error: e.message });
|
|
2579
3754
|
}
|
|
2580
|
-
|
|
2581
|
-
return JSON.stringify({ error: "Cloud mesh refine_node requires node daemonId" });
|
|
3755
|
+
ctx.mesh.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2582
3756
|
}
|
|
3757
|
+
return JSON.stringify(result, null, 2);
|
|
3758
|
+
}
|
|
3759
|
+
async function meshReviewInbox(ctx, args = {}) {
|
|
3760
|
+
await refreshMeshFromDaemon(ctx);
|
|
3761
|
+
const meshId = (args.mesh_id ?? ctx.mesh.id).trim();
|
|
3762
|
+
const result = await commandForNode(ctx, ctx.mesh.nodes[0], "get_mesh_review_inbox", {
|
|
3763
|
+
meshId,
|
|
3764
|
+
inlineMesh: ctx.mesh
|
|
3765
|
+
});
|
|
3766
|
+
return JSON.stringify(result, null, 2);
|
|
2583
3767
|
}
|
|
2584
3768
|
|
|
2585
3769
|
// src/help.ts
|
|
@@ -2603,28 +3787,24 @@ var STANDARD_TOOLS = [
|
|
|
2603
3787
|
function buildMcpHelpText() {
|
|
2604
3788
|
const meshTools = ALL_MESH_TOOLS.map((tool) => tool.name);
|
|
2605
3789
|
return `
|
|
2606
|
-
|
|
3790
|
+
ADHDev MCP Server
|
|
2607
3791
|
|
|
2608
3792
|
Usage:
|
|
2609
|
-
adhdev
|
|
2610
|
-
adhdev
|
|
2611
|
-
adhdev-mcp --
|
|
2612
|
-
adhdev-mcp --repo-mesh <mesh_id> Mesh mode (coordinator-scoped tools)
|
|
3793
|
+
adhdev mcp Local mode (requires standalone daemon)
|
|
3794
|
+
adhdev mcp --mode ipc --repo-mesh <mesh_id> Cloud daemon IPC mesh mode
|
|
3795
|
+
adhdev-mcp --help Compatibility bin (same server, legacy package entrypoint)
|
|
2613
3796
|
|
|
2614
3797
|
Options:
|
|
2615
|
-
--mode <mode> Transport: local
|
|
3798
|
+
--mode <mode> Transport: local or ipc
|
|
2616
3799
|
--port <n> Standalone or IPC daemon port (defaults: local 3847, ipc 19222)
|
|
2617
3800
|
--password <pass> Standalone daemon password (if set)
|
|
2618
|
-
--api-key <key> ADHDev cloud API key (switches to cloud mode)
|
|
2619
|
-
--base-url <url> Override cloud API base URL
|
|
2620
3801
|
--repo-mesh <mesh_id> Enable mesh mode \u2014 exposes only mesh-scoped coordinator tools
|
|
2621
3802
|
--help Show this help
|
|
2622
3803
|
|
|
2623
3804
|
Environment variables:
|
|
2624
|
-
ADHDEV_API_KEY API key (cloud mode)
|
|
2625
3805
|
ADHDEV_PASSWORD Daemon password (local mode)
|
|
2626
3806
|
ADHDEV_MESH_ID Mesh ID (mesh mode)
|
|
2627
|
-
ADHDEV_MCP_TRANSPORT Transport: local
|
|
3807
|
+
ADHDEV_MCP_TRANSPORT Transport: local or ipc
|
|
2628
3808
|
|
|
2629
3809
|
Standard tools: ${STANDARD_TOOLS.join(", ")}
|
|
2630
3810
|
Mesh tools: ${meshTools.join(", ")}
|
|
@@ -2634,6 +3814,7 @@ Mesh tools: ${meshTools.join(", ")}
|
|
|
2634
3814
|
// src/server.ts
|
|
2635
3815
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
2636
3816
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
3817
|
+
var import_node_os = __toESM(require("os"));
|
|
2637
3818
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
2638
3819
|
|
|
2639
3820
|
// src/transports/local.ts
|
|
@@ -2655,284 +3836,21 @@ var LocalTransport = class {
|
|
|
2655
3836
|
if (!res.ok) throw new Error(`Status fetch failed: ${res.status}`);
|
|
2656
3837
|
return res.json();
|
|
2657
3838
|
}
|
|
2658
|
-
async command(type, args = {}) {
|
|
2659
|
-
const res = await fetch(`${this.baseUrl}/api/v1/command`, {
|
|
2660
|
-
method: "POST",
|
|
2661
|
-
headers: this.headers(),
|
|
2662
|
-
body: JSON.stringify({ type, ...args })
|
|
2663
|
-
});
|
|
2664
|
-
if (!res.ok) {
|
|
2665
|
-
const text = await res.text().catch(() => res.statusText);
|
|
2666
|
-
throw new Error(`Command ${type} failed: ${res.status} ${text}`);
|
|
2667
|
-
}
|
|
2668
|
-
return res.json();
|
|
2669
|
-
}
|
|
2670
|
-
async ping() {
|
|
2671
|
-
try {
|
|
2672
|
-
await this.getStatus();
|
|
2673
|
-
return true;
|
|
2674
|
-
} catch {
|
|
2675
|
-
return false;
|
|
2676
|
-
}
|
|
2677
|
-
}
|
|
2678
|
-
};
|
|
2679
|
-
|
|
2680
|
-
// src/transports/cloud.ts
|
|
2681
|
-
var DEFAULT_BASE_URL = "https://api.adhf.dev";
|
|
2682
|
-
var CloudTransport = class {
|
|
2683
|
-
baseUrl;
|
|
2684
|
-
apiKey;
|
|
2685
|
-
constructor(opts) {
|
|
2686
|
-
this.apiKey = opts.apiKey;
|
|
2687
|
-
this.baseUrl = opts.baseUrl ?? DEFAULT_BASE_URL;
|
|
2688
|
-
}
|
|
2689
|
-
headers() {
|
|
2690
|
-
return {
|
|
2691
|
-
"Content-Type": "application/json",
|
|
2692
|
-
"Authorization": `Bearer ${this.apiKey}`
|
|
2693
|
-
};
|
|
2694
|
-
}
|
|
2695
|
-
async listRemoteMeshes() {
|
|
2696
|
-
const res = await fetch(`${this.baseUrl}/api/v1/repo-meshes`, { headers: this.headers() });
|
|
2697
|
-
if (!res.ok) throw new Error(`List remote meshes failed: ${res.status}`);
|
|
2698
|
-
return res.json();
|
|
2699
|
-
}
|
|
2700
|
-
async createRemoteMesh(data) {
|
|
2701
|
-
const res = await fetch(`${this.baseUrl}/api/v1/repo-meshes`, {
|
|
3839
|
+
async command(type, args = {}) {
|
|
3840
|
+
const res = await fetch(`${this.baseUrl}/api/v1/command`, {
|
|
2702
3841
|
method: "POST",
|
|
2703
3842
|
headers: this.headers(),
|
|
2704
|
-
body: JSON.stringify(
|
|
2705
|
-
});
|
|
2706
|
-
if (!res.ok) throw new Error(`Create remote mesh failed: ${res.status}`);
|
|
2707
|
-
return res.json();
|
|
2708
|
-
}
|
|
2709
|
-
async deleteRemoteMesh(meshId) {
|
|
2710
|
-
const res = await fetch(`${this.baseUrl}/api/v1/repo-meshes/${encodeURIComponent(meshId)}`, {
|
|
2711
|
-
method: "DELETE",
|
|
2712
|
-
headers: this.headers()
|
|
3843
|
+
body: JSON.stringify({ type, ...args })
|
|
2713
3844
|
});
|
|
2714
|
-
if (!res.ok)
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
if (!res.ok) throw new Error(`List daemons failed: ${res.status}`);
|
|
2719
|
-
return res.json();
|
|
2720
|
-
}
|
|
2721
|
-
async getStatus(targetId) {
|
|
2722
|
-
const res = await fetch(
|
|
2723
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(targetId)}/status`,
|
|
2724
|
-
{ headers: this.headers() }
|
|
2725
|
-
);
|
|
2726
|
-
if (!res.ok) throw new Error(`Status failed: ${res.status}`);
|
|
2727
|
-
return res.json();
|
|
2728
|
-
}
|
|
2729
|
-
/** Get all sessions for a daemon (returns CompactSessionEntry[]). */
|
|
2730
|
-
async getDaemonStatus(daemonId) {
|
|
2731
|
-
const res = await fetch(
|
|
2732
|
-
`${this.baseUrl}/api/v1/daemons/${encodeURIComponent(daemonId)}/status`,
|
|
2733
|
-
{ headers: this.headers() }
|
|
2734
|
-
);
|
|
2735
|
-
if (!res.ok) throw new Error(`Daemon status failed: ${res.status}`);
|
|
2736
|
-
return res.json();
|
|
2737
|
-
}
|
|
2738
|
-
async readChat(targetId, opts = {}) {
|
|
2739
|
-
const params = new URLSearchParams();
|
|
2740
|
-
if (opts.limit) params.set("limit", String(opts.limit));
|
|
2741
|
-
if (opts.sessionId) params.set("sessionId", opts.sessionId);
|
|
2742
|
-
const qs = params.toString() ? `?${params}` : "";
|
|
2743
|
-
const res = await fetch(
|
|
2744
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(targetId)}/chat${qs}`,
|
|
2745
|
-
{ headers: this.headers() }
|
|
2746
|
-
);
|
|
2747
|
-
if (!res.ok) throw new Error(`Read chat failed: ${res.status}`);
|
|
2748
|
-
return res.json();
|
|
2749
|
-
}
|
|
2750
|
-
async getChatDebugBundle(targetId, opts = {}) {
|
|
2751
|
-
const res = await fetch(
|
|
2752
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(targetId)}/chat/debug`,
|
|
2753
|
-
{
|
|
2754
|
-
method: "POST",
|
|
2755
|
-
headers: this.headers(),
|
|
2756
|
-
body: JSON.stringify({
|
|
2757
|
-
...opts.agentType ? { agentType: opts.agentType } : {},
|
|
2758
|
-
...opts.sessionId ? { sessionId: opts.sessionId } : {},
|
|
2759
|
-
...opts.tailLimit ? { tailLimit: opts.tailLimit } : {},
|
|
2760
|
-
...opts.delivery ? { delivery: opts.delivery } : {}
|
|
2761
|
-
})
|
|
2762
|
-
}
|
|
2763
|
-
);
|
|
2764
|
-
if (!res.ok) throw new Error(`Chat debug bundle failed: ${res.status}`);
|
|
2765
|
-
return res.json();
|
|
2766
|
-
}
|
|
2767
|
-
async sendChat(targetId, message, opts = {}) {
|
|
2768
|
-
const res = await fetch(
|
|
2769
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(targetId)}/chat`,
|
|
2770
|
-
{
|
|
2771
|
-
method: "POST",
|
|
2772
|
-
headers: this.headers(),
|
|
2773
|
-
body: JSON.stringify({ message, ...opts })
|
|
2774
|
-
}
|
|
2775
|
-
);
|
|
2776
|
-
if (!res.ok) throw new Error(`Send chat failed: ${res.status}`);
|
|
2777
|
-
return res.json();
|
|
2778
|
-
}
|
|
2779
|
-
async approve(targetId, action, agentType) {
|
|
2780
|
-
const res = await fetch(
|
|
2781
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(targetId)}/approve`,
|
|
2782
|
-
{
|
|
2783
|
-
method: "POST",
|
|
2784
|
-
headers: this.headers(),
|
|
2785
|
-
body: JSON.stringify({ action, ...agentType ? { agentType } : {} })
|
|
2786
|
-
}
|
|
2787
|
-
);
|
|
2788
|
-
if (!res.ok) throw new Error(`Approve failed: ${res.status}`);
|
|
2789
|
-
return res.json();
|
|
2790
|
-
}
|
|
2791
|
-
async gitStatus(daemonId, workspace, includeDiff = true, refreshUpstream = false) {
|
|
2792
|
-
const params = new URLSearchParams({ workspace, includeDiff: String(includeDiff), refreshUpstream: String(refreshUpstream) });
|
|
2793
|
-
const res = await fetch(
|
|
2794
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-status?${params}`,
|
|
2795
|
-
{ headers: this.headers() }
|
|
2796
|
-
);
|
|
2797
|
-
if (!res.ok) throw new Error(`Git status failed: ${res.status}`);
|
|
2798
|
-
return res.json();
|
|
2799
|
-
}
|
|
2800
|
-
async stop(daemonId, opts) {
|
|
2801
|
-
const res = await fetch(
|
|
2802
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/stop`,
|
|
2803
|
-
{
|
|
2804
|
-
method: "POST",
|
|
2805
|
-
headers: this.headers(),
|
|
2806
|
-
body: JSON.stringify(opts)
|
|
2807
|
-
}
|
|
2808
|
-
);
|
|
2809
|
-
if (!res.ok) throw new Error(`Stop failed: ${res.status}`);
|
|
2810
|
-
return res.json();
|
|
2811
|
-
}
|
|
2812
|
-
async launch(daemonId, opts) {
|
|
2813
|
-
const res = await fetch(
|
|
2814
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/launch`,
|
|
2815
|
-
{
|
|
2816
|
-
method: "POST",
|
|
2817
|
-
headers: this.headers(),
|
|
2818
|
-
body: JSON.stringify(opts)
|
|
2819
|
-
}
|
|
2820
|
-
);
|
|
2821
|
-
if (!res.ok) throw new Error(`Launch failed: ${res.status}`);
|
|
2822
|
-
return res.json();
|
|
2823
|
-
}
|
|
2824
|
-
async gitLog(daemonId, workspace, opts = {}) {
|
|
2825
|
-
const params = new URLSearchParams({ workspace });
|
|
2826
|
-
if (opts.limit) params.set("limit", String(opts.limit));
|
|
2827
|
-
if (opts.file) params.set("file", opts.file);
|
|
2828
|
-
if (opts.since) params.set("since", opts.since);
|
|
2829
|
-
if (opts.until) params.set("until", opts.until);
|
|
2830
|
-
const res = await fetch(
|
|
2831
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-log?${params}`,
|
|
2832
|
-
{ headers: this.headers() }
|
|
2833
|
-
);
|
|
2834
|
-
if (!res.ok) throw new Error(`Git log failed: ${res.status}`);
|
|
2835
|
-
return res.json();
|
|
2836
|
-
}
|
|
2837
|
-
async gitDiff(daemonId, workspace, opts = {}) {
|
|
2838
|
-
const params = new URLSearchParams({ workspace });
|
|
2839
|
-
if (opts.file) params.set("file", opts.file);
|
|
2840
|
-
if (opts.maxLines) params.set("maxLines", String(opts.maxLines));
|
|
2841
|
-
if (opts.staged) params.set("staged", "true");
|
|
2842
|
-
const res = await fetch(
|
|
2843
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-diff?${params}`,
|
|
2844
|
-
{ headers: this.headers() }
|
|
2845
|
-
);
|
|
2846
|
-
if (!res.ok) throw new Error(`Git diff failed: ${res.status}`);
|
|
2847
|
-
return res.json();
|
|
2848
|
-
}
|
|
2849
|
-
async gitPush(daemonId, opts) {
|
|
2850
|
-
const res = await fetch(
|
|
2851
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-push`,
|
|
2852
|
-
{
|
|
2853
|
-
method: "POST",
|
|
2854
|
-
headers: this.headers(),
|
|
2855
|
-
body: JSON.stringify(opts)
|
|
2856
|
-
}
|
|
2857
|
-
);
|
|
2858
|
-
if (!res.ok) throw new Error(`Git push failed: ${res.status}`);
|
|
2859
|
-
return res.json();
|
|
2860
|
-
}
|
|
2861
|
-
async gitCheckpoint(daemonId, opts) {
|
|
2862
|
-
const res = await fetch(
|
|
2863
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/git-checkpoint`,
|
|
2864
|
-
{
|
|
2865
|
-
method: "POST",
|
|
2866
|
-
headers: this.headers(),
|
|
2867
|
-
body: JSON.stringify(opts)
|
|
2868
|
-
}
|
|
2869
|
-
);
|
|
2870
|
-
if (!res.ok) throw new Error(`Git checkpoint failed: ${res.status}`);
|
|
2871
|
-
return res.json();
|
|
2872
|
-
}
|
|
2873
|
-
async meshCloneNode(daemonId, payload) {
|
|
2874
|
-
const res = await fetch(
|
|
2875
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/mesh/clone-node`,
|
|
2876
|
-
{
|
|
2877
|
-
method: "POST",
|
|
2878
|
-
headers: this.headers(),
|
|
2879
|
-
body: JSON.stringify(payload)
|
|
2880
|
-
}
|
|
2881
|
-
);
|
|
2882
|
-
if (!res.ok) throw new Error(`Mesh clone node failed: ${res.status}`);
|
|
2883
|
-
return res.json();
|
|
2884
|
-
}
|
|
2885
|
-
async meshRemoveNode(daemonId, payload) {
|
|
2886
|
-
const res = await fetch(
|
|
2887
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/mesh/remove-node`,
|
|
2888
|
-
{
|
|
2889
|
-
method: "POST",
|
|
2890
|
-
headers: this.headers(),
|
|
2891
|
-
body: JSON.stringify(payload)
|
|
2892
|
-
}
|
|
2893
|
-
);
|
|
2894
|
-
if (!res.ok) throw new Error(`Mesh remove node failed: ${res.status}`);
|
|
2895
|
-
return res.json();
|
|
2896
|
-
}
|
|
2897
|
-
async meshCleanupSessions(daemonId, payload) {
|
|
2898
|
-
const res = await fetch(
|
|
2899
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/mesh/cleanup-sessions`,
|
|
2900
|
-
{
|
|
2901
|
-
method: "POST",
|
|
2902
|
-
headers: this.headers(),
|
|
2903
|
-
body: JSON.stringify(payload)
|
|
2904
|
-
}
|
|
2905
|
-
);
|
|
2906
|
-
if (!res.ok) throw new Error(`Mesh cleanup sessions failed: ${res.status}`);
|
|
2907
|
-
return res.json();
|
|
2908
|
-
}
|
|
2909
|
-
async meshEnqueueTask(daemonId, payload) {
|
|
2910
|
-
const res = await fetch(
|
|
2911
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/mesh/enqueue`,
|
|
2912
|
-
{
|
|
2913
|
-
method: "POST",
|
|
2914
|
-
headers: this.headers(),
|
|
2915
|
-
body: JSON.stringify(payload)
|
|
2916
|
-
}
|
|
2917
|
-
);
|
|
2918
|
-
if (!res.ok) throw new Error(`Mesh enqueue task failed: ${res.status}`);
|
|
2919
|
-
return res.json();
|
|
2920
|
-
}
|
|
2921
|
-
async meshRefineNode(daemonId, payload) {
|
|
2922
|
-
const res = await fetch(
|
|
2923
|
-
`${this.baseUrl}/api/v1/shortcuts/${encodeURIComponent(daemonId)}/mesh/refine-node`,
|
|
2924
|
-
{
|
|
2925
|
-
method: "POST",
|
|
2926
|
-
headers: this.headers(),
|
|
2927
|
-
body: JSON.stringify(payload)
|
|
2928
|
-
}
|
|
2929
|
-
);
|
|
2930
|
-
if (!res.ok) throw new Error(`Mesh refine node failed: ${res.status}`);
|
|
3845
|
+
if (!res.ok) {
|
|
3846
|
+
const text = await res.text().catch(() => res.statusText);
|
|
3847
|
+
throw new Error(`Command ${type} failed: ${res.status} ${text}`);
|
|
3848
|
+
}
|
|
2931
3849
|
return res.json();
|
|
2932
3850
|
}
|
|
2933
3851
|
async ping() {
|
|
2934
3852
|
try {
|
|
2935
|
-
await this.
|
|
3853
|
+
await this.getStatus();
|
|
2936
3854
|
return true;
|
|
2937
3855
|
} catch {
|
|
2938
3856
|
return false;
|
|
@@ -2950,14 +3868,10 @@ var FORMAT_PROP = {
|
|
|
2950
3868
|
};
|
|
2951
3869
|
var LIST_SESSIONS_TOOL = {
|
|
2952
3870
|
name: "list_sessions",
|
|
2953
|
-
description: "List all connected agent sessions.
|
|
3871
|
+
description: "List all connected agent sessions.",
|
|
2954
3872
|
inputSchema: {
|
|
2955
3873
|
type: "object",
|
|
2956
3874
|
properties: {
|
|
2957
|
-
daemon_id: {
|
|
2958
|
-
type: "string",
|
|
2959
|
-
description: "Daemon ID (cloud mode only). Omit to list sessions across all daemons."
|
|
2960
|
-
},
|
|
2961
3875
|
...FORMAT_PROP
|
|
2962
3876
|
},
|
|
2963
3877
|
required: []
|
|
@@ -2965,87 +3879,35 @@ var LIST_SESSIONS_TOOL = {
|
|
|
2965
3879
|
};
|
|
2966
3880
|
async function listSessions(transport, args = {}) {
|
|
2967
3881
|
const asJson = args.format === "json";
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
const sessions = status?.sessions ?? [];
|
|
2971
|
-
if (asJson) {
|
|
2972
|
-
return JSON.stringify({
|
|
2973
|
-
sessions: sessions.map((s) => ({
|
|
2974
|
-
id: s.id,
|
|
2975
|
-
type: s.providerType ?? s.type ?? "unknown",
|
|
2976
|
-
label: s.label ?? null,
|
|
2977
|
-
status: s.status ?? s.agentStatus ?? null,
|
|
2978
|
-
workspace: s.workspace ?? null
|
|
2979
|
-
}))
|
|
2980
|
-
}, null, 2);
|
|
2981
|
-
}
|
|
2982
|
-
if (sessions.length === 0) return "No active sessions.";
|
|
2983
|
-
const lines = sessions.map((s) => {
|
|
2984
|
-
const parts = [`id: ${s.id}`, `type: ${s.providerType ?? s.type ?? "unknown"}`];
|
|
2985
|
-
if (s.label) parts.push(`label: ${s.label}`);
|
|
2986
|
-
if (s.status ?? s.agentStatus) parts.push(`status: ${s.status ?? s.agentStatus}`);
|
|
2987
|
-
if (s.workspace) parts.push(`workspace: ${s.workspace}`);
|
|
2988
|
-
return parts.join(", ");
|
|
2989
|
-
});
|
|
2990
|
-
return `Sessions (${sessions.length}):
|
|
2991
|
-
${lines.join("\n")}`;
|
|
2992
|
-
}
|
|
2993
|
-
return listSessionsCloud(transport, args.daemon_id, asJson);
|
|
2994
|
-
}
|
|
2995
|
-
async function listSessionsCloud(transport, daemonId, asJson) {
|
|
2996
|
-
const collected = [];
|
|
2997
|
-
if (daemonId) {
|
|
2998
|
-
const daemonStatus = await transport.getDaemonStatus(daemonId);
|
|
2999
|
-
for (const s of daemonStatus?.sessions ?? []) {
|
|
3000
|
-
collected.push({ daemonId, session: s });
|
|
3001
|
-
}
|
|
3002
|
-
} else {
|
|
3003
|
-
const data = await transport.listDaemons();
|
|
3004
|
-
const daemons = data?.daemons ?? [];
|
|
3005
|
-
for (let i = 0; i < daemons.length; i += 5) {
|
|
3006
|
-
await Promise.allSettled(
|
|
3007
|
-
daemons.slice(i, i + 5).map(async (d) => {
|
|
3008
|
-
try {
|
|
3009
|
-
const daemonStatus = await transport.getDaemonStatus(d.id);
|
|
3010
|
-
for (const s of daemonStatus?.sessions ?? []) {
|
|
3011
|
-
collected.push({ daemonId: d.id, session: s });
|
|
3012
|
-
}
|
|
3013
|
-
} catch {
|
|
3014
|
-
}
|
|
3015
|
-
})
|
|
3016
|
-
);
|
|
3017
|
-
}
|
|
3018
|
-
}
|
|
3882
|
+
const status = await transport.getStatus();
|
|
3883
|
+
const sessions = status?.sessions ?? [];
|
|
3019
3884
|
if (asJson) {
|
|
3020
3885
|
return JSON.stringify({
|
|
3021
|
-
sessions:
|
|
3022
|
-
daemon_id: dId,
|
|
3886
|
+
sessions: sessions.map((s) => ({
|
|
3023
3887
|
id: s.id,
|
|
3024
|
-
type: s.providerType ?? "unknown",
|
|
3025
|
-
|
|
3888
|
+
type: s.providerType ?? s.type ?? "unknown",
|
|
3889
|
+
label: s.label ?? null,
|
|
3890
|
+
status: s.status ?? s.agentStatus ?? null,
|
|
3026
3891
|
workspace: s.workspace ?? null
|
|
3027
3892
|
}))
|
|
3028
3893
|
}, null, 2);
|
|
3029
3894
|
}
|
|
3030
|
-
if (
|
|
3031
|
-
const lines =
|
|
3032
|
-
const parts = [
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
`type: ${s.providerType ?? "unknown"}`
|
|
3036
|
-
];
|
|
3037
|
-
if (s.status) parts.push(`status: ${s.status}`);
|
|
3895
|
+
if (sessions.length === 0) return "No active sessions.";
|
|
3896
|
+
const lines = sessions.map((s) => {
|
|
3897
|
+
const parts = [`id: ${s.id}`, `type: ${s.providerType ?? s.type ?? "unknown"}`];
|
|
3898
|
+
if (s.label) parts.push(`label: ${s.label}`);
|
|
3899
|
+
if (s.status ?? s.agentStatus) parts.push(`status: ${s.status ?? s.agentStatus}`);
|
|
3038
3900
|
if (s.workspace) parts.push(`workspace: ${s.workspace}`);
|
|
3039
3901
|
return parts.join(", ");
|
|
3040
3902
|
});
|
|
3041
|
-
return `Sessions (${
|
|
3903
|
+
return `Sessions (${sessions.length}):
|
|
3042
3904
|
${lines.join("\n")}`;
|
|
3043
3905
|
}
|
|
3044
3906
|
|
|
3045
3907
|
// src/tools/list-daemons.ts
|
|
3046
3908
|
var LIST_DAEMONS_TOOL = {
|
|
3047
3909
|
name: "list_daemons",
|
|
3048
|
-
description: "List
|
|
3910
|
+
description: "List the connected daemon (machine running the ADHDev agent). Returns the daemon identity extracted from its status report.",
|
|
3049
3911
|
inputSchema: {
|
|
3050
3912
|
type: "object",
|
|
3051
3913
|
properties: {
|
|
@@ -3056,46 +3918,17 @@ var LIST_DAEMONS_TOOL = {
|
|
|
3056
3918
|
};
|
|
3057
3919
|
async function listDaemons(transport, args = {}) {
|
|
3058
3920
|
const asJson = args.format === "json";
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
return `Daemons (1):
|
|
3921
|
+
const status = await transport.getStatus();
|
|
3922
|
+
const daemon = {
|
|
3923
|
+
id: status?.id ?? status?.instanceId ?? "standalone",
|
|
3924
|
+
hostname: status?.hostname ?? status?.machine?.hostname ?? "localhost",
|
|
3925
|
+
platform: status?.platform ?? status?.machine?.platform ?? "unknown",
|
|
3926
|
+
version: status?.version ?? null,
|
|
3927
|
+
sessions: (status?.sessions ?? []).length
|
|
3928
|
+
};
|
|
3929
|
+
if (asJson) return JSON.stringify({ daemons: [daemon] }, null, 2);
|
|
3930
|
+
return `Daemons (1):
|
|
3070
3931
|
id: ${daemon.id}, hostname: ${daemon.hostname}, platform: ${daemon.platform}${daemon.version ? `, version: ${daemon.version}` : ""}, sessions: ${daemon.sessions}`;
|
|
3071
|
-
}
|
|
3072
|
-
const data = await transport.listDaemons();
|
|
3073
|
-
const daemons = data?.daemons ?? [];
|
|
3074
|
-
if (asJson) {
|
|
3075
|
-
return JSON.stringify({
|
|
3076
|
-
daemons: daemons.map((d) => ({
|
|
3077
|
-
id: d.id,
|
|
3078
|
-
hostname: d.hostname ?? null,
|
|
3079
|
-
platform: d.platform ?? null,
|
|
3080
|
-
nickname: d.nickname ?? null,
|
|
3081
|
-
version: d.version ?? null,
|
|
3082
|
-
p2p_available: d.p2p?.available ?? null,
|
|
3083
|
-
cdp_connected: d.cdpConnected ?? null
|
|
3084
|
-
}))
|
|
3085
|
-
}, null, 2);
|
|
3086
|
-
}
|
|
3087
|
-
if (daemons.length === 0) return "No connected daemons.";
|
|
3088
|
-
const lines = daemons.map((d) => {
|
|
3089
|
-
const parts = [`id: ${d.id}`];
|
|
3090
|
-
if (d.nickname) parts.push(`nickname: ${d.nickname}`);
|
|
3091
|
-
if (d.hostname) parts.push(`hostname: ${d.hostname}`);
|
|
3092
|
-
if (d.platform) parts.push(`platform: ${d.platform}`);
|
|
3093
|
-
if (d.version) parts.push(`version: ${d.version}`);
|
|
3094
|
-
if (d.p2p?.available != null) parts.push(`p2p: ${d.p2p.available ? "yes" : "no"}`);
|
|
3095
|
-
return parts.join(", ");
|
|
3096
|
-
});
|
|
3097
|
-
return `Daemons (${daemons.length}):
|
|
3098
|
-
${lines.join("\n")}`;
|
|
3099
3932
|
}
|
|
3100
3933
|
|
|
3101
3934
|
// src/tools/read-chat.ts
|
|
@@ -3113,10 +3946,6 @@ var READ_CHAT_TOOL = {
|
|
|
3113
3946
|
type: "number",
|
|
3114
3947
|
description: "Max messages to return (default: 50)."
|
|
3115
3948
|
},
|
|
3116
|
-
daemon_id: {
|
|
3117
|
-
type: "string",
|
|
3118
|
-
description: "Daemon ID (cloud mode only). Omit for local mode."
|
|
3119
|
-
},
|
|
3120
3949
|
compact: {
|
|
3121
3950
|
type: "boolean",
|
|
3122
3951
|
description: "Opt-in compact mode: filters tool/terminal/system/internal/control/debug/status chatter and returns user-visible messages plus lightweight summary metadata."
|
|
@@ -3128,23 +3957,12 @@ var READ_CHAT_TOOL = {
|
|
|
3128
3957
|
};
|
|
3129
3958
|
async function readChat(transport, args) {
|
|
3130
3959
|
const limit = args.limit ?? 50;
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
});
|
|
3136
|
-
const annotated2 = annotateRapidReadChatAdvisory(result2, {
|
|
3137
|
-
key: `local:${args.session_id ?? "__active__"}`,
|
|
3138
|
-
toolName: "read_chat",
|
|
3139
|
-
completionCallbackExpected: false
|
|
3140
|
-
});
|
|
3141
|
-
return formatChatResult(annotated2, args.session_id, args.format, limit, args.compact);
|
|
3142
|
-
}
|
|
3143
|
-
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
3144
|
-
const targetId = args.session_id ? `${args.daemon_id}:session:${args.session_id}` : args.daemon_id;
|
|
3145
|
-
const result = await transport.readChat(targetId, { limit, sessionId: args.session_id });
|
|
3960
|
+
const result = await transport.command("read_chat", {
|
|
3961
|
+
...args.session_id ? { targetSessionId: args.session_id } : {},
|
|
3962
|
+
tailLimit: limit
|
|
3963
|
+
});
|
|
3146
3964
|
const annotated = annotateRapidReadChatAdvisory(result, {
|
|
3147
|
-
key: `
|
|
3965
|
+
key: `local:${args.session_id ?? "__active__"}`,
|
|
3148
3966
|
toolName: "read_chat",
|
|
3149
3967
|
completionCallbackExpected: false
|
|
3150
3968
|
});
|
|
@@ -3185,11 +4003,17 @@ function formatChatResult(result, sessionId, format, limit = 50, compact = false
|
|
|
3185
4003
|
}, null, 2);
|
|
3186
4004
|
}
|
|
3187
4005
|
if ((format === "text" || format === void 0) && compact && compactPayload) {
|
|
3188
|
-
const
|
|
4006
|
+
const summaryText = typeof compactPayload.summary === "string" ? compactPayload.summary.trim() : "";
|
|
4007
|
+
const tail = outputMessages.slice(-limit);
|
|
4008
|
+
const lastIndex = tail.length - 1;
|
|
4009
|
+
const lines2 = tail.flatMap((m, idx) => {
|
|
3189
4010
|
const role = m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role;
|
|
3190
4011
|
const content = messageContent(m);
|
|
4012
|
+
if (idx === lastIndex && (role === "Agent" || m.role === "agent") && summaryText && content.trim() === summaryText) {
|
|
4013
|
+
return [];
|
|
4014
|
+
}
|
|
3191
4015
|
const truncated = content.length > 500 ? `${content.slice(0, 500)}\u2026` : content;
|
|
3192
|
-
return `[${role}] ${truncated}
|
|
4016
|
+
return [`[${role}] ${truncated}`];
|
|
3193
4017
|
});
|
|
3194
4018
|
if (compactPayload.summary) {
|
|
3195
4019
|
const truncatedSummary = compactPayload.summary.length > 500 ? `${compactPayload.summary.slice(0, 500)}\u2026` : compactPayload.summary;
|
|
@@ -3228,10 +4052,6 @@ var READ_CHAT_DEBUG_TOOL = {
|
|
|
3228
4052
|
type: "string",
|
|
3229
4053
|
description: "Target session ID (from list_sessions). Required for reliable routing."
|
|
3230
4054
|
},
|
|
3231
|
-
daemon_id: {
|
|
3232
|
-
type: "string",
|
|
3233
|
-
description: "Daemon ID (cloud mode only). Omit for local mode."
|
|
3234
|
-
},
|
|
3235
4055
|
agent_type: {
|
|
3236
4056
|
type: "string",
|
|
3237
4057
|
description: "Optional provider/agent type hint, e.g. hermes-cli, claude-cli, codex-cli."
|
|
@@ -3261,19 +4081,7 @@ async function readChatDebug(transport, args) {
|
|
|
3261
4081
|
...args.agent_type ? { agentType: args.agent_type, providerType: args.agent_type } : {},
|
|
3262
4082
|
...delivery === "daemon_file" ? { delivery: "daemon_file" } : {}
|
|
3263
4083
|
};
|
|
3264
|
-
|
|
3265
|
-
if (isLocalTransport(transport)) {
|
|
3266
|
-
result = await transport.command("get_chat_debug_bundle", commandArgs);
|
|
3267
|
-
} else {
|
|
3268
|
-
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
3269
|
-
const targetId = `${args.daemon_id}:session:${sessionId}`;
|
|
3270
|
-
result = await transport.getChatDebugBundle(targetId, {
|
|
3271
|
-
sessionId,
|
|
3272
|
-
agentType: args.agent_type,
|
|
3273
|
-
tailLimit,
|
|
3274
|
-
delivery
|
|
3275
|
-
});
|
|
3276
|
-
}
|
|
4084
|
+
const result = await transport.command("get_chat_debug_bundle", commandArgs);
|
|
3277
4085
|
return formatChatDebugResult(result, { sessionId, delivery, format: args.format });
|
|
3278
4086
|
}
|
|
3279
4087
|
function formatChatDebugResult(result, options) {
|
|
@@ -3304,6 +4112,79 @@ function formatChatDebugResult(result, options) {
|
|
|
3304
4112
|
return JSON.stringify(result, null, 2);
|
|
3305
4113
|
}
|
|
3306
4114
|
|
|
4115
|
+
// src/tools/spec-debug.ts
|
|
4116
|
+
var SPEC_DEBUG_TOOL = {
|
|
4117
|
+
name: "spec_debug",
|
|
4118
|
+
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.",
|
|
4119
|
+
inputSchema: {
|
|
4120
|
+
type: "object",
|
|
4121
|
+
properties: {
|
|
4122
|
+
session_id: {
|
|
4123
|
+
type: "string",
|
|
4124
|
+
description: "Target session ID (from list_sessions)."
|
|
4125
|
+
},
|
|
4126
|
+
...FORMAT_PROP
|
|
4127
|
+
},
|
|
4128
|
+
required: ["session_id"]
|
|
4129
|
+
}
|
|
4130
|
+
};
|
|
4131
|
+
async function specDebug(transport, args) {
|
|
4132
|
+
const sessionId = typeof args.session_id === "string" ? args.session_id.trim() : "";
|
|
4133
|
+
if (!sessionId) throw new Error("session_id is required");
|
|
4134
|
+
const result = await transport.command("get_spec_debug", { targetSessionId: sessionId });
|
|
4135
|
+
return formatSpecDebugResult(result, { sessionId, format: args.format });
|
|
4136
|
+
}
|
|
4137
|
+
function formatSpecDebugResult(result, options) {
|
|
4138
|
+
if (!result?.success) {
|
|
4139
|
+
const err = result?.error || "Unknown error";
|
|
4140
|
+
if (options.format === "json") return JSON.stringify({ success: false, error: err }, null, 2);
|
|
4141
|
+
return `Error: ${err}`;
|
|
4142
|
+
}
|
|
4143
|
+
if (options.format === "json") return JSON.stringify(result, null, 2);
|
|
4144
|
+
const snap = result.snapshot;
|
|
4145
|
+
if (!snap) {
|
|
4146
|
+
return [
|
|
4147
|
+
`session_id: ${options.sessionId}`,
|
|
4148
|
+
`provider_type: ${String(result.providerType || "")}`,
|
|
4149
|
+
"is_spec_provider: false",
|
|
4150
|
+
"No spec debug data available (not a spec-driven provider)."
|
|
4151
|
+
].join("\n");
|
|
4152
|
+
}
|
|
4153
|
+
const lines = [];
|
|
4154
|
+
lines.push(`session_id: ${options.sessionId}`);
|
|
4155
|
+
lines.push(`provider_type: ${String(result.providerType || snap.cliType || "")}`);
|
|
4156
|
+
lines.push(`spec_id: ${String(snap.spec_id || "")}`);
|
|
4157
|
+
lines.push(`spec_path: ${String(snap.specPath || "")}`);
|
|
4158
|
+
lines.push(`current_state: ${snap.current_state ? `${snap.current_state.id} (${snap.current_state.label})` : "none"}`);
|
|
4159
|
+
lines.push(`idle_hold_pending: ${String(snap.idleHoldPending ?? false)}`);
|
|
4160
|
+
lines.push(`last_busy_at: ${snap.lastBusyAt ? new Date(snap.lastBusyAt).toISOString() : "never"}`);
|
|
4161
|
+
lines.push(`exited: ${String(snap.exited ?? false)}`);
|
|
4162
|
+
if (snap.current_modal) {
|
|
4163
|
+
lines.push(`current_modal: ${JSON.stringify(snap.current_modal)}`);
|
|
4164
|
+
}
|
|
4165
|
+
if (snap.sections && typeof snap.sections === "object") {
|
|
4166
|
+
lines.push("");
|
|
4167
|
+
lines.push("\u2500\u2500 sections \u2500\u2500");
|
|
4168
|
+
for (const [id, text] of Object.entries(snap.sections)) {
|
|
4169
|
+
const preview = String(text || "").replace(/\n/g, "\u21B5").slice(0, 120);
|
|
4170
|
+
lines.push(` ${id}: ${preview}`);
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
const history = Array.isArray(snap.stateHistory) ? snap.stateHistory : [];
|
|
4174
|
+
if (history.length > 0) {
|
|
4175
|
+
lines.push("");
|
|
4176
|
+
lines.push("\u2500\u2500 state history (newest first) \u2500\u2500");
|
|
4177
|
+
const now = Date.now();
|
|
4178
|
+
for (const entry of [...history].reverse().slice(0, 20)) {
|
|
4179
|
+
const agoMs = now - entry.at;
|
|
4180
|
+
const ago = agoMs < 2e3 ? `${agoMs}ms ago` : `${(agoMs / 1e3).toFixed(1)}s ago`;
|
|
4181
|
+
const dur = entry.durationMs > 0 ? ` held ${entry.durationMs}ms` : "";
|
|
4182
|
+
lines.push(` ${String(entry.stateId).padEnd(18)} ${ago}${dur}`);
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
return lines.join("\n");
|
|
4186
|
+
}
|
|
4187
|
+
|
|
3307
4188
|
// src/tools/send-chat.ts
|
|
3308
4189
|
var SEND_CHAT_TOOL = {
|
|
3309
4190
|
name: "send_chat",
|
|
@@ -3318,10 +4199,6 @@ var SEND_CHAT_TOOL = {
|
|
|
3318
4199
|
session_id: {
|
|
3319
4200
|
type: "string",
|
|
3320
4201
|
description: "Target session ID (from list_sessions). Omit to use the active session."
|
|
3321
|
-
},
|
|
3322
|
-
daemon_id: {
|
|
3323
|
-
type: "string",
|
|
3324
|
-
description: "Daemon ID (cloud mode only). Omit for local mode."
|
|
3325
4202
|
}
|
|
3326
4203
|
},
|
|
3327
4204
|
required: ["message"]
|
|
@@ -3329,18 +4206,9 @@ var SEND_CHAT_TOOL = {
|
|
|
3329
4206
|
};
|
|
3330
4207
|
async function sendChat(transport, args) {
|
|
3331
4208
|
if (!args.message?.trim()) throw new Error("message is required");
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
...args.session_id ? { targetSessionId: args.session_id } : {}
|
|
3336
|
-
});
|
|
3337
|
-
if (result2?.success === false) return `Error: ${result2.error ?? "send_chat failed"}`;
|
|
3338
|
-
return "Message sent.";
|
|
3339
|
-
}
|
|
3340
|
-
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
3341
|
-
const targetId = args.session_id ? `${args.daemon_id}:session:${args.session_id}` : args.daemon_id;
|
|
3342
|
-
const result = await transport.sendChat(targetId, args.message, {
|
|
3343
|
-
...args.session_id ? { sessionId: args.session_id } : {}
|
|
4209
|
+
const result = await transport.command("send_chat", {
|
|
4210
|
+
message: args.message,
|
|
4211
|
+
...args.session_id ? { targetSessionId: args.session_id } : {}
|
|
3344
4212
|
});
|
|
3345
4213
|
if (result?.success === false) return `Error: ${result.error ?? "send_chat failed"}`;
|
|
3346
4214
|
return "Message sent.";
|
|
@@ -3361,10 +4229,6 @@ var APPROVE_TOOL = {
|
|
|
3361
4229
|
session_id: {
|
|
3362
4230
|
type: "string",
|
|
3363
4231
|
description: "Target session ID. Omit to use the active session."
|
|
3364
|
-
},
|
|
3365
|
-
daemon_id: {
|
|
3366
|
-
type: "string",
|
|
3367
|
-
description: "Daemon ID (cloud mode only)."
|
|
3368
4232
|
}
|
|
3369
4233
|
},
|
|
3370
4234
|
required: ["action"]
|
|
@@ -3372,25 +4236,18 @@ var APPROVE_TOOL = {
|
|
|
3372
4236
|
};
|
|
3373
4237
|
async function approve(transport, args) {
|
|
3374
4238
|
const action = args.action === "reject" ? "reject" : "approve";
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
if (result2?.success === false) return `Error: ${result2.error ?? "resolve_action failed"}`;
|
|
3381
|
-
return `Action ${action}d.`;
|
|
3382
|
-
}
|
|
3383
|
-
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
3384
|
-
const targetId = args.session_id ? `${args.daemon_id}:session:${args.session_id}` : args.daemon_id;
|
|
3385
|
-
const result = await transport.approve(targetId, action);
|
|
3386
|
-
if (result?.success === false) return `Error: ${result.error ?? "approve failed"}`;
|
|
4239
|
+
const result = await transport.command("resolve_action", {
|
|
4240
|
+
action,
|
|
4241
|
+
...args.session_id ? { targetSessionId: args.session_id } : {}
|
|
4242
|
+
});
|
|
4243
|
+
if (result?.success === false) return `Error: ${result.error ?? "resolve_action failed"}`;
|
|
3387
4244
|
return `Action ${action}d.`;
|
|
3388
4245
|
}
|
|
3389
4246
|
|
|
3390
4247
|
// src/tools/screenshot.ts
|
|
3391
4248
|
var SCREENSHOT_TOOL = {
|
|
3392
4249
|
name: "screenshot",
|
|
3393
|
-
description: "Capture a screenshot of the current IDE window. Returns the image.
|
|
4250
|
+
description: "Capture a screenshot of the current IDE window. Returns the image.",
|
|
3394
4251
|
inputSchema: {
|
|
3395
4252
|
type: "object",
|
|
3396
4253
|
properties: {
|
|
@@ -3403,14 +4260,9 @@ var SCREENSHOT_TOOL = {
|
|
|
3403
4260
|
}
|
|
3404
4261
|
};
|
|
3405
4262
|
async function screenshot(transport, args) {
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
...args.session_id ? { targetSessionId: args.session_id } : {}
|
|
3410
|
-
});
|
|
3411
|
-
} else {
|
|
3412
|
-
return { type: "text", text: "Screenshots are not available in cloud mode. Run adhdev mcp in local mode (requires standalone daemon)." };
|
|
3413
|
-
}
|
|
4263
|
+
const result = await transport.command("screenshot", {
|
|
4264
|
+
...args.session_id ? { targetSessionId: args.session_id } : {}
|
|
4265
|
+
});
|
|
3414
4266
|
if (result?.success === false) {
|
|
3415
4267
|
return { type: "text", text: `Error: ${result.error ?? "screenshot failed"}` };
|
|
3416
4268
|
}
|
|
@@ -3437,42 +4289,22 @@ var GIT_STATUS_TOOL = {
|
|
|
3437
4289
|
type: "boolean",
|
|
3438
4290
|
description: "Include changed file list (default: true)."
|
|
3439
4291
|
},
|
|
3440
|
-
daemon_id: {
|
|
3441
|
-
type: "string",
|
|
3442
|
-
description: "Daemon ID (cloud mode only)."
|
|
3443
|
-
},
|
|
3444
4292
|
...FORMAT_PROP
|
|
3445
4293
|
},
|
|
3446
4294
|
required: ["workspace"]
|
|
3447
4295
|
}
|
|
3448
4296
|
};
|
|
3449
4297
|
async function gitStatus(transport, args) {
|
|
3450
|
-
let status;
|
|
3451
4298
|
let diffSummary;
|
|
3452
|
-
|
|
3453
|
-
|
|
4299
|
+
const statusResult = await transport.command("git_status", {
|
|
4300
|
+
workspace: args.workspace
|
|
4301
|
+
});
|
|
4302
|
+
const status = statusResult?.status ?? statusResult;
|
|
4303
|
+
if (args.include_diff !== false) {
|
|
4304
|
+
const diffResult = await transport.command("git_diff_summary", {
|
|
3454
4305
|
workspace: args.workspace
|
|
3455
4306
|
});
|
|
3456
|
-
|
|
3457
|
-
if (args.include_diff !== false) {
|
|
3458
|
-
const diffResult = await transport.command("git_diff_summary", {
|
|
3459
|
-
workspace: args.workspace
|
|
3460
|
-
});
|
|
3461
|
-
diffSummary = diffResult?.diffSummary ?? diffResult;
|
|
3462
|
-
}
|
|
3463
|
-
} else {
|
|
3464
|
-
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
3465
|
-
const result = await transport.gitStatus(
|
|
3466
|
-
args.daemon_id,
|
|
3467
|
-
args.workspace,
|
|
3468
|
-
args.include_diff !== false
|
|
3469
|
-
);
|
|
3470
|
-
if (result?.error) {
|
|
3471
|
-
if (args.format === "json") return JSON.stringify({ error: result.error }, null, 2);
|
|
3472
|
-
return `Error: ${result.error}`;
|
|
3473
|
-
}
|
|
3474
|
-
status = result?.status;
|
|
3475
|
-
diffSummary = result?.diff;
|
|
4307
|
+
diffSummary = diffResult?.diffSummary ?? diffResult;
|
|
3476
4308
|
}
|
|
3477
4309
|
if (status?.success === false || status?.reason) {
|
|
3478
4310
|
const msg = status?.error ?? status?.reason ?? "unknown";
|
|
@@ -3564,10 +4396,6 @@ var GIT_LOG_TOOL = {
|
|
|
3564
4396
|
type: "string",
|
|
3565
4397
|
description: "Only commits before this date (ISO 8601 or git date string, optional)."
|
|
3566
4398
|
},
|
|
3567
|
-
daemon_id: {
|
|
3568
|
-
type: "string",
|
|
3569
|
-
description: "Daemon ID (cloud mode only, required)."
|
|
3570
|
-
},
|
|
3571
4399
|
...FORMAT_PROP
|
|
3572
4400
|
},
|
|
3573
4401
|
required: ["workspace"]
|
|
@@ -3575,26 +4403,14 @@ var GIT_LOG_TOOL = {
|
|
|
3575
4403
|
};
|
|
3576
4404
|
async function gitLog(transport, args) {
|
|
3577
4405
|
const limit = Math.max(1, Math.min(100, args.limit ?? 20));
|
|
3578
|
-
let raw
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
});
|
|
3587
|
-
raw = raw?.log ?? raw;
|
|
3588
|
-
} else {
|
|
3589
|
-
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
3590
|
-
const result = await transport.gitLog(args.daemon_id, args.workspace, {
|
|
3591
|
-
limit,
|
|
3592
|
-
file: args.file,
|
|
3593
|
-
since: args.since,
|
|
3594
|
-
until: args.until
|
|
3595
|
-
});
|
|
3596
|
-
raw = result?.log ?? result;
|
|
3597
|
-
}
|
|
4406
|
+
let raw = await transport.command("git_log", {
|
|
4407
|
+
workspace: args.workspace,
|
|
4408
|
+
limit,
|
|
4409
|
+
...args.file ? { path: args.file } : {},
|
|
4410
|
+
...args.since ? { since: args.since } : {},
|
|
4411
|
+
...args.until ? { until: args.until } : {}
|
|
4412
|
+
});
|
|
4413
|
+
raw = raw?.log ?? raw;
|
|
3598
4414
|
if (raw?.success === false || raw?.reason) {
|
|
3599
4415
|
const msg = raw?.error ?? raw?.reason ?? "unknown";
|
|
3600
4416
|
if (args.format === "json") return JSON.stringify({ error: msg }, null, 2);
|
|
@@ -3657,10 +4473,6 @@ var GIT_DIFF_TOOL = {
|
|
|
3657
4473
|
type: "boolean",
|
|
3658
4474
|
description: "Show staged changes instead of unstaged (default: false)."
|
|
3659
4475
|
},
|
|
3660
|
-
daemon_id: {
|
|
3661
|
-
type: "string",
|
|
3662
|
-
description: "Daemon ID (cloud mode only, required)."
|
|
3663
|
-
},
|
|
3664
4476
|
...FORMAT_PROP
|
|
3665
4477
|
},
|
|
3666
4478
|
required: ["workspace"]
|
|
@@ -3669,20 +4481,7 @@ var GIT_DIFF_TOOL = {
|
|
|
3669
4481
|
async function gitDiff(transport, args) {
|
|
3670
4482
|
const maxLines = Math.max(10, Math.min(2e3, args.max_lines ?? 300));
|
|
3671
4483
|
const staged = args.staged ?? false;
|
|
3672
|
-
|
|
3673
|
-
return localGitDiff(transport, args.workspace, args.file, maxLines, staged, args.format);
|
|
3674
|
-
}
|
|
3675
|
-
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
3676
|
-
const result = await transport.gitDiff(args.daemon_id, args.workspace, {
|
|
3677
|
-
file: args.file,
|
|
3678
|
-
maxLines,
|
|
3679
|
-
staged
|
|
3680
|
-
});
|
|
3681
|
-
if (result?.error) {
|
|
3682
|
-
if (args.format === "json") return JSON.stringify({ error: result.error }, null, 2);
|
|
3683
|
-
return `Git diff error: ${result.error}`;
|
|
3684
|
-
}
|
|
3685
|
-
return formatDiffResult(result, args.format);
|
|
4484
|
+
return localGitDiff(transport, args.workspace, args.file, maxLines, staged, args.format);
|
|
3686
4485
|
}
|
|
3687
4486
|
async function localGitDiff(transport, workspace, file, maxLines, staged, format) {
|
|
3688
4487
|
if (file) {
|
|
@@ -3804,10 +4603,6 @@ var GIT_CHECKPOINT_TOOL = {
|
|
|
3804
4603
|
include_untracked: {
|
|
3805
4604
|
type: "boolean",
|
|
3806
4605
|
description: "Also stage and commit untracked files (default: false)."
|
|
3807
|
-
},
|
|
3808
|
-
daemon_id: {
|
|
3809
|
-
type: "string",
|
|
3810
|
-
description: "Daemon ID (cloud mode only, required)."
|
|
3811
4606
|
}
|
|
3812
4607
|
},
|
|
3813
4608
|
required: ["workspace", "message"]
|
|
@@ -3817,23 +4612,12 @@ async function gitCheckpoint(transport, args) {
|
|
|
3817
4612
|
const message = args.message?.trim();
|
|
3818
4613
|
if (!message) return "Error: message is required";
|
|
3819
4614
|
if (message.length > 200) return "Error: message must be 200 characters or fewer";
|
|
3820
|
-
let raw
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
});
|
|
3827
|
-
raw = raw?.checkpoint ?? raw;
|
|
3828
|
-
} else {
|
|
3829
|
-
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
3830
|
-
const result = await transport.gitCheckpoint(args.daemon_id, {
|
|
3831
|
-
workspace: args.workspace,
|
|
3832
|
-
message,
|
|
3833
|
-
includeUntracked: args.include_untracked ?? false
|
|
3834
|
-
});
|
|
3835
|
-
raw = result?.checkpoint ?? result;
|
|
3836
|
-
}
|
|
4615
|
+
let raw = await transport.command("git_checkpoint", {
|
|
4616
|
+
workspace: args.workspace,
|
|
4617
|
+
message,
|
|
4618
|
+
includeUntracked: args.include_untracked ?? false
|
|
4619
|
+
});
|
|
4620
|
+
raw = raw?.checkpoint ?? raw;
|
|
3837
4621
|
if (raw?.success === false || raw?.reason) {
|
|
3838
4622
|
const msg = raw?.error ?? raw?.reason ?? "unknown";
|
|
3839
4623
|
if (msg.includes("Nothing to commit") || msg.includes("nothing to commit")) {
|
|
@@ -3864,33 +4648,18 @@ var GIT_PUSH_TOOL = {
|
|
|
3864
4648
|
branch: {
|
|
3865
4649
|
type: "string",
|
|
3866
4650
|
description: "Branch to push (default: current branch)."
|
|
3867
|
-
},
|
|
3868
|
-
daemon_id: {
|
|
3869
|
-
type: "string",
|
|
3870
|
-
description: "Daemon ID (cloud mode only, required)."
|
|
3871
4651
|
}
|
|
3872
4652
|
},
|
|
3873
4653
|
required: ["workspace"]
|
|
3874
4654
|
}
|
|
3875
4655
|
};
|
|
3876
4656
|
async function gitPush(transport, args) {
|
|
3877
|
-
let raw
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
});
|
|
3884
|
-
raw = raw?.push ?? raw;
|
|
3885
|
-
} else {
|
|
3886
|
-
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
3887
|
-
const result = await transport.gitPush(args.daemon_id, {
|
|
3888
|
-
workspace: args.workspace,
|
|
3889
|
-
remote: args.remote,
|
|
3890
|
-
branch: args.branch
|
|
3891
|
-
});
|
|
3892
|
-
raw = result?.push ?? result;
|
|
3893
|
-
}
|
|
4657
|
+
let raw = await transport.command("git_push", {
|
|
4658
|
+
workspace: args.workspace,
|
|
4659
|
+
remote: args.remote ?? "origin",
|
|
4660
|
+
...args.branch ? { branch: args.branch } : {}
|
|
4661
|
+
});
|
|
4662
|
+
raw = raw?.push ?? raw;
|
|
3894
4663
|
if (raw?.success === false || raw?.reason) {
|
|
3895
4664
|
const msg = raw?.error ?? raw?.reason ?? "unknown";
|
|
3896
4665
|
return `Git push error: ${msg}`;
|
|
@@ -3921,32 +4690,17 @@ var LAUNCH_SESSION_TOOL = {
|
|
|
3921
4690
|
model: {
|
|
3922
4691
|
type: "string",
|
|
3923
4692
|
description: "Model override for ACP agents (e.g. claude-opus-4-7)."
|
|
3924
|
-
},
|
|
3925
|
-
daemon_id: {
|
|
3926
|
-
type: "string",
|
|
3927
|
-
description: "Daemon ID (cloud mode only). Required in cloud mode."
|
|
3928
4693
|
}
|
|
3929
4694
|
},
|
|
3930
4695
|
required: ["type"]
|
|
3931
4696
|
}
|
|
3932
4697
|
};
|
|
3933
4698
|
async function launchSession(transport, args) {
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
if (result2?.success === false) return `Error: ${result2.error ?? "launch failed"}`;
|
|
3940
|
-
const id2 = result2?.id ?? result2?.sessionId;
|
|
3941
|
-
return id2 ? `Session launched. id: ${id2}, type: ${args.type}` : `Launched: ${JSON.stringify(result2)}`;
|
|
3942
|
-
}
|
|
3943
|
-
if (!args.daemon_id) throw new Error("daemon_id is required in cloud mode");
|
|
3944
|
-
const result = await transport.launch(args.daemon_id, {
|
|
3945
|
-
type: args.type,
|
|
3946
|
-
dir: args.workspace,
|
|
3947
|
-
model: args.model
|
|
3948
|
-
});
|
|
3949
|
-
if (result?.success === false || result?.error) return `Error: ${result.error ?? "launch failed"}`;
|
|
4699
|
+
const isCliOrAcp = args.type.includes("-cli") || args.type.includes("-acp") || args.type === "codex";
|
|
4700
|
+
const commandType = isCliOrAcp ? "launch_cli" : "launch_ide";
|
|
4701
|
+
const payload = isCliOrAcp ? { cliType: args.type, dir: args.workspace ?? "~", ...args.model ? { model: args.model } : {} } : { ideType: args.type, enableCdp: true };
|
|
4702
|
+
const result = await transport.command(commandType, payload);
|
|
4703
|
+
if (result?.success === false) return `Error: ${result.error ?? "launch failed"}`;
|
|
3950
4704
|
const id = result?.id ?? result?.sessionId;
|
|
3951
4705
|
return id ? `Session launched. id: ${id}, type: ${args.type}` : `Launched: ${JSON.stringify(result)}`;
|
|
3952
4706
|
}
|
|
@@ -3962,43 +4716,29 @@ var STOP_SESSION_TOOL = {
|
|
|
3962
4716
|
type: "string",
|
|
3963
4717
|
description: "Session ID to stop (from list_sessions)."
|
|
3964
4718
|
},
|
|
3965
|
-
daemon_id: {
|
|
3966
|
-
type: "string",
|
|
3967
|
-
description: "Daemon ID (cloud mode only, required)."
|
|
3968
|
-
},
|
|
3969
4719
|
type: {
|
|
3970
4720
|
type: "string",
|
|
3971
|
-
description: "Provider type (e.g. hermes-cli, claude-cli).
|
|
4721
|
+
description: "Provider type (e.g. hermes-cli, claude-cli). Auto-resolved from session_id if omitted."
|
|
3972
4722
|
}
|
|
3973
4723
|
},
|
|
3974
4724
|
required: ["session_id"]
|
|
3975
4725
|
}
|
|
3976
4726
|
};
|
|
3977
4727
|
async function stopSession(transport, args) {
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
const session = (status?.sessions ?? []).find((s) => s.id === args.session_id);
|
|
3984
|
-
resolvedType = session?.providerType ?? session?.type;
|
|
3985
|
-
}
|
|
3986
|
-
if (!resolvedType) {
|
|
3987
|
-
return `Error: could not resolve session type for ${args.session_id}. Pass type= explicitly.`;
|
|
3988
|
-
}
|
|
3989
|
-
const result2 = await local.command("stop_cli", {
|
|
3990
|
-
targetSessionId: args.session_id,
|
|
3991
|
-
cliType: resolvedType
|
|
3992
|
-
});
|
|
3993
|
-
if (result2?.success === false) return `Error: ${result2.error ?? "stop failed"}`;
|
|
3994
|
-
return `Session ${args.session_id} stopped.`;
|
|
4728
|
+
let resolvedType = args.type;
|
|
4729
|
+
if (!resolvedType) {
|
|
4730
|
+
const status = await transport.getStatus();
|
|
4731
|
+
const session = (status?.sessions ?? []).find((s) => s.id === args.session_id);
|
|
4732
|
+
resolvedType = session?.providerType ?? session?.type;
|
|
3995
4733
|
}
|
|
3996
|
-
if (!
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4734
|
+
if (!resolvedType) {
|
|
4735
|
+
return `Error: could not resolve session type for ${args.session_id}. Pass type= explicitly.`;
|
|
4736
|
+
}
|
|
4737
|
+
const result = await transport.command("stop_cli", {
|
|
4738
|
+
targetSessionId: args.session_id,
|
|
4739
|
+
cliType: resolvedType
|
|
4000
4740
|
});
|
|
4001
|
-
if (result?.success === false
|
|
4741
|
+
if (result?.success === false) return `Error: ${result.error ?? "stop failed"}`;
|
|
4002
4742
|
return `Session ${args.session_id} stopped.`;
|
|
4003
4743
|
}
|
|
4004
4744
|
|
|
@@ -4009,28 +4749,18 @@ var CHECK_PENDING_TOOL = {
|
|
|
4009
4749
|
inputSchema: {
|
|
4010
4750
|
type: "object",
|
|
4011
4751
|
properties: {
|
|
4012
|
-
daemon_id: {
|
|
4013
|
-
type: "string",
|
|
4014
|
-
description: "Daemon ID to check (cloud mode). Omit to check all daemons."
|
|
4015
|
-
},
|
|
4016
4752
|
...FORMAT_PROP
|
|
4017
4753
|
},
|
|
4018
4754
|
required: []
|
|
4019
4755
|
}
|
|
4020
4756
|
};
|
|
4021
4757
|
async function checkPending(transport, args) {
|
|
4022
|
-
if (isLocalTransport(transport)) {
|
|
4023
|
-
return checkPendingLocal(transport, args.format);
|
|
4024
|
-
}
|
|
4025
|
-
return checkPendingCloud(transport, args.daemon_id, args.format);
|
|
4026
|
-
}
|
|
4027
|
-
async function checkPendingLocal(transport, format) {
|
|
4028
4758
|
const status = await transport.getStatus();
|
|
4029
4759
|
const sessions = status?.sessions ?? [];
|
|
4030
4760
|
const pending = sessions.filter(
|
|
4031
4761
|
(s) => s.status === "waiting_approval" || s.agentStatus === "waiting_approval"
|
|
4032
4762
|
);
|
|
4033
|
-
if (format === "json") {
|
|
4763
|
+
if (args.format === "json") {
|
|
4034
4764
|
return JSON.stringify({
|
|
4035
4765
|
pending: pending.map((s) => ({
|
|
4036
4766
|
session_id: s.id,
|
|
@@ -4053,56 +4783,6 @@ async function checkPendingLocal(transport, format) {
|
|
|
4053
4783
|
});
|
|
4054
4784
|
return `Pending approvals (${pending.length}):
|
|
4055
4785
|
|
|
4056
|
-
${lines.join("\n\n")}`;
|
|
4057
|
-
}
|
|
4058
|
-
async function checkPendingCloud(transport, daemonId, format) {
|
|
4059
|
-
const pending = [];
|
|
4060
|
-
if (daemonId) {
|
|
4061
|
-
const daemonStatus = await transport.getDaemonStatus(daemonId);
|
|
4062
|
-
const sessions = daemonStatus?.sessions ?? [];
|
|
4063
|
-
for (const s of sessions) {
|
|
4064
|
-
if (s.status === "waiting_approval") pending.push({ daemonId, session: s });
|
|
4065
|
-
}
|
|
4066
|
-
} else {
|
|
4067
|
-
const data = await transport.listDaemons();
|
|
4068
|
-
const daemons = data?.daemons ?? [];
|
|
4069
|
-
for (let i = 0; i < daemons.length; i += 5) {
|
|
4070
|
-
await Promise.allSettled(
|
|
4071
|
-
daemons.slice(i, i + 5).map(async (d) => {
|
|
4072
|
-
try {
|
|
4073
|
-
const daemonStatus = await transport.getDaemonStatus(d.id);
|
|
4074
|
-
const sessions = daemonStatus?.sessions ?? [];
|
|
4075
|
-
for (const s of sessions) {
|
|
4076
|
-
if (s.status === "waiting_approval") pending.push({ daemonId: d.id, session: s });
|
|
4077
|
-
}
|
|
4078
|
-
} catch {
|
|
4079
|
-
}
|
|
4080
|
-
})
|
|
4081
|
-
);
|
|
4082
|
-
}
|
|
4083
|
-
}
|
|
4084
|
-
if (format === "json") {
|
|
4085
|
-
return JSON.stringify({
|
|
4086
|
-
pending: pending.map(({ daemonId: dId, session: s }) => ({
|
|
4087
|
-
daemon_id: dId,
|
|
4088
|
-
session_id: s.id,
|
|
4089
|
-
workspace: s.workspace ?? null,
|
|
4090
|
-
type: s.providerType ?? null,
|
|
4091
|
-
modal_message: null,
|
|
4092
|
-
buttons: []
|
|
4093
|
-
}))
|
|
4094
|
-
}, null, 2);
|
|
4095
|
-
}
|
|
4096
|
-
if (pending.length === 0) return "No sessions waiting for approval.";
|
|
4097
|
-
const lines = pending.map(({ daemonId: dId, session: s }) => {
|
|
4098
|
-
const parts = [`daemon_id: ${dId}`, `session_id: ${s.id}`];
|
|
4099
|
-
if (s.workspace) parts.push(`workspace: ${s.workspace}`);
|
|
4100
|
-
if (s.providerType) parts.push(`type: ${s.providerType}`);
|
|
4101
|
-
parts.push("(use read_chat to see the approval prompt)");
|
|
4102
|
-
return parts.join("\n ");
|
|
4103
|
-
});
|
|
4104
|
-
return `Pending approvals (${pending.length}):
|
|
4105
|
-
|
|
4106
4786
|
${lines.join("\n\n")}`;
|
|
4107
4787
|
}
|
|
4108
4788
|
|
|
@@ -4116,10 +4796,10 @@ async function buildMeshModeCoordinatorPrompt(mesh) {
|
|
|
4116
4796
|
}
|
|
4117
4797
|
}
|
|
4118
4798
|
async function startMcpServer(opts) {
|
|
4119
|
-
const transport = opts.mode === "
|
|
4799
|
+
const transport = opts.mode === "ipc" ? new IpcTransport({ port: opts.port }) : new LocalTransport({ port: opts.port, password: opts.password });
|
|
4120
4800
|
const alive = await transport.ping();
|
|
4121
4801
|
if (!alive) {
|
|
4122
|
-
const hint = opts.mode === "local" ? `Make sure the standalone daemon is running (adhdev standalone or npx @adhdev/daemon-standalone).` :
|
|
4802
|
+
const hint = opts.mode === "local" ? `Make sure the standalone daemon is running (adhdev standalone or npx @adhdev/daemon-standalone).` : `Make sure the cloud daemon is running with local IPC enabled (adhdev daemon).`;
|
|
4123
4803
|
process.stderr.write(`[adhdev-mcp] Cannot reach ${opts.mode} daemon. ${hint}
|
|
4124
4804
|
`);
|
|
4125
4805
|
process.exit(1);
|
|
@@ -4134,63 +4814,6 @@ async function startMcpServer(opts) {
|
|
|
4134
4814
|
`);
|
|
4135
4815
|
} catch (e) {
|
|
4136
4816
|
process.stderr.write(`[adhdev-mcp] Failed to parse ADHDEV_INLINE_MESH: ${e.message}
|
|
4137
|
-
`);
|
|
4138
|
-
}
|
|
4139
|
-
}
|
|
4140
|
-
if (!mesh && opts.mode === "cloud" && opts.apiKey) {
|
|
4141
|
-
try {
|
|
4142
|
-
const base = opts.baseUrl || "https://api.adhf.dev";
|
|
4143
|
-
const res = await fetch(`${base}/api/v1/repo-meshes/${opts.meshId}`, {
|
|
4144
|
-
headers: { "Authorization": `Bearer ${opts.apiKey}`, "Content-Type": "application/json" }
|
|
4145
|
-
});
|
|
4146
|
-
if (res.ok) {
|
|
4147
|
-
const data = await res.json();
|
|
4148
|
-
const rm = data.mesh;
|
|
4149
|
-
const nodes = data.nodes || [];
|
|
4150
|
-
let policy = {};
|
|
4151
|
-
try {
|
|
4152
|
-
policy = JSON.parse(rm.policy_json || rm.policy || "{}");
|
|
4153
|
-
} catch {
|
|
4154
|
-
}
|
|
4155
|
-
let coordinator = {};
|
|
4156
|
-
try {
|
|
4157
|
-
coordinator = JSON.parse(rm.coordinator_json || rm.coordinator_config || "{}");
|
|
4158
|
-
} catch {
|
|
4159
|
-
}
|
|
4160
|
-
mesh = {
|
|
4161
|
-
id: rm.id,
|
|
4162
|
-
name: rm.name,
|
|
4163
|
-
repoIdentity: rm.repo_identity,
|
|
4164
|
-
repoRemoteUrl: rm.repo_remote_url,
|
|
4165
|
-
defaultBranch: rm.default_branch,
|
|
4166
|
-
policy: {
|
|
4167
|
-
requirePreTaskCheckpoint: false,
|
|
4168
|
-
requirePostTaskCheckpoint: true,
|
|
4169
|
-
requireApprovalForPush: true,
|
|
4170
|
-
requireApprovalForDestructiveGit: true,
|
|
4171
|
-
dirtyWorkspaceBehavior: "warn",
|
|
4172
|
-
maxParallelTasks: 2,
|
|
4173
|
-
spawnedSessionVisibility: "visible",
|
|
4174
|
-
...policy
|
|
4175
|
-
},
|
|
4176
|
-
coordinator,
|
|
4177
|
-
nodes: nodes.map((n) => ({
|
|
4178
|
-
id: n.id,
|
|
4179
|
-
workspace: n.workspace,
|
|
4180
|
-
repoRoot: n.repo_root,
|
|
4181
|
-
daemonId: n.daemon_id,
|
|
4182
|
-
userOverrides: {},
|
|
4183
|
-
policy: {},
|
|
4184
|
-
isLocalWorktree: false
|
|
4185
|
-
})),
|
|
4186
|
-
createdAt: rm.created_at,
|
|
4187
|
-
updatedAt: rm.updated_at
|
|
4188
|
-
};
|
|
4189
|
-
process.stderr.write(`[adhdev-mcp] Loaded mesh config from cloud API
|
|
4190
|
-
`);
|
|
4191
|
-
}
|
|
4192
|
-
} catch (e) {
|
|
4193
|
-
process.stderr.write(`[adhdev-mcp] Cloud mesh fetch failed, falling back to local: ${e.message}
|
|
4194
4817
|
`);
|
|
4195
4818
|
}
|
|
4196
4819
|
}
|
|
@@ -4217,17 +4840,19 @@ async function startMcpServer(opts) {
|
|
|
4217
4840
|
}
|
|
4218
4841
|
}
|
|
4219
4842
|
if (!mesh) {
|
|
4220
|
-
process.stderr.write(`[adhdev-mcp] Mesh '${opts.meshId}' not found in
|
|
4843
|
+
process.stderr.write(`[adhdev-mcp] Mesh '${opts.meshId}' not found in local config. Use 'adhdev mesh list' to see available meshes.
|
|
4221
4844
|
`);
|
|
4222
4845
|
process.exit(1);
|
|
4223
4846
|
}
|
|
4224
4847
|
let localDaemonId;
|
|
4225
4848
|
let localMachineId;
|
|
4849
|
+
let coordinatorHostname = import_node_os.default.hostname();
|
|
4226
4850
|
if (transport instanceof LocalTransport || transport instanceof IpcTransport) {
|
|
4227
4851
|
try {
|
|
4228
4852
|
const { loadConfig } = await import("@adhdev/daemon-core");
|
|
4229
4853
|
const cfg = loadConfig();
|
|
4230
|
-
if (cfg.
|
|
4854
|
+
if (cfg.machineId) localMachineId = cfg.machineId;
|
|
4855
|
+
else if (cfg.registeredMachineId) localMachineId = cfg.registeredMachineId;
|
|
4231
4856
|
} catch {
|
|
4232
4857
|
}
|
|
4233
4858
|
}
|
|
@@ -4235,14 +4860,16 @@ async function startMcpServer(opts) {
|
|
|
4235
4860
|
try {
|
|
4236
4861
|
const statusResult = await transport.getStatus();
|
|
4237
4862
|
const instanceId = typeof statusResult?.status?.instanceId === "string" ? statusResult.status.instanceId.trim() : "";
|
|
4863
|
+
const hostname = typeof statusResult?.status?.hostname === "string" ? statusResult.status.hostname.trim() : typeof statusResult?.status?.machine?.hostname === "string" ? statusResult.status.machine.hostname.trim() : "";
|
|
4238
4864
|
if (instanceId) localDaemonId = instanceId;
|
|
4865
|
+
if (hostname) coordinatorHostname = hostname;
|
|
4239
4866
|
} catch {
|
|
4240
4867
|
}
|
|
4241
4868
|
}
|
|
4242
|
-
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {} };
|
|
4869
|
+
const meshCtx = { mesh, transport, ...localDaemonId ? { localDaemonId } : {}, ...localMachineId ? { localMachineId } : {}, ...coordinatorHostname ? { coordinatorHostname } : {} };
|
|
4243
4870
|
const coordinatorPrompt = await buildMeshModeCoordinatorPrompt(mesh);
|
|
4244
4871
|
const server2 = new import_server.Server(
|
|
4245
|
-
{ name: "adhdev-mcp-server", version: "0.9.
|
|
4872
|
+
{ name: "adhdev-mcp-server", version: "0.9.82" },
|
|
4246
4873
|
{ capabilities: { tools: {}, resources: {} } }
|
|
4247
4874
|
);
|
|
4248
4875
|
const { ListResourcesRequestSchema, ReadResourceRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
|
|
@@ -4268,7 +4895,7 @@ async function startMcpServer(opts) {
|
|
|
4268
4895
|
let text;
|
|
4269
4896
|
switch (name) {
|
|
4270
4897
|
case "mesh_status":
|
|
4271
|
-
text = await meshStatus(meshCtx);
|
|
4898
|
+
text = await meshStatus(meshCtx, a);
|
|
4272
4899
|
break;
|
|
4273
4900
|
case "mesh_list_nodes":
|
|
4274
4901
|
text = await meshListNodes(meshCtx);
|
|
@@ -4300,6 +4927,9 @@ async function startMcpServer(opts) {
|
|
|
4300
4927
|
case "mesh_git_status":
|
|
4301
4928
|
text = await meshGitStatus(meshCtx, a);
|
|
4302
4929
|
break;
|
|
4930
|
+
case "mesh_fast_forward_node":
|
|
4931
|
+
text = await meshFastForwardNode(meshCtx, a);
|
|
4932
|
+
break;
|
|
4303
4933
|
case "mesh_checkpoint":
|
|
4304
4934
|
text = await meshCheckpoint(meshCtx, a);
|
|
4305
4935
|
break;
|
|
@@ -4315,6 +4945,21 @@ async function startMcpServer(opts) {
|
|
|
4315
4945
|
case "mesh_refine_node":
|
|
4316
4946
|
text = await meshRefineNode(meshCtx, a);
|
|
4317
4947
|
break;
|
|
4948
|
+
case "mesh_refine_batch":
|
|
4949
|
+
text = await meshRefineBatch(meshCtx, a);
|
|
4950
|
+
break;
|
|
4951
|
+
case "mesh_refine_config_schema":
|
|
4952
|
+
text = await meshRefineConfigSchema(meshCtx);
|
|
4953
|
+
break;
|
|
4954
|
+
case "mesh_validate_refine_config":
|
|
4955
|
+
text = await meshValidateRefineConfig(meshCtx, a);
|
|
4956
|
+
break;
|
|
4957
|
+
case "mesh_suggest_refine_config":
|
|
4958
|
+
text = await meshSuggestRefineConfig(meshCtx, a);
|
|
4959
|
+
break;
|
|
4960
|
+
case "mesh_refine_plan":
|
|
4961
|
+
text = await meshRefinePlan(meshCtx, a);
|
|
4962
|
+
break;
|
|
4318
4963
|
case "mesh_cleanup_sessions":
|
|
4319
4964
|
text = await meshCleanupSessions(meshCtx, a);
|
|
4320
4965
|
break;
|
|
@@ -4324,6 +4969,12 @@ async function startMcpServer(opts) {
|
|
|
4324
4969
|
case "mesh_reconcile_ledger":
|
|
4325
4970
|
text = await meshReconcileLedger(meshCtx, a);
|
|
4326
4971
|
break;
|
|
4972
|
+
case "mesh_mission_upsert":
|
|
4973
|
+
text = await meshMissionUpsert(meshCtx, a);
|
|
4974
|
+
break;
|
|
4975
|
+
case "mesh_review_inbox":
|
|
4976
|
+
text = await meshReviewInbox(meshCtx, a);
|
|
4977
|
+
break;
|
|
4327
4978
|
default:
|
|
4328
4979
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
4329
4980
|
}
|
|
@@ -4346,6 +4997,7 @@ async function startMcpServer(opts) {
|
|
|
4346
4997
|
CHECK_PENDING_TOOL,
|
|
4347
4998
|
READ_CHAT_TOOL,
|
|
4348
4999
|
READ_CHAT_DEBUG_TOOL,
|
|
5000
|
+
SPEC_DEBUG_TOOL,
|
|
4349
5001
|
SEND_CHAT_TOOL,
|
|
4350
5002
|
APPROVE_TOOL,
|
|
4351
5003
|
GIT_STATUS_TOOL,
|
|
@@ -4370,7 +5022,7 @@ async function startMcpServer(opts) {
|
|
|
4370
5022
|
return { content: [{ type: "text", text }] };
|
|
4371
5023
|
}
|
|
4372
5024
|
case "list_sessions": {
|
|
4373
|
-
const text = await listSessions(transport, { format: a.format
|
|
5025
|
+
const text = await listSessions(transport, { format: a.format });
|
|
4374
5026
|
return { content: [{ type: "text", text }] };
|
|
4375
5027
|
}
|
|
4376
5028
|
case "read_chat": {
|
|
@@ -4381,13 +5033,17 @@ async function startMcpServer(opts) {
|
|
|
4381
5033
|
const text = await readChatDebug(transport, a);
|
|
4382
5034
|
return { content: [{ type: "text", text }] };
|
|
4383
5035
|
}
|
|
5036
|
+
case "spec_debug": {
|
|
5037
|
+
const text = await specDebug(transport, a);
|
|
5038
|
+
return { content: [{ type: "text", text }] };
|
|
5039
|
+
}
|
|
4384
5040
|
case "send_chat": {
|
|
4385
|
-
const text = await sendChat(transport, { message: a.message, session_id: a.session_id
|
|
5041
|
+
const text = await sendChat(transport, { message: a.message, session_id: a.session_id });
|
|
4386
5042
|
return { content: [{ type: "text", text }] };
|
|
4387
5043
|
}
|
|
4388
5044
|
case "approve": {
|
|
4389
5045
|
const action = a.action === "reject" ? "reject" : "approve";
|
|
4390
|
-
const text = await approve(transport, { action, session_id: a.session_id
|
|
5046
|
+
const text = await approve(transport, { action, session_id: a.session_id });
|
|
4391
5047
|
return { content: [{ type: "text", text }] };
|
|
4392
5048
|
}
|
|
4393
5049
|
case "screenshot": {
|
|
@@ -4400,44 +5056,42 @@ async function startMcpServer(opts) {
|
|
|
4400
5056
|
return { content: [{ type: "text", text: result.text }] };
|
|
4401
5057
|
}
|
|
4402
5058
|
case "git_status": {
|
|
4403
|
-
const text = await gitStatus(transport, { workspace: a.workspace, include_diff: a.include_diff,
|
|
5059
|
+
const text = await gitStatus(transport, { workspace: a.workspace, include_diff: a.include_diff, format: a.format });
|
|
4404
5060
|
return { content: [{ type: "text", text }] };
|
|
4405
5061
|
}
|
|
4406
5062
|
case "git_log": {
|
|
4407
|
-
const text = await gitLog(transport, { workspace: a.workspace, limit: a.limit, file: a.file, since: a.since, until: a.until,
|
|
5063
|
+
const text = await gitLog(transport, { workspace: a.workspace, limit: a.limit, file: a.file, since: a.since, until: a.until, format: a.format });
|
|
4408
5064
|
return { content: [{ type: "text", text }] };
|
|
4409
5065
|
}
|
|
4410
5066
|
case "git_diff": {
|
|
4411
|
-
const text = await gitDiff(transport, { workspace: a.workspace, file: a.file, max_lines: a.max_lines, staged: a.staged,
|
|
5067
|
+
const text = await gitDiff(transport, { workspace: a.workspace, file: a.file, max_lines: a.max_lines, staged: a.staged, format: a.format });
|
|
4412
5068
|
return { content: [{ type: "text", text }] };
|
|
4413
5069
|
}
|
|
4414
5070
|
case "git_checkpoint": {
|
|
4415
|
-
const text = await gitCheckpoint(transport, { workspace: a.workspace, message: a.message, include_untracked: a.include_untracked
|
|
5071
|
+
const text = await gitCheckpoint(transport, { workspace: a.workspace, message: a.message, include_untracked: a.include_untracked });
|
|
4416
5072
|
return { content: [{ type: "text", text }] };
|
|
4417
5073
|
}
|
|
4418
5074
|
case "git_push": {
|
|
4419
|
-
const text = await gitPush(transport, { workspace: a.workspace, remote: a.remote, branch: a.branch
|
|
5075
|
+
const text = await gitPush(transport, { workspace: a.workspace, remote: a.remote, branch: a.branch });
|
|
4420
5076
|
return { content: [{ type: "text", text }] };
|
|
4421
5077
|
}
|
|
4422
5078
|
case "launch_session": {
|
|
4423
5079
|
const text = await launchSession(transport, {
|
|
4424
5080
|
type: a.type,
|
|
4425
5081
|
workspace: a.workspace,
|
|
4426
|
-
model: a.model
|
|
4427
|
-
daemon_id: a.daemon_id
|
|
5082
|
+
model: a.model
|
|
4428
5083
|
});
|
|
4429
5084
|
return { content: [{ type: "text", text }] };
|
|
4430
5085
|
}
|
|
4431
5086
|
case "stop_session": {
|
|
4432
5087
|
const text = await stopSession(transport, {
|
|
4433
5088
|
session_id: a.session_id,
|
|
4434
|
-
daemon_id: a.daemon_id,
|
|
4435
5089
|
type: a.type
|
|
4436
5090
|
});
|
|
4437
5091
|
return { content: [{ type: "text", text }] };
|
|
4438
5092
|
}
|
|
4439
5093
|
case "check_pending": {
|
|
4440
|
-
const text = await checkPending(transport, {
|
|
5094
|
+
const text = await checkPending(transport, { format: a.format });
|
|
4441
5095
|
return { content: [{ type: "text", text }] };
|
|
4442
5096
|
}
|
|
4443
5097
|
default:
|
|
@@ -4459,26 +5113,18 @@ async function startMcpServer(opts) {
|
|
|
4459
5113
|
// src/index.ts
|
|
4460
5114
|
function parseArgs(argv, env = process.env) {
|
|
4461
5115
|
const args = argv.slice(2);
|
|
4462
|
-
let apiKey;
|
|
4463
|
-
let baseUrl;
|
|
4464
5116
|
let port;
|
|
4465
5117
|
let password;
|
|
4466
5118
|
let meshId;
|
|
4467
5119
|
let explicitMode;
|
|
4468
5120
|
for (let i = 0; i < args.length; i++) {
|
|
4469
5121
|
const arg = args[i];
|
|
4470
|
-
if (
|
|
4471
|
-
apiKey = args[++i];
|
|
4472
|
-
} else if (arg?.startsWith("--api-key=")) {
|
|
4473
|
-
apiKey = arg.slice("--api-key=".length);
|
|
4474
|
-
} else if (arg === "--base-url" && args[i + 1]) {
|
|
4475
|
-
baseUrl = args[++i];
|
|
4476
|
-
} else if (arg === "--mode" && args[i + 1]) {
|
|
5122
|
+
if (arg === "--mode" && args[i + 1]) {
|
|
4477
5123
|
const value = String(args[++i]).trim();
|
|
4478
|
-
if (value === "local" || value === "
|
|
5124
|
+
if (value === "local" || value === "ipc") explicitMode = value;
|
|
4479
5125
|
} else if (arg?.startsWith("--mode=")) {
|
|
4480
5126
|
const value = arg.slice("--mode=".length).trim();
|
|
4481
|
-
if (value === "local" || value === "
|
|
5127
|
+
if (value === "local" || value === "ipc") explicitMode = value;
|
|
4482
5128
|
} else if (arg === "--port" && args[i + 1]) {
|
|
4483
5129
|
port = Number(args[++i]);
|
|
4484
5130
|
} else if (arg?.startsWith("--port=")) {
|
|
@@ -4494,15 +5140,14 @@ function parseArgs(argv, env = process.env) {
|
|
|
4494
5140
|
process.exit(0);
|
|
4495
5141
|
}
|
|
4496
5142
|
}
|
|
4497
|
-
if (!apiKey && env.ADHDEV_API_KEY) apiKey = env.ADHDEV_API_KEY;
|
|
4498
5143
|
if (!password && env.ADHDEV_PASSWORD) password = env.ADHDEV_PASSWORD;
|
|
4499
5144
|
if (!meshId && env.ADHDEV_MESH_ID) meshId = env.ADHDEV_MESH_ID;
|
|
4500
5145
|
if (!explicitMode && env.ADHDEV_MCP_TRANSPORT) {
|
|
4501
5146
|
const value = env.ADHDEV_MCP_TRANSPORT.trim();
|
|
4502
|
-
if (value === "local" || value === "
|
|
5147
|
+
if (value === "local" || value === "ipc") explicitMode = value;
|
|
4503
5148
|
}
|
|
4504
|
-
const mode = explicitMode || (
|
|
4505
|
-
return { mode, port, password,
|
|
5149
|
+
const mode = explicitMode || (meshId && env.ADHDEV_INLINE_MESH ? "ipc" : "local");
|
|
5150
|
+
return { mode, port, password, meshId };
|
|
4506
5151
|
}
|
|
4507
5152
|
function printHelp() {
|
|
4508
5153
|
console.error(buildMcpHelpText());
|