@adhdev/daemon-standalone 0.9.82-rc.136 → 0.9.82-rc.138
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 +3538 -2274
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/public/assets/index-CNP9SlXf.js +101 -0
- package/public/index.html +1 -1
- package/vendor/mcp-server/index.js +246 -114
- package/vendor/mcp-server/index.js.map +1 -1
package/public/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<meta name="description" content="ADHDev self-hosted dashboard for controlling AI agents" />
|
|
8
8
|
<link rel="icon" href="/otter-logo.png" />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-CNP9SlXf.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/vendor-DNk1FT1R.js">
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-If4eNpuX.css">
|
|
13
13
|
</head>
|
|
@@ -50,6 +50,113 @@ var IPC_COMMAND_TIMEOUTS_MS = {
|
|
|
50
50
|
fast_forward_mesh_node: 12e4,
|
|
51
51
|
mesh_status: 12e4
|
|
52
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
|
+
}
|
|
53
160
|
var IpcTransport = class {
|
|
54
161
|
port;
|
|
55
162
|
path;
|
|
@@ -78,86 +185,41 @@ var IpcTransport = class {
|
|
|
78
185
|
args
|
|
79
186
|
});
|
|
80
187
|
}
|
|
81
|
-
|
|
188
|
+
sendIpcCommand(type, args) {
|
|
82
189
|
const WebSocketCtor = globalThis.WebSocket;
|
|
83
190
|
if (!WebSocketCtor) {
|
|
84
|
-
|
|
191
|
+
return Promise.reject(new Error("WebSocket is not available in this Node runtime; Node 20+ is required for daemon IPC mode"));
|
|
85
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}`;
|
|
86
205
|
return new Promise((resolve, reject) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} catch {
|
|
97
|
-
}
|
|
98
|
-
fn();
|
|
99
|
-
};
|
|
100
|
-
const nestedCommand = typeof args?.command === "string" ? args.command : "";
|
|
101
|
-
const targetDaemonId = typeof args?.targetDaemonId === "string" ? args.targetDaemonId : "";
|
|
102
|
-
const effectiveType = type === "mesh_relay_command" && nestedCommand ? nestedCommand : type;
|
|
103
|
-
const timeoutMs = Math.max(
|
|
104
|
-
IPC_COMMAND_TIMEOUTS_MS[type] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS,
|
|
105
|
-
IPC_COMMAND_TIMEOUTS_MS[effectiveType] ?? DEFAULT_IPC_COMMAND_TIMEOUT_MS
|
|
106
|
-
);
|
|
107
|
-
const diagnosticParts = [
|
|
108
|
-
`command='${type}'`,
|
|
109
|
-
...nestedCommand ? [`relayedCommand='${nestedCommand}'`] : [],
|
|
110
|
-
...targetDaemonId ? [`targetDaemonId='${targetDaemonId.slice(0, 12)}'`] : [],
|
|
111
|
-
...typeof args?.nodeId === "string" ? [`nodeId='${args.nodeId}'`] : [],
|
|
112
|
-
...typeof args?.workspace === "string" ? [`workspace='${args.workspace}'`] : []
|
|
113
|
-
];
|
|
114
|
-
const timeout = setTimeout(() => {
|
|
115
|
-
finish(() => reject(new Error(`Daemon IPC ${diagnosticParts.join(" ")} timed out after ${Math.round(timeoutMs / 1e3)}s (requestId=${requestId})`)));
|
|
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})`));
|
|
116
215
|
}, timeoutMs);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}));
|
|
125
|
-
};
|
|
126
|
-
ws.addEventListener("open", () => {
|
|
127
|
-
ws.send(JSON.stringify({
|
|
128
|
-
type: "ext:register",
|
|
129
|
-
payload: {
|
|
130
|
-
ideType: "mcp-server",
|
|
131
|
-
ideVersion: "1.0.0",
|
|
132
|
-
extensionVersion: "1.0.0",
|
|
133
|
-
instanceId: `mcp-server-${process.pid}`,
|
|
134
|
-
machineId: "mcp-server",
|
|
135
|
-
workspaceFolders: []
|
|
136
|
-
}
|
|
137
|
-
}));
|
|
138
|
-
});
|
|
139
|
-
ws.addEventListener("message", (event) => {
|
|
140
|
-
try {
|
|
141
|
-
const raw = typeof event.data === "string" ? event.data : String(event.data);
|
|
142
|
-
const msg = JSON.parse(raw);
|
|
143
|
-
if (msg?.type === "daemon:welcome") {
|
|
144
|
-
send();
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (msg?.type !== "ext:command_result") return;
|
|
148
|
-
if (msg?.payload?.requestId !== requestId) return;
|
|
149
|
-
const payload = msg.payload;
|
|
150
|
-
if (payload?.success === false) {
|
|
151
|
-
finish(() => reject(new Error(payload.error || `Daemon IPC command '${type}' failed`)));
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
finish(() => resolve(payload?.result ?? payload));
|
|
155
|
-
} catch {
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
ws.addEventListener("error", () => {
|
|
159
|
-
finish(() => reject(new Error(`Cannot connect to daemon IPC at ws://127.0.0.1:${this.port}${this.path}`)));
|
|
160
|
-
});
|
|
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
|
+
}
|
|
161
223
|
});
|
|
162
224
|
}
|
|
163
225
|
};
|
|
@@ -266,7 +328,17 @@ function annotateRapidReadChatAdvisory(payload, options) {
|
|
|
266
328
|
|
|
267
329
|
// src/tools/mesh-tools.ts
|
|
268
330
|
var import_daemon_core = require("@adhdev/daemon-core");
|
|
331
|
+
var SESSION_PROVIDER_METADATA_TTL_MS = 30 * 6e4;
|
|
269
332
|
var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
333
|
+
function getSessionMetadata(key) {
|
|
334
|
+
const entry = meshSessionProviderMetadata.get(key);
|
|
335
|
+
if (!entry) return void 0;
|
|
336
|
+
if (entry.expiresAt <= Date.now()) {
|
|
337
|
+
meshSessionProviderMetadata.delete(key);
|
|
338
|
+
return void 0;
|
|
339
|
+
}
|
|
340
|
+
return entry;
|
|
341
|
+
}
|
|
270
342
|
var ACTIVE_WORK_POLLING_BACKOFF_MS = 6e4;
|
|
271
343
|
function buildActiveWorkPollingGuidance(summary, now = Date.now()) {
|
|
272
344
|
if (!summary || summary.generatingCount <= 0) return void 0;
|
|
@@ -313,7 +385,7 @@ var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
|
313
385
|
var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
|
|
314
386
|
var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
315
387
|
async function refreshMeshFromDaemon(ctx) {
|
|
316
|
-
if (!(ctx.transport
|
|
388
|
+
if (!isLocalTransport(ctx.transport)) return;
|
|
317
389
|
try {
|
|
318
390
|
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
319
391
|
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
@@ -728,9 +800,8 @@ function isIdleSessionRecord(session) {
|
|
|
728
800
|
function isMeshOwnedDelegateSession(session, meshId, nodeId) {
|
|
729
801
|
const settings = session?.settings;
|
|
730
802
|
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
731
|
-
const coordinatorDaemonId = typeof settings?.meshCoordinatorDaemonId === "string" ? settings.meshCoordinatorDaemonId.trim() : "";
|
|
732
803
|
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
733
|
-
if (sessionMeshId !== meshId
|
|
804
|
+
if (sessionMeshId !== meshId) return false;
|
|
734
805
|
return !sessionNodeId || sessionNodeId === nodeId;
|
|
735
806
|
}
|
|
736
807
|
function chooseDispatchableSession(sessions, providerType, meshId, nodeId) {
|
|
@@ -1220,10 +1291,11 @@ function rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, metadata)
|
|
|
1220
1291
|
const providerType = readString(metadata.providerType);
|
|
1221
1292
|
const providerSessionId = readString(metadata.providerSessionId);
|
|
1222
1293
|
if (!providerType && !providerSessionId) return;
|
|
1223
|
-
const existing =
|
|
1294
|
+
const existing = getSessionMetadata(meshSessionCacheKey(keyNodeId, keySessionId)) || { providerType: "" };
|
|
1224
1295
|
meshSessionProviderMetadata.set(meshSessionCacheKey(keyNodeId, keySessionId), {
|
|
1225
1296
|
providerType: providerType || existing.providerType,
|
|
1226
|
-
providerSessionId: providerSessionId || existing.providerSessionId
|
|
1297
|
+
providerSessionId: providerSessionId || existing.providerSessionId,
|
|
1298
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
1227
1299
|
});
|
|
1228
1300
|
}
|
|
1229
1301
|
function rememberMeshSessionProviderMetadataFromEvent(event) {
|
|
@@ -1236,7 +1308,7 @@ function rememberMeshSessionProviderMetadataFromEvent(event) {
|
|
|
1236
1308
|
});
|
|
1237
1309
|
}
|
|
1238
1310
|
function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
|
|
1239
|
-
const entries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail:
|
|
1311
|
+
const entries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 50 });
|
|
1240
1312
|
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1241
1313
|
const entry = entries[i];
|
|
1242
1314
|
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
@@ -1255,7 +1327,7 @@ function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessio
|
|
|
1255
1327
|
return void 0;
|
|
1256
1328
|
}
|
|
1257
1329
|
function resolveMeshSessionProviderMetadata(ctx, nodeId, runtimeSessionId) {
|
|
1258
|
-
const cached =
|
|
1330
|
+
const cached = getSessionMetadata(meshSessionCacheKey(nodeId, runtimeSessionId));
|
|
1259
1331
|
if (cached?.providerType || cached?.providerSessionId) return cached;
|
|
1260
1332
|
const fromLedger = resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId);
|
|
1261
1333
|
if (fromLedger) rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, fromLedger);
|
|
@@ -1273,6 +1345,20 @@ function isGitStatusDirty(status) {
|
|
|
1273
1345
|
if (typeof status?.dirty === "boolean") return status.dirty;
|
|
1274
1346
|
return countUncommittedChanges(status) > 0;
|
|
1275
1347
|
}
|
|
1348
|
+
function slimLedgerPayload(payload) {
|
|
1349
|
+
const slim = {};
|
|
1350
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
1351
|
+
if (k === "message" || k === "taskSummary") {
|
|
1352
|
+
slim[k] = typeof v === "string" && v.length > 200 ? v.slice(0, 200) + "\u2026" : v;
|
|
1353
|
+
} else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
|
|
1354
|
+
} else if (k === "finalSummary") {
|
|
1355
|
+
slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
|
|
1356
|
+
} else {
|
|
1357
|
+
slim[k] = v;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
return slim;
|
|
1361
|
+
}
|
|
1276
1362
|
function readRelatedRepos(node) {
|
|
1277
1363
|
const raw = Array.isArray(node.relatedRepos) ? node.relatedRepos : Array.isArray(node.policy?.relatedRepos) ? node.policy.relatedRepos : [];
|
|
1278
1364
|
return raw.map((entry) => ({
|
|
@@ -1592,7 +1678,7 @@ async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
|
1592
1678
|
return surfacedEvents;
|
|
1593
1679
|
}
|
|
1594
1680
|
if (isLocalTransport(ctx.transport)) {
|
|
1595
|
-
const events = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id).filter(matchesCurrentMesh);
|
|
1681
|
+
const events = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
|
|
1596
1682
|
events.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
1597
1683
|
return events;
|
|
1598
1684
|
}
|
|
@@ -1951,9 +2037,8 @@ var ALL_MESH_TOOLS = [
|
|
|
1951
2037
|
async function meshStatus(ctx, args = {}) {
|
|
1952
2038
|
await refreshMeshFromDaemon(ctx);
|
|
1953
2039
|
const { mesh, transport } = ctx;
|
|
1954
|
-
const results = [];
|
|
1955
2040
|
const ledgerSummary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
1956
|
-
|
|
2041
|
+
const results = await Promise.all(mesh.nodes.map(async (node) => {
|
|
1957
2042
|
const entry = {
|
|
1958
2043
|
nodeId: node.id,
|
|
1959
2044
|
workspace: node.workspace,
|
|
@@ -2027,7 +2112,7 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2027
2112
|
if (recoveryContext.consecutiveNodeFailures > 0) {
|
|
2028
2113
|
entry.recoveryHints = {
|
|
2029
2114
|
consecutiveFailures: recoveryContext.consecutiveNodeFailures,
|
|
2030
|
-
lastTaskMessage: recoveryContext.lastTaskMessage,
|
|
2115
|
+
lastTaskMessage: typeof recoveryContext.lastTaskMessage === "string" ? recoveryContext.lastTaskMessage.slice(0, 100) + (recoveryContext.lastTaskMessage.length > 100 ? "\u2026" : "") : recoveryContext.lastTaskMessage,
|
|
2031
2116
|
advice: recoveryContext.advice,
|
|
2032
2117
|
retryRecommended: recoveryContext.retryRecommended
|
|
2033
2118
|
};
|
|
@@ -2069,11 +2154,16 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2069
2154
|
if (relatedRepos.length) entry.relatedRepos = relatedRepos;
|
|
2070
2155
|
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
2071
2156
|
if (liveSessions.length > 0) {
|
|
2072
|
-
entry.sessions = liveSessions
|
|
2157
|
+
entry.sessions = liveSessions.map((s) => ({
|
|
2158
|
+
id: s.instanceId ?? s.id ?? s.sessionId,
|
|
2159
|
+
status: s.status ?? s.lifecycle ?? s.state,
|
|
2160
|
+
providerType: s.providerType ?? s.cliType ?? s.type,
|
|
2161
|
+
...s.activeChat?.status ? { chatStatus: s.activeChat.status } : {}
|
|
2162
|
+
})).filter((s) => s.id);
|
|
2073
2163
|
}
|
|
2074
|
-
|
|
2075
|
-
}
|
|
2076
|
-
const ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail:
|
|
2164
|
+
return entry;
|
|
2165
|
+
}));
|
|
2166
|
+
const ledgerEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail: 200 });
|
|
2077
2167
|
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2078
2168
|
meshId: mesh.id,
|
|
2079
2169
|
queue: (0, import_daemon_core.getQueue)(mesh.id),
|
|
@@ -2101,7 +2191,8 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2101
2191
|
activeWork: activeWorkEvidence.activeWork,
|
|
2102
2192
|
staleDirectWorkSummary,
|
|
2103
2193
|
...args.includeStaleDirectWorkDetails === true ? { staleDirectWork: activeWorkEvidence.staleDirectWork } : {},
|
|
2104
|
-
terminalDirectWork
|
|
2194
|
+
// terminalDirectWork is historical (completed/failed direct dispatches) — opt-in only.
|
|
2195
|
+
...args.includeTerminalDirectWork === true ? { terminalDirectWork: activeWorkEvidence.terminalDirectWork } : {},
|
|
2105
2196
|
activeWorkSummary: activeWorkEvidence.summary,
|
|
2106
2197
|
...pollingGuidance ? { pollingGuidance } : {},
|
|
2107
2198
|
branchConvergenceSummary: summarizeBranchConvergence(results)
|
|
@@ -2132,7 +2223,11 @@ async function meshTaskHistory(ctx, args) {
|
|
|
2132
2223
|
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
2133
2224
|
const tail = typeof args.tail === "number" && args.tail > 0 ? args.tail : 20;
|
|
2134
2225
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
2135
|
-
const
|
|
2226
|
+
const rawEntries = (0, import_daemon_core.readLedgerEntries)(mesh.id, { tail, kind });
|
|
2227
|
+
const entries = rawEntries.map((e) => ({
|
|
2228
|
+
...e,
|
|
2229
|
+
payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
|
|
2230
|
+
}));
|
|
2136
2231
|
const summary = (0, import_daemon_core.getLedgerSummary)(mesh.id);
|
|
2137
2232
|
return JSON.stringify({
|
|
2138
2233
|
meshId: mesh.id,
|
|
@@ -2282,7 +2377,21 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
2282
2377
|
} catch {
|
|
2283
2378
|
}
|
|
2284
2379
|
}
|
|
2285
|
-
}).catch(() => {
|
|
2380
|
+
}).catch((err) => {
|
|
2381
|
+
try {
|
|
2382
|
+
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2383
|
+
kind: "p2p_dispatch_failed",
|
|
2384
|
+
nodeId: node.id,
|
|
2385
|
+
payload: {
|
|
2386
|
+
source: "queue",
|
|
2387
|
+
via: "p2p_direct",
|
|
2388
|
+
taskId: task.id,
|
|
2389
|
+
error: err?.message || String(err),
|
|
2390
|
+
dispatchFailedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2391
|
+
}
|
|
2392
|
+
});
|
|
2393
|
+
} catch {
|
|
2394
|
+
}
|
|
2286
2395
|
})
|
|
2287
2396
|
);
|
|
2288
2397
|
}
|
|
@@ -2310,12 +2419,25 @@ async function meshViewQueue(ctx, args) {
|
|
|
2310
2419
|
const visibleSummary = buildQueueStatusSummary(queue);
|
|
2311
2420
|
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
2312
2421
|
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
2422
|
+
(0, import_daemon_core.markStaleDirectDispatches)(ctx.mesh.id);
|
|
2423
|
+
const ledgerEntries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
2424
|
+
const directDispatches = (0, import_daemon_core.getActiveDirectDispatches)(ctx.mesh.id);
|
|
2313
2425
|
const activeWorkEvidence = (0, import_daemon_core.buildMeshActiveWork)({
|
|
2314
2426
|
meshId: ctx.mesh.id,
|
|
2315
2427
|
queue: fullQueue,
|
|
2316
|
-
ledgerEntries
|
|
2428
|
+
ledgerEntries,
|
|
2429
|
+
// Always pass BeadsDB records (may be empty). buildMeshActiveWork uses them for local
|
|
2430
|
+
// dispatches and falls through to ledger scan for remote P2P dispatches not in BeadsDB.
|
|
2431
|
+
directDispatches,
|
|
2317
2432
|
nodes: liveNodes
|
|
2318
2433
|
});
|
|
2434
|
+
const recentDispatchFailures = ledgerEntries.filter((e) => e.kind === "p2p_dispatch_failed").slice(-20).map((e) => ({
|
|
2435
|
+
nodeId: e.nodeId,
|
|
2436
|
+
taskId: e.payload?.taskId,
|
|
2437
|
+
error: e.payload?.error,
|
|
2438
|
+
via: e.payload?.via,
|
|
2439
|
+
failedAt: e.payload?.dispatchFailedAt || e.timestamp
|
|
2440
|
+
}));
|
|
2319
2441
|
const staleAssignedTasks = maintenance.staleAssignedTasks || [];
|
|
2320
2442
|
const requestedHistoricalRows = queue.some((task) => HISTORICAL_QUEUE_STATUSES.has(String(task?.status || "")));
|
|
2321
2443
|
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
@@ -2333,25 +2455,20 @@ async function meshViewQueue(ctx, args) {
|
|
|
2333
2455
|
filtered: Boolean(statusFilter?.length) || view !== "all"
|
|
2334
2456
|
},
|
|
2335
2457
|
queue,
|
|
2336
|
-
visibleQueue: queue,
|
|
2337
2458
|
activeWork: activeWorkEvidence.activeWork,
|
|
2338
2459
|
staleDirectWork: activeWorkEvidence.staleDirectWork,
|
|
2339
2460
|
activeWorkSummary: activeWorkEvidence.summary,
|
|
2340
2461
|
...pollingGuidance ? { pollingGuidance } : {},
|
|
2341
|
-
visibleSummary,
|
|
2342
2462
|
summary,
|
|
2343
|
-
activeCounts: summary.activeCounts,
|
|
2344
|
-
historicalCounts: summary.historicalCounts,
|
|
2345
|
-
activeCount: summary.activeCount,
|
|
2346
|
-
historicalCount: summary.historicalCount,
|
|
2347
|
-
visibleActiveCounts: visibleSummary.activeCounts,
|
|
2348
|
-
visibleHistoricalCounts: visibleSummary.historicalCounts,
|
|
2349
|
-
visibleActiveCount: visibleSummary.activeCount,
|
|
2350
|
-
visibleHistoricalCount: visibleSummary.historicalCount,
|
|
2351
2463
|
staleAssignedTasks,
|
|
2352
2464
|
staleAssignedCount: maintenance.staleAssignedCount,
|
|
2353
2465
|
queueMaintenance: maintenance,
|
|
2354
2466
|
cleanupDryRun: maintenance,
|
|
2467
|
+
...recentDispatchFailures.length > 0 ? {
|
|
2468
|
+
recentDispatchFailures,
|
|
2469
|
+
dispatchFailureCount: recentDispatchFailures.length,
|
|
2470
|
+
dispatchFailureNote: "Remote P2P dispatch attempts that failed. Affected tasks remain pending and may require mesh_queue_requeue if no idle session picks them up."
|
|
2471
|
+
} : {},
|
|
2355
2472
|
...view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status)) ? {
|
|
2356
2473
|
activeQueue: queue.filter((task) => ACTIVE_QUEUE_STATUSES.has(String(task?.status || "")))
|
|
2357
2474
|
} : {},
|
|
@@ -2489,7 +2606,7 @@ async function meshSendTask(ctx, args) {
|
|
|
2489
2606
|
}
|
|
2490
2607
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2491
2608
|
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
2492
|
-
const cached =
|
|
2609
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id || ""));
|
|
2493
2610
|
const taskId = (0, import_node_crypto.randomUUID)();
|
|
2494
2611
|
const result2 = await ipcDispatchToRemoteAgent(ctx, node, {
|
|
2495
2612
|
session_id: args.session_id,
|
|
@@ -2527,7 +2644,7 @@ async function meshSendTask(ctx, args) {
|
|
|
2527
2644
|
});
|
|
2528
2645
|
}
|
|
2529
2646
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
2530
|
-
const cached =
|
|
2647
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
|
|
2531
2648
|
let resolvedProviderType = cached?.providerType || "";
|
|
2532
2649
|
if (!resolvedProviderType) {
|
|
2533
2650
|
let explicitSession = explicitTargetSession;
|
|
@@ -2582,7 +2699,8 @@ async function meshSendTask(ctx, args) {
|
|
|
2582
2699
|
if (resolvedProviderType) {
|
|
2583
2700
|
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, args.session_id), {
|
|
2584
2701
|
providerType: resolvedProviderType,
|
|
2585
|
-
providerSessionId: readString(explicitSession?.providerSessionId) || void 0
|
|
2702
|
+
providerSessionId: readString(explicitSession?.providerSessionId) || void 0,
|
|
2703
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
2586
2704
|
});
|
|
2587
2705
|
}
|
|
2588
2706
|
}
|
|
@@ -2621,6 +2739,7 @@ async function meshSendTask(ctx, args) {
|
|
|
2621
2739
|
});
|
|
2622
2740
|
}
|
|
2623
2741
|
const taskId = (0, import_node_crypto.randomUUID)();
|
|
2742
|
+
const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2624
2743
|
try {
|
|
2625
2744
|
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
2626
2745
|
kind: "task_dispatched",
|
|
@@ -2637,6 +2756,17 @@ async function meshSendTask(ctx, args) {
|
|
|
2637
2756
|
});
|
|
2638
2757
|
} catch {
|
|
2639
2758
|
}
|
|
2759
|
+
(0, import_daemon_core.insertDirectDispatch)(ctx.mesh.id, {
|
|
2760
|
+
taskId,
|
|
2761
|
+
nodeId: args.node_id,
|
|
2762
|
+
sessionId: args.session_id,
|
|
2763
|
+
providerType: resolvedProviderType || void 0,
|
|
2764
|
+
message: args.message,
|
|
2765
|
+
taskMode: taskMode || void 0,
|
|
2766
|
+
via: "local_direct",
|
|
2767
|
+
dispatchedToIdleSession: sessionWasIdle,
|
|
2768
|
+
dispatchedAt
|
|
2769
|
+
});
|
|
2640
2770
|
return JSON.stringify({
|
|
2641
2771
|
success: true,
|
|
2642
2772
|
dispatched: true,
|
|
@@ -2662,7 +2792,7 @@ async function meshSendTask(ctx, args) {
|
|
|
2662
2792
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
2663
2793
|
});
|
|
2664
2794
|
}
|
|
2665
|
-
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id) : [];
|
|
2795
|
+
const pendingEvents = isLocalTransport(ctx.transport) ? (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId) : [];
|
|
2666
2796
|
const result = { success: true, source: "queue", nodeId: args.node_id, taskId: task.id, status: task.status, taskMode: task.taskMode };
|
|
2667
2797
|
if (pendingEvents.length > 0) {
|
|
2668
2798
|
result.pendingCoordinatorEvents = pendingEvents;
|
|
@@ -2695,18 +2825,19 @@ async function meshReadChat(ctx, args) {
|
|
|
2695
2825
|
workspace: node.workspace,
|
|
2696
2826
|
...cached?.providerType ? { agentType: cached.providerType, providerType: cached.providerType } : {},
|
|
2697
2827
|
...providerSessionId ? { providerSessionId } : {},
|
|
2698
|
-
tailLimit: args.tail ??
|
|
2828
|
+
tailLimit: args.tail ?? 3
|
|
2699
2829
|
});
|
|
2700
2830
|
const payload = annotateRapidReadChatAdvisory(unwrapCommandPayload(result), {
|
|
2701
2831
|
key: `mesh:${args.node_id}:${args.session_id}`,
|
|
2702
2832
|
toolName: "mesh_read_chat",
|
|
2703
2833
|
completionCallbackExpected: true
|
|
2704
2834
|
});
|
|
2705
|
-
|
|
2835
|
+
const useCompact = args.compact !== false;
|
|
2836
|
+
if (useCompact) {
|
|
2706
2837
|
const compactPayload = compactChatPayload(payload, {
|
|
2707
2838
|
nodeId: args.node_id,
|
|
2708
2839
|
sessionId: args.session_id,
|
|
2709
|
-
limit: args.tail ??
|
|
2840
|
+
limit: args.tail ?? 3
|
|
2710
2841
|
});
|
|
2711
2842
|
return JSON.stringify(
|
|
2712
2843
|
payload.pollingAdvisory ? { ...compactPayload, pollingAdvisory: payload.pollingAdvisory } : compactPayload,
|
|
@@ -2719,7 +2850,7 @@ async function meshReadChat(ctx, args) {
|
|
|
2719
2850
|
try {
|
|
2720
2851
|
const targetId = `${node.daemonId}:session:${args.session_id}`;
|
|
2721
2852
|
const res = await ctx.transport.readChat(targetId, {
|
|
2722
|
-
limit: args.tail ??
|
|
2853
|
+
limit: args.tail ?? 3,
|
|
2723
2854
|
sessionId: args.session_id
|
|
2724
2855
|
});
|
|
2725
2856
|
return JSON.stringify(res, null, 2);
|
|
@@ -2821,7 +2952,8 @@ async function meshLaunchSession(ctx, args) {
|
|
|
2821
2952
|
if (runtimeSessionId) {
|
|
2822
2953
|
meshSessionProviderMetadata.set(meshSessionCacheKey(args.node_id, runtimeSessionId), {
|
|
2823
2954
|
providerType: resolvedProviderType,
|
|
2824
|
-
...providerSessionId ? { providerSessionId } : {}
|
|
2955
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
2956
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
2825
2957
|
});
|
|
2826
2958
|
}
|
|
2827
2959
|
try {
|
|
@@ -3044,7 +3176,7 @@ async function meshCheckpoint(ctx, args) {
|
|
|
3044
3176
|
async function meshApprove(ctx, args) {
|
|
3045
3177
|
const node = await findNodeWithRefresh(ctx, args.node_id);
|
|
3046
3178
|
if (isLocalTransport(ctx.transport)) {
|
|
3047
|
-
const cached =
|
|
3179
|
+
const cached = getSessionMetadata(meshSessionCacheKey(args.node_id, args.session_id));
|
|
3048
3180
|
const providerSessionId = cached?.providerSessionId;
|
|
3049
3181
|
const result = await commandForNode(ctx, node, "resolve_action", {
|
|
3050
3182
|
sessionId: args.session_id,
|