@adhdev/daemon-standalone 0.9.77-rc.33 → 0.9.77-rc.35
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/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-Crr3Vqz7.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/vendor-CLec0455.js">
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-DftJ2WZr.css">
|
|
13
13
|
</head>
|
|
@@ -25120,6 +25120,7 @@ __export(dist_exports, {
|
|
|
25120
25120
|
buildThoughtChatMessage: () => buildThoughtChatMessage,
|
|
25121
25121
|
buildToolChatMessage: () => buildToolChatMessage,
|
|
25122
25122
|
buildUserChatMessage: () => buildUserChatMessage,
|
|
25123
|
+
cancelTask: () => cancelTask,
|
|
25123
25124
|
claimNextTask: () => claimNextTask,
|
|
25124
25125
|
classifyChatMessageVisibility: () => classifyChatMessageVisibility,
|
|
25125
25126
|
classifyHotChatSessionsForSubscriptionFlush: () => classifyHotChatSessionsForSubscriptionFlush,
|
|
@@ -25231,6 +25232,7 @@ __export(dist_exports, {
|
|
|
25231
25232
|
registerExtensionProviders: () => registerExtensionProviders,
|
|
25232
25233
|
removeNode: () => removeNode,
|
|
25233
25234
|
removeWorktree: () => removeWorktree,
|
|
25235
|
+
requeueTask: () => requeueTask,
|
|
25234
25236
|
resetConfig: () => resetConfig,
|
|
25235
25237
|
resetDebugRuntimeConfig: () => resetDebugRuntimeConfig,
|
|
25236
25238
|
resetState: () => resetState,
|
|
@@ -26074,6 +26076,40 @@ function updateTaskStatus(meshId, taskId, status) {
|
|
|
26074
26076
|
writeQueue(meshId, queue);
|
|
26075
26077
|
return queue[idx];
|
|
26076
26078
|
}
|
|
26079
|
+
function cancelTask(meshId, taskId, opts) {
|
|
26080
|
+
const queue = readQueue(meshId);
|
|
26081
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
26082
|
+
if (idx === -1) return null;
|
|
26083
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
26084
|
+
queue[idx].status = "cancelled";
|
|
26085
|
+
queue[idx].updatedAt = now;
|
|
26086
|
+
queue[idx].cancelledAt = now;
|
|
26087
|
+
if (opts?.reason) queue[idx].cancelReason = opts.reason;
|
|
26088
|
+
writeQueue(meshId, queue);
|
|
26089
|
+
return queue[idx];
|
|
26090
|
+
}
|
|
26091
|
+
function requeueTask(meshId, taskId, opts) {
|
|
26092
|
+
const queue = readQueue(meshId);
|
|
26093
|
+
const idx = queue.findIndex((q) => q.id === taskId);
|
|
26094
|
+
if (idx === -1) return null;
|
|
26095
|
+
const entry = queue[idx];
|
|
26096
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
26097
|
+
entry.status = "pending";
|
|
26098
|
+
delete entry.assignedNodeId;
|
|
26099
|
+
delete entry.assignedSessionId;
|
|
26100
|
+
delete entry.cancelledAt;
|
|
26101
|
+
delete entry.cancelReason;
|
|
26102
|
+
if (opts?.clearTargetNode) delete entry.targetNodeId;
|
|
26103
|
+
if (typeof opts?.targetNodeId === "string") entry.targetNodeId = opts.targetNodeId;
|
|
26104
|
+
if (opts?.clearTargetSession !== false) delete entry.targetSessionId;
|
|
26105
|
+
if (typeof opts?.targetSessionId === "string") entry.targetSessionId = opts.targetSessionId;
|
|
26106
|
+
entry.updatedAt = now;
|
|
26107
|
+
entry.requeuedAt = now;
|
|
26108
|
+
entry.requeueCount = (entry.requeueCount || 0) + 1;
|
|
26109
|
+
if (opts?.reason) entry.requeueReason = opts.reason;
|
|
26110
|
+
writeQueue(meshId, queue);
|
|
26111
|
+
return entry;
|
|
26112
|
+
}
|
|
26077
26113
|
function updateSessionTaskStatus(meshId, sessionId, status) {
|
|
26078
26114
|
const queue = readQueue(meshId);
|
|
26079
26115
|
for (let i = queue.length - 1; i >= 0; i--) {
|
|
@@ -26092,7 +26128,8 @@ function getMeshQueueStats(meshId) {
|
|
|
26092
26128
|
pending: queue.filter((q) => q.status === "pending").length,
|
|
26093
26129
|
assigned: queue.filter((q) => q.status === "assigned").length,
|
|
26094
26130
|
completed: queue.filter((q) => q.status === "completed").length,
|
|
26095
|
-
failed: queue.filter((q) => q.status === "failed").length
|
|
26131
|
+
failed: queue.filter((q) => q.status === "failed").length,
|
|
26132
|
+
cancelled: queue.filter((q) => q.status === "cancelled").length
|
|
26096
26133
|
};
|
|
26097
26134
|
}
|
|
26098
26135
|
function setLogLevel(level) {
|
|
@@ -26379,12 +26416,14 @@ Do NOT retry on this node. Consider reassigning to a different node or asking th
|
|
|
26379
26416
|
return "";
|
|
26380
26417
|
}
|
|
26381
26418
|
function injectMeshSystemMessage(components, args) {
|
|
26419
|
+
let completedTaskForLedger = null;
|
|
26382
26420
|
if (args.event === "agent:generating_completed") {
|
|
26383
26421
|
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
26384
26422
|
const nodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
26385
26423
|
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
26386
26424
|
if (sessionId) {
|
|
26387
|
-
updateSessionTaskStatus(args.meshId, sessionId, "completed");
|
|
26425
|
+
const completedTask = updateSessionTaskStatus(args.meshId, sessionId, "completed");
|
|
26426
|
+
completedTaskForLedger = completedTask ? { id: completedTask.id } : null;
|
|
26388
26427
|
if (nodeId && providerType) {
|
|
26389
26428
|
setTimeout(() => {
|
|
26390
26429
|
tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
|
|
@@ -26397,6 +26436,7 @@ function injectMeshSystemMessage(components, args) {
|
|
|
26397
26436
|
const providerType = readNonEmptyString(args.metadataEvent.providerType);
|
|
26398
26437
|
const completedTask = sessionId ? updateSessionTaskStatus(args.meshId, sessionId, "completed") : null;
|
|
26399
26438
|
if (completedTask) {
|
|
26439
|
+
completedTaskForLedger = { id: completedTask.id };
|
|
26400
26440
|
try {
|
|
26401
26441
|
appendLedgerEntry(args.meshId, {
|
|
26402
26442
|
kind: "task_completed",
|
|
@@ -26408,7 +26448,8 @@ function injectMeshSystemMessage(components, args) {
|
|
|
26408
26448
|
nodeLabel: args.nodeLabel,
|
|
26409
26449
|
taskId: completedTask.id,
|
|
26410
26450
|
completedViaReady: true,
|
|
26411
|
-
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0
|
|
26451
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
26452
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
26412
26453
|
}
|
|
26413
26454
|
});
|
|
26414
26455
|
} catch (e) {
|
|
@@ -26451,7 +26492,9 @@ function injectMeshSystemMessage(components, args) {
|
|
|
26451
26492
|
payload: {
|
|
26452
26493
|
event: args.event,
|
|
26453
26494
|
nodeLabel: args.nodeLabel,
|
|
26454
|
-
|
|
26495
|
+
taskId: completedTaskForLedger?.id || void 0,
|
|
26496
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
26497
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
26455
26498
|
}
|
|
26456
26499
|
});
|
|
26457
26500
|
} catch (e) {
|
|
@@ -26566,7 +26609,8 @@ function handleMeshForwardEvent(components, payload) {
|
|
|
26566
26609
|
metadataEvent: {
|
|
26567
26610
|
targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.instanceId),
|
|
26568
26611
|
providerType: readNonEmptyString(payload.providerType),
|
|
26569
|
-
providerSessionId: readNonEmptyString(payload.providerSessionId)
|
|
26612
|
+
providerSessionId: readNonEmptyString(payload.providerSessionId),
|
|
26613
|
+
finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary)
|
|
26570
26614
|
}
|
|
26571
26615
|
});
|
|
26572
26616
|
}
|
|
@@ -41350,10 +41394,12 @@ Follow these recovery rules:
|
|
|
41350
41394
|
});
|
|
41351
41395
|
mesh_work_queue_exports = {};
|
|
41352
41396
|
__export2(mesh_work_queue_exports, {
|
|
41397
|
+
cancelTask: () => cancelTask,
|
|
41353
41398
|
claimNextTask: () => claimNextTask,
|
|
41354
41399
|
enqueueTask: () => enqueueTask,
|
|
41355
41400
|
getMeshQueueStats: () => getMeshQueueStats,
|
|
41356
41401
|
getQueue: () => getQueue,
|
|
41402
|
+
requeueTask: () => requeueTask,
|
|
41357
41403
|
updateSessionTaskStatus: () => updateSessionTaskStatus,
|
|
41358
41404
|
updateTaskStatus: () => updateTaskStatus
|
|
41359
41405
|
});
|
|
@@ -43034,6 +43080,11 @@ ${lastSnapshot}`;
|
|
|
43034
43080
|
};
|
|
43035
43081
|
}
|
|
43036
43082
|
// ─── Script Execution ──────────────────────────
|
|
43083
|
+
invokeCliScript(script, input) {
|
|
43084
|
+
const hasStateFactory = typeof this.cliScripts?.createState === "function";
|
|
43085
|
+
const expectsStateArgument = hasStateFactory || this.scriptState !== null || script.length >= 2;
|
|
43086
|
+
return expectsStateArgument ? script(this.scriptState, input) : script(input);
|
|
43087
|
+
}
|
|
43037
43088
|
runParseSession() {
|
|
43038
43089
|
if (typeof this.cliScripts?.parseSession !== "function") {
|
|
43039
43090
|
this.parseErrorMessage = `${this.cliType} parseSession unavailable`;
|
|
@@ -43054,7 +43105,10 @@ ${lastSnapshot}`;
|
|
|
43054
43105
|
scope: this.currentTurnScope,
|
|
43055
43106
|
runtimeSettings: this.runtimeSettings
|
|
43056
43107
|
});
|
|
43057
|
-
const session = this.
|
|
43108
|
+
const session = this.invokeCliScript(
|
|
43109
|
+
this.cliScripts.parseSession,
|
|
43110
|
+
{ ...input, tail, tailScreen: buildCliScreenSnapshot(tail) }
|
|
43111
|
+
);
|
|
43058
43112
|
this.parseErrorMessage = null;
|
|
43059
43113
|
return session && typeof session === "object" ? session : null;
|
|
43060
43114
|
} catch (e) {
|
|
@@ -43068,7 +43122,7 @@ ${lastSnapshot}`;
|
|
|
43068
43122
|
if (!this.cliScripts?.detectStatus) return null;
|
|
43069
43123
|
try {
|
|
43070
43124
|
const screenText = this.terminalScreen.getText();
|
|
43071
|
-
const status = this.cliScripts.detectStatus
|
|
43125
|
+
const status = this.invokeCliScript(this.cliScripts.detectStatus, {
|
|
43072
43126
|
tail: text.slice(-500),
|
|
43073
43127
|
screenText,
|
|
43074
43128
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -43087,7 +43141,7 @@ ${lastSnapshot}`;
|
|
|
43087
43141
|
try {
|
|
43088
43142
|
const screenText = this.terminalScreen.getText();
|
|
43089
43143
|
const buffer = screenText || this.accumulatedBuffer;
|
|
43090
|
-
return this.cliScripts.parseApproval
|
|
43144
|
+
return this.invokeCliScript(this.cliScripts.parseApproval, {
|
|
43091
43145
|
buffer,
|
|
43092
43146
|
screenText,
|
|
43093
43147
|
rawBuffer: this.accumulatedRawBuffer,
|
|
@@ -53146,6 +53200,39 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53146
53200
|
return { success: false, error: e.message };
|
|
53147
53201
|
}
|
|
53148
53202
|
}
|
|
53203
|
+
case "cancel_mesh_queue_task": {
|
|
53204
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
53205
|
+
const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
|
|
53206
|
+
if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
|
|
53207
|
+
try {
|
|
53208
|
+
const { cancelTask: cancelTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
53209
|
+
const reason = typeof args?.reason === "string" ? args.reason : void 0;
|
|
53210
|
+
const task = cancelTask2(meshId, taskId, { reason });
|
|
53211
|
+
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
53212
|
+
return { success: true, task };
|
|
53213
|
+
} catch (e) {
|
|
53214
|
+
return { success: false, error: e.message };
|
|
53215
|
+
}
|
|
53216
|
+
}
|
|
53217
|
+
case "requeue_mesh_queue_task": {
|
|
53218
|
+
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
53219
|
+
const taskId = typeof args?.taskId === "string" ? args.taskId.trim() : "";
|
|
53220
|
+
if (!meshId || !taskId) return { success: false, error: "meshId and taskId required" };
|
|
53221
|
+
try {
|
|
53222
|
+
const { requeueTask: requeueTask2 } = await Promise.resolve().then(() => (init_mesh_work_queue(), mesh_work_queue_exports));
|
|
53223
|
+
const task = requeueTask2(meshId, taskId, {
|
|
53224
|
+
reason: typeof args?.reason === "string" ? args.reason : void 0,
|
|
53225
|
+
targetNodeId: typeof args?.targetNodeId === "string" ? args.targetNodeId.trim() : void 0,
|
|
53226
|
+
targetSessionId: typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : void 0,
|
|
53227
|
+
clearTargetNode: args?.clearTargetNode === true,
|
|
53228
|
+
clearTargetSession: args?.clearTargetSession !== false
|
|
53229
|
+
});
|
|
53230
|
+
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
53231
|
+
return { success: true, task };
|
|
53232
|
+
} catch (e) {
|
|
53233
|
+
return { success: false, error: e.message };
|
|
53234
|
+
}
|
|
53235
|
+
}
|
|
53149
53236
|
case "add_mesh_node": {
|
|
53150
53237
|
const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
|
|
53151
53238
|
const workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
|
|
@@ -53303,7 +53390,13 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
53303
53390
|
appendLedgerEntry2(meshId, {
|
|
53304
53391
|
kind: "node_removed",
|
|
53305
53392
|
nodeId,
|
|
53306
|
-
payload: {
|
|
53393
|
+
payload: {
|
|
53394
|
+
worktree: !!node?.isLocalWorktree,
|
|
53395
|
+
sessionCleanupMode,
|
|
53396
|
+
workspace: typeof node?.workspace === "string" ? node.workspace : void 0,
|
|
53397
|
+
daemonId: typeof node?.daemonId === "string" ? node.daemonId : void 0,
|
|
53398
|
+
worktreeBranch: typeof node?.worktreeBranch === "string" ? node.worktreeBranch : void 0
|
|
53399
|
+
}
|
|
53307
53400
|
});
|
|
53308
53401
|
} catch {
|
|
53309
53402
|
}
|
|
@@ -57459,6 +57552,11 @@ function annotateRapidReadChatAdvisory(payload, options) {
|
|
|
57459
57552
|
// src/tools/mesh-tools.ts
|
|
57460
57553
|
init_dist2();
|
|
57461
57554
|
var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
57555
|
+
function readString(value) {
|
|
57556
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
57557
|
+
}
|
|
57558
|
+
var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
|
|
57559
|
+
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
57462
57560
|
async function refreshMeshFromDaemon(ctx) {
|
|
57463
57561
|
if (!(ctx.transport instanceof IpcTransport)) return;
|
|
57464
57562
|
try {
|
|
@@ -57479,6 +57577,138 @@ async function findNodeWithRefresh(ctx, nodeId) {
|
|
|
57479
57577
|
if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
|
|
57480
57578
|
return refreshed;
|
|
57481
57579
|
}
|
|
57580
|
+
async function findOptionalNodeWithRefresh(ctx, nodeId) {
|
|
57581
|
+
const hit = ctx.mesh.nodes.find((n) => n.id === nodeId);
|
|
57582
|
+
if (hit) return hit;
|
|
57583
|
+
await refreshMeshFromDaemon(ctx);
|
|
57584
|
+
return ctx.mesh.nodes.find((n) => n.id === nodeId) ?? null;
|
|
57585
|
+
}
|
|
57586
|
+
function hasRecentDuplicateDispatch(ctx, args) {
|
|
57587
|
+
const now = Date.now();
|
|
57588
|
+
const normalizedMessage = args.message.trim();
|
|
57589
|
+
for (const task of getQueue(ctx.mesh.id)) {
|
|
57590
|
+
const timestamp2 = new Date(task.updatedAt || task.createdAt).getTime();
|
|
57591
|
+
if (!Number.isFinite(timestamp2) || now - timestamp2 > DUPLICATE_DISPATCH_WINDOW_MS) continue;
|
|
57592
|
+
if (task.targetNodeId && task.targetNodeId !== args.node_id) continue;
|
|
57593
|
+
if (task.assignedNodeId && task.assignedNodeId !== args.node_id) continue;
|
|
57594
|
+
if (args.session_id && task.targetSessionId !== args.session_id && task.assignedSessionId !== args.session_id) continue;
|
|
57595
|
+
if (task.message?.trim() === normalizedMessage) {
|
|
57596
|
+
return { duplicate: true, entry: task, source: "queue" };
|
|
57597
|
+
}
|
|
57598
|
+
}
|
|
57599
|
+
const entries = readLedgerEntries(ctx.mesh.id, { tail: 200 });
|
|
57600
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
57601
|
+
const entry = entries[i];
|
|
57602
|
+
const timestamp2 = new Date(entry.timestamp).getTime();
|
|
57603
|
+
if (Number.isFinite(timestamp2) && now - timestamp2 > DUPLICATE_DISPATCH_WINDOW_MS) break;
|
|
57604
|
+
if (entry.kind !== "task_dispatched") continue;
|
|
57605
|
+
if (entry.nodeId !== args.node_id) continue;
|
|
57606
|
+
if (args.session_id && entry.sessionId !== args.session_id) continue;
|
|
57607
|
+
if (typeof entry.payload?.message !== "string") continue;
|
|
57608
|
+
if (entry.payload.message.trim() === normalizedMessage) {
|
|
57609
|
+
return { duplicate: true, entry, source: "ledger" };
|
|
57610
|
+
}
|
|
57611
|
+
}
|
|
57612
|
+
return { duplicate: false };
|
|
57613
|
+
}
|
|
57614
|
+
function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
57615
|
+
const entries = readLedgerEntries(ctx.mesh.id, { tail: 300 });
|
|
57616
|
+
const relatedEntries = entries.filter((entry) => entry.nodeId === args.node_id || entry.sessionId === args.session_id);
|
|
57617
|
+
const completedEntries = relatedEntries.filter((entry) => entry.kind === "task_completed");
|
|
57618
|
+
const lastDispatch = [...relatedEntries].reverse().find((entry) => entry.kind === "task_dispatched");
|
|
57619
|
+
const lastTerminal = [...relatedEntries].reverse().find((entry) => entry.kind === "task_completed" || entry.kind === "task_failed" || entry.kind === "task_stalled");
|
|
57620
|
+
const lastRemoved = [...relatedEntries].reverse().find((entry) => entry.kind === "node_removed");
|
|
57621
|
+
const lastLaunch = [...relatedEntries].reverse().find((entry) => entry.kind === "session_launched");
|
|
57622
|
+
const providerSessionId = args.provider_session_id || readString(lastTerminal?.payload?.providerSessionId) || readString(lastLaunch?.payload?.providerSessionId) || readString(lastDispatch?.payload?.providerSessionId);
|
|
57623
|
+
const finalSummary = readString(lastTerminal?.payload?.finalSummary) || readString(lastTerminal?.payload?.compactSummary) || readString(lastTerminal?.payload?.summary);
|
|
57624
|
+
const ledger = {
|
|
57625
|
+
taskCompletedFound: completedEntries.length > 0,
|
|
57626
|
+
nodeRemovedFound: !!lastRemoved,
|
|
57627
|
+
providerType: lastTerminal?.providerType || lastLaunch?.providerType || lastDispatch?.providerType,
|
|
57628
|
+
providerSessionId,
|
|
57629
|
+
nodeRemovedAt: lastRemoved?.timestamp,
|
|
57630
|
+
sessionCleanupMode: readString(lastRemoved?.payload?.sessionCleanupMode),
|
|
57631
|
+
readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
|
|
57632
|
+
};
|
|
57633
|
+
if (finalSummary) {
|
|
57634
|
+
return {
|
|
57635
|
+
success: true,
|
|
57636
|
+
compact: args.compact === true,
|
|
57637
|
+
recoveredFromLedger: true,
|
|
57638
|
+
nodeId: args.node_id,
|
|
57639
|
+
sessionId: args.session_id,
|
|
57640
|
+
summary: finalSummary,
|
|
57641
|
+
ledger,
|
|
57642
|
+
messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
|
|
57643
|
+
};
|
|
57644
|
+
}
|
|
57645
|
+
return {
|
|
57646
|
+
success: false,
|
|
57647
|
+
recoverable: true,
|
|
57648
|
+
code: "mesh_removed_node_transcript_unavailable",
|
|
57649
|
+
error: `Node '${args.node_id}' is not a current member of mesh '${ctx.mesh.name}'.`,
|
|
57650
|
+
nodeId: args.node_id,
|
|
57651
|
+
sessionId: args.session_id,
|
|
57652
|
+
providerSessionId,
|
|
57653
|
+
reason: "node_not_in_current_mesh_snapshot",
|
|
57654
|
+
ledger,
|
|
57655
|
+
completedSessionSeenInLedger: ledger.taskCompletedFound,
|
|
57656
|
+
lastDispatch: lastDispatch ? {
|
|
57657
|
+
timestamp: lastDispatch.timestamp,
|
|
57658
|
+
sessionId: lastDispatch.sessionId,
|
|
57659
|
+
providerType: lastDispatch.providerType,
|
|
57660
|
+
taskId: typeof lastDispatch.payload?.taskId === "string" ? lastDispatch.payload.taskId : void 0,
|
|
57661
|
+
messagePreview: typeof lastDispatch.payload?.message === "string" ? lastDispatch.payload.message.slice(0, 500) : void 0
|
|
57662
|
+
} : null,
|
|
57663
|
+
lastTerminalEvent: lastTerminal ? {
|
|
57664
|
+
kind: lastTerminal.kind,
|
|
57665
|
+
timestamp: lastTerminal.timestamp,
|
|
57666
|
+
sessionId: lastTerminal.sessionId,
|
|
57667
|
+
providerType: lastTerminal.providerType,
|
|
57668
|
+
taskId: typeof lastTerminal.payload?.taskId === "string" ? lastTerminal.payload.taskId : void 0,
|
|
57669
|
+
payload: lastTerminal.payload
|
|
57670
|
+
} : null,
|
|
57671
|
+
nextSteps: [
|
|
57672
|
+
providerSessionId ? `Retry mesh_read_chat with provider_session_id='${providerSessionId}' on a current live node for the same daemon if one exists.` : "If the node UI shows a provider transcript id, retry mesh_read_chat/mesh_read_debug with provider_session_id.",
|
|
57673
|
+
"Use mesh_read_debug with the provider_session_id or daemon-side debug bundle locator if available.",
|
|
57674
|
+
"Check mesh_task_history for task_completed and node_removed entries before redispatching; do not resend solely because transcript recovery failed.",
|
|
57675
|
+
"If this node was removed with stop_and_delete, the runtime transcript may be gone; rely on the ledger summary/locator or ask the operator for the saved UI output."
|
|
57676
|
+
],
|
|
57677
|
+
recoveryHints: [
|
|
57678
|
+
"The worktree/node may have been removed or the mesh snapshot may be stale after task completion.",
|
|
57679
|
+
"If you have a provider_session_id, retry mesh_read_chat with that value while targeting a live node for the same daemon if available.",
|
|
57680
|
+
"Use mesh_read_debug with provider_session_id, or inspect the daemon/session-host history locator if the transcript has already been archived.",
|
|
57681
|
+
"Avoid redispatching the same task solely because read_chat could not recover the transcript; check task_history and git status first."
|
|
57682
|
+
]
|
|
57683
|
+
};
|
|
57684
|
+
}
|
|
57685
|
+
function annotateQueueStaleness(queue) {
|
|
57686
|
+
const now = Date.now();
|
|
57687
|
+
return queue.map((task) => {
|
|
57688
|
+
const taskStatus = typeof task?.status === "string" ? task.status : void 0;
|
|
57689
|
+
const annotated = {
|
|
57690
|
+
...task,
|
|
57691
|
+
taskStatus,
|
|
57692
|
+
dispatchedAt: task?.createdAt,
|
|
57693
|
+
...taskStatus === "assigned" ? { activeTaskId: task.id } : {},
|
|
57694
|
+
...taskStatus === "completed" || taskStatus === "failed" ? {
|
|
57695
|
+
isHistorical: true,
|
|
57696
|
+
completedAt: task.updatedAt
|
|
57697
|
+
} : {}
|
|
57698
|
+
};
|
|
57699
|
+
if (taskStatus !== "assigned") return annotated;
|
|
57700
|
+
const updatedAt = new Date(task.updatedAt).getTime();
|
|
57701
|
+
const ageMs = Number.isFinite(updatedAt) ? now - updatedAt : null;
|
|
57702
|
+
if (ageMs === null || ageMs < STALE_ASSIGNED_QUEUE_MS) return annotated;
|
|
57703
|
+
return {
|
|
57704
|
+
...annotated,
|
|
57705
|
+
stale: true,
|
|
57706
|
+
staleAssigned: true,
|
|
57707
|
+
staleReason: "assigned task has not reached a terminal state within 30 minutes",
|
|
57708
|
+
assignedAgeMs: ageMs
|
|
57709
|
+
};
|
|
57710
|
+
});
|
|
57711
|
+
}
|
|
57482
57712
|
function unwrapCommandPayload(value) {
|
|
57483
57713
|
let current = value;
|
|
57484
57714
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -57722,18 +57952,46 @@ var MESH_ENQUEUE_TASK_TOOL = {
|
|
|
57722
57952
|
};
|
|
57723
57953
|
var MESH_VIEW_QUEUE_TOOL = {
|
|
57724
57954
|
name: "mesh_view_queue",
|
|
57725
|
-
description: "View the current status of the mesh work queue (pending, assigned, completed, failed tasks).",
|
|
57955
|
+
description: "View the current status of the mesh work queue (pending, assigned, completed, failed, cancelled tasks).",
|
|
57726
57956
|
inputSchema: {
|
|
57727
57957
|
type: "object",
|
|
57728
57958
|
properties: {
|
|
57729
57959
|
status: {
|
|
57730
57960
|
type: "array",
|
|
57731
57961
|
items: { type: "string" },
|
|
57732
|
-
description: "Filter by task status: pending, assigned, completed, failed. Returns all if omitted."
|
|
57962
|
+
description: "Filter by task status: pending, assigned, completed, failed, cancelled. Returns all if omitted."
|
|
57733
57963
|
}
|
|
57734
57964
|
}
|
|
57735
57965
|
}
|
|
57736
57966
|
};
|
|
57967
|
+
var MESH_QUEUE_CANCEL_TOOL = {
|
|
57968
|
+
name: "mesh_queue_cancel",
|
|
57969
|
+
description: "Cancel a pending/assigned/completed/failed mesh queue task without deleting audit history. Use this to retire stale queue items that target dead sessions.",
|
|
57970
|
+
inputSchema: {
|
|
57971
|
+
type: "object",
|
|
57972
|
+
properties: {
|
|
57973
|
+
task_id: { type: "string", description: "Queue task ID to cancel." },
|
|
57974
|
+
reason: { type: "string", description: "Optional operator-visible reason for cancellation." }
|
|
57975
|
+
},
|
|
57976
|
+
required: ["task_id"]
|
|
57977
|
+
}
|
|
57978
|
+
};
|
|
57979
|
+
var MESH_QUEUE_REQUEUE_TOOL = {
|
|
57980
|
+
name: "mesh_queue_requeue",
|
|
57981
|
+
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.",
|
|
57982
|
+
inputSchema: {
|
|
57983
|
+
type: "object",
|
|
57984
|
+
properties: {
|
|
57985
|
+
task_id: { type: "string", description: "Queue task ID to requeue." },
|
|
57986
|
+
reason: { type: "string", description: "Optional operator-visible reason for requeueing." },
|
|
57987
|
+
target_node_id: { type: "string", description: "Optional replacement target node ID." },
|
|
57988
|
+
target_session_id: { type: "string", description: "Optional replacement target runtime session ID." },
|
|
57989
|
+
clear_target_node: { type: "boolean", description: "When true, remove any existing target node constraint." },
|
|
57990
|
+
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." }
|
|
57991
|
+
},
|
|
57992
|
+
required: ["task_id"]
|
|
57993
|
+
}
|
|
57994
|
+
};
|
|
57737
57995
|
var MESH_SEND_TASK_TOOL = {
|
|
57738
57996
|
name: "mesh_send_task",
|
|
57739
57997
|
description: "Legacy push-based task assignment. Enqueues a task specifically targeted at a given node. The node will pull it immediately if idle.",
|
|
@@ -57903,6 +58161,8 @@ var ALL_MESH_TOOLS = [
|
|
|
57903
58161
|
MESH_LIST_NODES_TOOL,
|
|
57904
58162
|
MESH_ENQUEUE_TASK_TOOL,
|
|
57905
58163
|
MESH_VIEW_QUEUE_TOOL,
|
|
58164
|
+
MESH_QUEUE_CANCEL_TOOL,
|
|
58165
|
+
MESH_QUEUE_REQUEUE_TOOL,
|
|
57906
58166
|
MESH_SEND_TASK_TOOL,
|
|
57907
58167
|
MESH_READ_CHAT_TOOL,
|
|
57908
58168
|
MESH_READ_DEBUG_TOOL,
|
|
@@ -58078,8 +58338,51 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
58078
58338
|
}
|
|
58079
58339
|
async function meshViewQueue(ctx, args) {
|
|
58080
58340
|
try {
|
|
58081
|
-
const queue = getQueue(ctx.mesh.id, { status: args.status });
|
|
58082
|
-
|
|
58341
|
+
const queue = annotateQueueStaleness(getQueue(ctx.mesh.id, { status: args.status }));
|
|
58342
|
+
const staleAssignedTasks = queue.filter((task) => task?.status === "assigned" && task?.staleAssigned);
|
|
58343
|
+
return JSON.stringify({
|
|
58344
|
+
success: true,
|
|
58345
|
+
queue,
|
|
58346
|
+
staleAssignedTasks,
|
|
58347
|
+
staleAssignedCount: staleAssignedTasks.length,
|
|
58348
|
+
// Back-compat alias for callers already reading the first hardening payload.
|
|
58349
|
+
staleAssignments: staleAssignedTasks
|
|
58350
|
+
}, null, 2);
|
|
58351
|
+
} catch (e) {
|
|
58352
|
+
return JSON.stringify({ success: false, error: e.message });
|
|
58353
|
+
}
|
|
58354
|
+
}
|
|
58355
|
+
async function meshQueueCancel(ctx, args) {
|
|
58356
|
+
try {
|
|
58357
|
+
const taskId = (args.task_id || args.taskId || "").trim();
|
|
58358
|
+
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
58359
|
+
const task = cancelTask(ctx.mesh.id, taskId, { reason: args.reason });
|
|
58360
|
+
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
58361
|
+
return JSON.stringify({ success: true, task }, null, 2);
|
|
58362
|
+
} catch (e) {
|
|
58363
|
+
return JSON.stringify({ success: false, error: e.message });
|
|
58364
|
+
}
|
|
58365
|
+
}
|
|
58366
|
+
async function meshQueueRequeue(ctx, args) {
|
|
58367
|
+
try {
|
|
58368
|
+
const taskId = (args.task_id || args.taskId || "").trim();
|
|
58369
|
+
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
58370
|
+
const targetNodeId = (args.target_node_id || args.targetNodeId || "").trim() || void 0;
|
|
58371
|
+
const targetSessionId = (args.target_session_id || args.targetSessionId || "").trim() || void 0;
|
|
58372
|
+
const keepTargetSession = args.keep_target_session === true || args.keepTargetSession === true;
|
|
58373
|
+
const task = requeueTask(ctx.mesh.id, taskId, {
|
|
58374
|
+
reason: args.reason,
|
|
58375
|
+
targetNodeId,
|
|
58376
|
+
targetSessionId,
|
|
58377
|
+
clearTargetNode: args.clear_target_node === true || args.clearTargetNode === true,
|
|
58378
|
+
clearTargetSession: targetSessionId ? false : !keepTargetSession
|
|
58379
|
+
});
|
|
58380
|
+
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
58381
|
+
if (isLocalTransport(ctx.transport)) {
|
|
58382
|
+
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
58383
|
+
});
|
|
58384
|
+
}
|
|
58385
|
+
return JSON.stringify({ success: true, task }, null, 2);
|
|
58083
58386
|
} catch (e) {
|
|
58084
58387
|
return JSON.stringify({ success: false, error: e.message });
|
|
58085
58388
|
}
|
|
@@ -58089,6 +58392,24 @@ async function meshSendTask(ctx, args) {
|
|
|
58089
58392
|
if (node.policy?.readOnly) {
|
|
58090
58393
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
58091
58394
|
}
|
|
58395
|
+
const duplicate = hasRecentDuplicateDispatch(ctx, args);
|
|
58396
|
+
if (duplicate.duplicate) {
|
|
58397
|
+
return JSON.stringify({
|
|
58398
|
+
success: true,
|
|
58399
|
+
duplicate: true,
|
|
58400
|
+
dispatched: false,
|
|
58401
|
+
warning: "Duplicate mesh_send_task suppressed: the same node/session/message was dispatched recently.",
|
|
58402
|
+
nodeId: args.node_id,
|
|
58403
|
+
sessionId: args.session_id,
|
|
58404
|
+
source: duplicate.source,
|
|
58405
|
+
previousDispatch: duplicate.entry ? {
|
|
58406
|
+
id: duplicate.entry.id,
|
|
58407
|
+
timestamp: duplicate.entry.timestamp || duplicate.entry.updatedAt || duplicate.entry.createdAt,
|
|
58408
|
+
nodeId: duplicate.entry.nodeId || duplicate.entry.targetNodeId || duplicate.entry.assignedNodeId,
|
|
58409
|
+
sessionId: duplicate.entry.sessionId || duplicate.entry.targetSessionId || duplicate.entry.assignedSessionId
|
|
58410
|
+
} : void 0
|
|
58411
|
+
});
|
|
58412
|
+
}
|
|
58092
58413
|
try {
|
|
58093
58414
|
if (!isLocalTransport(ctx.transport) && node.daemonId) {
|
|
58094
58415
|
const res = await ctx.transport.meshEnqueueTask(node.daemonId, {
|
|
@@ -58107,17 +58428,22 @@ async function meshSendTask(ctx, args) {
|
|
|
58107
58428
|
providerType: cached2?.providerType
|
|
58108
58429
|
});
|
|
58109
58430
|
if (result.success) {
|
|
58431
|
+
const dispatchedSessionId = args.session_id || result.sessionId;
|
|
58110
58432
|
try {
|
|
58111
58433
|
appendLedgerEntry(ctx.mesh.id, {
|
|
58112
58434
|
kind: "task_dispatched",
|
|
58113
58435
|
nodeId: args.node_id,
|
|
58114
|
-
sessionId:
|
|
58115
|
-
payload: {
|
|
58436
|
+
sessionId: dispatchedSessionId,
|
|
58437
|
+
payload: {
|
|
58438
|
+
message: args.message,
|
|
58439
|
+
via: "p2p_direct",
|
|
58440
|
+
...dispatchedSessionId ? { targetSessionId: dispatchedSessionId } : {}
|
|
58441
|
+
}
|
|
58116
58442
|
});
|
|
58117
58443
|
} catch {
|
|
58118
58444
|
}
|
|
58119
58445
|
}
|
|
58120
|
-
return JSON.stringify({ ...result, nodeId: args.node_id });
|
|
58446
|
+
return JSON.stringify({ ...result, nodeId: args.node_id, dispatched: result.success === true });
|
|
58121
58447
|
}
|
|
58122
58448
|
if (args.session_id && isLocalTransport(ctx.transport)) {
|
|
58123
58449
|
const cached2 = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
@@ -58162,7 +58488,10 @@ async function meshSendTask(ctx, args) {
|
|
|
58162
58488
|
}
|
|
58163
58489
|
}
|
|
58164
58490
|
async function meshReadChat(ctx, args) {
|
|
58165
|
-
const node = await
|
|
58491
|
+
const node = await findOptionalNodeWithRefresh(ctx, args.node_id);
|
|
58492
|
+
if (!node) {
|
|
58493
|
+
return JSON.stringify(buildMissingNodeReadChatRecovery(ctx, args), null, 2);
|
|
58494
|
+
}
|
|
58166
58495
|
if (isLocalTransport(ctx.transport)) {
|
|
58167
58496
|
const cached2 = meshSessionProviderMetadata.get(meshSessionCacheKey(args.node_id, args.session_id));
|
|
58168
58497
|
const providerSessionId = typeof args.provider_session_id === "string" && args.provider_session_id.trim() ? args.provider_session_id.trim() : cached2?.providerSessionId;
|
|
@@ -60292,6 +60621,12 @@ async function startMcpServer(opts) {
|
|
|
60292
60621
|
case "mesh_view_queue":
|
|
60293
60622
|
text = await meshViewQueue(meshCtx, a);
|
|
60294
60623
|
break;
|
|
60624
|
+
case "mesh_queue_cancel":
|
|
60625
|
+
text = await meshQueueCancel(meshCtx, a);
|
|
60626
|
+
break;
|
|
60627
|
+
case "mesh_queue_requeue":
|
|
60628
|
+
text = await meshQueueRequeue(meshCtx, a);
|
|
60629
|
+
break;
|
|
60295
60630
|
case "mesh_send_task":
|
|
60296
60631
|
text = await meshSendTask(meshCtx, a);
|
|
60297
60632
|
break;
|