@adhdev/daemon-standalone 0.9.82-rc.371 → 0.9.82-rc.374
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 +281 -72
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/vendor/mcp-server/index.js +1878 -1857
- package/vendor/mcp-server/index.js.map +1 -1
|
@@ -382,213 +382,57 @@ function annotateRapidReadChatAdvisory(payload, options) {
|
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
// src/tools/mesh-tools.ts
|
|
385
|
-
var
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
function getSessionMetadata(key) {
|
|
389
|
-
const entry = meshSessionProviderMetadata.get(key);
|
|
390
|
-
if (!entry) return void 0;
|
|
391
|
-
if (entry.expiresAt <= Date.now()) {
|
|
392
|
-
meshSessionProviderMetadata.delete(key);
|
|
393
|
-
return void 0;
|
|
394
|
-
}
|
|
395
|
-
return entry;
|
|
396
|
-
}
|
|
397
|
-
var ACTIVE_WORK_POLLING_BACKOFF_MS = 6e4;
|
|
398
|
-
function buildActiveWorkPollingGuidance(summary, now = Date.now()) {
|
|
399
|
-
if (!summary || summary.generatingCount <= 0) return void 0;
|
|
400
|
-
return {
|
|
401
|
-
activeGeneratingWork: true,
|
|
402
|
-
generatingCount: summary.generatingCount,
|
|
403
|
-
doNotPollBefore: new Date(now + ACTIVE_WORK_POLLING_BACKOFF_MS).toISOString(),
|
|
404
|
-
eventSurface: "pendingCoordinatorEvents",
|
|
405
|
-
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.",
|
|
406
|
-
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."
|
|
407
|
-
};
|
|
408
|
-
}
|
|
385
|
+
var import_daemon_core2 = require("@adhdev/daemon-core");
|
|
386
|
+
|
|
387
|
+
// src/tools/mesh-tool-shared.ts
|
|
409
388
|
function readString(value) {
|
|
410
389
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
411
390
|
}
|
|
412
|
-
function
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
return { taskTitle: taskTitle || "(untitled task)", taskSummary };
|
|
416
|
-
}
|
|
417
|
-
function buildDirectTaskPayload(message, via, opts) {
|
|
418
|
-
const descriptor = summarizeTaskMessage(message);
|
|
419
|
-
return {
|
|
420
|
-
source: "direct",
|
|
421
|
-
via,
|
|
422
|
-
taskId: opts.taskId,
|
|
423
|
-
message,
|
|
424
|
-
taskTitle: descriptor.taskTitle,
|
|
425
|
-
taskSummary: descriptor.taskSummary,
|
|
426
|
-
...opts.taskMode ? { taskMode: opts.taskMode } : {},
|
|
427
|
-
...opts.providerType ? { providerType: opts.providerType } : {},
|
|
428
|
-
...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {},
|
|
429
|
-
...opts.dispatchedToIdleSession !== void 0 ? { dispatchedToIdleSession: opts.dispatchedToIdleSession } : {}
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
function findNode(mesh, nodeId) {
|
|
433
|
-
const node = mesh.nodes.find((n) => (0, import_daemon_core.meshNodeIdMatches)(n, nodeId));
|
|
434
|
-
if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
|
|
435
|
-
return node;
|
|
436
|
-
}
|
|
437
|
-
var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
|
|
438
|
-
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
439
|
-
var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
440
|
-
var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
|
|
441
|
-
var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
442
|
-
async function refreshMeshFromDaemon(ctx) {
|
|
443
|
-
try {
|
|
444
|
-
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
445
|
-
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
446
|
-
const refreshedNodes = result.mesh.nodes.filter((n) => n?.id).map((n) => n);
|
|
447
|
-
ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...refreshedNodes);
|
|
448
|
-
ctx.mesh.updatedAt = result.mesh.updatedAt ?? ctx.mesh.updatedAt;
|
|
449
|
-
} catch {
|
|
450
|
-
}
|
|
391
|
+
function readNumeric(value, fallback = 0) {
|
|
392
|
+
const parsed = Number(value);
|
|
393
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
451
394
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
});
|
|
459
|
-
} catch {
|
|
395
|
+
var LARGE_LEDGER_FIELD_KEYS = /* @__PURE__ */ new Set(["plan", "validationPlan", "suggestedConfig", "payload"]);
|
|
396
|
+
var LARGE_LEDGER_OBJECT_THRESHOLD = 800;
|
|
397
|
+
var LARGE_LEDGER_NESTED_BYTES_THRESHOLD = 2e3;
|
|
398
|
+
function summarizeLargeLedgerField(key, value) {
|
|
399
|
+
if (typeof value === "string") {
|
|
400
|
+
return value.length > 500 ? value.slice(0, 500) + "\u2026" : value;
|
|
460
401
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
await refreshMeshFromDaemon(ctx);
|
|
466
|
-
const refreshed = ctx.mesh.nodes.find((n) => (0, import_daemon_core.meshNodeIdMatches)(n, nodeId));
|
|
467
|
-
if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
|
|
468
|
-
return refreshed;
|
|
469
|
-
}
|
|
470
|
-
async function findOptionalNodeWithRefresh(ctx, nodeId) {
|
|
471
|
-
const hit = ctx.mesh.nodes.find((n) => (0, import_daemon_core.meshNodeIdMatches)(n, nodeId));
|
|
472
|
-
if (hit && !hit.isLocalWorktree) return hit;
|
|
473
|
-
await refreshMeshFromDaemon(ctx);
|
|
474
|
-
return ctx.mesh.nodes.find((n) => (0, import_daemon_core.meshNodeIdMatches)(n, nodeId)) ?? null;
|
|
475
|
-
}
|
|
476
|
-
function hasRecentDuplicateDispatch(ctx, args) {
|
|
477
|
-
const now = Date.now();
|
|
478
|
-
const normalizedMessage = args.message.trim();
|
|
479
|
-
for (const task of (0, import_daemon_core.getQueue)(ctx.mesh.id)) {
|
|
480
|
-
const timestamp = new Date(task.updatedAt || task.createdAt).getTime();
|
|
481
|
-
if (!Number.isFinite(timestamp) || now - timestamp > DUPLICATE_DISPATCH_WINDOW_MS) continue;
|
|
482
|
-
if (task.targetNodeId && task.targetNodeId !== args.node_id) continue;
|
|
483
|
-
if (task.assignedNodeId && task.assignedNodeId !== args.node_id) continue;
|
|
484
|
-
if (args.session_id && task.targetSessionId !== args.session_id && task.assignedSessionId !== args.session_id) continue;
|
|
485
|
-
if (task.message?.trim() === normalizedMessage) {
|
|
486
|
-
return { duplicate: true, entry: task, source: "queue" };
|
|
402
|
+
if (Array.isArray(value)) {
|
|
403
|
+
const serialized = JSON.stringify(value);
|
|
404
|
+
if (serialized && serialized.length > LARGE_LEDGER_OBJECT_THRESHOLD) {
|
|
405
|
+
return `[${key} summarized: ${value.length} items \u2014 use verbose=true or mesh_reconcile_ledger]`;
|
|
487
406
|
}
|
|
407
|
+
return value;
|
|
488
408
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
if (Number.isFinite(timestamp) && now - timestamp > DUPLICATE_DISPATCH_WINDOW_MS) break;
|
|
494
|
-
if (entry.kind !== "task_dispatched") continue;
|
|
495
|
-
if (entry.nodeId !== args.node_id) continue;
|
|
496
|
-
if (args.session_id && entry.sessionId !== args.session_id) continue;
|
|
497
|
-
if (typeof entry.payload?.message !== "string") continue;
|
|
498
|
-
if (entry.payload.message.trim() === normalizedMessage) {
|
|
499
|
-
return { duplicate: true, entry, source: "ledger" };
|
|
409
|
+
if (value && typeof value === "object") {
|
|
410
|
+
const serialized = JSON.stringify(value);
|
|
411
|
+
if (serialized && serialized.length > LARGE_LEDGER_OBJECT_THRESHOLD) {
|
|
412
|
+
return `[${key} summarized: ${Object.keys(value).length} keys \u2014 use verbose=true or mesh_reconcile_ledger]`;
|
|
500
413
|
}
|
|
414
|
+
return value;
|
|
501
415
|
}
|
|
502
|
-
return
|
|
416
|
+
return value;
|
|
503
417
|
}
|
|
504
|
-
function
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const lastDispatch = [...relatedEntries].reverse().find((entry) => entry.kind === "task_dispatched");
|
|
509
|
-
const lastTerminal = [...relatedEntries].reverse().find((entry) => entry.kind === "task_completed" || entry.kind === "task_failed" || entry.kind === "task_stalled");
|
|
510
|
-
const lastRemoved = [...relatedEntries].reverse().find((entry) => entry.kind === "node_removed");
|
|
511
|
-
const lastLaunch = [...relatedEntries].reverse().find((entry) => entry.kind === "session_launched");
|
|
512
|
-
const providerSessionId = args.provider_session_id || readString(lastTerminal?.payload?.providerSessionId) || readString(lastLaunch?.payload?.providerSessionId) || readString(lastDispatch?.payload?.providerSessionId);
|
|
513
|
-
const finalSummary = readString(lastTerminal?.payload?.finalSummary) || readString(lastTerminal?.payload?.compactSummary) || readString(lastTerminal?.payload?.summary);
|
|
514
|
-
const ledger = {
|
|
515
|
-
taskCompletedFound: completedEntries.length > 0,
|
|
516
|
-
nodeRemovedFound: !!lastRemoved,
|
|
517
|
-
providerType: lastTerminal?.providerType || lastLaunch?.providerType || lastDispatch?.providerType,
|
|
518
|
-
providerSessionId,
|
|
519
|
-
nodeRemovedAt: lastRemoved?.timestamp,
|
|
520
|
-
sessionCleanupMode: readString(lastRemoved?.payload?.sessionCleanupMode),
|
|
521
|
-
readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
|
|
522
|
-
};
|
|
523
|
-
if (finalSummary) {
|
|
524
|
-
if (args.compact === true) {
|
|
525
|
-
return {
|
|
526
|
-
...compactChatPayload({
|
|
527
|
-
success: true,
|
|
528
|
-
status: "idle",
|
|
529
|
-
providerSessionId,
|
|
530
|
-
summary: finalSummary,
|
|
531
|
-
messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
|
|
532
|
-
}, {
|
|
533
|
-
nodeId: args.node_id,
|
|
534
|
-
sessionId: args.session_id,
|
|
535
|
-
limit: args.tail ?? 10
|
|
536
|
-
}),
|
|
537
|
-
recoveredFromLedger: true,
|
|
538
|
-
ledger
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
return {
|
|
542
|
-
success: true,
|
|
543
|
-
compact: false,
|
|
544
|
-
recoveredFromLedger: true,
|
|
545
|
-
nodeId: args.node_id,
|
|
546
|
-
sessionId: args.session_id,
|
|
547
|
-
summary: finalSummary,
|
|
548
|
-
ledger,
|
|
549
|
-
messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
|
|
550
|
-
};
|
|
418
|
+
function elideLargeNestedValue(key, value) {
|
|
419
|
+
if (value === null || value === void 0) return value;
|
|
420
|
+
if (typeof value === "string") {
|
|
421
|
+
return value.length > 1e3 ? value.slice(0, 1e3) + "\u2026" : value;
|
|
551
422
|
}
|
|
423
|
+
if (typeof value !== "object") return value;
|
|
424
|
+
const serialized = JSON.stringify(value);
|
|
425
|
+
const bytes = serialized ? serialized.length : 0;
|
|
426
|
+
if (bytes <= LARGE_LEDGER_NESTED_BYTES_THRESHOLD) return value;
|
|
552
427
|
return {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
nodeId: args.node_id,
|
|
558
|
-
sessionId: args.session_id,
|
|
559
|
-
providerSessionId,
|
|
560
|
-
reason: "node_not_in_current_mesh_snapshot",
|
|
561
|
-
ledger,
|
|
562
|
-
completedSessionSeenInLedger: ledger.taskCompletedFound,
|
|
563
|
-
lastDispatch: lastDispatch ? {
|
|
564
|
-
timestamp: lastDispatch.timestamp,
|
|
565
|
-
sessionId: lastDispatch.sessionId,
|
|
566
|
-
providerType: lastDispatch.providerType,
|
|
567
|
-
taskId: typeof lastDispatch.payload?.taskId === "string" ? lastDispatch.payload.taskId : void 0,
|
|
568
|
-
messagePreview: typeof lastDispatch.payload?.message === "string" ? lastDispatch.payload.message.slice(0, 500) : void 0
|
|
569
|
-
} : null,
|
|
570
|
-
lastTerminalEvent: lastTerminal ? {
|
|
571
|
-
kind: lastTerminal.kind,
|
|
572
|
-
timestamp: lastTerminal.timestamp,
|
|
573
|
-
sessionId: lastTerminal.sessionId,
|
|
574
|
-
providerType: lastTerminal.providerType,
|
|
575
|
-
taskId: typeof lastTerminal.payload?.taskId === "string" ? lastTerminal.payload.taskId : void 0,
|
|
576
|
-
payload: lastTerminal.payload
|
|
577
|
-
} : null,
|
|
578
|
-
nextSteps: [
|
|
579
|
-
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.",
|
|
580
|
-
"Use mesh_read_debug with the provider_session_id or daemon-side debug bundle locator if available.",
|
|
581
|
-
"Check mesh_task_history for task_completed and node_removed entries before redispatching; do not resend solely because transcript recovery failed.",
|
|
582
|
-
"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."
|
|
583
|
-
],
|
|
584
|
-
recoveryHints: [
|
|
585
|
-
"The worktree/node may have been removed or the mesh snapshot may be stale after task completion.",
|
|
586
|
-
"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.",
|
|
587
|
-
"Use mesh_read_debug with provider_session_id, or inspect the daemon/session-host history locator if the transcript has already been archived.",
|
|
588
|
-
"Avoid redispatching the same task solely because read_chat could not recover the transcript; check task_history and git status first."
|
|
589
|
-
]
|
|
428
|
+
_elided: true,
|
|
429
|
+
_kind: key,
|
|
430
|
+
_bytes: bytes,
|
|
431
|
+
_hint: "full evidence via mesh_reconcile_ledger"
|
|
590
432
|
};
|
|
591
433
|
}
|
|
434
|
+
|
|
435
|
+
// src/tools/mesh-session-helpers.ts
|
|
592
436
|
function readSessionRecordId(session) {
|
|
593
437
|
return readString(session?.id) || readString(session?.sessionId) || readString(session?.session_id) || readString(session?.runtimeSessionId) || readString(session?.runtime_session_id) || readString(session?.instanceId) || readString(session?.instance_id);
|
|
594
438
|
}
|
|
@@ -656,6 +500,36 @@ function collectNodeSessionIds(node) {
|
|
|
656
500
|
sessionRecords.forEach((session) => addSessionRecord(sessions, session));
|
|
657
501
|
return sessions;
|
|
658
502
|
}
|
|
503
|
+
function unwrapCommandPayload(value) {
|
|
504
|
+
let current = value;
|
|
505
|
+
const seen = /* @__PURE__ */ new Set();
|
|
506
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
507
|
+
if (!current || typeof current !== "object" || seen.has(current)) break;
|
|
508
|
+
seen.add(current);
|
|
509
|
+
const nested = current.result ?? current.payload;
|
|
510
|
+
if (!nested || typeof nested !== "object") break;
|
|
511
|
+
current = nested;
|
|
512
|
+
}
|
|
513
|
+
return current;
|
|
514
|
+
}
|
|
515
|
+
function isTerminalSessionRecord(session) {
|
|
516
|
+
const status = typeof session?.status === "string" ? session.status.toLowerCase() : "";
|
|
517
|
+
const lifecycle = typeof session?.lifecycle === "string" ? session.lifecycle.toLowerCase() : "";
|
|
518
|
+
const state = typeof session?.state === "string" ? session.state.toLowerCase() : "";
|
|
519
|
+
return [status, lifecycle, state].some((value) => ["stopped", "failed", "terminated", "exited", "closed"].includes(value));
|
|
520
|
+
}
|
|
521
|
+
function isIdleSessionRecord(session) {
|
|
522
|
+
if (isTerminalSessionRecord(session)) return false;
|
|
523
|
+
const status = typeof session?.status === "string" ? session.status.toLowerCase() : "";
|
|
524
|
+
const chatStatus = typeof session?.activeChat?.status === "string" ? session.activeChat.status.toLowerCase() : "";
|
|
525
|
+
return status === "idle" || chatStatus === "waiting_input";
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/tools/mesh-queue-helpers.ts
|
|
529
|
+
var STALE_ASSIGNED_QUEUE_MS = 30 * 6e4;
|
|
530
|
+
var OLD_HISTORICAL_QUEUE_RECORD_MS = 7 * 24 * 60 * 6e4;
|
|
531
|
+
var ACTIVE_QUEUE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned"]);
|
|
532
|
+
var HISTORICAL_QUEUE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
659
533
|
function buildQueueLivenessIndex(mesh) {
|
|
660
534
|
const nodeIds = /* @__PURE__ */ new Set();
|
|
661
535
|
const nodeSessionIds = /* @__PURE__ */ new Map();
|
|
@@ -885,401 +759,105 @@ function annotateQueueStaleness(queue, mesh) {
|
|
|
885
759
|
};
|
|
886
760
|
});
|
|
887
761
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
for (const value of [message?.timestamp, message?.createdAt, message?.created_at, message?.updatedAt, message?.time]) {
|
|
908
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
909
|
-
const ms = value > 1e10 ? value : value * 1e3;
|
|
910
|
-
return new Date(ms).toISOString();
|
|
911
|
-
}
|
|
912
|
-
if (typeof value === "string" && value.trim()) {
|
|
913
|
-
const ms = new Date(value.trim()).getTime();
|
|
914
|
-
if (Number.isFinite(ms)) return new Date(ms).toISOString();
|
|
915
|
-
}
|
|
762
|
+
|
|
763
|
+
// src/tools/mesh-compact.ts
|
|
764
|
+
function buildCompactGitSnapshot(status) {
|
|
765
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return void 0;
|
|
766
|
+
const slim = {};
|
|
767
|
+
const carry = [
|
|
768
|
+
"isGitRepo",
|
|
769
|
+
"branch",
|
|
770
|
+
"headCommit",
|
|
771
|
+
"upstream",
|
|
772
|
+
"upstreamStatus",
|
|
773
|
+
"ahead",
|
|
774
|
+
"behind",
|
|
775
|
+
"dirty",
|
|
776
|
+
"detached",
|
|
777
|
+
"submodules"
|
|
778
|
+
];
|
|
779
|
+
for (const key of carry) {
|
|
780
|
+
if (status[key] !== void 0) slim[key] = status[key];
|
|
916
781
|
}
|
|
917
|
-
return
|
|
782
|
+
return slim;
|
|
918
783
|
}
|
|
919
|
-
function
|
|
920
|
-
|
|
921
|
-
const
|
|
922
|
-
const role = String(message?.role ?? "").toLowerCase();
|
|
923
|
-
return (role === "assistant" || role === "agent") && messageContent(message).trim();
|
|
924
|
-
});
|
|
925
|
-
const finalSummary = messageContent(finalAssistant).trim() || (typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : void 0);
|
|
784
|
+
function summarizeCompactSubmodules(submodules) {
|
|
785
|
+
if (!Array.isArray(submodules) || submodules.length === 0) return void 0;
|
|
786
|
+
const outOfSync = submodules.filter((s) => s?.outOfSync).map((s) => s?.path).filter(Boolean);
|
|
926
787
|
return {
|
|
927
|
-
|
|
928
|
-
|
|
788
|
+
count: submodules.length,
|
|
789
|
+
...outOfSync.length > 0 ? { outOfSyncPaths: outOfSync } : {}
|
|
929
790
|
};
|
|
930
791
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
if (!taskId || seenTaskIds.has(taskId)) continue;
|
|
945
|
-
seenTaskIds.add(taskId);
|
|
946
|
-
candidates.push(dispatch);
|
|
947
|
-
}
|
|
948
|
-
for (const entry of ledgerEntries || []) {
|
|
949
|
-
if (!isDirectDispatchLedgerEntry(entry)) continue;
|
|
950
|
-
const taskId = readString(entry.payload?.taskId);
|
|
951
|
-
if (!taskId || seenTaskIds.has(taskId)) continue;
|
|
952
|
-
seenTaskIds.add(taskId);
|
|
953
|
-
candidates.push({
|
|
954
|
-
taskId,
|
|
955
|
-
nodeId: entry.nodeId,
|
|
956
|
-
sessionId: entry.sessionId,
|
|
957
|
-
providerType: entry.providerType || readString(entry.payload?.providerType),
|
|
958
|
-
message: readString(entry.payload?.message),
|
|
959
|
-
dispatchedAt: entry.timestamp,
|
|
960
|
-
via: readString(entry.payload?.via)
|
|
961
|
-
});
|
|
962
|
-
}
|
|
963
|
-
return candidates;
|
|
964
|
-
}
|
|
965
|
-
async function reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries) {
|
|
966
|
-
let attempted = 0;
|
|
967
|
-
let reconciled = 0;
|
|
968
|
-
let skipped = 0;
|
|
969
|
-
const candidates = buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries);
|
|
970
|
-
for (const dispatch of candidates) {
|
|
971
|
-
const taskId = readString(dispatch?.taskId);
|
|
972
|
-
const nodeId = readString(dispatch?.nodeId);
|
|
973
|
-
const sessionId = readString(dispatch?.sessionId);
|
|
974
|
-
if (!taskId || !nodeId || !sessionId) {
|
|
975
|
-
skipped += 1;
|
|
976
|
-
continue;
|
|
977
|
-
}
|
|
978
|
-
const { session } = findNodeSession(liveNodes, nodeId, sessionId);
|
|
979
|
-
if (!session || !isIdleSessionRecord(session)) {
|
|
980
|
-
skipped += 1;
|
|
981
|
-
continue;
|
|
982
|
-
}
|
|
983
|
-
const node = await findOptionalNodeWithRefresh(ctx, nodeId).catch(() => null);
|
|
984
|
-
if (!node) {
|
|
985
|
-
skipped += 1;
|
|
986
|
-
continue;
|
|
987
|
-
}
|
|
988
|
-
const providerType = readString(dispatch?.providerType) || resolveSessionProviderType(session);
|
|
989
|
-
const providerSessionId = readString(session?.providerSessionId) || readString(session?.activeChat?.providerSessionId) || readString(session?.settings?.providerSessionId) || resolveMeshSessionProviderMetadata(ctx, nodeId, sessionId)?.providerSessionId;
|
|
990
|
-
attempted += 1;
|
|
991
|
-
try {
|
|
992
|
-
const readResult = await commandForNode(ctx, node, "read_chat", {
|
|
993
|
-
sessionId,
|
|
994
|
-
targetSessionId: sessionId,
|
|
995
|
-
workspace: node.workspace,
|
|
996
|
-
...providerType ? { agentType: providerType, providerType } : {},
|
|
997
|
-
...providerSessionId ? { providerSessionId } : {},
|
|
998
|
-
tailLimit: 10
|
|
999
|
-
});
|
|
1000
|
-
const payload = unwrapCommandPayload(readResult);
|
|
1001
|
-
if (payload?.success === false) continue;
|
|
1002
|
-
const evidence = readFinalAssistantTranscriptEvidence(payload);
|
|
1003
|
-
if (!evidence.finalSummary) continue;
|
|
1004
|
-
const result = (0, import_daemon_core.reconcileDirectDispatchCompletionFromTranscript)({
|
|
1005
|
-
meshId: ctx.mesh.id,
|
|
1006
|
-
nodeId,
|
|
1007
|
-
sessionId,
|
|
1008
|
-
providerType,
|
|
1009
|
-
providerSessionId: readString(payload?.providerSessionId) || providerSessionId,
|
|
1010
|
-
taskId,
|
|
1011
|
-
finalSummary: evidence.finalSummary,
|
|
1012
|
-
transcriptMessageAt: evidence.transcriptMessageAt,
|
|
1013
|
-
targetCoordinatorDaemonId: ctx.localDaemonId,
|
|
1014
|
-
source: "mcp_mesh_status_transcript_reconciliation"
|
|
1015
|
-
});
|
|
1016
|
-
if (result.reconciled) reconciled += 1;
|
|
1017
|
-
} catch {
|
|
1018
|
-
skipped += 1;
|
|
792
|
+
var MESH_COMPACT_PRESERVED_MARKER_FIELDS = ["dataFreshness"];
|
|
793
|
+
function compactMeshStatusNode(entry) {
|
|
794
|
+
if (!entry || typeof entry !== "object") return entry;
|
|
795
|
+
const next = { ...entry };
|
|
796
|
+
if (next.git !== void 0) {
|
|
797
|
+
const slimGit = buildCompactGitSnapshot(next.git);
|
|
798
|
+
if (slimGit) {
|
|
799
|
+
if (slimGit.submodules !== void 0) {
|
|
800
|
+
const subSummary = summarizeCompactSubmodules(slimGit.submodules);
|
|
801
|
+
if (subSummary) slimGit.submodules = subSummary;
|
|
802
|
+
else delete slimGit.submodules;
|
|
803
|
+
}
|
|
804
|
+
next.git = slimGit;
|
|
1019
805
|
}
|
|
1020
806
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
return {
|
|
1031
|
-
success: false,
|
|
1032
|
-
error: e?.message || String(e)
|
|
807
|
+
if (next.machine && typeof next.machine === "object") {
|
|
808
|
+
const m = next.machine;
|
|
809
|
+
next.machine = {
|
|
810
|
+
daemonId: m.daemonId,
|
|
811
|
+
machineId: m.machineId,
|
|
812
|
+
hostname: m.hostname,
|
|
813
|
+
displayName: m.displayName,
|
|
814
|
+
sameMachine: m.sameMachine,
|
|
815
|
+
locality: m.locality
|
|
1033
816
|
};
|
|
1034
817
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
if (queueTrigger.success === false) {
|
|
1039
|
-
return {
|
|
1040
|
-
queueClaimed: false,
|
|
1041
|
-
queueDispatchState: "trigger_failed",
|
|
1042
|
-
nextAction: "Do not assume the queued task is running. Check mesh_view_queue and daemon connectivity before redispatching."
|
|
1043
|
-
};
|
|
818
|
+
if (typeof next.submoduleWarning === "string") {
|
|
819
|
+
next.submodulesOutOfSync = true;
|
|
820
|
+
delete next.submoduleWarning;
|
|
1044
821
|
}
|
|
1045
|
-
if (
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
822
|
+
if (next.staleDaemonBuild && typeof next.staleDaemonBuild === "object") {
|
|
823
|
+
const b = next.staleDaemonBuild;
|
|
824
|
+
next.staleDaemonBuild = {
|
|
825
|
+
scope: b.scope,
|
|
826
|
+
isDaemonAffecting: b.isDaemonAffecting !== false,
|
|
827
|
+
seeStaleDaemonBuilds: true
|
|
1050
828
|
};
|
|
1051
829
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
};
|
|
830
|
+
delete next.capabilityTagsByProvider;
|
|
831
|
+
const elideSkip = /* @__PURE__ */ new Set(["git", "machine", "branchConvergence", "staleDaemonBuild", "sessions", ...MESH_COMPACT_PRESERVED_MARKER_FIELDS]);
|
|
832
|
+
for (const k of Object.keys(next)) {
|
|
833
|
+
if (elideSkip.has(k)) continue;
|
|
834
|
+
next[k] = elideLargeNestedValue(k, next[k]);
|
|
1058
835
|
}
|
|
1059
|
-
return
|
|
1060
|
-
queueClaimed: false,
|
|
1061
|
-
queueDispatchState: "pending_or_waiting_for_ready",
|
|
1062
|
-
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."
|
|
1063
|
-
};
|
|
836
|
+
return next;
|
|
1064
837
|
}
|
|
1065
|
-
function
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
838
|
+
function compactNodeSeverity(entry) {
|
|
839
|
+
if (!entry || typeof entry !== "object") return 0;
|
|
840
|
+
if (entry.error || entry.health && entry.health !== "online" && entry.health !== "dirty") return 5;
|
|
841
|
+
if (entry.launchReady === false) return 4;
|
|
842
|
+
if (entry.isDirty === true || entry.health === "dirty") return 3;
|
|
843
|
+
if (entry.branchConvergence?.needsConvergence === true) return 2;
|
|
844
|
+
if (entry.staleDaemonBuild || entry.submodulesOutOfSync || entry.recoveryHints) return 1;
|
|
845
|
+
return 0;
|
|
1070
846
|
}
|
|
1071
|
-
function
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
if (
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
const coordinatorOwned = settings?.launchedByCoordinator === true || Boolean(readString(settings?.meshCoordinatorDaemonId));
|
|
1086
|
-
if (!coordinatorOwned) return false;
|
|
1087
|
-
const lastNodeId = readString(settings?.meshLastNodeId);
|
|
1088
|
-
if (lastNodeId) return lastNodeId === nodeId;
|
|
1089
|
-
return true;
|
|
1090
|
-
}
|
|
1091
|
-
function hasRemoteRelayMetadata(session) {
|
|
1092
|
-
return Boolean(
|
|
1093
|
-
readString(session?.settings?.meshCoordinatorDaemonId) || readString(session?.meta?.meshCoordinatorDaemonId) || readString(session?.metadata?.meshCoordinatorDaemonId) || readString(session?.meshCoordinatorDaemonId)
|
|
1094
|
-
);
|
|
1095
|
-
}
|
|
1096
|
-
function classifyRemoteDelegateRelaySafety(session, meshId, nodeId, coordinatorDaemonId) {
|
|
1097
|
-
if (!isMeshOwnedDelegateSession(session, meshId, nodeId)) return "unsafe_alias";
|
|
1098
|
-
if (hasRemoteRelayMetadata(session)) return "safe";
|
|
1099
|
-
return coordinatorDaemonId ? "self_heal" : "missing_anchor";
|
|
1100
|
-
}
|
|
1101
|
-
function chooseDispatchableSession(sessions, providerType, meshId, nodeId, coordinatorDaemonId) {
|
|
1102
|
-
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
1103
|
-
const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
|
|
1104
|
-
const meshSessions = live.filter((session) => {
|
|
1105
|
-
const safety = classifyRemoteDelegateRelaySafety(session, meshId, nodeId, coordinatorDaemonId);
|
|
1106
|
-
return safety === "safe" || safety === "self_heal";
|
|
1107
|
-
});
|
|
1108
|
-
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || void 0;
|
|
1109
|
-
}
|
|
1110
|
-
function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType) {
|
|
1111
|
-
return {
|
|
1112
|
-
success: false,
|
|
1113
|
-
recoverable: true,
|
|
1114
|
-
code: "mesh_delegate_session_missing_relay_metadata",
|
|
1115
|
-
reason: "mesh_delegate_session_missing_relay_metadata",
|
|
1116
|
-
transport: "mesh_transport",
|
|
1117
|
-
retryRecommended: true,
|
|
1118
|
-
meshId: ctx.mesh.id,
|
|
1119
|
-
nodeId: node.id,
|
|
1120
|
-
daemonId: node.daemonId,
|
|
1121
|
-
workspace: node.workspace,
|
|
1122
|
-
sessionId,
|
|
1123
|
-
unsafeTranscriptAlias: true,
|
|
1124
|
-
...providerType ? { resolvedProviderType: providerType } : {},
|
|
1125
|
-
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).`,
|
|
1126
|
-
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.`,
|
|
1127
|
-
noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
|
|
1128
|
-
};
|
|
1129
|
-
}
|
|
1130
|
-
function buildMissingCoordinatorDaemonIdFailure(ctx, node, providerType) {
|
|
1131
|
-
return {
|
|
1132
|
-
success: false,
|
|
1133
|
-
recoverable: true,
|
|
1134
|
-
code: "mesh_coordinator_daemon_unknown",
|
|
1135
|
-
reason: "mesh_coordinator_daemon_unknown",
|
|
1136
|
-
transport: "mesh_transport",
|
|
1137
|
-
retryRecommended: true,
|
|
1138
|
-
meshId: ctx.mesh.id,
|
|
1139
|
-
nodeId: node.id,
|
|
1140
|
-
daemonId: node.daemonId,
|
|
1141
|
-
workspace: node.workspace,
|
|
1142
|
-
...providerType ? { resolvedProviderType: providerType } : {},
|
|
1143
|
-
error: `Cannot launch a remote mesh delegate for node '${node.id}': coordinator daemon identity is unavailable, so the worker would be unable to relay completion events back to the coordinator.`,
|
|
1144
|
-
nextAction: "Retry after the coordinator daemon identity is available (for example from an attached daemon-backed MCP session) so meshCoordinatorDaemonId can be stamped on the worker session.",
|
|
1145
|
-
noFallbackReason: "Launching without meshCoordinatorDaemonId would create a worker session that can finish work but cannot emit task_completed / generating_completed back to the coordinator."
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
|
-
function findNestedPayload(value, predicate) {
|
|
1149
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1150
|
-
const stack = [{ payload: value, depth: 0 }];
|
|
1151
|
-
while (stack.length) {
|
|
1152
|
-
const { payload, depth } = stack.pop();
|
|
1153
|
-
if (predicate(payload)) return payload;
|
|
1154
|
-
if (!payload || typeof payload !== "object" || seen.has(payload) || depth >= 8) continue;
|
|
1155
|
-
seen.add(payload);
|
|
1156
|
-
for (const key of ["payload", "result"]) {
|
|
1157
|
-
if (key in payload) stack.push({ payload: payload[key], depth: depth + 1 });
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
return value;
|
|
1161
|
-
}
|
|
1162
|
-
function extractCloneNodePayload(value) {
|
|
1163
|
-
return findNestedPayload(value, (payload) => Boolean(payload?.node?.id));
|
|
1164
|
-
}
|
|
1165
|
-
function extractGitStatus(value) {
|
|
1166
|
-
const payload = unwrapCommandPayload(value);
|
|
1167
|
-
return payload?.status ?? value?.status ?? payload;
|
|
1168
|
-
}
|
|
1169
|
-
function extractGitDiff(value) {
|
|
1170
|
-
const payload = unwrapCommandPayload(value);
|
|
1171
|
-
return payload?.diffSummary ?? payload?.diff ?? value?.diffSummary ?? value?.diff ?? payload;
|
|
1172
|
-
}
|
|
1173
|
-
function extractSubmodules(value, ignorePaths) {
|
|
1174
|
-
const payload = unwrapCommandPayload(value);
|
|
1175
|
-
const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
|
|
1176
|
-
if (!Array.isArray(subs)) return void 0;
|
|
1177
|
-
if (ignorePaths.length === 0) return subs;
|
|
1178
|
-
const ignoreSet = new Set(ignorePaths);
|
|
1179
|
-
return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
|
|
1180
|
-
}
|
|
1181
|
-
function assignFullGitSnapshot(entry, status) {
|
|
1182
|
-
if (!status || typeof status !== "object" || Array.isArray(status)) return;
|
|
1183
|
-
entry.git = status;
|
|
1184
|
-
}
|
|
1185
|
-
function buildCompactGitSnapshot(status) {
|
|
1186
|
-
if (!status || typeof status !== "object" || Array.isArray(status)) return void 0;
|
|
1187
|
-
const slim = {};
|
|
1188
|
-
const carry = [
|
|
1189
|
-
"isGitRepo",
|
|
1190
|
-
"branch",
|
|
1191
|
-
"headCommit",
|
|
1192
|
-
"upstream",
|
|
1193
|
-
"upstreamStatus",
|
|
1194
|
-
"ahead",
|
|
1195
|
-
"behind",
|
|
1196
|
-
"dirty",
|
|
1197
|
-
"detached",
|
|
1198
|
-
"submodules"
|
|
1199
|
-
];
|
|
1200
|
-
for (const key of carry) {
|
|
1201
|
-
if (status[key] !== void 0) slim[key] = status[key];
|
|
1202
|
-
}
|
|
1203
|
-
return slim;
|
|
1204
|
-
}
|
|
1205
|
-
function summarizeCompactSubmodules(submodules) {
|
|
1206
|
-
if (!Array.isArray(submodules) || submodules.length === 0) return void 0;
|
|
1207
|
-
const outOfSync = submodules.filter((s) => s?.outOfSync).map((s) => s?.path).filter(Boolean);
|
|
1208
|
-
return {
|
|
1209
|
-
count: submodules.length,
|
|
1210
|
-
...outOfSync.length > 0 ? { outOfSyncPaths: outOfSync } : {}
|
|
1211
|
-
};
|
|
1212
|
-
}
|
|
1213
|
-
function compactMeshStatusNode(entry) {
|
|
1214
|
-
if (!entry || typeof entry !== "object") return entry;
|
|
1215
|
-
const next = { ...entry };
|
|
1216
|
-
if (next.git !== void 0) {
|
|
1217
|
-
const slimGit = buildCompactGitSnapshot(next.git);
|
|
1218
|
-
if (slimGit) {
|
|
1219
|
-
if (slimGit.submodules !== void 0) {
|
|
1220
|
-
const subSummary = summarizeCompactSubmodules(slimGit.submodules);
|
|
1221
|
-
if (subSummary) slimGit.submodules = subSummary;
|
|
1222
|
-
else delete slimGit.submodules;
|
|
1223
|
-
}
|
|
1224
|
-
next.git = slimGit;
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
if (next.machine && typeof next.machine === "object") {
|
|
1228
|
-
const m = next.machine;
|
|
1229
|
-
next.machine = {
|
|
1230
|
-
daemonId: m.daemonId,
|
|
1231
|
-
machineId: m.machineId,
|
|
1232
|
-
hostname: m.hostname,
|
|
1233
|
-
displayName: m.displayName,
|
|
1234
|
-
sameMachine: m.sameMachine,
|
|
1235
|
-
locality: m.locality
|
|
1236
|
-
};
|
|
1237
|
-
}
|
|
1238
|
-
if (typeof next.submoduleWarning === "string") {
|
|
1239
|
-
next.submodulesOutOfSync = true;
|
|
1240
|
-
delete next.submoduleWarning;
|
|
1241
|
-
}
|
|
1242
|
-
if (next.staleDaemonBuild && typeof next.staleDaemonBuild === "object") {
|
|
1243
|
-
const b = next.staleDaemonBuild;
|
|
1244
|
-
next.staleDaemonBuild = {
|
|
1245
|
-
scope: b.scope,
|
|
1246
|
-
isDaemonAffecting: b.isDaemonAffecting !== false,
|
|
1247
|
-
seeStaleDaemonBuilds: true
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
|
-
delete next.capabilityTagsByProvider;
|
|
1251
|
-
for (const k of Object.keys(next)) {
|
|
1252
|
-
if (k === "git" || k === "machine" || k === "branchConvergence" || k === "staleDaemonBuild" || k === "sessions" || k === "dataFreshness") continue;
|
|
1253
|
-
next[k] = elideLargeNestedValue(k, next[k]);
|
|
1254
|
-
}
|
|
1255
|
-
return next;
|
|
1256
|
-
}
|
|
1257
|
-
var COMPACT_DETAILED_NODES_BYTE_BUDGET = 9e3;
|
|
1258
|
-
var COMPACT_NODES_TOTAL_BYTE_BUDGET = 13e3;
|
|
1259
|
-
var COMPACT_MISSIONS_BYTE_BUDGET = 6e3;
|
|
1260
|
-
function compactNodeSeverity(entry) {
|
|
1261
|
-
if (!entry || typeof entry !== "object") return 0;
|
|
1262
|
-
if (entry.error || entry.health && entry.health !== "online" && entry.health !== "dirty") return 5;
|
|
1263
|
-
if (entry.launchReady === false) return 4;
|
|
1264
|
-
if (entry.isDirty === true || entry.health === "dirty") return 3;
|
|
1265
|
-
if (entry.branchConvergence?.needsConvergence === true) return 2;
|
|
1266
|
-
if (entry.staleDaemonBuild || entry.submodulesOutOfSync || entry.recoveryHints) return 1;
|
|
1267
|
-
return 0;
|
|
1268
|
-
}
|
|
1269
|
-
function isNoteworthyCompactNode(entry) {
|
|
1270
|
-
if (!entry || typeof entry !== "object") return true;
|
|
1271
|
-
if (entry.health && entry.health !== "online") return true;
|
|
1272
|
-
if (entry.isDirty === true) return true;
|
|
1273
|
-
if (entry.error) return true;
|
|
1274
|
-
if (entry.launchReady === false) return true;
|
|
1275
|
-
if (entry.staleDaemonBuild) return true;
|
|
1276
|
-
if (entry.submoduleWarning || entry.submodulesOutOfSync) return true;
|
|
1277
|
-
if (entry.recoveryHints) return true;
|
|
1278
|
-
if (Array.isArray(entry.nextStepHints) && entry.nextStepHints.length > 0) return true;
|
|
1279
|
-
if (entry.branchConvergence?.needsConvergence === true) return true;
|
|
1280
|
-
const sessionCount = Array.isArray(entry.sessions) ? entry.sessions.length : entry.sessionSummary?.total ?? 0;
|
|
1281
|
-
if (sessionCount > 0) return true;
|
|
1282
|
-
return false;
|
|
847
|
+
function isNoteworthyCompactNode(entry) {
|
|
848
|
+
if (!entry || typeof entry !== "object") return true;
|
|
849
|
+
if (entry.health && entry.health !== "online") return true;
|
|
850
|
+
if (entry.isDirty === true) return true;
|
|
851
|
+
if (entry.error) return true;
|
|
852
|
+
if (entry.launchReady === false) return true;
|
|
853
|
+
if (entry.staleDaemonBuild) return true;
|
|
854
|
+
if (entry.submoduleWarning || entry.submodulesOutOfSync) return true;
|
|
855
|
+
if (entry.recoveryHints) return true;
|
|
856
|
+
if (Array.isArray(entry.nextStepHints) && entry.nextStepHints.length > 0) return true;
|
|
857
|
+
if (entry.branchConvergence?.needsConvergence === true) return true;
|
|
858
|
+
const sessionCount = Array.isArray(entry.sessions) ? entry.sessions.length : entry.sessionSummary?.total ?? 0;
|
|
859
|
+
if (sessionCount > 0) return true;
|
|
860
|
+
return false;
|
|
1283
861
|
}
|
|
1284
862
|
function minimalCompactNode(entry) {
|
|
1285
863
|
if (!entry || typeof entry !== "object") return entry;
|
|
@@ -1289,6 +867,10 @@ function minimalCompactNode(entry) {
|
|
|
1289
867
|
reason: entry.branchConvergence.reason,
|
|
1290
868
|
branch: entry.branchConvergence.branch
|
|
1291
869
|
} : void 0;
|
|
870
|
+
const preservedMarkers = {};
|
|
871
|
+
for (const field of MESH_COMPACT_PRESERVED_MARKER_FIELDS) {
|
|
872
|
+
if (entry[field] !== void 0) preservedMarkers[field] = entry[field];
|
|
873
|
+
}
|
|
1292
874
|
return {
|
|
1293
875
|
nodeId: entry.nodeId,
|
|
1294
876
|
workspace: entry.workspace,
|
|
@@ -1303,10 +885,7 @@ function minimalCompactNode(entry) {
|
|
|
1303
885
|
...entry.launchBlockedReason !== void 0 ? { launchBlockedReason: entry.launchBlockedReason } : {},
|
|
1304
886
|
...bc ? { branchConvergence: bc } : {},
|
|
1305
887
|
...entry.sessionSummary ? { sessionSummary: entry.sessionSummary } : {},
|
|
1306
|
-
|
|
1307
|
-
// on a QUIET node — is this idle peer live, cached, or unreachable? — and it is
|
|
1308
|
-
// tiny (6 scalar fields), so keep it even on the minimal stub.
|
|
1309
|
-
...entry.dataFreshness !== void 0 ? { dataFreshness: entry.dataFreshness } : {},
|
|
888
|
+
...preservedMarkers,
|
|
1310
889
|
folded: true
|
|
1311
890
|
};
|
|
1312
891
|
}
|
|
@@ -1332,284 +911,26 @@ function summarizeNodeSessions(sessions) {
|
|
|
1332
911
|
}
|
|
1333
912
|
return summary;
|
|
1334
913
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
function
|
|
1339
|
-
const
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
return p2pClassification;
|
|
914
|
+
|
|
915
|
+
// src/tools/mesh-node-identity.ts
|
|
916
|
+
var import_daemon_core = require("@adhdev/daemon-core");
|
|
917
|
+
function resolveCoordinatorNode(ctx) {
|
|
918
|
+
const preferredNodeId = typeof ctx.mesh.coordinator?.preferredNodeId === "string" ? ctx.mesh.coordinator.preferredNodeId.trim() : "";
|
|
919
|
+
if (preferredNodeId) {
|
|
920
|
+
const preferred = ctx.mesh.nodes.find((n) => n.id === preferredNodeId && typeof n.daemonId === "string" && n.daemonId.trim());
|
|
921
|
+
if (preferred) return preferred;
|
|
1344
922
|
}
|
|
1345
|
-
if (
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
reason: "local_daemon_ipc_unavailable",
|
|
1349
|
-
transport: "local_ipc",
|
|
1350
|
-
recoverable: true,
|
|
1351
|
-
retryRecommended: true,
|
|
1352
|
-
nextAction: "Check the local daemon IPC connection, then retry mesh_launch_session once after the daemon is reachable."
|
|
1353
|
-
};
|
|
923
|
+
if (ctx.localMachineId) {
|
|
924
|
+
const byMachine = ctx.mesh.nodes.find((n) => readNodeMachineId(n) === ctx.localMachineId);
|
|
925
|
+
if (byMachine) return byMachine;
|
|
1354
926
|
}
|
|
1355
|
-
if (
|
|
1356
|
-
return
|
|
1357
|
-
code: "mesh_transport_timeout",
|
|
1358
|
-
reason: "mesh_transport_timeout",
|
|
1359
|
-
transport: "mesh_transport",
|
|
1360
|
-
recoverable: true,
|
|
1361
|
-
retryRecommended: true,
|
|
1362
|
-
nextAction: "Check mesh transport health, then do one bounded retry before requeueing or relaunching the task."
|
|
1363
|
-
};
|
|
927
|
+
if (ctx.localDaemonId) {
|
|
928
|
+
return ctx.mesh.nodes.find((n) => readNodeDaemonId(n) === ctx.localDaemonId);
|
|
1364
929
|
}
|
|
1365
|
-
return
|
|
1366
|
-
code: "mesh_launch_failed",
|
|
1367
|
-
reason: "provider_launch_failed",
|
|
1368
|
-
transport: "mesh_transport",
|
|
1369
|
-
recoverable: false,
|
|
1370
|
-
retryRecommended: false,
|
|
1371
|
-
nextAction: "Inspect the provider launch error and fix the underlying provider/configuration issue before retrying."
|
|
1372
|
-
};
|
|
930
|
+
return void 0;
|
|
1373
931
|
}
|
|
1374
|
-
function
|
|
1375
|
-
|
|
1376
|
-
return {
|
|
1377
|
-
tool: "mesh_remove_node",
|
|
1378
|
-
args: { node_id: node.id, session_cleanup_mode: "preserve" },
|
|
1379
|
-
hint: `If the worktree is no longer needed, remove the orphan worktree node with mesh_remove_node(node_id: "${node.id}").`
|
|
1380
|
-
};
|
|
1381
|
-
}
|
|
1382
|
-
function buildRecoverableLaunchFailure(ctx, node, providerType, error) {
|
|
1383
|
-
const message = error instanceof Error ? error.message : String(error || "launch failed");
|
|
1384
|
-
const classified = classifyMeshLaunchFailure(error);
|
|
1385
|
-
const cleanup = buildWorktreeCleanupHint(node);
|
|
1386
|
-
return {
|
|
1387
|
-
success: false,
|
|
1388
|
-
recoverable: classified.recoverable,
|
|
1389
|
-
code: classified.code,
|
|
1390
|
-
reason: classified.reason,
|
|
1391
|
-
transport: classified.transport,
|
|
1392
|
-
retryRecommended: classified.retryRecommended,
|
|
1393
|
-
nextAction: classified.nextAction,
|
|
1394
|
-
...classified.noFallbackReason ? { noFallbackReason: classified.noFallbackReason } : {},
|
|
1395
|
-
error: message,
|
|
1396
|
-
meshId: ctx.mesh.id,
|
|
1397
|
-
nodeId: node.id,
|
|
1398
|
-
daemonId: node.daemonId,
|
|
1399
|
-
workspace: node.workspace,
|
|
1400
|
-
isLocalWorktree: node.isLocalWorktree === true,
|
|
1401
|
-
worktreeBranch: node.worktreeBranch,
|
|
1402
|
-
clonedFromNodeId: node.clonedFromNodeId,
|
|
1403
|
-
...providerType ? { resolvedProviderType: providerType } : {},
|
|
1404
|
-
retryHint: `Retry mesh_launch_session(node_id: "${node.id}"${providerType ? `, type: "${providerType}"` : ""}) after daemon mesh transport/P2P is healthy.`,
|
|
1405
|
-
...cleanup ? { cleanup } : {},
|
|
1406
|
-
nextStepHints: [
|
|
1407
|
-
`Retry mesh_launch_session(node_id: "${node.id}"${providerType ? `, type: "${providerType}"` : ""}) after checking daemon/P2P health.`,
|
|
1408
|
-
...cleanup ? [`Cleanup orphan worktree node with mesh_remove_node(node_id: "${node.id}") if retry is not desired.`] : [],
|
|
1409
|
-
"Run mesh_status to see the degraded reason and recovery hints before redispatching work."
|
|
1410
|
-
]
|
|
1411
|
-
};
|
|
1412
|
-
}
|
|
1413
|
-
function recordRecoverableLaunchFailure(ctx, node, providerType, error) {
|
|
1414
|
-
const failure = buildRecoverableLaunchFailure(ctx, node, providerType, error);
|
|
1415
|
-
try {
|
|
1416
|
-
(0, import_daemon_core.appendLedgerEntry)(ctx.mesh.id, {
|
|
1417
|
-
kind: "recovery_attempted",
|
|
1418
|
-
nodeId: node.id,
|
|
1419
|
-
providerType,
|
|
1420
|
-
payload: {
|
|
1421
|
-
event: "session_launch_failed",
|
|
1422
|
-
...failure
|
|
1423
|
-
}
|
|
1424
|
-
});
|
|
1425
|
-
} catch {
|
|
1426
|
-
}
|
|
1427
|
-
return failure;
|
|
1428
|
-
}
|
|
1429
|
-
function getLatestActiveLaunchFailure(meshId, nodeId) {
|
|
1430
|
-
const entries = (0, import_daemon_core.readLedgerEntries)(meshId, { tail: 200 });
|
|
1431
|
-
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1432
|
-
const entry = entries[i];
|
|
1433
|
-
if (entry.nodeId !== nodeId) continue;
|
|
1434
|
-
if (entry.kind === "session_launched" || entry.kind === "node_removed") return null;
|
|
1435
|
-
if (entry.kind === "recovery_attempted" && entry.payload?.event === "session_launch_failed") {
|
|
1436
|
-
return { timestamp: entry.timestamp, ...entry.payload };
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
return null;
|
|
1440
|
-
}
|
|
1441
|
-
function buildCoordinatorP2pRelayFailure(error, context) {
|
|
1442
|
-
const payload = (0, import_daemon_core.buildP2pRelayFailurePayload)(error, {
|
|
1443
|
-
command: context.command,
|
|
1444
|
-
targetDaemonId: context.targetDaemonId
|
|
1445
|
-
});
|
|
1446
|
-
return {
|
|
1447
|
-
...payload,
|
|
1448
|
-
...context.nodeId ? { nodeId: context.nodeId } : {},
|
|
1449
|
-
...context.sessionId ? { sessionId: context.sessionId } : {},
|
|
1450
|
-
retryHint: payload.retryRecommended ? payload.nextAction : "Do not retry as a P2P transport recovery; inspect the command/provider error first."
|
|
1451
|
-
};
|
|
1452
|
-
}
|
|
1453
|
-
async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
1454
|
-
const transport = ctx.transport;
|
|
1455
|
-
const daemonId = node.daemonId;
|
|
1456
|
-
const dispatchCoordinatorDaemonId = readString(args.meshContext?.coordinatorDaemonId) || "";
|
|
1457
|
-
let sessionId = args.session_id?.trim() || "";
|
|
1458
|
-
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
1459
|
-
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
1460
|
-
if (sessionId && args.verifiedSession) {
|
|
1461
|
-
const explicitSession = args.verifiedSession;
|
|
1462
|
-
const relaySafety = classifyRemoteDelegateRelaySafety(explicitSession, ctx.mesh.id, node.id, dispatchCoordinatorDaemonId);
|
|
1463
|
-
if (relaySafety === "unsafe_alias") {
|
|
1464
|
-
return buildRelayUnsafeRemoteSessionFailure(
|
|
1465
|
-
ctx,
|
|
1466
|
-
node,
|
|
1467
|
-
sessionId,
|
|
1468
|
-
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
1469
|
-
);
|
|
1470
|
-
}
|
|
1471
|
-
if (relaySafety === "missing_anchor") {
|
|
1472
|
-
return buildMissingCoordinatorDaemonIdFailure(
|
|
1473
|
-
ctx,
|
|
1474
|
-
node,
|
|
1475
|
-
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
1476
|
-
);
|
|
1477
|
-
}
|
|
1478
|
-
if (!resolvedProviderType) {
|
|
1479
|
-
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
1480
|
-
}
|
|
1481
|
-
} else if (!sessionId || args.session_id) {
|
|
1482
|
-
try {
|
|
1483
|
-
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
1484
|
-
const sessions = extractStatusMetadataSessions(relayResult);
|
|
1485
|
-
if (sessionId) {
|
|
1486
|
-
const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
|
|
1487
|
-
if (!explicitSession) {
|
|
1488
|
-
return {
|
|
1489
|
-
success: false,
|
|
1490
|
-
recoverable: true,
|
|
1491
|
-
code: "mesh_target_session_not_found",
|
|
1492
|
-
reason: "mesh_target_session_not_found",
|
|
1493
|
-
transport: "mesh_transport",
|
|
1494
|
-
retryRecommended: true,
|
|
1495
|
-
meshId: ctx.mesh.id,
|
|
1496
|
-
nodeId: node.id,
|
|
1497
|
-
daemonId,
|
|
1498
|
-
workspace: node.workspace,
|
|
1499
|
-
sessionId,
|
|
1500
|
-
...resolvedProviderType ? { resolvedProviderType } : {},
|
|
1501
|
-
error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
|
|
1502
|
-
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.`
|
|
1503
|
-
};
|
|
1504
|
-
}
|
|
1505
|
-
const relaySafety = classifyRemoteDelegateRelaySafety(explicitSession, ctx.mesh.id, node.id, dispatchCoordinatorDaemonId);
|
|
1506
|
-
if (relaySafety === "unsafe_alias") {
|
|
1507
|
-
return buildRelayUnsafeRemoteSessionFailure(
|
|
1508
|
-
ctx,
|
|
1509
|
-
node,
|
|
1510
|
-
sessionId,
|
|
1511
|
-
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
1512
|
-
);
|
|
1513
|
-
}
|
|
1514
|
-
if (relaySafety === "missing_anchor") {
|
|
1515
|
-
return buildMissingCoordinatorDaemonIdFailure(
|
|
1516
|
-
ctx,
|
|
1517
|
-
node,
|
|
1518
|
-
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
1519
|
-
);
|
|
1520
|
-
}
|
|
1521
|
-
if (!resolvedProviderType) {
|
|
1522
|
-
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
1523
|
-
}
|
|
1524
|
-
} else {
|
|
1525
|
-
const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id, dispatchCoordinatorDaemonId);
|
|
1526
|
-
if (targetSession?.id || targetSession?.sessionId) {
|
|
1527
|
-
sessionId = targetSession.id || targetSession.sessionId;
|
|
1528
|
-
if (!resolvedProviderType) {
|
|
1529
|
-
resolvedProviderType = resolveSessionProviderType(targetSession);
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
} catch (e) {
|
|
1534
|
-
if (sessionId) {
|
|
1535
|
-
return {
|
|
1536
|
-
...buildCoordinatorP2pRelayFailure(e, {
|
|
1537
|
-
command: "get_status_metadata",
|
|
1538
|
-
targetDaemonId: daemonId,
|
|
1539
|
-
nodeId: node.id,
|
|
1540
|
-
sessionId
|
|
1541
|
-
}),
|
|
1542
|
-
success: false,
|
|
1543
|
-
error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
|
|
1544
|
-
};
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
if (!resolvedProviderType) {
|
|
1549
|
-
return { success: false, error: `Cannot dispatch to remote node '${node.id}': providerType unknown. Set providerPriority on the node policy or call mesh_launch_session first.` };
|
|
1550
|
-
}
|
|
1551
|
-
try {
|
|
1552
|
-
const dispatchResult = await transport.meshCommand(daemonId, "agent_command", {
|
|
1553
|
-
...sessionId ? { targetSessionId: sessionId } : {},
|
|
1554
|
-
agentType: resolvedProviderType,
|
|
1555
|
-
cliType: resolvedProviderType,
|
|
1556
|
-
action: "send_chat",
|
|
1557
|
-
message: args.message,
|
|
1558
|
-
// WTCLAIM (B): carry the node workspace so a sessionless dispatch can be
|
|
1559
|
-
// scoped to THIS node's session on the worker (findAdapter dir match /
|
|
1560
|
-
// findMeshNodeAdapter). Without it, a worker hosting both a base node and a
|
|
1561
|
-
// cloned worktree node (same daemonId) would fall through to a provider-only
|
|
1562
|
-
// fuzzy match and could land worktree work on the base session.
|
|
1563
|
-
...node.workspace ? { dir: node.workspace } : {},
|
|
1564
|
-
...args.meshContext ? { meshContext: args.meshContext } : {}
|
|
1565
|
-
});
|
|
1566
|
-
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
1567
|
-
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
1568
|
-
const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
|
|
1569
|
-
const errorMessage = dispatchPayload?.error || dispatchResult?.error || "agent_command rejected the task";
|
|
1570
|
-
return {
|
|
1571
|
-
...buildCoordinatorP2pRelayFailure(source?.error || errorMessage, {
|
|
1572
|
-
command: "agent_command",
|
|
1573
|
-
targetDaemonId: daemonId,
|
|
1574
|
-
nodeId: node.id,
|
|
1575
|
-
sessionId
|
|
1576
|
-
}),
|
|
1577
|
-
...source && typeof source === "object" ? source : {},
|
|
1578
|
-
success: false,
|
|
1579
|
-
error: `P2P dispatch failed: ${errorMessage}`
|
|
1580
|
-
};
|
|
1581
|
-
}
|
|
1582
|
-
return { success: true, dispatched: true, sessionId: sessionId || "", providerType: resolvedProviderType };
|
|
1583
|
-
} catch (e) {
|
|
1584
|
-
const errorMessage = e?.message || String(e);
|
|
1585
|
-
return {
|
|
1586
|
-
...buildCoordinatorP2pRelayFailure(e, {
|
|
1587
|
-
command: "agent_command",
|
|
1588
|
-
targetDaemonId: daemonId,
|
|
1589
|
-
nodeId: node.id,
|
|
1590
|
-
sessionId
|
|
1591
|
-
}),
|
|
1592
|
-
error: `P2P dispatch failed: ${errorMessage}`
|
|
1593
|
-
};
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
function resolveCoordinatorNode(ctx) {
|
|
1597
|
-
const preferredNodeId = typeof ctx.mesh.coordinator?.preferredNodeId === "string" ? ctx.mesh.coordinator.preferredNodeId.trim() : "";
|
|
1598
|
-
if (preferredNodeId) {
|
|
1599
|
-
const preferred = ctx.mesh.nodes.find((n) => n.id === preferredNodeId && typeof n.daemonId === "string" && n.daemonId.trim());
|
|
1600
|
-
if (preferred) return preferred;
|
|
1601
|
-
}
|
|
1602
|
-
if (ctx.localMachineId) {
|
|
1603
|
-
const byMachine = ctx.mesh.nodes.find((n) => readNodeMachineId(n) === ctx.localMachineId);
|
|
1604
|
-
if (byMachine) return byMachine;
|
|
1605
|
-
}
|
|
1606
|
-
if (ctx.localDaemonId) {
|
|
1607
|
-
return ctx.mesh.nodes.find((n) => readNodeDaemonId(n) === ctx.localDaemonId);
|
|
1608
|
-
}
|
|
1609
|
-
return void 0;
|
|
1610
|
-
}
|
|
1611
|
-
function resolveCoordinatorDaemonId(ctx) {
|
|
1612
|
-
return readString(resolveCoordinatorNode(ctx)?.daemonId) || readString(ctx.localDaemonId) || readString(ctx.localMachineId);
|
|
932
|
+
function resolveCoordinatorDaemonId(ctx) {
|
|
933
|
+
return readString(resolveCoordinatorNode(ctx)?.daemonId) || readString(ctx.localDaemonId) || readString(ctx.localMachineId);
|
|
1613
934
|
}
|
|
1614
935
|
function readNodeMachineId(node) {
|
|
1615
936
|
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);
|
|
@@ -1757,1061 +1078,1758 @@ function resolvePreferredWorktreeNodeId(ctx) {
|
|
|
1757
1078
|
function isLocalControlPlaneNode(ctx, node) {
|
|
1758
1079
|
return !!getLocalControlPlaneMatchReason(ctx, node);
|
|
1759
1080
|
}
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
providerSessionId: providerSessionId || existing.providerSessionId,
|
|
1774
|
-
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
1775
|
-
});
|
|
1776
|
-
}
|
|
1777
|
-
function rememberMeshSessionProviderMetadataFromEvent(event) {
|
|
1778
|
-
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : event && typeof event === "object" ? event : {};
|
|
1779
|
-
const nodeId = readString(event?.nodeId) || readString(metadataEvent.nodeId) || readString(metadataEvent.meshNodeId);
|
|
1780
|
-
const sessionId = readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId) || readString(event?.sessionId);
|
|
1781
|
-
rememberMeshSessionProviderMetadata(nodeId, sessionId, {
|
|
1782
|
-
providerType: readString(metadataEvent.providerType) || readString(event?.providerType) || "",
|
|
1783
|
-
providerSessionId: readString(metadataEvent.providerSessionId) || readString(event?.providerSessionId)
|
|
1784
|
-
});
|
|
1785
|
-
}
|
|
1786
|
-
function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
|
|
1787
|
-
const entries = (0, import_daemon_core.readLedgerEntries)(ctx.mesh.id, { tail: 50 });
|
|
1788
|
-
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1789
|
-
const entry = entries[i];
|
|
1790
|
-
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
1791
|
-
const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
|
|
1792
|
-
if (entryNodeId && entryNodeId !== nodeId) continue;
|
|
1793
|
-
const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
|
|
1794
|
-
if (entrySessionId !== runtimeSessionId) continue;
|
|
1795
|
-
const providerType = readString(entry.providerType) || readString(payload.providerType);
|
|
1796
|
-
const completionDiagnostic = payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? payload.completionDiagnostic : {};
|
|
1797
|
-
const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : {};
|
|
1798
|
-
const providerSessionId = readString(payload.providerSessionId) || readString(completionDiagnostic.providerSessionId) || readString(metadataEvent.providerSessionId);
|
|
1799
|
-
if (providerType || providerSessionId) {
|
|
1800
|
-
return { providerType: providerType || "", providerSessionId };
|
|
1081
|
+
|
|
1082
|
+
// src/tools/mesh-tool-schemas.ts
|
|
1083
|
+
var MESH_STATUS_TOOL = {
|
|
1084
|
+
name: "mesh_status",
|
|
1085
|
+
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. Also reports the running daemon build per daemonId under top-level daemonBuilds ({commit, commitShort, version}); when a live daemon was built from a commit BEHIND its workspace HEAD it adds staleDaemonBuilds[] + staleDaemonBuildWarning \u2014 meaning a just-merged refinery/mesh-tool fix is NOT yet live on that daemon (awaiting deploy/restart; a local dist rebuild does not update a cloud daemon). Do not repeatedly call this to wait for generating delegated work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
|
|
1086
|
+
inputSchema: {
|
|
1087
|
+
type: "object",
|
|
1088
|
+
properties: {
|
|
1089
|
+
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." },
|
|
1090
|
+
includeStaleDirectWorkDetails: { type: "boolean", description: "Opt in to the full staleDirectWork array. Defaults false; normal status returns compact staleDirectWorkSummary only." },
|
|
1091
|
+
includeSessions: { type: "boolean", description: "Opt in to per-node live session arrays. Default false: compact mode returns a per-node sessionSummary (counts) and de-duplicated full session lists under top-level daemonSessions keyed by daemonId (sessions are not repeated for every node that shares a daemon). Set true to also include the full session array on each node." },
|
|
1092
|
+
compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Folds per-node session arrays to sessionSummary and de-duplicates daemon-shared sessions into daemonSessions. Set false (or verbose=true) for the full dashboard-grade payload." },
|
|
1093
|
+
verbose: { type: "boolean", description: "Force the full payload; overrides compact." }
|
|
1801
1094
|
}
|
|
1802
1095
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
}
|
|
1812
|
-
function countUncommittedChanges(status) {
|
|
1813
|
-
if (typeof status?.uncommittedChanges === "number") return status.uncommittedChanges;
|
|
1814
|
-
const keys = ["staged", "modified", "untracked", "deleted", "renamed"];
|
|
1815
|
-
const counted = keys.reduce((sum, key) => sum + (Number.isFinite(Number(status?.[key])) ? Number(status[key]) : 0), 0);
|
|
1816
|
-
const conflicts = Array.isArray(status?.conflictFiles) ? status.conflictFiles.length : status?.hasConflicts ? 1 : 0;
|
|
1817
|
-
return counted + conflicts;
|
|
1818
|
-
}
|
|
1819
|
-
function isGitStatusDirty(status) {
|
|
1820
|
-
if (typeof status?.isDirty === "boolean") return status.isDirty;
|
|
1821
|
-
if (typeof status?.dirty === "boolean") return status.dirty;
|
|
1822
|
-
if (Array.isArray(status?.submodules) && status.submodules.some((submodule) => submodule?.dirty || submodule?.outOfSync || submodule?.error)) return true;
|
|
1823
|
-
return countUncommittedChanges(status) > 0;
|
|
1824
|
-
}
|
|
1825
|
-
var LARGE_LEDGER_FIELD_KEYS = /* @__PURE__ */ new Set(["plan", "validationPlan", "suggestedConfig", "payload"]);
|
|
1826
|
-
var LARGE_LEDGER_OBJECT_THRESHOLD = 800;
|
|
1827
|
-
var LARGE_LEDGER_NESTED_BYTES_THRESHOLD = 2e3;
|
|
1828
|
-
function summarizeLargeLedgerField(key, value) {
|
|
1829
|
-
if (typeof value === "string") {
|
|
1830
|
-
return value.length > 500 ? value.slice(0, 500) + "\u2026" : value;
|
|
1831
|
-
}
|
|
1832
|
-
if (Array.isArray(value)) {
|
|
1833
|
-
const serialized = JSON.stringify(value);
|
|
1834
|
-
if (serialized && serialized.length > LARGE_LEDGER_OBJECT_THRESHOLD) {
|
|
1835
|
-
return `[${key} summarized: ${value.length} items \u2014 use verbose=true or mesh_reconcile_ledger]`;
|
|
1096
|
+
};
|
|
1097
|
+
var MESH_LIST_NODES_TOOL = {
|
|
1098
|
+
name: "mesh_list_nodes",
|
|
1099
|
+
description: "List all nodes in the mesh with their capabilities, platform, and workspace paths.",
|
|
1100
|
+
inputSchema: {
|
|
1101
|
+
type: "object",
|
|
1102
|
+
properties: {
|
|
1103
|
+
_gemini_compat: { type: "string", description: "Dummy property for Gemini compatibility. Ignore this." }
|
|
1836
1104
|
}
|
|
1837
|
-
return value;
|
|
1838
1105
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1106
|
+
};
|
|
1107
|
+
var MESH_ENQUEUE_TASK_TOOL = {
|
|
1108
|
+
name: "mesh_enqueue_task",
|
|
1109
|
+
description: "Add a new task to the mesh work queue. Idle nodes will automatically pull and execute tasks from this queue. Use this instead of mesh_send_task when you do not need to target a specific node.",
|
|
1110
|
+
inputSchema: {
|
|
1111
|
+
type: "object",
|
|
1112
|
+
properties: {
|
|
1113
|
+
message: { type: "string", description: "The task instruction for the agent." },
|
|
1114
|
+
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." },
|
|
1115
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
|
|
1116
|
+
requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
|
|
1117
|
+
required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." },
|
|
1118
|
+
target_node_id: { type: "string", description: "Optional: only this node may claim the task. Use to route a queued task to a specific (e.g. freshly cloned) worktree node instead of letting the first idle base node claim it. Takes priority over prefer_worktree." },
|
|
1119
|
+
targetNodeId: { type: "string", description: "CamelCase alias for target_node_id." },
|
|
1120
|
+
prefer_worktree: { type: "boolean", description: "Optional: when true, route this task to the most recently cloned idle worktree node (avoids the main/base workspace preemptively claiming an isolated task). No-op if no worktree node exists; resolves to a target_node_id when one does." },
|
|
1121
|
+
preferWorktree: { type: "boolean", description: "CamelCase alias for prefer_worktree." },
|
|
1122
|
+
depends_on: { type: "array", items: { type: "string" }, description: "Task ids that must complete before this task becomes claimable. Cycles are rejected at enqueue." },
|
|
1123
|
+
dependsOn: { type: "array", items: { type: "string" }, description: "CamelCase alias for depends_on." },
|
|
1124
|
+
mission_id: { type: "string", description: "Mission this task belongs to (mesh_mission record id)." },
|
|
1125
|
+
missionId: { type: "string", description: "CamelCase alias for mission_id." }
|
|
1126
|
+
},
|
|
1127
|
+
required: ["message"]
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
var MESH_VIEW_QUEUE_TOOL = {
|
|
1131
|
+
name: "mesh_view_queue",
|
|
1132
|
+
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.",
|
|
1133
|
+
inputSchema: {
|
|
1134
|
+
type: "object",
|
|
1135
|
+
properties: {
|
|
1136
|
+
status: {
|
|
1137
|
+
type: "array",
|
|
1138
|
+
items: { type: "string" },
|
|
1139
|
+
description: "Explicit row filter by task status: pending, assigned, completed, failed, cancelled. Source-of-truth counts remain unfiltered; visible* counts describe returned rows."
|
|
1140
|
+
},
|
|
1141
|
+
view: {
|
|
1142
|
+
type: "string",
|
|
1143
|
+
enum: ["all", "active", "historical"],
|
|
1144
|
+
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."
|
|
1145
|
+
},
|
|
1146
|
+
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." },
|
|
1147
|
+
verbose: { type: "boolean", description: "Force the full payload; overrides compact." }
|
|
1843
1148
|
}
|
|
1844
|
-
return value;
|
|
1845
1149
|
}
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1150
|
+
};
|
|
1151
|
+
var MESH_QUEUE_CANCEL_TOOL = {
|
|
1152
|
+
name: "mesh_queue_cancel",
|
|
1153
|
+
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.",
|
|
1154
|
+
inputSchema: {
|
|
1155
|
+
type: "object",
|
|
1156
|
+
properties: {
|
|
1157
|
+
task_id: { type: "string", description: "Queue task ID to cancel." },
|
|
1158
|
+
reason: { type: "string", description: "Optional operator-visible reason for cancellation." }
|
|
1159
|
+
},
|
|
1160
|
+
required: ["task_id"]
|
|
1852
1161
|
}
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
} else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
|
|
1870
|
-
} else if (k === "finalSummary") {
|
|
1871
|
-
slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
|
|
1872
|
-
} else if (LARGE_LEDGER_FIELD_KEYS.has(k)) {
|
|
1873
|
-
slim[k] = summarizeLargeLedgerField(k, v);
|
|
1874
|
-
} else {
|
|
1875
|
-
slim[k] = elideLargeNestedValue(k, v);
|
|
1876
|
-
}
|
|
1162
|
+
};
|
|
1163
|
+
var MESH_QUEUE_REQUEUE_TOOL = {
|
|
1164
|
+
name: "mesh_queue_requeue",
|
|
1165
|
+
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.",
|
|
1166
|
+
inputSchema: {
|
|
1167
|
+
type: "object",
|
|
1168
|
+
properties: {
|
|
1169
|
+
task_id: { type: "string", description: "Queue task ID to requeue." },
|
|
1170
|
+
reason: { type: "string", description: "Optional operator-visible reason for requeueing." },
|
|
1171
|
+
target_node_id: { type: "string", description: "Optional replacement target node ID." },
|
|
1172
|
+
target_session_id: { type: "string", description: "Optional replacement target runtime session ID." },
|
|
1173
|
+
clear_target_node: { type: "boolean", description: "When true, remove any existing target node constraint." },
|
|
1174
|
+
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." },
|
|
1175
|
+
force: { type: "boolean", description: "When true, bypass the retry cap and requeue even if maxRetries has been exceeded. Use only for explicit operator recovery." }
|
|
1176
|
+
},
|
|
1177
|
+
required: ["task_id"]
|
|
1877
1178
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
upstream: status?.upstream ?? null,
|
|
1895
|
-
upstreamStatus: typeof status?.upstreamStatus === "string" ? status.upstreamStatus : status?.upstream ? "unchecked" : "no_upstream",
|
|
1896
|
-
upstreamFetchedAt: Number.isFinite(Number(status?.upstreamFetchedAt)) ? Number(status.upstreamFetchedAt) : null,
|
|
1897
|
-
upstreamFetchError: typeof status?.upstreamFetchError === "string" ? status.upstreamFetchError : null,
|
|
1898
|
-
ahead: Number.isFinite(Number(status?.ahead)) ? Number(status.ahead) : 0,
|
|
1899
|
-
behind: Number.isFinite(Number(status?.behind)) ? Number(status.behind) : 0,
|
|
1900
|
-
dirty,
|
|
1901
|
-
uncommittedChanges: countUncommittedChanges(status),
|
|
1902
|
-
head: status?.headCommit ?? null,
|
|
1903
|
-
lastCommitSummary: status?.headMessage ?? null,
|
|
1904
|
-
...status?.reason ? { reason: status.reason } : {},
|
|
1905
|
-
...status?.error ? { error: status.error } : {}
|
|
1906
|
-
};
|
|
1907
|
-
}
|
|
1908
|
-
async function collectRelatedRepoStatuses(ctx, node) {
|
|
1909
|
-
const relatedRepos = readRelatedRepos(node);
|
|
1910
|
-
if (!relatedRepos.length) return [];
|
|
1911
|
-
const results = [];
|
|
1912
|
-
for (const repo of relatedRepos) {
|
|
1913
|
-
try {
|
|
1914
|
-
const statusResult = await commandForNode(ctx, node, "git_status", { workspace: repo.workspace, refreshUpstream: true });
|
|
1915
|
-
const status = extractGitStatus(statusResult);
|
|
1916
|
-
results.push(summarizeRelatedRepoStatus(repo, status));
|
|
1917
|
-
} catch (e) {
|
|
1918
|
-
results.push({
|
|
1919
|
-
label: repo.label,
|
|
1920
|
-
workspace: repo.workspace,
|
|
1921
|
-
error: e?.message || "related repo status failed"
|
|
1922
|
-
});
|
|
1923
|
-
}
|
|
1179
|
+
};
|
|
1180
|
+
var MESH_SEND_TASK_TOOL = {
|
|
1181
|
+
name: "mesh_send_task",
|
|
1182
|
+
description: "Legacy push-based task assignment. Enqueues a task specifically targeted at a given node. The node will pull it immediately if idle.",
|
|
1183
|
+
inputSchema: {
|
|
1184
|
+
type: "object",
|
|
1185
|
+
properties: {
|
|
1186
|
+
node_id: { type: "string", description: "Target node ID (from mesh_list_nodes)." },
|
|
1187
|
+
session_id: { type: "string", description: "Agent session ID on the target node." },
|
|
1188
|
+
message: { type: "string", description: "Natural-language task to send to the agent." },
|
|
1189
|
+
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." },
|
|
1190
|
+
taskMode: { type: "string", enum: ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"], description: "CamelCase alias for task_mode." },
|
|
1191
|
+
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." },
|
|
1192
|
+
missionId: { type: "string", description: "CamelCase alias for mission_id." }
|
|
1193
|
+
},
|
|
1194
|
+
required: ["node_id", "session_id", "message"]
|
|
1924
1195
|
}
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
}
|
|
1940
|
-
exposure.capabilityTagsByProvider = byProvider;
|
|
1196
|
+
};
|
|
1197
|
+
var MESH_READ_CHAT_TOOL = {
|
|
1198
|
+
name: "mesh_read_chat",
|
|
1199
|
+
description: "Read recent chat messages from a delegated agent session on a mesh node. Use compact=true for coordinator context-efficient review: it filters tool/internal/debug chatter and returns the final user-visible summary plus recent key messages. If the runtime session has completed, provider_session_id can explicitly target provider transcript history.",
|
|
1200
|
+
inputSchema: {
|
|
1201
|
+
type: "object",
|
|
1202
|
+
properties: {
|
|
1203
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1204
|
+
session_id: { type: "string", description: "Agent session ID to read from." },
|
|
1205
|
+
provider_session_id: { type: "string", description: "Optional provider transcript/session ID for completed sessions." },
|
|
1206
|
+
tail: { type: "number", description: "Number of recent messages to return (default: 10)." },
|
|
1207
|
+
compact: { type: "boolean", description: "When true, return a compact coordinator summary instead of the full transcript: tool/internal/control/debug messages are excluded and only recent user-visible key messages plus the final assistant summary are included." }
|
|
1208
|
+
},
|
|
1209
|
+
required: ["node_id", "session_id"]
|
|
1941
1210
|
}
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
providerPriority: readProviderPriority(node.policy),
|
|
1957
|
-
launchReady: false,
|
|
1958
|
-
launchBlockedReason: "worktree_bootstrap_failed",
|
|
1959
|
-
launchBlockedMessage: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : "Required worktree bootstrap failed; resolve it before launching an agent into this node.",
|
|
1960
|
-
worktreeBootstrap: bootstrap
|
|
1961
|
-
};
|
|
1211
|
+
};
|
|
1212
|
+
var MESH_READ_DEBUG_TOOL = {
|
|
1213
|
+
name: "mesh_read_debug",
|
|
1214
|
+
description: "Collect a daemon-side chat/parser debug bundle for a delegated agent session on a mesh node without opening the browser UI. Defaults to daemon_file delivery and returns a saved bundle locator.",
|
|
1215
|
+
inputSchema: {
|
|
1216
|
+
type: "object",
|
|
1217
|
+
properties: {
|
|
1218
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1219
|
+
session_id: { type: "string", description: "Agent session ID to debug." },
|
|
1220
|
+
provider_session_id: { type: "string", description: "Optional provider transcript/session ID for completed session history." },
|
|
1221
|
+
tail: { type: "number", description: "Number of recent read_chat messages to embed (default: 40)." },
|
|
1222
|
+
delivery: { type: "string", enum: ["daemon_file", "inline"], description: "daemon_file saves the full sanitized bundle on the daemon; inline returns it directly. Default: daemon_file." }
|
|
1223
|
+
},
|
|
1224
|
+
required: ["node_id", "session_id"]
|
|
1962
1225
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1226
|
+
};
|
|
1227
|
+
var MESH_LAUNCH_SESSION_TOOL = {
|
|
1228
|
+
name: "mesh_launch_session",
|
|
1229
|
+
description: "Launch a new agent session on a mesh node. Returns the session ID for subsequent send_task/read_chat calls. If the user names a provider, preserve it exactly: Hermes = hermes-cli, Claude Code/Claude = claude-cli, Codex = codex-cli, Gemini = gemini-cli. If type is omitted, resolve strictly from the node policy providerPriority and provider detection; fail closed when no configured provider is usable. Do not default to claude-cli.",
|
|
1230
|
+
inputSchema: {
|
|
1231
|
+
type: "object",
|
|
1232
|
+
properties: {
|
|
1233
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1234
|
+
type: { type: "string", description: "Optional provider type to launch. Use hermes-cli for Hermes, claude-cli for Claude Code, codex-cli for Codex, gemini-cli for Gemini. When omitted, node.policy.providerPriority is probed in order." }
|
|
1235
|
+
},
|
|
1236
|
+
required: ["node_id"]
|
|
1969
1237
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
const requireReady = !!(meshPolicy && typeof meshPolicy === "object" && meshPolicy.requireBootstrapBeforeLaunch === true);
|
|
1981
|
-
if (requireReady && bootstrap?.status !== "ready") {
|
|
1982
|
-
return {
|
|
1983
|
-
success: false,
|
|
1984
|
-
code: "bootstrap_not_ready",
|
|
1985
|
-
error: `Node '${node.id}' bootstrap state is '${bootstrap?.status ?? "unknown"}' and mesh policy requireBootstrapBeforeLaunch is enabled.`,
|
|
1986
|
-
nodeId: node.id,
|
|
1987
|
-
worktreeBootstrap: bootstrap ?? null,
|
|
1988
|
-
recoveryHint: "Run the worktree bootstrap (clone runOnClone or a refine with bootstrap inherit) until the node reports ready, or disable requireBootstrapBeforeLaunch."
|
|
1989
|
-
};
|
|
1238
|
+
};
|
|
1239
|
+
var MESH_GIT_STATUS_TOOL = {
|
|
1240
|
+
name: "mesh_git_status",
|
|
1241
|
+
description: "Get git status for a mesh node workspace \u2014 branch, dirty state, changed files.",
|
|
1242
|
+
inputSchema: {
|
|
1243
|
+
type: "object",
|
|
1244
|
+
properties: {
|
|
1245
|
+
node_id: { type: "string", description: "Target node ID." }
|
|
1246
|
+
},
|
|
1247
|
+
required: ["node_id"]
|
|
1990
1248
|
}
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
} catch {
|
|
2006
|
-
return [];
|
|
1249
|
+
};
|
|
1250
|
+
var MESH_READ_NODE_LOGS_TOOL = {
|
|
1251
|
+
name: "mesh_read_node_logs",
|
|
1252
|
+
description: "Fetch a recent daemon LOG tail directly from a (possibly remote) mesh node over P2P \u2014 no session launch, no PowerShell/shell grep on the remote machine. Use this to debug a node's daemon: read its error/warn lines, grep for a pattern, or read since a timestamp. The reply is byte-bounded (\u2264128KB, default 64KB; truncated:true when the file was larger, newest lines kept) and secrets (API keys, machine secrets, bearer tokens, JWTs, TURN credentials) are redacted before transmission. This reads the DAEMON log, not an agent session transcript \u2014 for a session transcript use mesh_read_chat / mesh_read_debug.",
|
|
1253
|
+
inputSchema: {
|
|
1254
|
+
type: "object",
|
|
1255
|
+
properties: {
|
|
1256
|
+
node_id: { type: "string", description: "Target node ID (the daemon owning it serves its own log)." },
|
|
1257
|
+
grep: { type: "string", description: "Optional regex (case-insensitive) \u2014 only matching log lines are returned. Invalid regex falls back to a literal substring match." },
|
|
1258
|
+
since_ms: { type: "number", description: "Optional epoch-ms floor \u2014 only log lines at/after this time are returned (lines without a parseable timestamp are kept)." },
|
|
1259
|
+
tail_bytes: { type: "number", description: "Max bytes of log tail to read (default 65536, capped at 131072). Larger files are truncated to the newest tail_bytes." },
|
|
1260
|
+
date: { type: "string", description: "Optional YYYY-MM-DD log date (defaults to today). Falls back to the size-rotation backup when the active file is absent." }
|
|
1261
|
+
},
|
|
1262
|
+
required: ["node_id"]
|
|
2007
1263
|
}
|
|
2008
|
-
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
1264
|
+
};
|
|
1265
|
+
var MESH_FAST_FORWARD_NODE_TOOL = {
|
|
1266
|
+
name: "mesh_fast_forward_node",
|
|
1267
|
+
description: 'Safely dry-run or execute an obvious direct fast-forward for a mesh node without launching an agent session. mode="merge" (default) absorbs upstream commits into the local branch via git merge --ff-only (ahead=0, behind>0). mode="push" publishes local commits to origin via a strict ff-only push (HEAD must be a descendant of origin/<branch>). Defaults to dry-run; execution requires execute=true. Never force-pushes, rebases, resets, cleans, or checks out arbitrary revisions. When the merge path finds the branch ahead with nothing to merge, it returns code "ahead_needs_push" pointing at mode="push".',
|
|
1268
|
+
inputSchema: {
|
|
1269
|
+
type: "object",
|
|
1270
|
+
properties: {
|
|
1271
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1272
|
+
mode: { type: "string", enum: ["merge", "push"], description: "merge (default): git merge --ff-only to absorb upstream. push: strict ff-only push of local commits to origin/<branch>; refuses any non-fast-forward." },
|
|
1273
|
+
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
1274
|
+
execute: { type: "boolean", description: "When true, apply the fast-forward/push if all safety gates pass. Defaults false/dry-run." },
|
|
1275
|
+
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
1276
|
+
update_submodules: { type: "boolean", description: 'mode="merge" only: when true, if the root fast-forward changes gitlinks, run only git submodule update --init --recursive and verify submodules clean.' },
|
|
1277
|
+
push_submodules: { type: "boolean", description: 'mode="push" only: also ff-only push submodule HEADs to their origin main. Gated by mesh policy allowAutoPublishSubmoduleMainCommits \u2014 skipped unless that policy is enabled. Defaults false (root push only).' }
|
|
1278
|
+
},
|
|
1279
|
+
required: ["node_id"]
|
|
2018
1280
|
}
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
...readString(build.builtAt) ? { builtAt: readString(build.builtAt) } : {}
|
|
2031
|
-
};
|
|
2032
|
-
}
|
|
2033
|
-
async function collectMeshViewQueueNodesWithLiveSessions(ctx) {
|
|
2034
|
-
const nodes = await Promise.all(ctx.mesh.nodes.map(async (node) => {
|
|
2035
|
-
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
2036
|
-
return liveSessions.length > 0 ? { ...node, sessions: liveSessions } : node;
|
|
2037
|
-
}));
|
|
2038
|
-
return nodes;
|
|
2039
|
-
}
|
|
2040
|
-
function readNumeric(value, fallback = 0) {
|
|
2041
|
-
const parsed = Number(value);
|
|
2042
|
-
return Number.isFinite(parsed) ? parsed : fallback;
|
|
2043
|
-
}
|
|
2044
|
-
function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
2045
|
-
const defaultBranch = readString(mesh.defaultBranch) ?? "main";
|
|
2046
|
-
const branch = readString(status?.branch) ?? readString(node.worktreeBranch) ?? null;
|
|
2047
|
-
const ahead = readNumeric(status?.ahead);
|
|
2048
|
-
const behind = readNumeric(status?.behind);
|
|
2049
|
-
const upstream = readString(status?.upstream) ?? null;
|
|
2050
|
-
const upstreamStatus = readString(status?.upstreamStatus) ?? (upstream ? "unchecked" : "no_upstream");
|
|
2051
|
-
const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
|
|
2052
|
-
const base = {
|
|
2053
|
-
defaultBranch,
|
|
2054
|
-
branch,
|
|
2055
|
-
upstream,
|
|
2056
|
-
upstreamStatus,
|
|
2057
|
-
ahead,
|
|
2058
|
-
behind,
|
|
2059
|
-
isWorktree: node.isLocalWorktree === true,
|
|
2060
|
-
isDefaultBranch: branch === defaultBranch
|
|
2061
|
-
};
|
|
2062
|
-
if (status?.isGitRepo !== true) {
|
|
2063
|
-
return {
|
|
2064
|
-
...base,
|
|
2065
|
-
status: "blocked_review",
|
|
2066
|
-
needsConvergence: true,
|
|
2067
|
-
reason: "git_status_unavailable",
|
|
2068
|
-
nextStep: `Resolve git status for node '${node.id}' before marking the task complete.`
|
|
2069
|
-
};
|
|
1281
|
+
};
|
|
1282
|
+
var MESH_RESTART_DAEMON_TOOL = {
|
|
1283
|
+
name: "mesh_restart_daemon",
|
|
1284
|
+
description: `Update a mesh node's daemon to the latest published version on its release channel and restart it \u2014 the same path as the dashboard "preview update" button, exposed as a mesh command so a coordinator can roll a worker daemon onto a freshly deployed version without a manual restart round-trip. No agent session is launched. Idle-gated: a node whose daemon has an active session (generating / waiting_approval / starting) is refused with code "blocking_sessions" so an in-flight turn is never interrupted. If the node is already on the latest version it is a no-op (no restart), matching the dashboard button (returns alreadyLatest:true). Targets a single node \u2014 call other (idle) nodes first; restarting the coordinator's OWN daemon is naturally refused while its calling turn is active. Passing channel switches the daemon's release channel (and server URL) before restarting; omit it to keep the daemon on its configured channel.`,
|
|
1285
|
+
inputSchema: {
|
|
1286
|
+
type: "object",
|
|
1287
|
+
properties: {
|
|
1288
|
+
node_id: { type: "string", description: "Target node ID \u2014 the daemon that owns this node is updated and restarted." },
|
|
1289
|
+
channel: { type: "string", enum: ["stable", "preview"], description: "Optional release channel to update from. Defaults to the daemon's configured updateChannel. Setting it also repoints the daemon's server URL to that channel." }
|
|
1290
|
+
},
|
|
1291
|
+
required: ["node_id"]
|
|
2070
1292
|
}
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
1293
|
+
};
|
|
1294
|
+
var MESH_CHECKPOINT_TOOL = {
|
|
1295
|
+
name: "mesh_checkpoint",
|
|
1296
|
+
description: "Create a git checkpoint (commit) on a mesh node workspace.",
|
|
1297
|
+
inputSchema: {
|
|
1298
|
+
type: "object",
|
|
1299
|
+
properties: {
|
|
1300
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1301
|
+
message: { type: "string", description: "Checkpoint commit message." }
|
|
1302
|
+
},
|
|
1303
|
+
required: ["node_id", "message"]
|
|
2079
1304
|
}
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
1305
|
+
};
|
|
1306
|
+
var MESH_MISSION_UPSERT_TOOL = {
|
|
1307
|
+
name: "mesh_mission_upsert",
|
|
1308
|
+
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.",
|
|
1309
|
+
inputSchema: {
|
|
1310
|
+
type: "object",
|
|
1311
|
+
properties: {
|
|
1312
|
+
mission_id: { type: "string", description: "Mission id to update. Omit to create a new mission." },
|
|
1313
|
+
title: { type: "string", description: "Short mission title." },
|
|
1314
|
+
goal: { type: "string", description: "Free-text mission goal/definition of done." },
|
|
1315
|
+
status: { type: "string", enum: ["active", "paused", "completed", "abandoned"], description: "Mission lifecycle status. Defaults to active on create." }
|
|
1316
|
+
},
|
|
1317
|
+
required: ["title"]
|
|
2088
1318
|
}
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
status: "blocked_review",
|
|
2094
|
-
needsConvergence: true,
|
|
2095
|
-
reason: "default_branch_upstream_unverified",
|
|
2096
|
-
nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${node.id}'.`
|
|
2097
|
-
};
|
|
2098
|
-
}
|
|
2099
|
-
if (ahead > 0 || behind > 0) {
|
|
2100
|
-
return {
|
|
2101
|
-
...base,
|
|
2102
|
-
status: "blocked_review",
|
|
2103
|
-
needsConvergence: true,
|
|
2104
|
-
reason: "default_branch_not_even_with_upstream",
|
|
2105
|
-
nextStep: `Bring ${defaultBranch} even with its upstream before declaring convergence complete.`
|
|
2106
|
-
};
|
|
2107
|
-
}
|
|
2108
|
-
return {
|
|
2109
|
-
...base,
|
|
2110
|
-
status: "merged_to_main",
|
|
2111
|
-
needsConvergence: false,
|
|
2112
|
-
reason: "clean_default_branch",
|
|
2113
|
-
nextStep: null
|
|
2114
|
-
};
|
|
2115
|
-
}
|
|
2116
|
-
if (node.isLocalWorktree) {
|
|
2117
|
-
return {
|
|
2118
|
-
...base,
|
|
2119
|
-
status: "cleanup_candidate",
|
|
2120
|
-
needsConvergence: true,
|
|
2121
|
-
reason: "clean_non_default_worktree_branch",
|
|
2122
|
-
nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
|
|
2123
|
-
};
|
|
2124
|
-
}
|
|
2125
|
-
if (upstream && upstreamStatus !== "fresh") {
|
|
2126
|
-
return {
|
|
2127
|
-
...base,
|
|
2128
|
-
status: "blocked_review",
|
|
2129
|
-
needsConvergence: true,
|
|
2130
|
-
reason: "feature_branch_upstream_unverified",
|
|
2131
|
-
nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`
|
|
2132
|
-
};
|
|
2133
|
-
}
|
|
2134
|
-
if (!upstream || ahead > 0 || behind > 0) {
|
|
2135
|
-
return {
|
|
2136
|
-
...base,
|
|
2137
|
-
status: "blocked_review",
|
|
2138
|
-
needsConvergence: true,
|
|
2139
|
-
reason: !upstream ? "feature_branch_missing_upstream" : "feature_branch_not_even_with_upstream",
|
|
2140
|
-
nextStep: `Push or reconcile branch '${branch}', then merge it into ${defaultBranch} or mark it not_mergeable with a reason.`
|
|
2141
|
-
};
|
|
2142
|
-
}
|
|
2143
|
-
return {
|
|
2144
|
-
...base,
|
|
2145
|
-
status: "pushed_feature_branch_needs_merge",
|
|
2146
|
-
needsConvergence: true,
|
|
2147
|
-
reason: "clean_non_default_branch",
|
|
2148
|
-
nextStep: `Review and merge branch '${branch}' into ${defaultBranch}; do not report the task as fully complete while it remains off main.`
|
|
2149
|
-
};
|
|
2150
|
-
}
|
|
2151
|
-
var COMPACT_MAX_CONVERGENCE_FOLLOWUPS = 12;
|
|
2152
|
-
function summarizeBranchConvergence(nodes, compact = false) {
|
|
2153
|
-
const allFollowUps = nodes.filter((node) => node?.branchConvergence?.needsConvergence === true).map((node) => ({
|
|
2154
|
-
nodeId: node.nodeId,
|
|
2155
|
-
// workspace is a long absolute path redundant with nodeId — drop it in
|
|
2156
|
-
// compact mode to keep this summary bounded.
|
|
2157
|
-
...compact ? {} : { workspace: node.workspace },
|
|
2158
|
-
branch: node.branchConvergence.branch,
|
|
2159
|
-
status: node.branchConvergence.status,
|
|
2160
|
-
reason: node.branchConvergence.reason,
|
|
2161
|
-
// The per-node nextStep is long prose that repeats node ids/branch names.
|
|
2162
|
-
// In compact mode drop it (the status+reason carry the actionable signal;
|
|
2163
|
-
// verbose still surfaces the full nextStep) so this summary stays bounded
|
|
2164
|
-
// as node count grows.
|
|
2165
|
-
...compact ? {} : { nextStep: node.branchConvergence.nextStep }
|
|
2166
|
-
}));
|
|
2167
|
-
const byStatus = {};
|
|
2168
|
-
for (const f of allFollowUps) {
|
|
2169
|
-
const s = typeof f.status === "string" ? f.status : "unknown";
|
|
2170
|
-
byStatus[s] = (byStatus[s] ?? 0) + 1;
|
|
2171
|
-
}
|
|
2172
|
-
const followUps = compact ? allFollowUps.slice(0, COMPACT_MAX_CONVERGENCE_FOLLOWUPS) : allFollowUps;
|
|
2173
|
-
const omitted = allFollowUps.length - followUps.length;
|
|
2174
|
-
return {
|
|
2175
|
-
needsFollowUp: allFollowUps.length > 0,
|
|
2176
|
-
unresolvedCount: allFollowUps.length,
|
|
2177
|
-
byStatus,
|
|
2178
|
-
requiredFinalStates: ["merged_to_main", "pushed_feature_branch_needs_merge", "blocked_review", "cleanup_candidate", "not_mergeable"],
|
|
2179
|
-
followUps,
|
|
2180
|
-
...omitted > 0 ? { followUpsOmitted: omitted, followUpsHint: "Per-node followUp rows are capped in compact mode; counts above are complete. Use verbose=true for the full list." } : {}
|
|
2181
|
-
};
|
|
2182
|
-
}
|
|
2183
|
-
async function commandForNode(ctx, node, command, args = {}) {
|
|
2184
|
-
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2185
|
-
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
2186
|
-
return ctx.transport.meshCommand(node.daemonId, command, args);
|
|
2187
|
-
}
|
|
2188
|
-
return ctx.transport.command(command, args);
|
|
2189
|
-
}
|
|
2190
|
-
function normalizePendingMeshCoordinatorEvents(value) {
|
|
2191
|
-
const payload = unwrapCommandPayload(value);
|
|
2192
|
-
const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
|
|
2193
|
-
return events.filter((event) => event && typeof event === "object");
|
|
2194
|
-
}
|
|
2195
|
-
function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
2196
|
-
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
|
|
2197
|
-
return {
|
|
2198
|
-
event: readString(event?.event),
|
|
2199
|
-
meshId: readString(event?.meshId),
|
|
2200
|
-
nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
|
|
2201
|
-
workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
|
|
2202
|
-
targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
|
|
2203
|
-
providerType: readString(metadataEvent.providerType),
|
|
2204
|
-
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
2205
|
-
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
2206
|
-
jobId: readString(metadataEvent.jobId),
|
|
2207
|
-
interactionId: readString(metadataEvent.interactionId),
|
|
2208
|
-
status: readString(metadataEvent.status),
|
|
2209
|
-
targetDaemonId: readString(metadataEvent.targetDaemonId),
|
|
2210
|
-
startedAt: readString(metadataEvent.startedAt),
|
|
2211
|
-
completedAt: readString(metadataEvent.completedAt),
|
|
2212
|
-
retryOfJobId: readString(metadataEvent.retryOfJobId),
|
|
2213
|
-
...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
|
|
2214
|
-
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
2215
|
-
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
2216
|
-
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
2217
|
-
...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
|
|
2218
|
-
...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
|
|
2219
|
-
...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
|
|
2220
|
-
...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
|
|
2221
|
-
};
|
|
2222
|
-
}
|
|
2223
|
-
async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
2224
|
-
const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
|
|
2225
|
-
const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
|
|
2226
|
-
if (ctx.transport instanceof IpcTransport) {
|
|
2227
|
-
const surfacedEvents = [];
|
|
2228
|
-
const coordinatorDaemonId = readString(ctx.localDaemonId);
|
|
2229
|
-
const pendingEventArgs = {
|
|
2230
|
-
meshId: ctx.mesh.id,
|
|
2231
|
-
...coordinatorDaemonId ? { coordinatorDaemonId } : {}
|
|
2232
|
-
};
|
|
2233
|
-
try {
|
|
2234
|
-
const localEvents = normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh);
|
|
2235
|
-
for (const event of localEvents) {
|
|
2236
|
-
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
2237
|
-
if (!payload.event || !payload.meshId) continue;
|
|
2238
|
-
let injected = false;
|
|
2239
|
-
try {
|
|
2240
|
-
await ctx.transport.command("mesh_forward_event", payload);
|
|
2241
|
-
injected = true;
|
|
2242
|
-
} catch {
|
|
2243
|
-
}
|
|
2244
|
-
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
2245
|
-
if (!injected) surfacedEvents.push(event);
|
|
2246
|
-
}
|
|
2247
|
-
} catch {
|
|
2248
|
-
}
|
|
2249
|
-
for (const node of ctx.mesh.nodes) {
|
|
2250
|
-
if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
|
|
2251
|
-
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
2252
|
-
try {
|
|
2253
|
-
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
2254
|
-
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", pendingEventArgs)
|
|
2255
|
-
).filter(matchesCurrentMesh);
|
|
2256
|
-
if (remoteEvents.length === 0) continue;
|
|
2257
|
-
for (const event of remoteEvents) {
|
|
2258
|
-
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
2259
|
-
if (!payload.event || !payload.meshId) continue;
|
|
2260
|
-
await ctx.transport.command("mesh_forward_event", payload);
|
|
2261
|
-
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
2262
|
-
}
|
|
2263
|
-
} catch {
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
try {
|
|
2267
|
-
const localEvents = normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh);
|
|
2268
|
-
for (const event of localEvents) {
|
|
2269
|
-
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
2270
|
-
if (!payload.event || !payload.meshId) continue;
|
|
2271
|
-
let injected = false;
|
|
2272
|
-
try {
|
|
2273
|
-
await ctx.transport.command("mesh_forward_event", payload);
|
|
2274
|
-
injected = true;
|
|
2275
|
-
} catch {
|
|
2276
|
-
}
|
|
2277
|
-
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
2278
|
-
if (!injected) surfacedEvents.push(event);
|
|
2279
|
-
}
|
|
2280
|
-
} catch {
|
|
2281
|
-
}
|
|
2282
|
-
return surfacedEvents;
|
|
2283
|
-
}
|
|
2284
|
-
const events = (0, import_daemon_core.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
|
|
2285
|
-
events.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
2286
|
-
return events;
|
|
2287
|
-
}
|
|
2288
|
-
function isP2pTransportUnavailableError(error) {
|
|
2289
|
-
return (0, import_daemon_core.isP2pRelayTransportFailure)(error);
|
|
2290
|
-
}
|
|
2291
|
-
function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode, force) {
|
|
2292
|
-
return {
|
|
2293
|
-
meshId: ctx.mesh.id,
|
|
2294
|
-
nodeId,
|
|
2295
|
-
...sessionCleanupMode ? { sessionCleanupMode } : {},
|
|
2296
|
-
...force === true ? { force: true } : {},
|
|
2297
|
-
inlineMesh: ctx.mesh
|
|
2298
|
-
};
|
|
2299
|
-
}
|
|
2300
|
-
var MESH_STATUS_TOOL = {
|
|
2301
|
-
name: "mesh_status",
|
|
2302
|
-
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. Also reports the running daemon build per daemonId under top-level daemonBuilds ({commit, commitShort, version}); when a live daemon was built from a commit BEHIND its workspace HEAD it adds staleDaemonBuilds[] + staleDaemonBuildWarning \u2014 meaning a just-merged refinery/mesh-tool fix is NOT yet live on that daemon (awaiting deploy/restart; a local dist rebuild does not update a cloud daemon). Do not repeatedly call this to wait for generating delegated work; wait for pendingCoordinatorEvents/completion events or an explicit user status request.",
|
|
1319
|
+
};
|
|
1320
|
+
var MESH_MISSION_LIST_TOOL = {
|
|
1321
|
+
name: "mesh_mission_list",
|
|
1322
|
+
description: 'List missions with their goal, status, and live task progress (total/pending/assigned/completed/failed). Unlike mesh_status (which surfaces live + recent missions), this returns every mission regardless of status by default, so paused/abandoned/completed missions are never hidden. Filter with `status` to scope (e.g. ["paused"] to find paused missions). Compact (default) elides the full goal to a capped preview; pass verbose=true for full goal text. Read-only.',
|
|
2303
1323
|
inputSchema: {
|
|
2304
1324
|
type: "object",
|
|
2305
1325
|
properties: {
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
1326
|
+
status: {
|
|
1327
|
+
type: "array",
|
|
1328
|
+
items: { type: "string", enum: ["active", "paused", "completed", "abandoned"] },
|
|
1329
|
+
description: "Optional status filter. Omit to return missions of every status."
|
|
1330
|
+
},
|
|
1331
|
+
verbose: { type: "boolean", description: "Return full goal text instead of a capped preview. Defaults to false (compact)." }
|
|
2311
1332
|
}
|
|
2312
1333
|
}
|
|
2313
1334
|
};
|
|
2314
|
-
var
|
|
2315
|
-
name: "
|
|
2316
|
-
description: "
|
|
1335
|
+
var MESH_APPROVE_TOOL = {
|
|
1336
|
+
name: "mesh_approve",
|
|
1337
|
+
description: "Approve or reject a pending action on a delegated agent session.",
|
|
2317
1338
|
inputSchema: {
|
|
2318
1339
|
type: "object",
|
|
2319
1340
|
properties: {
|
|
2320
|
-
|
|
2321
|
-
|
|
1341
|
+
node_id: { type: "string", description: "Target node ID." },
|
|
1342
|
+
session_id: { type: "string", description: "Agent session ID with pending approval." },
|
|
1343
|
+
action: { type: "string", enum: ["approve", "reject"], description: "Action to take." }
|
|
1344
|
+
},
|
|
1345
|
+
required: ["node_id", "session_id", "action"]
|
|
2322
1346
|
}
|
|
2323
1347
|
};
|
|
2324
|
-
var
|
|
2325
|
-
name: "
|
|
2326
|
-
description: "
|
|
1348
|
+
var MESH_CLONE_NODE_TOOL = {
|
|
1349
|
+
name: "mesh_clone_node",
|
|
1350
|
+
description: "Create a new worktree-based node from an existing node for isolated parallel work. Creates a git worktree on a new branch so multiple tasks can run on separate branches simultaneously.",
|
|
2327
1351
|
inputSchema: {
|
|
2328
1352
|
type: "object",
|
|
2329
1353
|
properties: {
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
requiredTags: { type: "array", items: { type: "string" }, description: "Optional capability tags that every eligible node must have, e.g. os=darwin, provider=codex-cli, gpu." },
|
|
2334
|
-
required_tags: { type: "array", items: { type: "string" }, description: "Snake_case alias for requiredTags." },
|
|
2335
|
-
target_node_id: { type: "string", description: "Optional: only this node may claim the task. Use to route a queued task to a specific (e.g. freshly cloned) worktree node instead of letting the first idle base node claim it. Takes priority over prefer_worktree." },
|
|
2336
|
-
targetNodeId: { type: "string", description: "CamelCase alias for target_node_id." },
|
|
2337
|
-
prefer_worktree: { type: "boolean", description: "Optional: when true, route this task to the most recently cloned idle worktree node (avoids the main/base workspace preemptively claiming an isolated task). No-op if no worktree node exists; resolves to a target_node_id when one does." },
|
|
2338
|
-
preferWorktree: { type: "boolean", description: "CamelCase alias for prefer_worktree." },
|
|
2339
|
-
depends_on: { type: "array", items: { type: "string" }, description: "Task ids that must complete before this task becomes claimable. Cycles are rejected at enqueue." },
|
|
2340
|
-
dependsOn: { type: "array", items: { type: "string" }, description: "CamelCase alias for depends_on." },
|
|
2341
|
-
mission_id: { type: "string", description: "Mission this task belongs to (mesh_mission record id)." },
|
|
2342
|
-
missionId: { type: "string", description: "CamelCase alias for mission_id." }
|
|
1354
|
+
source_node_id: { type: "string", description: "Node ID to clone from (from mesh_list_nodes)." },
|
|
1355
|
+
branch: { type: "string", description: 'Branch name for the new worktree (e.g. "feat/auth-refactor").' },
|
|
1356
|
+
base_branch: { type: "string", description: "Starting point for the branch (default: current HEAD)." }
|
|
2343
1357
|
},
|
|
2344
|
-
required: ["
|
|
1358
|
+
required: ["source_node_id", "branch"]
|
|
2345
1359
|
}
|
|
2346
1360
|
};
|
|
2347
|
-
var
|
|
2348
|
-
name: "
|
|
2349
|
-
description: "
|
|
1361
|
+
var MESH_REMOVE_NODE_TOOL = {
|
|
1362
|
+
name: "mesh_remove_node",
|
|
1363
|
+
description: "Remove a node from the mesh. If the node is a worktree, also cleans up the git worktree and directory. Session cleanup is controlled by mesh policy sessionCleanupOnNodeRemove unless session_cleanup_mode overrides it for this call. The coordinator's own local base node (same machine, NOT a worktree) is protected \u2014 removing it breaks live mesh membership and is rejected unless force:true is passed.",
|
|
2350
1364
|
inputSchema: {
|
|
2351
1365
|
type: "object",
|
|
2352
1366
|
properties: {
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
items: { type: "string" },
|
|
2356
|
-
description: "Explicit row filter by task status: pending, assigned, completed, failed, cancelled. Source-of-truth counts remain unfiltered; visible* counts describe returned rows."
|
|
2357
|
-
},
|
|
2358
|
-
view: {
|
|
1367
|
+
node_id: { type: "string", description: "Node ID to remove." },
|
|
1368
|
+
session_cleanup_mode: {
|
|
2359
1369
|
type: "string",
|
|
2360
|
-
enum: ["
|
|
2361
|
-
description: "Optional
|
|
1370
|
+
enum: ["preserve", "stop", "delete_stopped", "stop_and_delete"],
|
|
1371
|
+
description: "Optional override for cleanup of delegated sessions attached to this node. preserve keeps history/processes; stop stops live runtimes only; delete_stopped removes completed transcripts only; stop_and_delete stops live runtimes and deletes records."
|
|
2362
1372
|
},
|
|
2363
|
-
|
|
2364
|
-
verbose: { type: "boolean", description: "Force the full payload; overrides compact." }
|
|
2365
|
-
}
|
|
2366
|
-
}
|
|
2367
|
-
};
|
|
2368
|
-
var MESH_QUEUE_CANCEL_TOOL = {
|
|
2369
|
-
name: "mesh_queue_cancel",
|
|
2370
|
-
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.",
|
|
2371
|
-
inputSchema: {
|
|
2372
|
-
type: "object",
|
|
2373
|
-
properties: {
|
|
2374
|
-
task_id: { type: "string", description: "Queue task ID to cancel." },
|
|
2375
|
-
reason: { type: "string", description: "Optional operator-visible reason for cancellation." }
|
|
1373
|
+
force: { type: "boolean", description: "Override the coordinator-base-node guard. Only set true to intentionally tear down this mesh; the coordinator must then be re-registered/restarted. Worktree nodes never need force." }
|
|
2376
1374
|
},
|
|
2377
|
-
required: ["
|
|
1375
|
+
required: ["node_id"]
|
|
2378
1376
|
}
|
|
2379
1377
|
};
|
|
2380
|
-
var
|
|
2381
|
-
name: "
|
|
2382
|
-
description: "
|
|
1378
|
+
var MESH_CLEANUP_SESSIONS_TOOL = {
|
|
1379
|
+
name: "mesh_cleanup_sessions",
|
|
1380
|
+
description: "Manually clean up delegated session records for a mesh node without removing the node. Defaults should preserve reviewable history unless the caller chooses a mode explicitly.",
|
|
2383
1381
|
inputSchema: {
|
|
2384
1382
|
type: "object",
|
|
2385
1383
|
properties: {
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
1384
|
+
node_id: { type: "string", description: "Node ID whose delegated sessions should be considered for cleanup." },
|
|
1385
|
+
mode: {
|
|
1386
|
+
type: "string",
|
|
1387
|
+
enum: ["preserve", "stop", "delete_stopped", "stop_and_delete"],
|
|
1388
|
+
description: "preserve = no-op; stop = release process occupancy by stopping live runtimes; delete_stopped = remove completed/stopped records while leaving live runtimes alone; stop_and_delete = stop live runtimes and delete records."
|
|
1389
|
+
},
|
|
1390
|
+
session_ids: {
|
|
1391
|
+
type: "array",
|
|
1392
|
+
items: { type: "string" },
|
|
1393
|
+
description: "Optional explicit session IDs to limit cleanup to. When omitted, sessions are matched by node/workspace metadata."
|
|
1394
|
+
},
|
|
1395
|
+
dry_run: { type: "boolean", description: "Preview matched/stopped/deleted/skipped session IDs without mutating session-host state." }
|
|
2393
1396
|
},
|
|
2394
|
-
required: ["
|
|
1397
|
+
required: ["node_id", "mode"]
|
|
2395
1398
|
}
|
|
2396
1399
|
};
|
|
2397
|
-
var
|
|
2398
|
-
name: "
|
|
2399
|
-
description: "
|
|
1400
|
+
var MESH_TASK_HISTORY_TOOL = {
|
|
1401
|
+
name: "mesh_task_history",
|
|
1402
|
+
description: "Read the task ledger for this mesh \u2014 dispatched tasks, completions, failures, checkpoints, and node lifecycle events. Use to understand what has been done before deciding next steps, to detect repeated failures, and to inform recovery decisions.",
|
|
2400
1403
|
inputSchema: {
|
|
2401
1404
|
type: "object",
|
|
2402
1405
|
properties: {
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
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." },
|
|
2409
|
-
missionId: { type: "string", description: "CamelCase alias for mission_id." }
|
|
2410
|
-
},
|
|
2411
|
-
required: ["node_id", "session_id", "message"]
|
|
1406
|
+
tail: { type: "number", description: "Number of recent entries to return (default: 20; clamped to 40 in compact mode, 200 in verbose)." },
|
|
1407
|
+
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." },
|
|
1408
|
+
compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Truncates long payload strings (message/taskSummary \u2264200, finalSummary \u2264300) and elides any large nested evidence blob (>2KB serialized \u2014 e.g. validationSummary/result/patchEquivalence/submoduleReachability) to a {_elided,_kind,_bytes,_hint} placeholder; full evidence stays accessible via mesh_reconcile_ledger. Set false (or verbose=true) for full untruncated payloads." },
|
|
1409
|
+
verbose: { type: "boolean", description: "Force the full untruncated payload; overrides compact." }
|
|
1410
|
+
}
|
|
2412
1411
|
}
|
|
2413
1412
|
};
|
|
2414
|
-
var
|
|
2415
|
-
name: "
|
|
2416
|
-
description: "
|
|
1413
|
+
var MESH_RECONCILE_LEDGER_TOOL = {
|
|
1414
|
+
name: "mesh_reconcile_ledger",
|
|
1415
|
+
description: "Reconcile daemon-local mesh ledgers by querying bounded ledger slices over P2P/DataChannel and importing missing entries into the coordinator local JSONL ledger. Cloud/D1 is not used as a ledger source of truth.",
|
|
2417
1416
|
inputSchema: {
|
|
2418
1417
|
type: "object",
|
|
2419
1418
|
properties: {
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
}
|
|
2426
|
-
required: ["node_id", "session_id"]
|
|
1419
|
+
node_ids: { type: "array", items: { type: "string" }, description: "Optional node IDs to query. Defaults to all mesh nodes." },
|
|
1420
|
+
limit: { type: "number", description: "Bounded slice size per node. Defaults to 100 and is clamped by daemon-core." },
|
|
1421
|
+
after_id: { type: "string", description: "Optional cursor entry ID; remote slices return entries strictly after this ID when present." },
|
|
1422
|
+
since: { type: "string", description: "Optional ISO timestamp lower bound for queried entries." },
|
|
1423
|
+
import_entries: { type: "boolean", description: "When false, query and report evidence without importing remote entries. Defaults true." }
|
|
1424
|
+
}
|
|
2427
1425
|
}
|
|
2428
1426
|
};
|
|
2429
|
-
var
|
|
2430
|
-
name: "
|
|
2431
|
-
description: "
|
|
1427
|
+
var MESH_PRUNE_STALE_DIRECT_TOOL = {
|
|
1428
|
+
name: "mesh_prune_stale_direct",
|
|
1429
|
+
description: "Prune orphaned staleDirect dispatch records \u2014 direct task dispatches whose original node/session is no longer present in the live mesh. dry_run (default) reports exactly which records would be pruned without mutating anything; pass execute=true to delete them. Active/pending/assigned/generating work and fresh unacknowledged dispatch failures (node/session still live) are always preserved. The append-only mesh ledger audit history is left intact.",
|
|
2432
1430
|
inputSchema: {
|
|
2433
1431
|
type: "object",
|
|
2434
1432
|
properties: {
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
delivery: { type: "string", enum: ["daemon_file", "inline"], description: "daemon_file saves the full sanitized bundle on the daemon; inline returns it directly. Default: daemon_file." }
|
|
2440
|
-
},
|
|
2441
|
-
required: ["node_id", "session_id"]
|
|
1433
|
+
execute: { type: "boolean", description: "When true, actually delete the orphaned records. Defaults false (dry run). Ignored when dry_run=true." },
|
|
1434
|
+
dry_run: { type: "boolean", description: "Force a preview without mutation even if execute=true. Defaults to dry-run behavior when execute is not set." },
|
|
1435
|
+
include_terminal: { type: "boolean", description: "Also prune terminal (completed/failed) direct dispatch store rows in addition to orphans. Defaults false." }
|
|
1436
|
+
}
|
|
2442
1437
|
}
|
|
2443
1438
|
};
|
|
2444
|
-
var
|
|
2445
|
-
name: "
|
|
2446
|
-
description: "
|
|
1439
|
+
var MESH_REFINE_NODE_TOOL = {
|
|
1440
|
+
name: "mesh_refine_node",
|
|
1441
|
+
description: "The Refinery: validate \u2192 merge \u2192 push \u2192 clean up a completed worktree node onto the base branch. Defaults to dry-run (plan only): returns the validation plan with mergeWillRun:false/cleanupWillRun:false and performs NO merge/push/cleanup. Pass execute=true to actually converge the node. execute=true is async: 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. dry_run=true overrides execute. Matches the mesh_refine_batch / mesh_fast_forward_node dry_run/execute contract.",
|
|
2447
1442
|
inputSchema: {
|
|
2448
1443
|
type: "object",
|
|
2449
1444
|
properties: {
|
|
2450
|
-
node_id: { type: "string", description: "
|
|
2451
|
-
|
|
1445
|
+
node_id: { type: "string", description: "Node ID of the completed worktree node to refine and merge." },
|
|
1446
|
+
execute: { type: "boolean", description: "When true, run validation/merge/push/cleanup for this node. Defaults false/dry-run." },
|
|
1447
|
+
dry_run: { type: "boolean", description: "Preview the validation plan without merging. Defaults true unless execute=true; dry_run=true overrides execute." }
|
|
2452
1448
|
},
|
|
2453
1449
|
required: ["node_id"]
|
|
2454
1450
|
}
|
|
2455
1451
|
};
|
|
2456
|
-
var
|
|
2457
|
-
name: "
|
|
2458
|
-
description: "
|
|
1452
|
+
var MESH_REFINE_BATCH_TOOL = {
|
|
1453
|
+
name: "mesh_refine_batch",
|
|
1454
|
+
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. execute=true is async: the immediate response is async:true / status:'accepted' with the batch jobId and ordered target node list; per-node convergence runs in the background and the aggregate completion/failure (with per-node merged / blocked_review / not_mergeable results) is delivered as a terminal refine event via pending mesh events and the ledger \u2014 do not re-invoke while a batch is in flight. dry_run returns the plan synchronously.",
|
|
2459
1455
|
inputSchema: {
|
|
2460
1456
|
type: "object",
|
|
2461
1457
|
properties: {
|
|
2462
|
-
|
|
1458
|
+
node_ids: {
|
|
1459
|
+
type: "array",
|
|
1460
|
+
items: { type: "string" },
|
|
1461
|
+
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."
|
|
1462
|
+
},
|
|
1463
|
+
execute: { type: "boolean", description: "When true, run validation/rebase/merge for each node in order. Defaults false/dry-run." },
|
|
1464
|
+
dry_run: { type: "boolean", description: "Preview the ordering + per-node validation plan without executing. Defaults true unless execute=true; dry_run=true overrides execute." }
|
|
2463
1465
|
},
|
|
2464
|
-
required: [
|
|
1466
|
+
required: []
|
|
2465
1467
|
}
|
|
2466
1468
|
};
|
|
2467
|
-
var
|
|
2468
|
-
name: "
|
|
2469
|
-
description: "
|
|
1469
|
+
var MESH_REFINE_CONFIG_SCHEMA_TOOL = {
|
|
1470
|
+
name: "mesh_refine_config_schema",
|
|
1471
|
+
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.",
|
|
1472
|
+
inputSchema: { type: "object", properties: {} }
|
|
1473
|
+
};
|
|
1474
|
+
var MESH_VALIDATE_REFINE_CONFIG_TOOL = {
|
|
1475
|
+
name: "mesh_validate_refine_config",
|
|
1476
|
+
description: "Validate the repo mesh/refine config for a node/workspace without running validation commands or merging.",
|
|
2470
1477
|
inputSchema: {
|
|
2471
1478
|
type: "object",
|
|
2472
1479
|
properties: {
|
|
2473
|
-
node_id: { type: "string", description: "
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
tail_bytes: { type: "number", description: "Max bytes of log tail to read (default 65536, capped at 131072). Larger files are truncated to the newest tail_bytes." },
|
|
2477
|
-
date: { type: "string", description: "Optional YYYY-MM-DD log date (defaults to today). Falls back to the size-rotation backup when the active file is absent." }
|
|
2478
|
-
},
|
|
2479
|
-
required: ["node_id"]
|
|
1480
|
+
node_id: { type: "string", description: "Optional node/workspace whose refine config should be loaded. Defaults to the first mesh node." },
|
|
1481
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1482
|
+
}
|
|
2480
1483
|
}
|
|
2481
1484
|
};
|
|
2482
|
-
var
|
|
2483
|
-
name: "
|
|
2484
|
-
description:
|
|
1485
|
+
var MESH_SUGGEST_REFINE_CONFIG_TOOL = {
|
|
1486
|
+
name: "mesh_suggest_refine_config",
|
|
1487
|
+
description: "Suggest a repo mesh/refine config scaffold from project context/package scripts. Suggestions are never executed until saved as explicit refine config.",
|
|
2485
1488
|
inputSchema: {
|
|
2486
1489
|
type: "object",
|
|
2487
1490
|
properties: {
|
|
2488
|
-
node_id: { type: "string", description: "
|
|
2489
|
-
|
|
2490
|
-
branch: { type: "string", description: "Optional guard: require the node's current branch to match this branch before planning/executing." },
|
|
2491
|
-
execute: { type: "boolean", description: "When true, apply the fast-forward/push if all safety gates pass. Defaults false/dry-run." },
|
|
2492
|
-
dry_run: { type: "boolean", description: "Preview only. Defaults true unless execute=true; dry_run=true overrides execute." },
|
|
2493
|
-
update_submodules: { type: "boolean", description: 'mode="merge" only: when true, if the root fast-forward changes gitlinks, run only git submodule update --init --recursive and verify submodules clean.' },
|
|
2494
|
-
push_submodules: { type: "boolean", description: 'mode="push" only: also ff-only push submodule HEADs to their origin main. Gated by mesh policy allowAutoPublishSubmoduleMainCommits \u2014 skipped unless that policy is enabled. Defaults false (root push only).' }
|
|
2495
|
-
},
|
|
2496
|
-
required: ["node_id"]
|
|
1491
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
1492
|
+
}
|
|
2497
1493
|
}
|
|
2498
1494
|
};
|
|
2499
|
-
var
|
|
2500
|
-
name: "
|
|
2501
|
-
description:
|
|
1495
|
+
var MESH_CHANGE_IMPACT_CONFIG_SCHEMA_TOOL = {
|
|
1496
|
+
name: "mesh_change_impact_config_schema",
|
|
1497
|
+
description: "Return the Change Impact config JSON schema and supported repo-local config locations. Change Impact config declaratively classifies which package/file changes between the live daemon build and workspace HEAD require a daemon rebuild/restart vs. a web-only redeploy vs. nothing. Declarative only \u2014 config is parsed, never executed.",
|
|
1498
|
+
inputSchema: { type: "object", properties: {} }
|
|
1499
|
+
};
|
|
1500
|
+
var MESH_VALIDATE_CHANGE_IMPACT_CONFIG_TOOL = {
|
|
1501
|
+
name: "mesh_validate_change_impact_config",
|
|
1502
|
+
description: "Validate a Change Impact config for a node/workspace and report valid/errors. Loads .adhdev/change-impact.{json,yaml,yml} (or repo-mesh-change-impact.* alias) from the repo unless an inline config is provided.",
|
|
2502
1503
|
inputSchema: {
|
|
2503
1504
|
type: "object",
|
|
2504
1505
|
properties: {
|
|
2505
|
-
node_id: { type: "string", description: "
|
|
2506
|
-
|
|
2507
|
-
}
|
|
2508
|
-
required: ["node_id"]
|
|
1506
|
+
node_id: { type: "string", description: "Optional node/workspace whose change-impact config should be loaded. Defaults to the first mesh node." },
|
|
1507
|
+
config: { type: "object", description: "Optional inline config object to validate instead of loading from the repo." }
|
|
1508
|
+
}
|
|
2509
1509
|
}
|
|
2510
1510
|
};
|
|
2511
|
-
var
|
|
2512
|
-
name: "
|
|
2513
|
-
description: "
|
|
1511
|
+
var MESH_SUGGEST_CHANGE_IMPACT_CONFIG_TOOL = {
|
|
1512
|
+
name: "mesh_suggest_change_impact_config",
|
|
1513
|
+
description: "Suggest a Change Impact config scaffold from the repo package layout (web-* \u2192 web-only, others \u2192 daemon-runtime, plus docs/license markers as non-runtime). Heuristic scaffold only \u2014 the draft must be reviewed and saved before it takes effect; nothing is executed.",
|
|
2514
1514
|
inputSchema: {
|
|
2515
1515
|
type: "object",
|
|
2516
1516
|
properties: {
|
|
2517
|
-
node_id: { type: "string", description: "
|
|
2518
|
-
|
|
2519
|
-
},
|
|
2520
|
-
required: ["node_id", "message"]
|
|
1517
|
+
node_id: { type: "string", description: "Optional node/workspace used for suggestions. Defaults to the first mesh node." }
|
|
1518
|
+
}
|
|
2521
1519
|
}
|
|
2522
1520
|
};
|
|
2523
|
-
var
|
|
2524
|
-
name: "
|
|
2525
|
-
description: "
|
|
1521
|
+
var MESH_INIT_TOOL = {
|
|
1522
|
+
name: "mesh_init",
|
|
1523
|
+
description: "One-click mesh onboarding for an existing git project. Detects installed CLI providers, suggests Refinery (.adhdev/refine.json) and worktree bootstrap (.adhdev/worktree_bootstrap.json) configs, optionally writes them to disk, and recommends a node providerPriority from the detected providers. Suggestions are scaffold only and never execute until saved; providerPriority is a recommendation to apply to node policy, not auto-applied. Defaults to dry-run (no files written) and never overwrites an existing config unless overwrite=true.",
|
|
2526
1524
|
inputSchema: {
|
|
2527
1525
|
type: "object",
|
|
2528
1526
|
properties: {
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
},
|
|
2534
|
-
required: ["title"]
|
|
1527
|
+
node_id: { type: "string", description: "Optional node/workspace to onboard. Defaults to the first mesh node with a workspace." },
|
|
1528
|
+
write: { type: "boolean", description: "When true, persist the suggested configs to disk. Defaults false (dry-run preview only)." },
|
|
1529
|
+
overwrite: { type: "boolean", description: "When true, overwrite an existing config file. Defaults false (never clobber an existing refine/bootstrap config)." }
|
|
1530
|
+
}
|
|
2535
1531
|
}
|
|
2536
1532
|
};
|
|
2537
|
-
var
|
|
2538
|
-
name: "
|
|
2539
|
-
description:
|
|
1533
|
+
var MESH_REFINE_PLAN_TOOL = {
|
|
1534
|
+
name: "mesh_refine_plan",
|
|
1535
|
+
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.",
|
|
2540
1536
|
inputSchema: {
|
|
2541
1537
|
type: "object",
|
|
2542
1538
|
properties: {
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
description: "Optional status filter. Omit to return missions of every status."
|
|
2547
|
-
},
|
|
2548
|
-
verbose: { type: "boolean", description: "Return full goal text instead of a capped preview. Defaults to false (compact)." }
|
|
2549
|
-
}
|
|
1539
|
+
node_id: { type: "string", description: "Node ID of the worktree node to plan." }
|
|
1540
|
+
},
|
|
1541
|
+
required: ["node_id"]
|
|
2550
1542
|
}
|
|
2551
1543
|
};
|
|
2552
|
-
var
|
|
2553
|
-
name: "
|
|
2554
|
-
description: "
|
|
1544
|
+
var MESH_REVIEW_INBOX_TOOL = {
|
|
1545
|
+
name: "mesh_review_inbox",
|
|
1546
|
+
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.",
|
|
2555
1547
|
inputSchema: {
|
|
2556
1548
|
type: "object",
|
|
2557
1549
|
properties: {
|
|
2558
|
-
|
|
2559
|
-
session_id: { type: "string", description: "Agent session ID with pending approval." },
|
|
2560
|
-
action: { type: "string", enum: ["approve", "reject"], description: "Action to take." }
|
|
1550
|
+
mesh_id: { type: "string", description: "Mesh ID (optional \u2014 inferred from active mesh if omitted)." }
|
|
2561
1551
|
},
|
|
2562
|
-
required: [
|
|
1552
|
+
required: []
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
var ALL_MESH_TOOLS = [
|
|
1556
|
+
MESH_STATUS_TOOL,
|
|
1557
|
+
MESH_LIST_NODES_TOOL,
|
|
1558
|
+
MESH_ENQUEUE_TASK_TOOL,
|
|
1559
|
+
MESH_VIEW_QUEUE_TOOL,
|
|
1560
|
+
MESH_QUEUE_CANCEL_TOOL,
|
|
1561
|
+
MESH_QUEUE_REQUEUE_TOOL,
|
|
1562
|
+
MESH_SEND_TASK_TOOL,
|
|
1563
|
+
MESH_READ_CHAT_TOOL,
|
|
1564
|
+
MESH_READ_DEBUG_TOOL,
|
|
1565
|
+
MESH_LAUNCH_SESSION_TOOL,
|
|
1566
|
+
MESH_GIT_STATUS_TOOL,
|
|
1567
|
+
MESH_READ_NODE_LOGS_TOOL,
|
|
1568
|
+
MESH_FAST_FORWARD_NODE_TOOL,
|
|
1569
|
+
MESH_RESTART_DAEMON_TOOL,
|
|
1570
|
+
MESH_CHECKPOINT_TOOL,
|
|
1571
|
+
MESH_APPROVE_TOOL,
|
|
1572
|
+
MESH_CLONE_NODE_TOOL,
|
|
1573
|
+
MESH_REMOVE_NODE_TOOL,
|
|
1574
|
+
MESH_REFINE_NODE_TOOL,
|
|
1575
|
+
MESH_REFINE_BATCH_TOOL,
|
|
1576
|
+
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
1577
|
+
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
1578
|
+
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
1579
|
+
MESH_CHANGE_IMPACT_CONFIG_SCHEMA_TOOL,
|
|
1580
|
+
MESH_VALIDATE_CHANGE_IMPACT_CONFIG_TOOL,
|
|
1581
|
+
MESH_SUGGEST_CHANGE_IMPACT_CONFIG_TOOL,
|
|
1582
|
+
MESH_INIT_TOOL,
|
|
1583
|
+
MESH_REFINE_PLAN_TOOL,
|
|
1584
|
+
MESH_CLEANUP_SESSIONS_TOOL,
|
|
1585
|
+
MESH_PRUNE_STALE_DIRECT_TOOL,
|
|
1586
|
+
MESH_TASK_HISTORY_TOOL,
|
|
1587
|
+
MESH_RECONCILE_LEDGER_TOOL,
|
|
1588
|
+
MESH_MISSION_UPSERT_TOOL,
|
|
1589
|
+
MESH_MISSION_LIST_TOOL,
|
|
1590
|
+
MESH_REVIEW_INBOX_TOOL
|
|
1591
|
+
];
|
|
1592
|
+
|
|
1593
|
+
// src/tools/mesh-tools.ts
|
|
1594
|
+
var SESSION_PROVIDER_METADATA_TTL_MS = 30 * 6e4;
|
|
1595
|
+
var meshSessionProviderMetadata = /* @__PURE__ */ new Map();
|
|
1596
|
+
function getSessionMetadata(key) {
|
|
1597
|
+
const entry = meshSessionProviderMetadata.get(key);
|
|
1598
|
+
if (!entry) return void 0;
|
|
1599
|
+
if (entry.expiresAt <= Date.now()) {
|
|
1600
|
+
meshSessionProviderMetadata.delete(key);
|
|
1601
|
+
return void 0;
|
|
1602
|
+
}
|
|
1603
|
+
return entry;
|
|
1604
|
+
}
|
|
1605
|
+
var ACTIVE_WORK_POLLING_BACKOFF_MS = 6e4;
|
|
1606
|
+
function buildActiveWorkPollingGuidance(summary, now = Date.now()) {
|
|
1607
|
+
if (!summary || summary.generatingCount <= 0) return void 0;
|
|
1608
|
+
return {
|
|
1609
|
+
activeGeneratingWork: true,
|
|
1610
|
+
generatingCount: summary.generatingCount,
|
|
1611
|
+
doNotPollBefore: new Date(now + ACTIVE_WORK_POLLING_BACKOFF_MS).toISOString(),
|
|
1612
|
+
eventSurface: "pendingCoordinatorEvents",
|
|
1613
|
+
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.",
|
|
1614
|
+
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."
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
function summarizeTaskMessage(message) {
|
|
1618
|
+
const taskSummary = message.replace(/\s+/g, " ").trim();
|
|
1619
|
+
const taskTitle = taskSummary.length > 96 ? `${taskSummary.slice(0, 93)}...` : taskSummary;
|
|
1620
|
+
return { taskTitle: taskTitle || "(untitled task)", taskSummary };
|
|
1621
|
+
}
|
|
1622
|
+
function buildDirectTaskPayload(message, via, opts) {
|
|
1623
|
+
const descriptor = summarizeTaskMessage(message);
|
|
1624
|
+
return {
|
|
1625
|
+
source: "direct",
|
|
1626
|
+
via,
|
|
1627
|
+
taskId: opts.taskId,
|
|
1628
|
+
message,
|
|
1629
|
+
taskTitle: descriptor.taskTitle,
|
|
1630
|
+
taskSummary: descriptor.taskSummary,
|
|
1631
|
+
...opts.taskMode ? { taskMode: opts.taskMode } : {},
|
|
1632
|
+
...opts.providerType ? { providerType: opts.providerType } : {},
|
|
1633
|
+
...opts.targetSessionId ? { targetSessionId: opts.targetSessionId } : {},
|
|
1634
|
+
...opts.dispatchedToIdleSession !== void 0 ? { dispatchedToIdleSession: opts.dispatchedToIdleSession } : {}
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
function findNode(mesh, nodeId) {
|
|
1638
|
+
const node = mesh.nodes.find((n) => (0, import_daemon_core2.meshNodeIdMatches)(n, nodeId));
|
|
1639
|
+
if (!node) throw new Error(`Node '${nodeId}' is not a member of mesh '${mesh.name}'`);
|
|
1640
|
+
return node;
|
|
1641
|
+
}
|
|
1642
|
+
var DUPLICATE_DISPATCH_WINDOW_MS = 6e4;
|
|
1643
|
+
async function refreshMeshFromDaemon(ctx) {
|
|
1644
|
+
try {
|
|
1645
|
+
const result = await ctx.transport.command("get_mesh", { meshId: ctx.mesh.id });
|
|
1646
|
+
if (!result?.success || !Array.isArray(result.mesh?.nodes)) return;
|
|
1647
|
+
const refreshedNodes = result.mesh.nodes.filter((n) => n?.id).map((n) => n);
|
|
1648
|
+
ctx.mesh.nodes.splice(0, ctx.mesh.nodes.length, ...refreshedNodes);
|
|
1649
|
+
ctx.mesh.updatedAt = result.mesh.updatedAt ?? ctx.mesh.updatedAt;
|
|
1650
|
+
} catch {
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
async function syncCoordinatorDaemonMeshCache(ctx) {
|
|
1654
|
+
if (!(ctx.transport instanceof IpcTransport)) return;
|
|
1655
|
+
try {
|
|
1656
|
+
await ctx.transport.command("get_mesh", {
|
|
1657
|
+
meshId: ctx.mesh.id,
|
|
1658
|
+
inlineMesh: ctx.mesh
|
|
1659
|
+
});
|
|
1660
|
+
} catch {
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
async function findNodeWithRefresh(ctx, nodeId) {
|
|
1664
|
+
const hit = ctx.mesh.nodes.find((n) => (0, import_daemon_core2.meshNodeIdMatches)(n, nodeId));
|
|
1665
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
1666
|
+
await refreshMeshFromDaemon(ctx);
|
|
1667
|
+
const refreshed = ctx.mesh.nodes.find((n) => (0, import_daemon_core2.meshNodeIdMatches)(n, nodeId));
|
|
1668
|
+
if (!refreshed) throw new Error(`Node '${nodeId}' is not a member of mesh '${ctx.mesh.name}'`);
|
|
1669
|
+
return refreshed;
|
|
1670
|
+
}
|
|
1671
|
+
async function findOptionalNodeWithRefresh(ctx, nodeId) {
|
|
1672
|
+
const hit = ctx.mesh.nodes.find((n) => (0, import_daemon_core2.meshNodeIdMatches)(n, nodeId));
|
|
1673
|
+
if (hit && !hit.isLocalWorktree) return hit;
|
|
1674
|
+
await refreshMeshFromDaemon(ctx);
|
|
1675
|
+
return ctx.mesh.nodes.find((n) => (0, import_daemon_core2.meshNodeIdMatches)(n, nodeId)) ?? null;
|
|
1676
|
+
}
|
|
1677
|
+
function hasRecentDuplicateDispatch(ctx, args) {
|
|
1678
|
+
const now = Date.now();
|
|
1679
|
+
const normalizedMessage = args.message.trim();
|
|
1680
|
+
for (const task of (0, import_daemon_core2.getQueue)(ctx.mesh.id)) {
|
|
1681
|
+
const timestamp = new Date(task.updatedAt || task.createdAt).getTime();
|
|
1682
|
+
if (!Number.isFinite(timestamp) || now - timestamp > DUPLICATE_DISPATCH_WINDOW_MS) continue;
|
|
1683
|
+
if (task.targetNodeId && task.targetNodeId !== args.node_id) continue;
|
|
1684
|
+
if (task.assignedNodeId && task.assignedNodeId !== args.node_id) continue;
|
|
1685
|
+
if (args.session_id && task.targetSessionId !== args.session_id && task.assignedSessionId !== args.session_id) continue;
|
|
1686
|
+
if (task.message?.trim() === normalizedMessage) {
|
|
1687
|
+
return { duplicate: true, entry: task, source: "queue" };
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
const entries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
1691
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1692
|
+
const entry = entries[i];
|
|
1693
|
+
const timestamp = new Date(entry.timestamp).getTime();
|
|
1694
|
+
if (Number.isFinite(timestamp) && now - timestamp > DUPLICATE_DISPATCH_WINDOW_MS) break;
|
|
1695
|
+
if (entry.kind !== "task_dispatched") continue;
|
|
1696
|
+
if (entry.nodeId !== args.node_id) continue;
|
|
1697
|
+
if (args.session_id && entry.sessionId !== args.session_id) continue;
|
|
1698
|
+
if (typeof entry.payload?.message !== "string") continue;
|
|
1699
|
+
if (entry.payload.message.trim() === normalizedMessage) {
|
|
1700
|
+
return { duplicate: true, entry, source: "ledger" };
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
return { duplicate: false };
|
|
1704
|
+
}
|
|
1705
|
+
function buildMissingNodeReadChatRecovery(ctx, args) {
|
|
1706
|
+
const entries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 300 });
|
|
1707
|
+
const relatedEntries = entries.filter((entry) => entry.nodeId === args.node_id || entry.sessionId === args.session_id);
|
|
1708
|
+
const completedEntries = relatedEntries.filter((entry) => entry.kind === "task_completed");
|
|
1709
|
+
const lastDispatch = [...relatedEntries].reverse().find((entry) => entry.kind === "task_dispatched");
|
|
1710
|
+
const lastTerminal = [...relatedEntries].reverse().find((entry) => entry.kind === "task_completed" || entry.kind === "task_failed" || entry.kind === "task_stalled");
|
|
1711
|
+
const lastRemoved = [...relatedEntries].reverse().find((entry) => entry.kind === "node_removed");
|
|
1712
|
+
const lastLaunch = [...relatedEntries].reverse().find((entry) => entry.kind === "session_launched");
|
|
1713
|
+
const providerSessionId = args.provider_session_id || readString(lastTerminal?.payload?.providerSessionId) || readString(lastLaunch?.payload?.providerSessionId) || readString(lastDispatch?.payload?.providerSessionId);
|
|
1714
|
+
const finalSummary = readString(lastTerminal?.payload?.finalSummary) || readString(lastTerminal?.payload?.compactSummary) || readString(lastTerminal?.payload?.summary);
|
|
1715
|
+
const ledger = {
|
|
1716
|
+
taskCompletedFound: completedEntries.length > 0,
|
|
1717
|
+
nodeRemovedFound: !!lastRemoved,
|
|
1718
|
+
providerType: lastTerminal?.providerType || lastLaunch?.providerType || lastDispatch?.providerType,
|
|
1719
|
+
providerSessionId,
|
|
1720
|
+
nodeRemovedAt: lastRemoved?.timestamp,
|
|
1721
|
+
sessionCleanupMode: readString(lastRemoved?.payload?.sessionCleanupMode),
|
|
1722
|
+
readDebugLocator: readString(lastTerminal?.payload?.readDebugLocator) || readString(lastTerminal?.payload?.debugBundlePath)
|
|
1723
|
+
};
|
|
1724
|
+
if (finalSummary) {
|
|
1725
|
+
if (args.compact === true) {
|
|
1726
|
+
return {
|
|
1727
|
+
...compactChatPayload({
|
|
1728
|
+
success: true,
|
|
1729
|
+
status: "idle",
|
|
1730
|
+
providerSessionId,
|
|
1731
|
+
summary: finalSummary,
|
|
1732
|
+
messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
|
|
1733
|
+
}, {
|
|
1734
|
+
nodeId: args.node_id,
|
|
1735
|
+
sessionId: args.session_id,
|
|
1736
|
+
limit: args.tail ?? 10
|
|
1737
|
+
}),
|
|
1738
|
+
recoveredFromLedger: true,
|
|
1739
|
+
ledger
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
return {
|
|
1743
|
+
success: true,
|
|
1744
|
+
compact: false,
|
|
1745
|
+
recoveredFromLedger: true,
|
|
1746
|
+
nodeId: args.node_id,
|
|
1747
|
+
sessionId: args.session_id,
|
|
1748
|
+
summary: finalSummary,
|
|
1749
|
+
ledger,
|
|
1750
|
+
messages: [{ role: "assistant", content: finalSummary, isHistorical: true }]
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
return {
|
|
1754
|
+
success: false,
|
|
1755
|
+
recoverable: true,
|
|
1756
|
+
code: "mesh_removed_node_transcript_unavailable",
|
|
1757
|
+
error: `Node '${args.node_id}' is not a current member of mesh '${ctx.mesh.name}'.`,
|
|
1758
|
+
nodeId: args.node_id,
|
|
1759
|
+
sessionId: args.session_id,
|
|
1760
|
+
providerSessionId,
|
|
1761
|
+
reason: "node_not_in_current_mesh_snapshot",
|
|
1762
|
+
ledger,
|
|
1763
|
+
completedSessionSeenInLedger: ledger.taskCompletedFound,
|
|
1764
|
+
lastDispatch: lastDispatch ? {
|
|
1765
|
+
timestamp: lastDispatch.timestamp,
|
|
1766
|
+
sessionId: lastDispatch.sessionId,
|
|
1767
|
+
providerType: lastDispatch.providerType,
|
|
1768
|
+
taskId: typeof lastDispatch.payload?.taskId === "string" ? lastDispatch.payload.taskId : void 0,
|
|
1769
|
+
messagePreview: typeof lastDispatch.payload?.message === "string" ? lastDispatch.payload.message.slice(0, 500) : void 0
|
|
1770
|
+
} : null,
|
|
1771
|
+
lastTerminalEvent: lastTerminal ? {
|
|
1772
|
+
kind: lastTerminal.kind,
|
|
1773
|
+
timestamp: lastTerminal.timestamp,
|
|
1774
|
+
sessionId: lastTerminal.sessionId,
|
|
1775
|
+
providerType: lastTerminal.providerType,
|
|
1776
|
+
taskId: typeof lastTerminal.payload?.taskId === "string" ? lastTerminal.payload.taskId : void 0,
|
|
1777
|
+
payload: lastTerminal.payload
|
|
1778
|
+
} : null,
|
|
1779
|
+
nextSteps: [
|
|
1780
|
+
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.",
|
|
1781
|
+
"Use mesh_read_debug with the provider_session_id or daemon-side debug bundle locator if available.",
|
|
1782
|
+
"Check mesh_task_history for task_completed and node_removed entries before redispatching; do not resend solely because transcript recovery failed.",
|
|
1783
|
+
"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."
|
|
1784
|
+
],
|
|
1785
|
+
recoveryHints: [
|
|
1786
|
+
"The worktree/node may have been removed or the mesh snapshot may be stale after task completion.",
|
|
1787
|
+
"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.",
|
|
1788
|
+
"Use mesh_read_debug with provider_session_id, or inspect the daemon/session-host history locator if the transcript has already been archived.",
|
|
1789
|
+
"Avoid redispatching the same task solely because read_chat could not recover the transcript; check task_history and git status first."
|
|
1790
|
+
]
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
function isDirectDispatchLedgerEntry(entry) {
|
|
1794
|
+
if (entry?.kind !== "task_dispatched") return false;
|
|
1795
|
+
const payload = entry.payload || {};
|
|
1796
|
+
const via = readString(payload.via);
|
|
1797
|
+
return payload.source === "direct" || via === "p2p_direct" || via === "local_direct" || via === "mesh_send_task";
|
|
1798
|
+
}
|
|
1799
|
+
function readMessageTimestampIso(message) {
|
|
1800
|
+
for (const value of [message?.timestamp, message?.createdAt, message?.created_at, message?.updatedAt, message?.time]) {
|
|
1801
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1802
|
+
const ms = value > 1e10 ? value : value * 1e3;
|
|
1803
|
+
return new Date(ms).toISOString();
|
|
1804
|
+
}
|
|
1805
|
+
if (typeof value === "string" && value.trim()) {
|
|
1806
|
+
const ms = new Date(value.trim()).getTime();
|
|
1807
|
+
if (Number.isFinite(ms)) return new Date(ms).toISOString();
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
return void 0;
|
|
1811
|
+
}
|
|
1812
|
+
function readFinalAssistantTranscriptEvidence(payload) {
|
|
1813
|
+
const rawMessages = Array.isArray(payload?.messages) ? payload.messages : [];
|
|
1814
|
+
const finalAssistant = [...rawMessages].reverse().filter(isCoordinatorVisibleMessage).find((message) => {
|
|
1815
|
+
const role = String(message?.role ?? "").toLowerCase();
|
|
1816
|
+
return (role === "assistant" || role === "agent") && messageContent(message).trim();
|
|
1817
|
+
});
|
|
1818
|
+
const finalSummary = messageContent(finalAssistant).trim() || (typeof payload?.summary === "string" && payload.summary.trim() ? payload.summary.trim() : void 0);
|
|
1819
|
+
return {
|
|
1820
|
+
finalSummary,
|
|
1821
|
+
transcriptMessageAt: finalAssistant ? readMessageTimestampIso(finalAssistant) : void 0
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
function findNodeSession(nodes, nodeId, sessionId) {
|
|
1825
|
+
if (!nodeId || !sessionId) return {};
|
|
1826
|
+
const node = nodes.find((candidate) => (0, import_daemon_core2.meshNodeIdMatches)(candidate, nodeId));
|
|
1827
|
+
if (!node) return {};
|
|
1828
|
+
const sessions = Array.isArray(node.sessions) ? node.sessions : [];
|
|
1829
|
+
const session = sessions.find((candidate) => readSessionRecordId(candidate) === sessionId);
|
|
1830
|
+
return { node, session };
|
|
1831
|
+
}
|
|
1832
|
+
function buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries) {
|
|
1833
|
+
const candidates = [];
|
|
1834
|
+
const seenTaskIds = /* @__PURE__ */ new Set();
|
|
1835
|
+
for (const dispatch of directDispatches || []) {
|
|
1836
|
+
const taskId = readString(dispatch?.taskId);
|
|
1837
|
+
if (!taskId || seenTaskIds.has(taskId)) continue;
|
|
1838
|
+
seenTaskIds.add(taskId);
|
|
1839
|
+
candidates.push(dispatch);
|
|
1840
|
+
}
|
|
1841
|
+
for (const entry of ledgerEntries || []) {
|
|
1842
|
+
if (!isDirectDispatchLedgerEntry(entry)) continue;
|
|
1843
|
+
const taskId = readString(entry.payload?.taskId);
|
|
1844
|
+
if (!taskId || seenTaskIds.has(taskId)) continue;
|
|
1845
|
+
seenTaskIds.add(taskId);
|
|
1846
|
+
candidates.push({
|
|
1847
|
+
taskId,
|
|
1848
|
+
nodeId: entry.nodeId,
|
|
1849
|
+
sessionId: entry.sessionId,
|
|
1850
|
+
providerType: entry.providerType || readString(entry.payload?.providerType),
|
|
1851
|
+
message: readString(entry.payload?.message),
|
|
1852
|
+
dispatchedAt: entry.timestamp,
|
|
1853
|
+
via: readString(entry.payload?.via)
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
return candidates;
|
|
1857
|
+
}
|
|
1858
|
+
async function reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries) {
|
|
1859
|
+
let attempted = 0;
|
|
1860
|
+
let reconciled = 0;
|
|
1861
|
+
let skipped = 0;
|
|
1862
|
+
const candidates = buildDirectDispatchReconciliationCandidates(directDispatches, ledgerEntries);
|
|
1863
|
+
for (const dispatch of candidates) {
|
|
1864
|
+
const taskId = readString(dispatch?.taskId);
|
|
1865
|
+
const nodeId = readString(dispatch?.nodeId);
|
|
1866
|
+
const sessionId = readString(dispatch?.sessionId);
|
|
1867
|
+
if (!taskId || !nodeId || !sessionId) {
|
|
1868
|
+
skipped += 1;
|
|
1869
|
+
continue;
|
|
1870
|
+
}
|
|
1871
|
+
const { session } = findNodeSession(liveNodes, nodeId, sessionId);
|
|
1872
|
+
if (!session || !isIdleSessionRecord(session)) {
|
|
1873
|
+
skipped += 1;
|
|
1874
|
+
continue;
|
|
1875
|
+
}
|
|
1876
|
+
const node = await findOptionalNodeWithRefresh(ctx, nodeId).catch(() => null);
|
|
1877
|
+
if (!node) {
|
|
1878
|
+
skipped += 1;
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
const providerType = readString(dispatch?.providerType) || resolveSessionProviderType(session);
|
|
1882
|
+
const providerSessionId = readString(session?.providerSessionId) || readString(session?.activeChat?.providerSessionId) || readString(session?.settings?.providerSessionId) || resolveMeshSessionProviderMetadata(ctx, nodeId, sessionId)?.providerSessionId;
|
|
1883
|
+
attempted += 1;
|
|
1884
|
+
try {
|
|
1885
|
+
const readResult = await commandForNode(ctx, node, "read_chat", {
|
|
1886
|
+
sessionId,
|
|
1887
|
+
targetSessionId: sessionId,
|
|
1888
|
+
workspace: node.workspace,
|
|
1889
|
+
...providerType ? { agentType: providerType, providerType } : {},
|
|
1890
|
+
...providerSessionId ? { providerSessionId } : {},
|
|
1891
|
+
tailLimit: 10
|
|
1892
|
+
});
|
|
1893
|
+
const payload = unwrapCommandPayload(readResult);
|
|
1894
|
+
if (payload?.success === false) continue;
|
|
1895
|
+
const evidence = readFinalAssistantTranscriptEvidence(payload);
|
|
1896
|
+
if (!evidence.finalSummary) continue;
|
|
1897
|
+
const result = (0, import_daemon_core2.reconcileDirectDispatchCompletionFromTranscript)({
|
|
1898
|
+
meshId: ctx.mesh.id,
|
|
1899
|
+
nodeId,
|
|
1900
|
+
sessionId,
|
|
1901
|
+
providerType,
|
|
1902
|
+
providerSessionId: readString(payload?.providerSessionId) || providerSessionId,
|
|
1903
|
+
taskId,
|
|
1904
|
+
finalSummary: evidence.finalSummary,
|
|
1905
|
+
transcriptMessageAt: evidence.transcriptMessageAt,
|
|
1906
|
+
targetCoordinatorDaemonId: ctx.localDaemonId,
|
|
1907
|
+
source: "mcp_mesh_status_transcript_reconciliation"
|
|
1908
|
+
});
|
|
1909
|
+
if (result.reconciled) reconciled += 1;
|
|
1910
|
+
} catch {
|
|
1911
|
+
skipped += 1;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
return { attempted, reconciled, skipped };
|
|
1915
|
+
}
|
|
1916
|
+
async function triggerMeshQueueAndReport(ctx) {
|
|
1917
|
+
try {
|
|
1918
|
+
const raw = await ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id });
|
|
1919
|
+
const payload = unwrapCommandPayload(raw);
|
|
1920
|
+
const trigger = payload?.trigger && typeof payload.trigger === "object" ? payload.trigger : payload;
|
|
1921
|
+
return trigger && typeof trigger === "object" ? trigger : { success: true };
|
|
1922
|
+
} catch (e) {
|
|
1923
|
+
return {
|
|
1924
|
+
success: false,
|
|
1925
|
+
error: e?.message || String(e)
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
function buildQueueTriggerGuidance(queueTrigger) {
|
|
1930
|
+
if (!queueTrigger || queueTrigger.claimed === true) return void 0;
|
|
1931
|
+
if (queueTrigger.success === false) {
|
|
1932
|
+
return {
|
|
1933
|
+
queueClaimed: false,
|
|
1934
|
+
queueDispatchState: "trigger_failed",
|
|
1935
|
+
nextAction: "Do not assume the queued task is running. Check mesh_view_queue and daemon connectivity before redispatching."
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
if (queueTrigger.autoLaunchPending === true) {
|
|
1939
|
+
return {
|
|
1940
|
+
queueClaimed: false,
|
|
1941
|
+
queueDispatchState: "pending_waiting_for_autolaunch",
|
|
1942
|
+
nextAction: "A worker session was just auto-launched for this task and is booting; it will claim the task shortly. Wait for it to claim \u2014 do NOT launch another session. Use mesh_view_queue to confirm the assignment lands."
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
if (queueTrigger.noIdleMeshSessionAvailable === true) {
|
|
1946
|
+
return {
|
|
1947
|
+
queueClaimed: false,
|
|
1948
|
+
queueDispatchState: "pending_no_idle_mesh_session",
|
|
1949
|
+
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."
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
return {
|
|
1953
|
+
queueClaimed: false,
|
|
1954
|
+
queueDispatchState: "pending_or_waiting_for_ready",
|
|
1955
|
+
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."
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
function isMeshOwnedDelegateSession(session, meshId, nodeId) {
|
|
1959
|
+
const settings = session?.settings;
|
|
1960
|
+
const sessionMeshId = typeof settings?.meshNodeFor === "string" ? settings.meshNodeFor.trim() : "";
|
|
1961
|
+
const sessionNodeId = typeof settings?.meshNodeId === "string" ? settings.meshNodeId.trim() : "";
|
|
1962
|
+
if (sessionMeshId) {
|
|
1963
|
+
if (sessionMeshId !== meshId) return false;
|
|
1964
|
+
return !sessionNodeId || sessionNodeId === nodeId;
|
|
1965
|
+
}
|
|
1966
|
+
const coordinatorOwned = settings?.launchedByCoordinator === true || Boolean(readString(settings?.meshCoordinatorDaemonId));
|
|
1967
|
+
if (!coordinatorOwned) return false;
|
|
1968
|
+
const lastNodeId = readString(settings?.meshLastNodeId);
|
|
1969
|
+
if (lastNodeId) return lastNodeId === nodeId;
|
|
1970
|
+
return true;
|
|
1971
|
+
}
|
|
1972
|
+
function hasRemoteRelayMetadata(session) {
|
|
1973
|
+
return Boolean(
|
|
1974
|
+
readString(session?.settings?.meshCoordinatorDaemonId) || readString(session?.meta?.meshCoordinatorDaemonId) || readString(session?.metadata?.meshCoordinatorDaemonId) || readString(session?.meshCoordinatorDaemonId)
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
1977
|
+
function classifyRemoteDelegateRelaySafety(session, meshId, nodeId, coordinatorDaemonId) {
|
|
1978
|
+
if (!isMeshOwnedDelegateSession(session, meshId, nodeId)) return "unsafe_alias";
|
|
1979
|
+
if (hasRemoteRelayMetadata(session)) return "safe";
|
|
1980
|
+
return coordinatorDaemonId ? "self_heal" : "missing_anchor";
|
|
1981
|
+
}
|
|
1982
|
+
function chooseDispatchableSession(sessions, providerType, meshId, nodeId, coordinatorDaemonId) {
|
|
1983
|
+
const live = sessions.filter((session) => !isTerminalSessionRecord(session));
|
|
1984
|
+
const matchingProvider = (session) => !providerType || session?.providerType === providerType || session?.cliType === providerType;
|
|
1985
|
+
const meshSessions = live.filter((session) => {
|
|
1986
|
+
const safety = classifyRemoteDelegateRelaySafety(session, meshId, nodeId, coordinatorDaemonId);
|
|
1987
|
+
return safety === "safe" || safety === "self_heal";
|
|
1988
|
+
});
|
|
1989
|
+
return meshSessions.find((session) => isIdleSessionRecord(session) && matchingProvider(session)) || void 0;
|
|
1990
|
+
}
|
|
1991
|
+
function buildRelayUnsafeRemoteSessionFailure(ctx, node, sessionId, providerType) {
|
|
1992
|
+
return {
|
|
1993
|
+
success: false,
|
|
1994
|
+
recoverable: true,
|
|
1995
|
+
code: "mesh_delegate_session_missing_relay_metadata",
|
|
1996
|
+
reason: "mesh_delegate_session_missing_relay_metadata",
|
|
1997
|
+
transport: "mesh_transport",
|
|
1998
|
+
retryRecommended: true,
|
|
1999
|
+
meshId: ctx.mesh.id,
|
|
2000
|
+
nodeId: node.id,
|
|
2001
|
+
daemonId: node.daemonId,
|
|
2002
|
+
workspace: node.workspace,
|
|
2003
|
+
sessionId,
|
|
2004
|
+
unsafeTranscriptAlias: true,
|
|
2005
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
2006
|
+
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).`,
|
|
2007
|
+
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.`,
|
|
2008
|
+
noFallbackReason: "Blindly reusing a remote session without mesh relay metadata would silently drop task_completed / generating_completed events."
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
function buildMissingCoordinatorDaemonIdFailure(ctx, node, providerType) {
|
|
2012
|
+
return {
|
|
2013
|
+
success: false,
|
|
2014
|
+
recoverable: true,
|
|
2015
|
+
code: "mesh_coordinator_daemon_unknown",
|
|
2016
|
+
reason: "mesh_coordinator_daemon_unknown",
|
|
2017
|
+
transport: "mesh_transport",
|
|
2018
|
+
retryRecommended: true,
|
|
2019
|
+
meshId: ctx.mesh.id,
|
|
2020
|
+
nodeId: node.id,
|
|
2021
|
+
daemonId: node.daemonId,
|
|
2022
|
+
workspace: node.workspace,
|
|
2023
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
2024
|
+
error: `Cannot launch a remote mesh delegate for node '${node.id}': coordinator daemon identity is unavailable, so the worker would be unable to relay completion events back to the coordinator.`,
|
|
2025
|
+
nextAction: "Retry after the coordinator daemon identity is available (for example from an attached daemon-backed MCP session) so meshCoordinatorDaemonId can be stamped on the worker session.",
|
|
2026
|
+
noFallbackReason: "Launching without meshCoordinatorDaemonId would create a worker session that can finish work but cannot emit task_completed / generating_completed back to the coordinator."
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
function findNestedPayload(value, predicate) {
|
|
2030
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2031
|
+
const stack = [{ payload: value, depth: 0 }];
|
|
2032
|
+
while (stack.length) {
|
|
2033
|
+
const { payload, depth } = stack.pop();
|
|
2034
|
+
if (predicate(payload)) return payload;
|
|
2035
|
+
if (!payload || typeof payload !== "object" || seen.has(payload) || depth >= 8) continue;
|
|
2036
|
+
seen.add(payload);
|
|
2037
|
+
for (const key of ["payload", "result"]) {
|
|
2038
|
+
if (key in payload) stack.push({ payload: payload[key], depth: depth + 1 });
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
return value;
|
|
2042
|
+
}
|
|
2043
|
+
function extractCloneNodePayload(value) {
|
|
2044
|
+
return findNestedPayload(value, (payload) => Boolean(payload?.node?.id));
|
|
2045
|
+
}
|
|
2046
|
+
function extractGitStatus(value) {
|
|
2047
|
+
const payload = unwrapCommandPayload(value);
|
|
2048
|
+
return payload?.status ?? value?.status ?? payload;
|
|
2049
|
+
}
|
|
2050
|
+
function extractGitDiff(value) {
|
|
2051
|
+
const payload = unwrapCommandPayload(value);
|
|
2052
|
+
return payload?.diffSummary ?? payload?.diff ?? value?.diffSummary ?? value?.diff ?? payload;
|
|
2053
|
+
}
|
|
2054
|
+
function extractSubmodules(value, ignorePaths) {
|
|
2055
|
+
const payload = unwrapCommandPayload(value);
|
|
2056
|
+
const subs = payload?.status?.submodules ?? payload?.submodules ?? value?.status?.submodules ?? value?.submodules;
|
|
2057
|
+
if (!Array.isArray(subs)) return void 0;
|
|
2058
|
+
if (ignorePaths.length === 0) return subs;
|
|
2059
|
+
const ignoreSet = new Set(ignorePaths);
|
|
2060
|
+
return subs.filter((s) => s?.path && !ignoreSet.has(s.path));
|
|
2061
|
+
}
|
|
2062
|
+
function assignFullGitSnapshot(entry, status) {
|
|
2063
|
+
if (!status || typeof status !== "object" || Array.isArray(status)) return;
|
|
2064
|
+
entry.git = status;
|
|
2065
|
+
}
|
|
2066
|
+
var COMPACT_DETAILED_NODES_BYTE_BUDGET = 9e3;
|
|
2067
|
+
var COMPACT_NODES_TOTAL_BYTE_BUDGET = 13e3;
|
|
2068
|
+
var COMPACT_MISSIONS_BYTE_BUDGET = 6e3;
|
|
2069
|
+
function extractLaunchPayload(value) {
|
|
2070
|
+
return findNestedPayload(value, (payload) => Boolean(payload?.sessionId || payload?.id || payload?.runtimeSessionId));
|
|
2071
|
+
}
|
|
2072
|
+
function classifyMeshLaunchFailure(error) {
|
|
2073
|
+
const message = error instanceof Error ? error.message : String(error || "launch failed");
|
|
2074
|
+
const lower = message.toLowerCase();
|
|
2075
|
+
const p2pClassification = (0, import_daemon_core2.classifyP2pRelayFailure)(error, { command: "launch_cli" });
|
|
2076
|
+
if (p2pClassification.recoverable) {
|
|
2077
|
+
return p2pClassification;
|
|
2078
|
+
}
|
|
2079
|
+
if (lower.includes("cannot connect to daemon ipc") || lower.includes("daemon ipc command")) {
|
|
2080
|
+
return {
|
|
2081
|
+
code: "local_ipc_unavailable",
|
|
2082
|
+
reason: "local_daemon_ipc_unavailable",
|
|
2083
|
+
transport: "local_ipc",
|
|
2084
|
+
recoverable: true,
|
|
2085
|
+
retryRecommended: true,
|
|
2086
|
+
nextAction: "Check the local daemon IPC connection, then retry mesh_launch_session once after the daemon is reachable."
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
if (lower.includes("timed out") || lower.includes("timeout")) {
|
|
2090
|
+
return {
|
|
2091
|
+
code: "mesh_transport_timeout",
|
|
2092
|
+
reason: "mesh_transport_timeout",
|
|
2093
|
+
transport: "mesh_transport",
|
|
2094
|
+
recoverable: true,
|
|
2095
|
+
retryRecommended: true,
|
|
2096
|
+
nextAction: "Check mesh transport health, then do one bounded retry before requeueing or relaunching the task."
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
return {
|
|
2100
|
+
code: "mesh_launch_failed",
|
|
2101
|
+
reason: "provider_launch_failed",
|
|
2102
|
+
transport: "mesh_transport",
|
|
2103
|
+
recoverable: false,
|
|
2104
|
+
retryRecommended: false,
|
|
2105
|
+
nextAction: "Inspect the provider launch error and fix the underlying provider/configuration issue before retrying."
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
function buildWorktreeCleanupHint(node) {
|
|
2109
|
+
if (!node.isLocalWorktree) return void 0;
|
|
2110
|
+
return {
|
|
2111
|
+
tool: "mesh_remove_node",
|
|
2112
|
+
args: { node_id: node.id, session_cleanup_mode: "preserve" },
|
|
2113
|
+
hint: `If the worktree is no longer needed, remove the orphan worktree node with mesh_remove_node(node_id: "${node.id}").`
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
function buildRecoverableLaunchFailure(ctx, node, providerType, error) {
|
|
2117
|
+
const message = error instanceof Error ? error.message : String(error || "launch failed");
|
|
2118
|
+
const classified = classifyMeshLaunchFailure(error);
|
|
2119
|
+
const cleanup = buildWorktreeCleanupHint(node);
|
|
2120
|
+
return {
|
|
2121
|
+
success: false,
|
|
2122
|
+
recoverable: classified.recoverable,
|
|
2123
|
+
code: classified.code,
|
|
2124
|
+
reason: classified.reason,
|
|
2125
|
+
transport: classified.transport,
|
|
2126
|
+
retryRecommended: classified.retryRecommended,
|
|
2127
|
+
nextAction: classified.nextAction,
|
|
2128
|
+
...classified.noFallbackReason ? { noFallbackReason: classified.noFallbackReason } : {},
|
|
2129
|
+
error: message,
|
|
2130
|
+
meshId: ctx.mesh.id,
|
|
2131
|
+
nodeId: node.id,
|
|
2132
|
+
daemonId: node.daemonId,
|
|
2133
|
+
workspace: node.workspace,
|
|
2134
|
+
isLocalWorktree: node.isLocalWorktree === true,
|
|
2135
|
+
worktreeBranch: node.worktreeBranch,
|
|
2136
|
+
clonedFromNodeId: node.clonedFromNodeId,
|
|
2137
|
+
...providerType ? { resolvedProviderType: providerType } : {},
|
|
2138
|
+
retryHint: `Retry mesh_launch_session(node_id: "${node.id}"${providerType ? `, type: "${providerType}"` : ""}) after daemon mesh transport/P2P is healthy.`,
|
|
2139
|
+
...cleanup ? { cleanup } : {},
|
|
2140
|
+
nextStepHints: [
|
|
2141
|
+
`Retry mesh_launch_session(node_id: "${node.id}"${providerType ? `, type: "${providerType}"` : ""}) after checking daemon/P2P health.`,
|
|
2142
|
+
...cleanup ? [`Cleanup orphan worktree node with mesh_remove_node(node_id: "${node.id}") if retry is not desired.`] : [],
|
|
2143
|
+
"Run mesh_status to see the degraded reason and recovery hints before redispatching work."
|
|
2144
|
+
]
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
function recordRecoverableLaunchFailure(ctx, node, providerType, error) {
|
|
2148
|
+
const failure = buildRecoverableLaunchFailure(ctx, node, providerType, error);
|
|
2149
|
+
try {
|
|
2150
|
+
(0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
|
|
2151
|
+
kind: "recovery_attempted",
|
|
2152
|
+
nodeId: node.id,
|
|
2153
|
+
providerType,
|
|
2154
|
+
payload: {
|
|
2155
|
+
event: "session_launch_failed",
|
|
2156
|
+
...failure
|
|
2157
|
+
}
|
|
2158
|
+
});
|
|
2159
|
+
} catch {
|
|
2160
|
+
}
|
|
2161
|
+
return failure;
|
|
2162
|
+
}
|
|
2163
|
+
function getLatestActiveLaunchFailure(meshId, nodeId) {
|
|
2164
|
+
const entries = (0, import_daemon_core2.readLedgerEntries)(meshId, { tail: 200 });
|
|
2165
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
2166
|
+
const entry = entries[i];
|
|
2167
|
+
if (entry.nodeId !== nodeId) continue;
|
|
2168
|
+
if (entry.kind === "session_launched" || entry.kind === "node_removed") return null;
|
|
2169
|
+
if (entry.kind === "recovery_attempted" && entry.payload?.event === "session_launch_failed") {
|
|
2170
|
+
return { timestamp: entry.timestamp, ...entry.payload };
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
return null;
|
|
2174
|
+
}
|
|
2175
|
+
function buildCoordinatorP2pRelayFailure(error, context) {
|
|
2176
|
+
const payload = (0, import_daemon_core2.buildP2pRelayFailurePayload)(error, {
|
|
2177
|
+
command: context.command,
|
|
2178
|
+
targetDaemonId: context.targetDaemonId
|
|
2179
|
+
});
|
|
2180
|
+
return {
|
|
2181
|
+
...payload,
|
|
2182
|
+
...context.nodeId ? { nodeId: context.nodeId } : {},
|
|
2183
|
+
...context.sessionId ? { sessionId: context.sessionId } : {},
|
|
2184
|
+
retryHint: payload.retryRecommended ? payload.nextAction : "Do not retry as a P2P transport recovery; inspect the command/provider error first."
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
async function ipcDispatchToRemoteAgent(ctx, node, args) {
|
|
2188
|
+
const transport = ctx.transport;
|
|
2189
|
+
const daemonId = node.daemonId;
|
|
2190
|
+
const dispatchCoordinatorDaemonId = readString(args.meshContext?.coordinatorDaemonId) || "";
|
|
2191
|
+
let sessionId = args.session_id?.trim() || "";
|
|
2192
|
+
const providerPriorityList = Array.isArray(node.policy?.providerPriority) ? node.policy.providerPriority : [];
|
|
2193
|
+
let resolvedProviderType = args.providerType?.trim() || providerPriorityList[0] || "";
|
|
2194
|
+
if (sessionId && args.verifiedSession) {
|
|
2195
|
+
const explicitSession = args.verifiedSession;
|
|
2196
|
+
const relaySafety = classifyRemoteDelegateRelaySafety(explicitSession, ctx.mesh.id, node.id, dispatchCoordinatorDaemonId);
|
|
2197
|
+
if (relaySafety === "unsafe_alias") {
|
|
2198
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
2199
|
+
ctx,
|
|
2200
|
+
node,
|
|
2201
|
+
sessionId,
|
|
2202
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
if (relaySafety === "missing_anchor") {
|
|
2206
|
+
return buildMissingCoordinatorDaemonIdFailure(
|
|
2207
|
+
ctx,
|
|
2208
|
+
node,
|
|
2209
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
if (!resolvedProviderType) {
|
|
2213
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2214
|
+
}
|
|
2215
|
+
} else if (!sessionId || args.session_id) {
|
|
2216
|
+
try {
|
|
2217
|
+
const relayResult = await transport.meshCommand(daemonId, "get_status_metadata", {});
|
|
2218
|
+
const sessions = extractStatusMetadataSessions(relayResult);
|
|
2219
|
+
if (sessionId) {
|
|
2220
|
+
const explicitSession = sessions.find((session) => readSessionRecordId(session) === sessionId);
|
|
2221
|
+
if (!explicitSession) {
|
|
2222
|
+
return {
|
|
2223
|
+
success: false,
|
|
2224
|
+
recoverable: true,
|
|
2225
|
+
code: "mesh_target_session_not_found",
|
|
2226
|
+
reason: "mesh_target_session_not_found",
|
|
2227
|
+
transport: "mesh_transport",
|
|
2228
|
+
retryRecommended: true,
|
|
2229
|
+
meshId: ctx.mesh.id,
|
|
2230
|
+
nodeId: node.id,
|
|
2231
|
+
daemonId,
|
|
2232
|
+
workspace: node.workspace,
|
|
2233
|
+
sessionId,
|
|
2234
|
+
...resolvedProviderType ? { resolvedProviderType } : {},
|
|
2235
|
+
error: `Remote session '${sessionId}' is not present in the live status for node '${node.id}'.`,
|
|
2236
|
+
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.`
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
const relaySafety = classifyRemoteDelegateRelaySafety(explicitSession, ctx.mesh.id, node.id, dispatchCoordinatorDaemonId);
|
|
2240
|
+
if (relaySafety === "unsafe_alias") {
|
|
2241
|
+
return buildRelayUnsafeRemoteSessionFailure(
|
|
2242
|
+
ctx,
|
|
2243
|
+
node,
|
|
2244
|
+
sessionId,
|
|
2245
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
if (relaySafety === "missing_anchor") {
|
|
2249
|
+
return buildMissingCoordinatorDaemonIdFailure(
|
|
2250
|
+
ctx,
|
|
2251
|
+
node,
|
|
2252
|
+
resolvedProviderType || resolveSessionProviderType(explicitSession) || void 0
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
2255
|
+
if (!resolvedProviderType) {
|
|
2256
|
+
resolvedProviderType = resolveSessionProviderType(explicitSession);
|
|
2257
|
+
}
|
|
2258
|
+
} else {
|
|
2259
|
+
const targetSession = chooseDispatchableSession(sessions, resolvedProviderType, ctx.mesh.id, node.id, dispatchCoordinatorDaemonId);
|
|
2260
|
+
if (targetSession?.id || targetSession?.sessionId) {
|
|
2261
|
+
sessionId = targetSession.id || targetSession.sessionId;
|
|
2262
|
+
if (!resolvedProviderType) {
|
|
2263
|
+
resolvedProviderType = resolveSessionProviderType(targetSession);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
} catch (e) {
|
|
2268
|
+
if (sessionId) {
|
|
2269
|
+
return {
|
|
2270
|
+
...buildCoordinatorP2pRelayFailure(e, {
|
|
2271
|
+
command: "get_status_metadata",
|
|
2272
|
+
targetDaemonId: daemonId,
|
|
2273
|
+
nodeId: node.id,
|
|
2274
|
+
sessionId
|
|
2275
|
+
}),
|
|
2276
|
+
success: false,
|
|
2277
|
+
error: `Cannot verify remote session '${sessionId}' before dispatch: ${e?.message || String(e)}`
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
if (!resolvedProviderType) {
|
|
2283
|
+
return { success: false, error: `Cannot dispatch to remote node '${node.id}': providerType unknown. Set providerPriority on the node policy or call mesh_launch_session first.` };
|
|
2284
|
+
}
|
|
2285
|
+
try {
|
|
2286
|
+
const dispatchResult = await transport.meshCommand(daemonId, "agent_command", {
|
|
2287
|
+
...sessionId ? { targetSessionId: sessionId } : {},
|
|
2288
|
+
agentType: resolvedProviderType,
|
|
2289
|
+
cliType: resolvedProviderType,
|
|
2290
|
+
action: "send_chat",
|
|
2291
|
+
message: args.message,
|
|
2292
|
+
// WTCLAIM (B): carry the node workspace so a sessionless dispatch can be
|
|
2293
|
+
// scoped to THIS node's session on the worker (findAdapter dir match /
|
|
2294
|
+
// findMeshNodeAdapter). Without it, a worker hosting both a base node and a
|
|
2295
|
+
// cloned worktree node (same daemonId) would fall through to a provider-only
|
|
2296
|
+
// fuzzy match and could land worktree work on the base session.
|
|
2297
|
+
...node.workspace ? { dir: node.workspace } : {},
|
|
2298
|
+
...args.meshContext ? { meshContext: args.meshContext } : {}
|
|
2299
|
+
});
|
|
2300
|
+
const dispatchPayload = unwrapCommandPayload(dispatchResult);
|
|
2301
|
+
if (dispatchPayload?.success === false || dispatchResult?.success === false) {
|
|
2302
|
+
const source = dispatchPayload?.success === false ? dispatchPayload : dispatchResult;
|
|
2303
|
+
const errorMessage = dispatchPayload?.error || dispatchResult?.error || "agent_command rejected the task";
|
|
2304
|
+
return {
|
|
2305
|
+
...buildCoordinatorP2pRelayFailure(source?.error || errorMessage, {
|
|
2306
|
+
command: "agent_command",
|
|
2307
|
+
targetDaemonId: daemonId,
|
|
2308
|
+
nodeId: node.id,
|
|
2309
|
+
sessionId
|
|
2310
|
+
}),
|
|
2311
|
+
...source && typeof source === "object" ? source : {},
|
|
2312
|
+
success: false,
|
|
2313
|
+
error: `P2P dispatch failed: ${errorMessage}`
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
return { success: true, dispatched: true, sessionId: sessionId || "", providerType: resolvedProviderType };
|
|
2317
|
+
} catch (e) {
|
|
2318
|
+
const errorMessage = e?.message || String(e);
|
|
2319
|
+
return {
|
|
2320
|
+
...buildCoordinatorP2pRelayFailure(e, {
|
|
2321
|
+
command: "agent_command",
|
|
2322
|
+
targetDaemonId: daemonId,
|
|
2323
|
+
nodeId: node.id,
|
|
2324
|
+
sessionId
|
|
2325
|
+
}),
|
|
2326
|
+
error: `P2P dispatch failed: ${errorMessage}`
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
function meshSessionCacheKey(nodeId, runtimeSessionId) {
|
|
2331
|
+
return `${nodeId}:${runtimeSessionId}`;
|
|
2332
|
+
}
|
|
2333
|
+
function rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, metadata) {
|
|
2334
|
+
const keyNodeId = readString(nodeId);
|
|
2335
|
+
const keySessionId = readString(runtimeSessionId);
|
|
2336
|
+
if (!keyNodeId || !keySessionId) return;
|
|
2337
|
+
const providerType = readString(metadata.providerType);
|
|
2338
|
+
const providerSessionId = readString(metadata.providerSessionId);
|
|
2339
|
+
if (!providerType && !providerSessionId) return;
|
|
2340
|
+
const existing = getSessionMetadata(meshSessionCacheKey(keyNodeId, keySessionId)) || { providerType: "" };
|
|
2341
|
+
meshSessionProviderMetadata.set(meshSessionCacheKey(keyNodeId, keySessionId), {
|
|
2342
|
+
providerType: providerType || existing.providerType,
|
|
2343
|
+
providerSessionId: providerSessionId || existing.providerSessionId,
|
|
2344
|
+
expiresAt: Date.now() + SESSION_PROVIDER_METADATA_TTL_MS
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
function rememberMeshSessionProviderMetadataFromEvent(event) {
|
|
2348
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : event && typeof event === "object" ? event : {};
|
|
2349
|
+
const nodeId = readString(event?.nodeId) || readString(metadataEvent.nodeId) || readString(metadataEvent.meshNodeId);
|
|
2350
|
+
const sessionId = readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId) || readString(event?.sessionId);
|
|
2351
|
+
rememberMeshSessionProviderMetadata(nodeId, sessionId, {
|
|
2352
|
+
providerType: readString(metadataEvent.providerType) || readString(event?.providerType) || "",
|
|
2353
|
+
providerSessionId: readString(metadataEvent.providerSessionId) || readString(event?.providerSessionId)
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
function resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId) {
|
|
2357
|
+
const entries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 50 });
|
|
2358
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
2359
|
+
const entry = entries[i];
|
|
2360
|
+
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
2361
|
+
const entryNodeId = readString(entry.nodeId) || readString(payload.nodeId) || readString(payload.meshNodeId);
|
|
2362
|
+
if (entryNodeId && entryNodeId !== nodeId) continue;
|
|
2363
|
+
const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
|
|
2364
|
+
if (entrySessionId !== runtimeSessionId) continue;
|
|
2365
|
+
const providerType = readString(entry.providerType) || readString(payload.providerType);
|
|
2366
|
+
const completionDiagnostic = payload.completionDiagnostic && typeof payload.completionDiagnostic === "object" && !Array.isArray(payload.completionDiagnostic) ? payload.completionDiagnostic : {};
|
|
2367
|
+
const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : {};
|
|
2368
|
+
const providerSessionId = readString(payload.providerSessionId) || readString(completionDiagnostic.providerSessionId) || readString(metadataEvent.providerSessionId);
|
|
2369
|
+
if (providerType || providerSessionId) {
|
|
2370
|
+
return { providerType: providerType || "", providerSessionId };
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
return void 0;
|
|
2374
|
+
}
|
|
2375
|
+
function resolveMeshSessionProviderMetadata(ctx, nodeId, runtimeSessionId) {
|
|
2376
|
+
const cached = getSessionMetadata(meshSessionCacheKey(nodeId, runtimeSessionId));
|
|
2377
|
+
if (cached?.providerType || cached?.providerSessionId) return cached;
|
|
2378
|
+
const fromLedger = resolveMeshSessionProviderMetadataFromLedger(ctx, nodeId, runtimeSessionId);
|
|
2379
|
+
if (fromLedger) rememberMeshSessionProviderMetadata(nodeId, runtimeSessionId, fromLedger);
|
|
2380
|
+
return fromLedger;
|
|
2381
|
+
}
|
|
2382
|
+
function countUncommittedChanges(status) {
|
|
2383
|
+
if (typeof status?.uncommittedChanges === "number") return status.uncommittedChanges;
|
|
2384
|
+
const keys = ["staged", "modified", "untracked", "deleted", "renamed"];
|
|
2385
|
+
const counted = keys.reduce((sum, key) => sum + (Number.isFinite(Number(status?.[key])) ? Number(status[key]) : 0), 0);
|
|
2386
|
+
const conflicts = Array.isArray(status?.conflictFiles) ? status.conflictFiles.length : status?.hasConflicts ? 1 : 0;
|
|
2387
|
+
return counted + conflicts;
|
|
2388
|
+
}
|
|
2389
|
+
function isGitStatusDirty(status) {
|
|
2390
|
+
if (typeof status?.isDirty === "boolean") return status.isDirty;
|
|
2391
|
+
if (typeof status?.dirty === "boolean") return status.dirty;
|
|
2392
|
+
if (Array.isArray(status?.submodules) && status.submodules.some((submodule) => submodule?.dirty || submodule?.outOfSync || submodule?.error)) return true;
|
|
2393
|
+
return countUncommittedChanges(status) > 0;
|
|
2394
|
+
}
|
|
2395
|
+
function slimLedgerPayload(payload) {
|
|
2396
|
+
const slim = {};
|
|
2397
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
2398
|
+
if (k === "message" || k === "taskSummary") {
|
|
2399
|
+
slim[k] = typeof v === "string" && v.length > 200 ? v.slice(0, 200) + "\u2026" : v;
|
|
2400
|
+
} else if (k === "evidence" || k === "workerResult" || k === "gitStatus" || k === "validationResults") {
|
|
2401
|
+
} else if (k === "finalSummary") {
|
|
2402
|
+
slim[k] = typeof v === "string" && v.length > 300 ? v.slice(0, 300) + "\u2026" : v;
|
|
2403
|
+
} else if (LARGE_LEDGER_FIELD_KEYS.has(k)) {
|
|
2404
|
+
slim[k] = summarizeLargeLedgerField(k, v);
|
|
2405
|
+
} else {
|
|
2406
|
+
slim[k] = elideLargeNestedValue(k, v);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
return slim;
|
|
2410
|
+
}
|
|
2411
|
+
function readRelatedRepos(node) {
|
|
2412
|
+
const raw = Array.isArray(node.relatedRepos) ? node.relatedRepos : Array.isArray(node.policy?.relatedRepos) ? node.policy.relatedRepos : [];
|
|
2413
|
+
return raw.map((entry) => ({
|
|
2414
|
+
label: typeof entry?.label === "string" ? entry.label.trim() : "",
|
|
2415
|
+
workspace: typeof entry?.workspace === "string" ? entry.workspace.trim() : ""
|
|
2416
|
+
})).filter((entry) => Boolean(entry.label && entry.workspace));
|
|
2417
|
+
}
|
|
2418
|
+
function summarizeRelatedRepoStatus(repo, status) {
|
|
2419
|
+
const dirty = isGitStatusDirty(status);
|
|
2420
|
+
return {
|
|
2421
|
+
label: repo.label,
|
|
2422
|
+
workspace: repo.workspace,
|
|
2423
|
+
isGitRepo: status?.isGitRepo === true,
|
|
2424
|
+
branch: status?.branch ?? null,
|
|
2425
|
+
upstream: status?.upstream ?? null,
|
|
2426
|
+
upstreamStatus: typeof status?.upstreamStatus === "string" ? status.upstreamStatus : status?.upstream ? "unchecked" : "no_upstream",
|
|
2427
|
+
upstreamFetchedAt: Number.isFinite(Number(status?.upstreamFetchedAt)) ? Number(status.upstreamFetchedAt) : null,
|
|
2428
|
+
upstreamFetchError: typeof status?.upstreamFetchError === "string" ? status.upstreamFetchError : null,
|
|
2429
|
+
ahead: Number.isFinite(Number(status?.ahead)) ? Number(status.ahead) : 0,
|
|
2430
|
+
behind: Number.isFinite(Number(status?.behind)) ? Number(status.behind) : 0,
|
|
2431
|
+
dirty,
|
|
2432
|
+
uncommittedChanges: countUncommittedChanges(status),
|
|
2433
|
+
head: status?.headCommit ?? null,
|
|
2434
|
+
lastCommitSummary: status?.headMessage ?? null,
|
|
2435
|
+
...status?.reason ? { reason: status.reason } : {},
|
|
2436
|
+
...status?.error ? { error: status.error } : {}
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
async function collectRelatedRepoStatuses(ctx, node) {
|
|
2440
|
+
const relatedRepos = readRelatedRepos(node);
|
|
2441
|
+
if (!relatedRepos.length) return [];
|
|
2442
|
+
const results = [];
|
|
2443
|
+
for (const repo of relatedRepos) {
|
|
2444
|
+
try {
|
|
2445
|
+
const statusResult = await commandForNode(ctx, node, "git_status", { workspace: repo.workspace, refreshUpstream: true });
|
|
2446
|
+
const status = extractGitStatus(statusResult);
|
|
2447
|
+
results.push(summarizeRelatedRepoStatus(repo, status));
|
|
2448
|
+
} catch (e) {
|
|
2449
|
+
results.push({
|
|
2450
|
+
label: repo.label,
|
|
2451
|
+
workspace: repo.workspace,
|
|
2452
|
+
error: e?.message || "related repo status failed"
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
return results;
|
|
2457
|
+
}
|
|
2458
|
+
function readProviderPriority(policy) {
|
|
2459
|
+
const raw = policy?.providerPriority;
|
|
2460
|
+
return Array.isArray(raw) ? raw.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
|
|
2461
|
+
}
|
|
2462
|
+
function buildNodeCapabilityExposure(node) {
|
|
2463
|
+
const providers = readProviderPriority(node.policy);
|
|
2464
|
+
const capabilityTags = (0, import_daemon_core2.buildMeshNodeCapabilityTags)(node);
|
|
2465
|
+
const exposure = { capabilityTags };
|
|
2466
|
+
if (providers.length) {
|
|
2467
|
+
const byProvider = {};
|
|
2468
|
+
for (const provider of providers) {
|
|
2469
|
+
byProvider[provider] = (0, import_daemon_core2.buildMeshNodeCapabilityTags)(node, provider);
|
|
2470
|
+
}
|
|
2471
|
+
exposure.capabilityTagsByProvider = byProvider;
|
|
2472
|
+
}
|
|
2473
|
+
const capabilities = Array.isArray(node.capabilities) ? node.capabilities.filter((tag) => typeof tag === "string" && !!tag.trim()) : [];
|
|
2474
|
+
if (capabilities.length) exposure.capabilities = capabilities;
|
|
2475
|
+
return exposure;
|
|
2476
|
+
}
|
|
2477
|
+
function readSpawnedSessionVisibility(policy) {
|
|
2478
|
+
return policy?.spawnedSessionVisibility === "hidden" ? "hidden" : "visible";
|
|
2479
|
+
}
|
|
2480
|
+
function missingProviderPriorityMessage(nodeId) {
|
|
2481
|
+
return `Node '${nodeId}' has no providerPriority policy; pass type explicitly or configure node.policy.providerPriority`;
|
|
2482
|
+
}
|
|
2483
|
+
function getNodeLaunchReadiness(node) {
|
|
2484
|
+
const bootstrap = node.worktreeBootstrap;
|
|
2485
|
+
if (node.isLocalWorktree && bootstrap?.status === "failed" && bootstrap?.required !== false) {
|
|
2486
|
+
return {
|
|
2487
|
+
providerPriority: readProviderPriority(node.policy),
|
|
2488
|
+
launchReady: false,
|
|
2489
|
+
launchBlockedReason: "worktree_bootstrap_failed",
|
|
2490
|
+
launchBlockedMessage: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : "Required worktree bootstrap failed; resolve it before launching an agent into this node.",
|
|
2491
|
+
worktreeBootstrap: bootstrap
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
const providerPriority = readProviderPriority(node.policy);
|
|
2495
|
+
if (providerPriority.length) {
|
|
2496
|
+
return {
|
|
2497
|
+
providerPriority,
|
|
2498
|
+
launchReady: true
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
return {
|
|
2502
|
+
providerPriority,
|
|
2503
|
+
launchReady: false,
|
|
2504
|
+
launchBlockedReason: "missing_provider_priority",
|
|
2505
|
+
launchBlockedMessage: missingProviderPriorityMessage(node.id)
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
function getWorktreeBootstrapLaunchBlock(node, meshPolicy) {
|
|
2509
|
+
if (!node.isLocalWorktree) return void 0;
|
|
2510
|
+
const bootstrap = node.worktreeBootstrap;
|
|
2511
|
+
const requireReady = !!(meshPolicy && typeof meshPolicy === "object" && meshPolicy.requireBootstrapBeforeLaunch === true);
|
|
2512
|
+
if (requireReady && bootstrap?.status !== "ready") {
|
|
2513
|
+
return {
|
|
2514
|
+
success: false,
|
|
2515
|
+
code: "bootstrap_not_ready",
|
|
2516
|
+
error: `Node '${node.id}' bootstrap state is '${bootstrap?.status ?? "unknown"}' and mesh policy requireBootstrapBeforeLaunch is enabled.`,
|
|
2517
|
+
nodeId: node.id,
|
|
2518
|
+
worktreeBootstrap: bootstrap ?? null,
|
|
2519
|
+
recoveryHint: "Run the worktree bootstrap (clone runOnClone or a refine with bootstrap inherit) until the node reports ready, or disable requireBootstrapBeforeLaunch."
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
if (bootstrap?.status !== "failed" || bootstrap?.required === false) return void 0;
|
|
2523
|
+
return {
|
|
2524
|
+
success: false,
|
|
2525
|
+
code: "worktree_bootstrap_failed",
|
|
2526
|
+
error: typeof bootstrap.error === "string" && bootstrap.error.trim() ? bootstrap.error.trim() : `Node '${node.id}' has a failed required worktree bootstrap.`,
|
|
2527
|
+
nodeId: node.id,
|
|
2528
|
+
worktreeBootstrap: bootstrap,
|
|
2529
|
+
recoveryHint: "Fix the configured worktree bootstrap command or remove/recreate the worktree node before launching an agent."
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
async function collectLiveStatusSessions(ctx, node) {
|
|
2533
|
+
try {
|
|
2534
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2535
|
+
return extractStatusMetadataSessions(statusResult);
|
|
2536
|
+
} catch {
|
|
2537
|
+
return [];
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
async function collectLiveStatusProbe(ctx, node) {
|
|
2541
|
+
try {
|
|
2542
|
+
const statusResult = await commandForNode(ctx, node, "get_status_metadata", {});
|
|
2543
|
+
return {
|
|
2544
|
+
sessions: extractStatusMetadataSessions(statusResult),
|
|
2545
|
+
daemonBuild: extractDaemonBuildInfo(statusResult)
|
|
2546
|
+
};
|
|
2547
|
+
} catch {
|
|
2548
|
+
return { sessions: [] };
|
|
2563
2549
|
}
|
|
2564
|
-
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2550
|
+
}
|
|
2551
|
+
function extractDaemonBuildInfo(value) {
|
|
2552
|
+
const payload = unwrapCommandPayload(value);
|
|
2553
|
+
const build = payload?.daemonBuild && typeof payload.daemonBuild === "object" ? payload.daemonBuild : value?.daemonBuild && typeof value.daemonBuild === "object" ? value.daemonBuild : void 0;
|
|
2554
|
+
if (!build) return void 0;
|
|
2555
|
+
const commit = readString(build.commit);
|
|
2556
|
+
if (!commit) return void 0;
|
|
2557
|
+
return {
|
|
2558
|
+
commit,
|
|
2559
|
+
commitShort: readString(build.commitShort) || commit.slice(0, 7),
|
|
2560
|
+
version: readString(build.version) || "unknown",
|
|
2561
|
+
...readString(build.builtAt) ? { builtAt: readString(build.builtAt) } : {}
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
async function collectMeshViewQueueNodesWithLiveSessions(ctx) {
|
|
2565
|
+
const nodes = await Promise.all(ctx.mesh.nodes.map(async (node) => {
|
|
2566
|
+
const liveSessions = await collectLiveStatusSessions(ctx, node);
|
|
2567
|
+
return liveSessions.length > 0 ? { ...node, sessions: liveSessions } : node;
|
|
2568
|
+
}));
|
|
2569
|
+
return nodes;
|
|
2570
|
+
}
|
|
2571
|
+
function buildBranchConvergence(mesh, node, status, dirty, uncommittedChanges) {
|
|
2572
|
+
const defaultBranch = readString(mesh.defaultBranch) ?? "main";
|
|
2573
|
+
const branch = readString(status?.branch) ?? readString(node.worktreeBranch) ?? null;
|
|
2574
|
+
const ahead = readNumeric(status?.ahead);
|
|
2575
|
+
const behind = readNumeric(status?.behind);
|
|
2576
|
+
const upstream = readString(status?.upstream) ?? null;
|
|
2577
|
+
const upstreamStatus = readString(status?.upstreamStatus) ?? (upstream ? "unchecked" : "no_upstream");
|
|
2578
|
+
const hasConflicts = status?.hasConflicts === true || Array.isArray(status?.conflictFiles) && status.conflictFiles.length > 0;
|
|
2579
|
+
const base = {
|
|
2580
|
+
defaultBranch,
|
|
2581
|
+
branch,
|
|
2582
|
+
upstream,
|
|
2583
|
+
upstreamStatus,
|
|
2584
|
+
ahead,
|
|
2585
|
+
behind,
|
|
2586
|
+
isWorktree: node.isLocalWorktree === true,
|
|
2587
|
+
isDefaultBranch: branch === defaultBranch
|
|
2588
|
+
};
|
|
2589
|
+
if (status?.isGitRepo !== true) {
|
|
2590
|
+
return {
|
|
2591
|
+
...base,
|
|
2592
|
+
status: "blocked_review",
|
|
2593
|
+
needsConvergence: true,
|
|
2594
|
+
reason: "git_status_unavailable",
|
|
2595
|
+
nextStep: `Resolve git status for node '${node.id}' before marking the task complete.`
|
|
2596
|
+
};
|
|
2576
2597
|
}
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
session_cleanup_mode: {
|
|
2586
|
-
type: "string",
|
|
2587
|
-
enum: ["preserve", "stop", "delete_stopped", "stop_and_delete"],
|
|
2588
|
-
description: "Optional override for cleanup of delegated sessions attached to this node. preserve keeps history/processes; stop stops live runtimes only; delete_stopped removes completed transcripts only; stop_and_delete stops live runtimes and deletes records."
|
|
2589
|
-
},
|
|
2590
|
-
force: { type: "boolean", description: "Override the coordinator-base-node guard. Only set true to intentionally tear down this mesh; the coordinator must then be re-registered/restarted. Worktree nodes never need force." }
|
|
2591
|
-
},
|
|
2592
|
-
required: ["node_id"]
|
|
2598
|
+
if (!branch) {
|
|
2599
|
+
return {
|
|
2600
|
+
...base,
|
|
2601
|
+
status: "blocked_review",
|
|
2602
|
+
needsConvergence: true,
|
|
2603
|
+
reason: "branch_unknown",
|
|
2604
|
+
nextStep: `Inspect node '${node.id}' git branch before deciding whether it is merged to ${defaultBranch}.`
|
|
2605
|
+
};
|
|
2593
2606
|
}
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
mode: {
|
|
2603
|
-
type: "string",
|
|
2604
|
-
enum: ["preserve", "stop", "delete_stopped", "stop_and_delete"],
|
|
2605
|
-
description: "preserve = no-op; stop = release process occupancy by stopping live runtimes; delete_stopped = remove completed/stopped records while leaving live runtimes alone; stop_and_delete = stop live runtimes and delete records."
|
|
2606
|
-
},
|
|
2607
|
-
session_ids: {
|
|
2608
|
-
type: "array",
|
|
2609
|
-
items: { type: "string" },
|
|
2610
|
-
description: "Optional explicit session IDs to limit cleanup to. When omitted, sessions are matched by node/workspace metadata."
|
|
2611
|
-
},
|
|
2612
|
-
dry_run: { type: "boolean", description: "Preview matched/stopped/deleted/skipped session IDs without mutating session-host state." }
|
|
2613
|
-
},
|
|
2614
|
-
required: ["node_id", "mode"]
|
|
2607
|
+
if (hasConflicts || dirty || uncommittedChanges > 0) {
|
|
2608
|
+
return {
|
|
2609
|
+
...base,
|
|
2610
|
+
status: "not_mergeable",
|
|
2611
|
+
needsConvergence: true,
|
|
2612
|
+
reason: hasConflicts ? "conflicts_present" : "dirty_workspace",
|
|
2613
|
+
nextStep: `Commit, checkpoint, or resolve node '${node.id}' before any main convergence step.`
|
|
2614
|
+
};
|
|
2615
2615
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
compact: { type: "boolean", description: "Slim payload for LLM callers. Default true. Truncates long payload strings (message/taskSummary \u2264200, finalSummary \u2264300) and elides any large nested evidence blob (>2KB serialized \u2014 e.g. validationSummary/result/patchEquivalence/submoduleReachability) to a {_elided,_kind,_bytes,_hint} placeholder; full evidence stays accessible via mesh_reconcile_ledger. Set false (or verbose=true) for full untruncated payloads." },
|
|
2626
|
-
verbose: { type: "boolean", description: "Force the full untruncated payload; overrides compact." }
|
|
2616
|
+
if (branch === defaultBranch) {
|
|
2617
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
2618
|
+
return {
|
|
2619
|
+
...base,
|
|
2620
|
+
status: "blocked_review",
|
|
2621
|
+
needsConvergence: true,
|
|
2622
|
+
reason: "default_branch_upstream_unverified",
|
|
2623
|
+
nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${node.id}'.`
|
|
2624
|
+
};
|
|
2627
2625
|
}
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
node_ids: { type: "array", items: { type: "string" }, description: "Optional node IDs to query. Defaults to all mesh nodes." },
|
|
2637
|
-
limit: { type: "number", description: "Bounded slice size per node. Defaults to 100 and is clamped by daemon-core." },
|
|
2638
|
-
after_id: { type: "string", description: "Optional cursor entry ID; remote slices return entries strictly after this ID when present." },
|
|
2639
|
-
since: { type: "string", description: "Optional ISO timestamp lower bound for queried entries." },
|
|
2640
|
-
import_entries: { type: "boolean", description: "When false, query and report evidence without importing remote entries. Defaults true." }
|
|
2626
|
+
if (ahead > 0 || behind > 0) {
|
|
2627
|
+
return {
|
|
2628
|
+
...base,
|
|
2629
|
+
status: "blocked_review",
|
|
2630
|
+
needsConvergence: true,
|
|
2631
|
+
reason: "default_branch_not_even_with_upstream",
|
|
2632
|
+
nextStep: `Bring ${defaultBranch} even with its upstream before declaring convergence complete.`
|
|
2633
|
+
};
|
|
2641
2634
|
}
|
|
2635
|
+
return {
|
|
2636
|
+
...base,
|
|
2637
|
+
status: "merged_to_main",
|
|
2638
|
+
needsConvergence: false,
|
|
2639
|
+
reason: "clean_default_branch",
|
|
2640
|
+
nextStep: null
|
|
2641
|
+
};
|
|
2642
2642
|
}
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
dry_run: { type: "boolean", description: "Force a preview without mutation even if execute=true. Defaults to dry-run behavior when execute is not set." },
|
|
2652
|
-
include_terminal: { type: "boolean", description: "Also prune terminal (completed/failed) direct dispatch store rows in addition to orphans. Defaults false." }
|
|
2653
|
-
}
|
|
2643
|
+
if (node.isLocalWorktree) {
|
|
2644
|
+
return {
|
|
2645
|
+
...base,
|
|
2646
|
+
status: "cleanup_candidate",
|
|
2647
|
+
needsConvergence: true,
|
|
2648
|
+
reason: "clean_non_default_worktree_branch",
|
|
2649
|
+
nextStep: `Run mesh_refine_node(node_id: "${node.id}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`
|
|
2650
|
+
};
|
|
2654
2651
|
}
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
execute: { type: "boolean", description: "When true, run validation/merge/push/cleanup for this node. Defaults false/dry-run." },
|
|
2664
|
-
dry_run: { type: "boolean", description: "Preview the validation plan without merging. Defaults true unless execute=true; dry_run=true overrides execute." }
|
|
2665
|
-
},
|
|
2666
|
-
required: ["node_id"]
|
|
2652
|
+
if (upstream && upstreamStatus !== "fresh") {
|
|
2653
|
+
return {
|
|
2654
|
+
...base,
|
|
2655
|
+
status: "blocked_review",
|
|
2656
|
+
needsConvergence: true,
|
|
2657
|
+
reason: "feature_branch_upstream_unverified",
|
|
2658
|
+
nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`
|
|
2659
|
+
};
|
|
2667
2660
|
}
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
type: "array",
|
|
2677
|
-
items: { type: "string" },
|
|
2678
|
-
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."
|
|
2679
|
-
},
|
|
2680
|
-
execute: { type: "boolean", description: "When true, run validation/rebase/merge for each node in order. Defaults false/dry-run." },
|
|
2681
|
-
dry_run: { type: "boolean", description: "Preview the ordering + per-node validation plan without executing. Defaults true unless execute=true; dry_run=true overrides execute." }
|
|
2682
|
-
},
|
|
2683
|
-
required: []
|
|
2661
|
+
if (!upstream || ahead > 0 || behind > 0) {
|
|
2662
|
+
return {
|
|
2663
|
+
...base,
|
|
2664
|
+
status: "blocked_review",
|
|
2665
|
+
needsConvergence: true,
|
|
2666
|
+
reason: !upstream ? "feature_branch_missing_upstream" : "feature_branch_not_even_with_upstream",
|
|
2667
|
+
nextStep: `Push or reconcile branch '${branch}', then merge it into ${defaultBranch} or mark it not_mergeable with a reason.`
|
|
2668
|
+
};
|
|
2684
2669
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
};
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
}
|
|
2670
|
+
return {
|
|
2671
|
+
...base,
|
|
2672
|
+
status: "pushed_feature_branch_needs_merge",
|
|
2673
|
+
needsConvergence: true,
|
|
2674
|
+
reason: "clean_non_default_branch",
|
|
2675
|
+
nextStep: `Review and merge branch '${branch}' into ${defaultBranch}; do not report the task as fully complete while it remains off main.`
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
var COMPACT_MAX_CONVERGENCE_FOLLOWUPS = 12;
|
|
2679
|
+
function summarizeBranchConvergence(nodes, compact = false) {
|
|
2680
|
+
const allFollowUps = nodes.filter((node) => node?.branchConvergence?.needsConvergence === true).map((node) => ({
|
|
2681
|
+
nodeId: node.nodeId,
|
|
2682
|
+
// workspace is a long absolute path redundant with nodeId — drop it in
|
|
2683
|
+
// compact mode to keep this summary bounded.
|
|
2684
|
+
...compact ? {} : { workspace: node.workspace },
|
|
2685
|
+
branch: node.branchConvergence.branch,
|
|
2686
|
+
status: node.branchConvergence.status,
|
|
2687
|
+
reason: node.branchConvergence.reason,
|
|
2688
|
+
// The per-node nextStep is long prose that repeats node ids/branch names.
|
|
2689
|
+
// In compact mode drop it (the status+reason carry the actionable signal;
|
|
2690
|
+
// verbose still surfaces the full nextStep) so this summary stays bounded
|
|
2691
|
+
// as node count grows.
|
|
2692
|
+
...compact ? {} : { nextStep: node.branchConvergence.nextStep }
|
|
2693
|
+
}));
|
|
2694
|
+
const byStatus = {};
|
|
2695
|
+
for (const f of allFollowUps) {
|
|
2696
|
+
const s = typeof f.status === "string" ? f.status : "unknown";
|
|
2697
|
+
byStatus[s] = (byStatus[s] ?? 0) + 1;
|
|
2700
2698
|
}
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
}
|
|
2699
|
+
const followUps = compact ? allFollowUps.slice(0, COMPACT_MAX_CONVERGENCE_FOLLOWUPS) : allFollowUps;
|
|
2700
|
+
const omitted = allFollowUps.length - followUps.length;
|
|
2701
|
+
return {
|
|
2702
|
+
needsFollowUp: allFollowUps.length > 0,
|
|
2703
|
+
unresolvedCount: allFollowUps.length,
|
|
2704
|
+
byStatus,
|
|
2705
|
+
requiredFinalStates: ["merged_to_main", "pushed_feature_branch_needs_merge", "blocked_review", "cleanup_candidate", "not_mergeable"],
|
|
2706
|
+
followUps,
|
|
2707
|
+
...omitted > 0 ? { followUpsOmitted: omitted, followUpsHint: "Per-node followUp rows are capped in compact mode; counts above are complete. Use verbose=true for the full list." } : {}
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
async function commandForNode(ctx, node, command, args = {}) {
|
|
2711
|
+
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
2712
|
+
if (ctx.transport instanceof IpcTransport && node.daemonId && !isLocalNode) {
|
|
2713
|
+
return ctx.transport.meshCommand(node.daemonId, command, args);
|
|
2710
2714
|
}
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2715
|
+
return ctx.transport.command(command, args);
|
|
2716
|
+
}
|
|
2717
|
+
function normalizePendingMeshCoordinatorEvents(value) {
|
|
2718
|
+
const payload = unwrapCommandPayload(value);
|
|
2719
|
+
const events = Array.isArray(payload?.events) ? payload.events : Array.isArray(value?.events) ? value.events : [];
|
|
2720
|
+
return events.filter((event) => event && typeof event === "object");
|
|
2721
|
+
}
|
|
2722
|
+
function buildMeshForwardPayloadFromPendingEvent(event) {
|
|
2723
|
+
const metadataEvent = event?.metadataEvent && typeof event.metadataEvent === "object" ? event.metadataEvent : {};
|
|
2724
|
+
return {
|
|
2725
|
+
event: readString(event?.event),
|
|
2726
|
+
meshId: readString(event?.meshId),
|
|
2727
|
+
nodeId: readString(event?.nodeId) || readString(metadataEvent.meshNodeId),
|
|
2728
|
+
workspace: readString(event?.workspace) || readString(metadataEvent.workspace),
|
|
2729
|
+
targetSessionId: readString(metadataEvent.targetSessionId) || readString(metadataEvent.sessionId) || readString(metadataEvent.instanceId),
|
|
2730
|
+
providerType: readString(metadataEvent.providerType),
|
|
2731
|
+
providerSessionId: readString(metadataEvent.providerSessionId),
|
|
2732
|
+
finalSummary: readString(metadataEvent.finalSummary) || readString(metadataEvent.summary),
|
|
2733
|
+
jobId: readString(metadataEvent.jobId),
|
|
2734
|
+
interactionId: readString(metadataEvent.interactionId),
|
|
2735
|
+
status: readString(metadataEvent.status),
|
|
2736
|
+
targetDaemonId: readString(metadataEvent.targetDaemonId),
|
|
2737
|
+
startedAt: readString(metadataEvent.startedAt),
|
|
2738
|
+
completedAt: readString(metadataEvent.completedAt),
|
|
2739
|
+
retryOfJobId: readString(metadataEvent.retryOfJobId),
|
|
2740
|
+
...metadataEvent.result && typeof metadataEvent.result === "object" && !Array.isArray(metadataEvent.result) ? { result: metadataEvent.result } : {},
|
|
2741
|
+
...metadataEvent.intentional === true ? { intentional: true } : {},
|
|
2742
|
+
...metadataEvent.intentionalStop === true ? { intentionalStop: true } : {},
|
|
2743
|
+
...metadataEvent.operatorCleanup === true ? { operatorCleanup: true } : {},
|
|
2744
|
+
...readString(metadataEvent.reason) ? { reason: readString(metadataEvent.reason) } : {},
|
|
2745
|
+
...readString(metadataEvent.stopReason) ? { stopReason: readString(metadataEvent.stopReason) } : {},
|
|
2746
|
+
...readString(metadataEvent.cleanupReason) ? { cleanupReason: readString(metadataEvent.cleanupReason) } : {},
|
|
2747
|
+
...readString(metadataEvent.source) ? { source: readString(metadataEvent.source) } : {}
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
async function drainCoordinatorPendingEvents(ctx, opts) {
|
|
2751
|
+
const requestedNodeIds = opts?.nodeIds?.length ? new Set(opts.nodeIds) : null;
|
|
2752
|
+
const matchesCurrentMesh = (event) => readString(event?.meshId) === ctx.mesh.id;
|
|
2753
|
+
if (ctx.transport instanceof IpcTransport) {
|
|
2754
|
+
const surfacedEvents = [];
|
|
2755
|
+
const coordinatorDaemonId = readString(ctx.localDaemonId);
|
|
2756
|
+
const pendingEventArgs = {
|
|
2757
|
+
meshId: ctx.mesh.id,
|
|
2758
|
+
...coordinatorDaemonId ? { coordinatorDaemonId } : {}
|
|
2759
|
+
};
|
|
2760
|
+
try {
|
|
2761
|
+
const localEvents = normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh);
|
|
2762
|
+
for (const event of localEvents) {
|
|
2763
|
+
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
2764
|
+
if (!payload.event || !payload.meshId) continue;
|
|
2765
|
+
let injected = false;
|
|
2766
|
+
try {
|
|
2767
|
+
await ctx.transport.command("mesh_forward_event", payload);
|
|
2768
|
+
injected = true;
|
|
2769
|
+
} catch {
|
|
2770
|
+
}
|
|
2771
|
+
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
2772
|
+
if (!injected) surfacedEvents.push(event);
|
|
2773
|
+
}
|
|
2774
|
+
} catch {
|
|
2725
2775
|
}
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2776
|
+
for (const node of ctx.mesh.nodes) {
|
|
2777
|
+
if (!node.daemonId || isLocalControlPlaneNode(ctx, node)) continue;
|
|
2778
|
+
if (requestedNodeIds && !requestedNodeIds.has(node.id)) continue;
|
|
2779
|
+
try {
|
|
2780
|
+
const remoteEvents = normalizePendingMeshCoordinatorEvents(
|
|
2781
|
+
await ctx.transport.meshCommand(node.daemonId, "get_pending_mesh_events", pendingEventArgs)
|
|
2782
|
+
).filter(matchesCurrentMesh);
|
|
2783
|
+
if (remoteEvents.length === 0) continue;
|
|
2784
|
+
for (const event of remoteEvents) {
|
|
2785
|
+
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
2786
|
+
if (!payload.event || !payload.meshId) continue;
|
|
2787
|
+
await ctx.transport.command("mesh_forward_event", payload);
|
|
2788
|
+
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
2789
|
+
}
|
|
2790
|
+
} catch {
|
|
2791
|
+
}
|
|
2735
2792
|
}
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2793
|
+
try {
|
|
2794
|
+
const localEvents = normalizePendingMeshCoordinatorEvents(await ctx.transport.command("get_pending_mesh_events", pendingEventArgs)).filter(matchesCurrentMesh);
|
|
2795
|
+
for (const event of localEvents) {
|
|
2796
|
+
const payload = buildMeshForwardPayloadFromPendingEvent(event);
|
|
2797
|
+
if (!payload.event || !payload.meshId) continue;
|
|
2798
|
+
let injected = false;
|
|
2799
|
+
try {
|
|
2800
|
+
await ctx.transport.command("mesh_forward_event", payload);
|
|
2801
|
+
injected = true;
|
|
2802
|
+
} catch {
|
|
2803
|
+
}
|
|
2804
|
+
rememberMeshSessionProviderMetadataFromEvent({ ...event, metadataEvent: payload });
|
|
2805
|
+
if (!injected) surfacedEvents.push(event);
|
|
2806
|
+
}
|
|
2807
|
+
} catch {
|
|
2747
2808
|
}
|
|
2809
|
+
return surfacedEvents;
|
|
2748
2810
|
}
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
type: "object",
|
|
2766
|
-
properties: {
|
|
2767
|
-
mesh_id: { type: "string", description: "Mesh ID (optional \u2014 inferred from active mesh if omitted)." }
|
|
2768
|
-
},
|
|
2769
|
-
required: []
|
|
2770
|
-
}
|
|
2771
|
-
};
|
|
2772
|
-
var ALL_MESH_TOOLS = [
|
|
2773
|
-
MESH_STATUS_TOOL,
|
|
2774
|
-
MESH_LIST_NODES_TOOL,
|
|
2775
|
-
MESH_ENQUEUE_TASK_TOOL,
|
|
2776
|
-
MESH_VIEW_QUEUE_TOOL,
|
|
2777
|
-
MESH_QUEUE_CANCEL_TOOL,
|
|
2778
|
-
MESH_QUEUE_REQUEUE_TOOL,
|
|
2779
|
-
MESH_SEND_TASK_TOOL,
|
|
2780
|
-
MESH_READ_CHAT_TOOL,
|
|
2781
|
-
MESH_READ_DEBUG_TOOL,
|
|
2782
|
-
MESH_LAUNCH_SESSION_TOOL,
|
|
2783
|
-
MESH_GIT_STATUS_TOOL,
|
|
2784
|
-
MESH_READ_NODE_LOGS_TOOL,
|
|
2785
|
-
MESH_FAST_FORWARD_NODE_TOOL,
|
|
2786
|
-
MESH_RESTART_DAEMON_TOOL,
|
|
2787
|
-
MESH_CHECKPOINT_TOOL,
|
|
2788
|
-
MESH_APPROVE_TOOL,
|
|
2789
|
-
MESH_CLONE_NODE_TOOL,
|
|
2790
|
-
MESH_REMOVE_NODE_TOOL,
|
|
2791
|
-
MESH_REFINE_NODE_TOOL,
|
|
2792
|
-
MESH_REFINE_BATCH_TOOL,
|
|
2793
|
-
MESH_REFINE_CONFIG_SCHEMA_TOOL,
|
|
2794
|
-
MESH_VALIDATE_REFINE_CONFIG_TOOL,
|
|
2795
|
-
MESH_SUGGEST_REFINE_CONFIG_TOOL,
|
|
2796
|
-
MESH_CHANGE_IMPACT_CONFIG_SCHEMA_TOOL,
|
|
2797
|
-
MESH_VALIDATE_CHANGE_IMPACT_CONFIG_TOOL,
|
|
2798
|
-
MESH_SUGGEST_CHANGE_IMPACT_CONFIG_TOOL,
|
|
2799
|
-
MESH_INIT_TOOL,
|
|
2800
|
-
MESH_REFINE_PLAN_TOOL,
|
|
2801
|
-
MESH_CLEANUP_SESSIONS_TOOL,
|
|
2802
|
-
MESH_PRUNE_STALE_DIRECT_TOOL,
|
|
2803
|
-
MESH_TASK_HISTORY_TOOL,
|
|
2804
|
-
MESH_RECONCILE_LEDGER_TOOL,
|
|
2805
|
-
MESH_MISSION_UPSERT_TOOL,
|
|
2806
|
-
MESH_MISSION_LIST_TOOL,
|
|
2807
|
-
MESH_REVIEW_INBOX_TOOL
|
|
2808
|
-
];
|
|
2811
|
+
const events = (0, import_daemon_core2.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId).filter(matchesCurrentMesh);
|
|
2812
|
+
events.forEach(rememberMeshSessionProviderMetadataFromEvent);
|
|
2813
|
+
return events;
|
|
2814
|
+
}
|
|
2815
|
+
function isP2pTransportUnavailableError(error) {
|
|
2816
|
+
return (0, import_daemon_core2.isP2pRelayTransportFailure)(error);
|
|
2817
|
+
}
|
|
2818
|
+
function buildRemoveNodeArgs(ctx, nodeId, sessionCleanupMode, force) {
|
|
2819
|
+
return {
|
|
2820
|
+
meshId: ctx.mesh.id,
|
|
2821
|
+
nodeId,
|
|
2822
|
+
...sessionCleanupMode ? { sessionCleanupMode } : {},
|
|
2823
|
+
...force === true ? { force: true } : {},
|
|
2824
|
+
inlineMesh: ctx.mesh
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2809
2827
|
async function meshStatus(ctx, args = {}) {
|
|
2810
|
-
const rateResult = (0,
|
|
2828
|
+
const rateResult = (0, import_daemon_core2.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_status" });
|
|
2811
2829
|
const compact = args.verbose === true ? false : args.compact ?? true;
|
|
2812
2830
|
await refreshMeshFromDaemon(ctx);
|
|
2813
2831
|
const { mesh, transport } = ctx;
|
|
2814
|
-
let ledgerSummary = (0,
|
|
2832
|
+
let ledgerSummary = (0, import_daemon_core2.getLedgerSummary)(mesh.id);
|
|
2815
2833
|
const results = await Promise.all(mesh.nodes.map(async (node) => {
|
|
2816
2834
|
const entry = {
|
|
2817
2835
|
nodeId: node.id,
|
|
@@ -2867,24 +2885,14 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2867
2885
|
noFallbackReason: failure.noFallbackReason
|
|
2868
2886
|
});
|
|
2869
2887
|
}
|
|
2870
|
-
|
|
2871
|
-
const freshnessStatus = {
|
|
2888
|
+
entry.dataFreshness = (0, import_daemon_core2.buildMeshNodeProbeFreshness)({
|
|
2872
2889
|
git: entry.git,
|
|
2873
|
-
connection: { state: liveTruthProbed ? "connected" : "disconnected" }
|
|
2874
|
-
};
|
|
2875
|
-
if (liveTruthProbed) freshnessStatus[import_daemon_core.MESH_NODE_LIVE_TRUTH_MARKER] = true;
|
|
2876
|
-
entry.dataFreshness = (0, import_daemon_core.buildMeshNodeDataFreshness)({
|
|
2877
|
-
status: freshnessStatus,
|
|
2878
|
-
node,
|
|
2879
|
-
isSelfNode: entry.machine?.sameMachine === true,
|
|
2880
|
-
daemonId: freshnessDaemonId,
|
|
2881
2890
|
liveTruthProbed,
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
directTruthUnavailable: !liveTruthProbed && !!freshnessDaemonId
|
|
2891
|
+
isSelfNode: entry.machine?.sameMachine === true,
|
|
2892
|
+
daemonId: readNodeDaemonId(node),
|
|
2893
|
+
node
|
|
2886
2894
|
});
|
|
2887
|
-
const recoveryContext = (0,
|
|
2895
|
+
const recoveryContext = (0, import_daemon_core2.getSessionRecoveryContext)(mesh.id, { nodeId: node.id });
|
|
2888
2896
|
if (recoveryContext.consecutiveNodeFailures > 0) {
|
|
2889
2897
|
entry.recoveryHints = {
|
|
2890
2898
|
consecutiveFailures: recoveryContext.consecutiveNodeFailures,
|
|
@@ -2956,23 +2964,23 @@ async function meshStatus(ctx, args = {}) {
|
|
|
2956
2964
|
}
|
|
2957
2965
|
return entry;
|
|
2958
2966
|
}));
|
|
2959
|
-
let ledgerEntries = (0,
|
|
2960
|
-
let directDispatches = (0,
|
|
2967
|
+
let ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(mesh.id, { tail: 200 });
|
|
2968
|
+
let directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(mesh.id);
|
|
2961
2969
|
const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, results, directDispatches, ledgerEntries);
|
|
2962
2970
|
if (directReconciliation.reconciled > 0) {
|
|
2963
|
-
ledgerEntries = (0,
|
|
2964
|
-
directDispatches = (0,
|
|
2965
|
-
ledgerSummary = (0,
|
|
2971
|
+
ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(mesh.id, { tail: 200 });
|
|
2972
|
+
directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(mesh.id);
|
|
2973
|
+
ledgerSummary = (0, import_daemon_core2.getLedgerSummary)(mesh.id);
|
|
2966
2974
|
}
|
|
2967
|
-
const activeWorkEvidence = (0,
|
|
2975
|
+
const activeWorkEvidence = (0, import_daemon_core2.buildMeshActiveWork)({
|
|
2968
2976
|
meshId: mesh.id,
|
|
2969
|
-
queue: (0,
|
|
2977
|
+
queue: (0, import_daemon_core2.getQueue)(mesh.id),
|
|
2970
2978
|
ledgerEntries,
|
|
2971
2979
|
directDispatches,
|
|
2972
2980
|
nodes: results
|
|
2973
2981
|
});
|
|
2974
2982
|
const pollingGuidance = buildActiveWorkPollingGuidance(activeWorkEvidence.summary);
|
|
2975
|
-
const staleDirectWorkSummary = (0,
|
|
2983
|
+
const staleDirectWorkSummary = (0, import_daemon_core2.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
|
|
2976
2984
|
note: activeWorkEvidence.staleDirectWorkNote,
|
|
2977
2985
|
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."
|
|
2978
2986
|
});
|
|
@@ -3160,7 +3168,7 @@ async function meshStatus(ctx, args = {}) {
|
|
|
3160
3168
|
}
|
|
3161
3169
|
try {
|
|
3162
3170
|
if (compact) {
|
|
3163
|
-
const { live, historyFold } = (0,
|
|
3171
|
+
const { live, historyFold } = (0, import_daemon_core2.getMeshStatusMissionsCompact)(mesh.id);
|
|
3164
3172
|
const ranked = [...live].sort((a, b) => String(b.tasks?.lastActivityAt ?? "").localeCompare(String(a.tasks?.lastActivityAt ?? "")));
|
|
3165
3173
|
const kept = [];
|
|
3166
3174
|
const overflow = [];
|
|
@@ -3187,11 +3195,11 @@ async function meshStatus(ctx, args = {}) {
|
|
|
3187
3195
|
}
|
|
3188
3196
|
if (historyFold) response.missionsHistory = historyFold;
|
|
3189
3197
|
} else {
|
|
3190
|
-
const missions = (0,
|
|
3198
|
+
const missions = (0, import_daemon_core2.getMeshStatusMissionSummaries)(mesh.id, { verbose: true });
|
|
3191
3199
|
if (missions.length > 0) {
|
|
3192
3200
|
response.missions = missions.map((mission) => {
|
|
3193
3201
|
try {
|
|
3194
|
-
return { ...mission, stats: (0,
|
|
3202
|
+
return { ...mission, stats: (0, import_daemon_core2.computeMeshMissionStats)(mesh.id, mission.id) };
|
|
3195
3203
|
} catch {
|
|
3196
3204
|
return mission;
|
|
3197
3205
|
}
|
|
@@ -3202,14 +3210,14 @@ async function meshStatus(ctx, args = {}) {
|
|
|
3202
3210
|
}
|
|
3203
3211
|
try {
|
|
3204
3212
|
const pendingEvents = await drainCoordinatorPendingEvents(ctx);
|
|
3205
|
-
const asyncRefineJobs = (0,
|
|
3213
|
+
const asyncRefineJobs = (0, import_daemon_core2.buildMeshAsyncRefineJobs)({
|
|
3206
3214
|
meshId: mesh.id,
|
|
3207
3215
|
ledgerEntries,
|
|
3208
3216
|
pendingEvents
|
|
3209
3217
|
});
|
|
3210
3218
|
if (asyncRefineJobs.length > 0) {
|
|
3211
3219
|
if (compact) {
|
|
3212
|
-
const summary = (0,
|
|
3220
|
+
const summary = (0, import_daemon_core2.summarizeMeshAsyncRefineJobs)(asyncRefineJobs);
|
|
3213
3221
|
if (summary.activeJobs.length > 0) response.asyncRefineJobs = summary.activeJobs;
|
|
3214
3222
|
response.asyncRefineJobsSummary = {
|
|
3215
3223
|
total: summary.total,
|
|
@@ -3235,17 +3243,17 @@ async function meshTaskHistory(ctx, args) {
|
|
|
3235
3243
|
const compactCap = requestedTail > 50 ? 20 : 30;
|
|
3236
3244
|
const tail = compact ? Math.min(requestedTail, compactCap) : Math.min(requestedTail, 200);
|
|
3237
3245
|
const kind = typeof args.kind === "string" && args.kind.trim() ? [args.kind.trim()] : void 0;
|
|
3238
|
-
const rawEntries = (0,
|
|
3246
|
+
const rawEntries = (0, import_daemon_core2.readLedgerEntries)(mesh.id, { tail, kind });
|
|
3239
3247
|
const entries = compact ? rawEntries.map((e) => ({
|
|
3240
3248
|
...e,
|
|
3241
3249
|
payload: e.payload ? slimLedgerPayload(e.payload) : e.payload
|
|
3242
3250
|
})) : rawEntries;
|
|
3243
|
-
const summary = (0,
|
|
3251
|
+
const summary = (0, import_daemon_core2.getLedgerSummary)(mesh.id);
|
|
3244
3252
|
let taskStats;
|
|
3245
3253
|
try {
|
|
3246
3254
|
const taskIds = [...new Set(rawEntries.map((e) => typeof e.payload?.taskId === "string" ? e.payload.taskId : "").filter(Boolean))];
|
|
3247
3255
|
if (taskIds.length > 0) {
|
|
3248
|
-
const stats = (0,
|
|
3256
|
+
const stats = (0, import_daemon_core2.computeMeshTaskStats)(mesh.id, { taskIds });
|
|
3249
3257
|
if (stats.length > 0) taskStats = stats;
|
|
3250
3258
|
}
|
|
3251
3259
|
} catch {
|
|
@@ -3274,8 +3282,8 @@ async function meshReconcileLedger(ctx, args) {
|
|
|
3274
3282
|
for (const node of nodes) {
|
|
3275
3283
|
try {
|
|
3276
3284
|
if (isLocalControlPlaneNode(ctx, node) || !node.daemonId) {
|
|
3277
|
-
const slice2 = (0,
|
|
3278
|
-
replicas.push((0,
|
|
3285
|
+
const slice2 = (0, import_daemon_core2.readLedgerSliceFromStore)(ctx.mesh.id, queryArgs);
|
|
3286
|
+
replicas.push((0, import_daemon_core2.buildMeshLedgerReplicaEvidence)({
|
|
3279
3287
|
nodeId: node.id,
|
|
3280
3288
|
daemonId: node.daemonId,
|
|
3281
3289
|
transport: "local",
|
|
@@ -3293,8 +3301,8 @@ async function meshReconcileLedger(ctx, args) {
|
|
|
3293
3301
|
if (slice?.protocol !== "adhdev.mesh.ledger.slice.v1" || !Array.isArray(slice.entries)) {
|
|
3294
3302
|
throw new Error("remote daemon returned an invalid ledger slice payload");
|
|
3295
3303
|
}
|
|
3296
|
-
const importResult = shouldImport ? (0,
|
|
3297
|
-
replicas.push((0,
|
|
3304
|
+
const importResult = shouldImport ? (0, import_daemon_core2.appendRemoteLedgerEntries)(ctx.mesh.id, slice.entries) : { accepted: 0, skippedDuplicate: 0, rejectedInvalid: 0, entries: [] };
|
|
3305
|
+
replicas.push((0, import_daemon_core2.buildMeshLedgerReplicaEvidence)({
|
|
3298
3306
|
nodeId: node.id,
|
|
3299
3307
|
daemonId: node.daemonId,
|
|
3300
3308
|
transport: "p2p_datachannel",
|
|
@@ -3302,7 +3310,7 @@ async function meshReconcileLedger(ctx, args) {
|
|
|
3302
3310
|
importResult
|
|
3303
3311
|
}));
|
|
3304
3312
|
if (shouldImport && importResult.accepted > 0) {
|
|
3305
|
-
(0,
|
|
3313
|
+
(0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
|
|
3306
3314
|
kind: "ledger_replicated",
|
|
3307
3315
|
nodeId: node.id,
|
|
3308
3316
|
payload: {
|
|
@@ -3316,7 +3324,7 @@ async function meshReconcileLedger(ctx, args) {
|
|
|
3316
3324
|
});
|
|
3317
3325
|
}
|
|
3318
3326
|
} catch (e) {
|
|
3319
|
-
replicas.push((0,
|
|
3327
|
+
replicas.push((0, import_daemon_core2.buildMeshLedgerReplicaEvidence)({
|
|
3320
3328
|
nodeId: node.id,
|
|
3321
3329
|
daemonId: node.daemonId,
|
|
3322
3330
|
transport: node.daemonId ? "p2p_datachannel" : "local",
|
|
@@ -3325,8 +3333,8 @@ async function meshReconcileLedger(ctx, args) {
|
|
|
3325
3333
|
}));
|
|
3326
3334
|
}
|
|
3327
3335
|
}
|
|
3328
|
-
const evidence = (0,
|
|
3329
|
-
(0,
|
|
3336
|
+
const evidence = (0, import_daemon_core2.buildMeshLedgerReconciliationEvidence)(ctx.mesh.id, replicas);
|
|
3337
|
+
(0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
|
|
3330
3338
|
kind: "ledger_reconciled",
|
|
3331
3339
|
payload: {
|
|
3332
3340
|
protocol: evidence.protocol,
|
|
@@ -3342,11 +3350,11 @@ async function meshPruneStaleDirect(ctx, args = {}) {
|
|
|
3342
3350
|
const execute = args.execute === true && args.dry_run !== true;
|
|
3343
3351
|
const includeTerminal = args.include_terminal === true;
|
|
3344
3352
|
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
3345
|
-
const ledgerEntries = (0,
|
|
3346
|
-
const directDispatches = (0,
|
|
3347
|
-
const result = (0,
|
|
3353
|
+
const ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 500 });
|
|
3354
|
+
const directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(ctx.mesh.id);
|
|
3355
|
+
const result = (0, import_daemon_core2.pruneStaleDirectDispatches)({
|
|
3348
3356
|
meshId: ctx.mesh.id,
|
|
3349
|
-
queue: (0,
|
|
3357
|
+
queue: (0, import_daemon_core2.getQueue)(ctx.mesh.id),
|
|
3350
3358
|
ledgerEntries,
|
|
3351
3359
|
directDispatches,
|
|
3352
3360
|
nodes: liveNodes,
|
|
@@ -3409,7 +3417,7 @@ async function meshListNodes(ctx) {
|
|
|
3409
3417
|
}
|
|
3410
3418
|
async function meshMissionUpsert(ctx, args) {
|
|
3411
3419
|
try {
|
|
3412
|
-
const mission = (0,
|
|
3420
|
+
const mission = (0, import_daemon_core2.upsertMeshMission)(ctx.mesh.id, {
|
|
3413
3421
|
id: readString(args.mission_id) || readString(args.missionId) || void 0,
|
|
3414
3422
|
title: args.title,
|
|
3415
3423
|
goal: typeof args.goal === "string" ? args.goal : void 0,
|
|
@@ -3429,21 +3437,21 @@ async function meshMissionUpsert(ctx, args) {
|
|
|
3429
3437
|
async function meshMissionList(ctx, args = {}) {
|
|
3430
3438
|
try {
|
|
3431
3439
|
const rawStatuses = Array.isArray(args.status) ? args.status : typeof args.status === "string" && args.status.trim() ? [args.status] : [];
|
|
3432
|
-
const invalid = rawStatuses.filter((s) => !
|
|
3440
|
+
const invalid = rawStatuses.filter((s) => !import_daemon_core2.MESH_MISSION_STATUSES.includes(s));
|
|
3433
3441
|
if (invalid.length > 0) {
|
|
3434
3442
|
return JSON.stringify({
|
|
3435
3443
|
success: false,
|
|
3436
3444
|
code: "invalid_mission_status",
|
|
3437
|
-
error: `invalid status filter: ${invalid.join(", ")} (valid: ${
|
|
3445
|
+
error: `invalid status filter: ${invalid.join(", ")} (valid: ${import_daemon_core2.MESH_MISSION_STATUSES.join(", ")})`
|
|
3438
3446
|
});
|
|
3439
3447
|
}
|
|
3440
3448
|
const statuses = rawStatuses.length > 0 ? rawStatuses : void 0;
|
|
3441
|
-
const missions = (0,
|
|
3449
|
+
const missions = (0, import_daemon_core2.listMeshMissionSummaries)(ctx.mesh.id, {
|
|
3442
3450
|
statuses,
|
|
3443
3451
|
verbose: args.verbose === true
|
|
3444
3452
|
}).map((mission) => {
|
|
3445
3453
|
try {
|
|
3446
|
-
return { ...mission, stats: (0,
|
|
3454
|
+
return { ...mission, stats: (0, import_daemon_core2.computeMeshMissionStats)(ctx.mesh.id, mission.id) };
|
|
3447
3455
|
} catch {
|
|
3448
3456
|
return mission;
|
|
3449
3457
|
}
|
|
@@ -3460,14 +3468,14 @@ async function meshMissionList(ctx, args = {}) {
|
|
|
3460
3468
|
}
|
|
3461
3469
|
async function meshEnqueueTask(ctx, args) {
|
|
3462
3470
|
const taskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
3463
|
-
const requiredTags = (0,
|
|
3471
|
+
const requiredTags = (0, import_daemon_core2.normalizeMeshCapabilityTags)(Array.isArray(args.requiredTags) ? args.requiredTags : args.required_tags);
|
|
3464
3472
|
const dependsOn = Array.isArray(args.dependsOn) ? args.dependsOn : Array.isArray(args.depends_on) ? args.depends_on : void 0;
|
|
3465
3473
|
const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
|
|
3466
3474
|
const explicitTarget = readString(args.targetNodeId) || readString(args.target_node_id) || void 0;
|
|
3467
3475
|
const preferWorktree = args.preferWorktree === true || args.prefer_worktree === true;
|
|
3468
3476
|
const targetNodeId = explicitTarget || (preferWorktree ? resolvePreferredWorktreeNodeId(ctx) : void 0);
|
|
3469
3477
|
try {
|
|
3470
|
-
const task = (0,
|
|
3478
|
+
const task = (0, import_daemon_core2.enqueueTask)(ctx.mesh.id, args.message, { taskMode, requiredTags, dependsOn, missionId, targetNodeId, ...ctx.coordinatorSessionId ? { sourceCoordinatorSessionId: ctx.coordinatorSessionId } : {} });
|
|
3471
3479
|
if (!(ctx.transport instanceof IpcTransport)) {
|
|
3472
3480
|
const queueTrigger = await triggerMeshQueueAndReport(ctx);
|
|
3473
3481
|
return JSON.stringify({
|
|
@@ -3490,14 +3498,14 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
3490
3498
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
3491
3499
|
if (isLocalNode || !node.daemonId) continue;
|
|
3492
3500
|
if (targetNodeId && node.id !== targetNodeId) continue;
|
|
3493
|
-
if (!(0,
|
|
3501
|
+
if (!(0, import_daemon_core2.nodeSatisfiesRequiredTags)(requiredTags, (0, import_daemon_core2.buildMeshNodeCapabilityTags)(node))) continue;
|
|
3494
3502
|
dispatchPromises.push(
|
|
3495
3503
|
ipcDispatchToRemoteAgent(ctx, node, { message: args.message }).then((result) => {
|
|
3496
3504
|
if (result.success) {
|
|
3497
3505
|
try {
|
|
3498
3506
|
const providerType = result.providerType;
|
|
3499
3507
|
const descriptor = summarizeTaskMessage(args.message);
|
|
3500
|
-
(0,
|
|
3508
|
+
(0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
|
|
3501
3509
|
kind: "task_dispatched",
|
|
3502
3510
|
nodeId: node.id,
|
|
3503
3511
|
sessionId: result.sessionId,
|
|
@@ -3519,7 +3527,7 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
3519
3527
|
}
|
|
3520
3528
|
}).catch((err) => {
|
|
3521
3529
|
try {
|
|
3522
|
-
(0,
|
|
3530
|
+
(0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
|
|
3523
3531
|
kind: "p2p_dispatch_failed",
|
|
3524
3532
|
nodeId: node.id,
|
|
3525
3533
|
payload: {
|
|
@@ -3562,17 +3570,17 @@ async function meshEnqueueTask(ctx, args) {
|
|
|
3562
3570
|
}
|
|
3563
3571
|
}
|
|
3564
3572
|
async function meshViewQueue(ctx, args) {
|
|
3565
|
-
const rateResult = (0,
|
|
3573
|
+
const rateResult = (0, import_daemon_core2.recordMeshToolCall)({ meshId: ctx.mesh.id, tool: "mesh_view_queue" });
|
|
3566
3574
|
const compact = args.verbose === true ? false : args.compact ?? true;
|
|
3567
3575
|
try {
|
|
3568
3576
|
await refreshMeshFromDaemon(ctx);
|
|
3569
3577
|
const statusFilter = sanitizeQueueStatusFilter(args.status);
|
|
3570
3578
|
const view = normalizeQueueViewMode(args.view);
|
|
3571
|
-
const rawQueue = (0,
|
|
3579
|
+
const rawQueue = (0, import_daemon_core2.getQueue)(ctx.mesh.id);
|
|
3572
3580
|
const statusById = new Map(rawQueue.map((task) => [task.id, task.status]));
|
|
3573
3581
|
const withDependencies = rawQueue.map((task) => {
|
|
3574
3582
|
if (!Array.isArray(task.dependsOn) || task.dependsOn.length === 0) return task;
|
|
3575
|
-
const depState = (0,
|
|
3583
|
+
const depState = (0, import_daemon_core2.describeTaskDependencyState)(task, statusById);
|
|
3576
3584
|
return { ...task, ...depState };
|
|
3577
3585
|
});
|
|
3578
3586
|
const fullQueue = prioritizeActiveQueueRows(annotateQueueStaleness(withDependencies, ctx.mesh));
|
|
@@ -3581,16 +3589,16 @@ async function meshViewQueue(ctx, args) {
|
|
|
3581
3589
|
const visibleSummary = buildQueueStatusSummary(queue);
|
|
3582
3590
|
const maintenance = buildQueueMaintenanceReport(fullQueue);
|
|
3583
3591
|
const liveNodes = await collectMeshViewQueueNodesWithLiveSessions(ctx);
|
|
3584
|
-
let ledgerEntries = (0,
|
|
3585
|
-
let directDispatches = (0,
|
|
3592
|
+
let ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
3593
|
+
let directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(ctx.mesh.id);
|
|
3586
3594
|
const directReconciliation = await reconcileDirectDispatchesFromTranscriptEvidence(ctx, liveNodes, directDispatches, ledgerEntries);
|
|
3587
3595
|
if (directReconciliation.reconciled > 0) {
|
|
3588
|
-
ledgerEntries = (0,
|
|
3589
|
-
directDispatches = (0,
|
|
3596
|
+
ledgerEntries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
3597
|
+
directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(ctx.mesh.id);
|
|
3590
3598
|
}
|
|
3591
|
-
(0,
|
|
3592
|
-
directDispatches = (0,
|
|
3593
|
-
const activeWorkEvidence = (0,
|
|
3599
|
+
(0, import_daemon_core2.markStaleDirectDispatches)(ctx.mesh.id);
|
|
3600
|
+
directDispatches = (0, import_daemon_core2.getActiveDirectDispatches)(ctx.mesh.id);
|
|
3601
|
+
const activeWorkEvidence = (0, import_daemon_core2.buildMeshActiveWork)({
|
|
3594
3602
|
meshId: ctx.mesh.id,
|
|
3595
3603
|
queue: fullQueue,
|
|
3596
3604
|
ledgerEntries,
|
|
@@ -3615,7 +3623,7 @@ async function meshViewQueue(ctx, args) {
|
|
|
3615
3623
|
const wantActiveQueueArray = view === "active" || statusFilter?.some((status) => ACTIVE_QUEUE_STATUSES.has(status));
|
|
3616
3624
|
const wantHistoricalQueueArray = !compact && (view === "historical" || requestedHistoricalRows);
|
|
3617
3625
|
const activeWorkResult = compact ? compactActiveWorkRecords(activeWorkEvidence.activeWork) : { records: activeWorkEvidence.activeWork, omitted: 0 };
|
|
3618
|
-
const staleDirectWorkSummary = (0,
|
|
3626
|
+
const staleDirectWorkSummary = (0, import_daemon_core2.buildCompactStaleDirectWorkSummary)(activeWorkEvidence.staleDirectWork, {
|
|
3619
3627
|
note: activeWorkEvidence.staleDirectWorkNote,
|
|
3620
3628
|
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."
|
|
3621
3629
|
});
|
|
@@ -3690,7 +3698,7 @@ async function meshQueueCancel(ctx, args) {
|
|
|
3690
3698
|
try {
|
|
3691
3699
|
const taskId = (args.task_id || args.taskId || "").trim();
|
|
3692
3700
|
if (!taskId) return JSON.stringify({ success: false, error: "task_id required" });
|
|
3693
|
-
const task = (0,
|
|
3701
|
+
const task = (0, import_daemon_core2.cancelTask)(ctx.mesh.id, taskId, { reason: args.reason });
|
|
3694
3702
|
if (!task) return JSON.stringify({ success: false, error: `Queue task '${taskId}' not found` });
|
|
3695
3703
|
ctx.transport.command("trigger_mesh_queue", { meshId: ctx.mesh.id }).catch(() => {
|
|
3696
3704
|
});
|
|
@@ -3706,7 +3714,7 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
3706
3714
|
const targetNodeId = (args.target_node_id || args.targetNodeId || "").trim() || void 0;
|
|
3707
3715
|
const targetSessionId = (args.target_session_id || args.targetSessionId || "").trim() || void 0;
|
|
3708
3716
|
const keepTargetSession = args.keep_target_session === true || args.keepTargetSession === true;
|
|
3709
|
-
const task = (0,
|
|
3717
|
+
const task = (0, import_daemon_core2.requeueTask)(ctx.mesh.id, taskId, {
|
|
3710
3718
|
reason: args.reason,
|
|
3711
3719
|
targetNodeId,
|
|
3712
3720
|
targetSessionId,
|
|
@@ -3738,7 +3746,7 @@ async function meshQueueRequeue(ctx, args) {
|
|
|
3738
3746
|
async function meshSendTask(ctx, args) {
|
|
3739
3747
|
const requestedTaskMode = readString(args.task_mode) || readString(args.taskMode);
|
|
3740
3748
|
const missionId = readString(args.missionId) || readString(args.mission_id) || void 0;
|
|
3741
|
-
const modeValidation = (0,
|
|
3749
|
+
const modeValidation = (0, import_daemon_core2.validateMeshTaskModeRequest)(requestedTaskMode, args.message);
|
|
3742
3750
|
if (!modeValidation.valid) {
|
|
3743
3751
|
return JSON.stringify({
|
|
3744
3752
|
success: false,
|
|
@@ -3754,6 +3762,19 @@ async function meshSendTask(ctx, args) {
|
|
|
3754
3762
|
if (node.policy?.readOnly) {
|
|
3755
3763
|
return JSON.stringify({ error: `Node '${args.node_id}' is read-only` });
|
|
3756
3764
|
}
|
|
3765
|
+
if (taskMode === "convergence" && node.isLocalWorktree === true) {
|
|
3766
|
+
return JSON.stringify({
|
|
3767
|
+
success: false,
|
|
3768
|
+
recoverable: true,
|
|
3769
|
+
code: "mesh_convergence_target_is_worktree",
|
|
3770
|
+
reason: "mesh_convergence_target_is_worktree",
|
|
3771
|
+
nodeId: args.node_id,
|
|
3772
|
+
sessionId: args.session_id,
|
|
3773
|
+
taskMode,
|
|
3774
|
+
error: `Node '${args.node_id}' is a worktree clone; a convergence task is base-only (it merges/pushes onto base). Dispatching it to a worktree session risks a multi-worktree push/deploy race.`,
|
|
3775
|
+
nextAction: `Dispatch the convergence task to the base node for this mesh, or run the deterministic fast-forward convergence path (mesh_fast_forward_node / mesh_refine_node) instead of mesh_send_task.`
|
|
3776
|
+
});
|
|
3777
|
+
}
|
|
3757
3778
|
let explicitTargetSession;
|
|
3758
3779
|
if (args.session_id && isWorkerTaskMode(taskMode)) {
|
|
3759
3780
|
try {
|
|
@@ -3837,7 +3858,7 @@ async function meshSendTask(ctx, args) {
|
|
|
3837
3858
|
const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3838
3859
|
try {
|
|
3839
3860
|
const providerType = result2.providerType || cached?.providerType;
|
|
3840
|
-
(0,
|
|
3861
|
+
(0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
|
|
3841
3862
|
kind: "task_dispatched",
|
|
3842
3863
|
nodeId: args.node_id,
|
|
3843
3864
|
sessionId: dispatchedSessionId,
|
|
@@ -3849,7 +3870,7 @@ async function meshSendTask(ctx, args) {
|
|
|
3849
3870
|
targetSessionId: dispatchedSessionId
|
|
3850
3871
|
})
|
|
3851
3872
|
});
|
|
3852
|
-
(0,
|
|
3873
|
+
(0, import_daemon_core2.insertDirectDispatch)(ctx.mesh.id, {
|
|
3853
3874
|
taskId,
|
|
3854
3875
|
nodeId: args.node_id,
|
|
3855
3876
|
sessionId: dispatchedSessionId,
|
|
@@ -3860,7 +3881,7 @@ async function meshSendTask(ctx, args) {
|
|
|
3860
3881
|
dispatchedAt
|
|
3861
3882
|
});
|
|
3862
3883
|
if (missionId) {
|
|
3863
|
-
(0,
|
|
3884
|
+
(0, import_daemon_core2.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
|
|
3864
3885
|
id: taskId,
|
|
3865
3886
|
missionId,
|
|
3866
3887
|
assignedNodeId: args.node_id,
|
|
@@ -4019,7 +4040,7 @@ async function meshSendTask(ctx, args) {
|
|
|
4019
4040
|
});
|
|
4020
4041
|
}
|
|
4021
4042
|
try {
|
|
4022
|
-
(0,
|
|
4043
|
+
(0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
|
|
4023
4044
|
kind: "task_dispatched",
|
|
4024
4045
|
nodeId: args.node_id,
|
|
4025
4046
|
sessionId: args.session_id,
|
|
@@ -4034,7 +4055,7 @@ async function meshSendTask(ctx, args) {
|
|
|
4034
4055
|
});
|
|
4035
4056
|
} catch {
|
|
4036
4057
|
}
|
|
4037
|
-
(0,
|
|
4058
|
+
(0, import_daemon_core2.insertDirectDispatch)(ctx.mesh.id, {
|
|
4038
4059
|
taskId,
|
|
4039
4060
|
nodeId: args.node_id,
|
|
4040
4061
|
sessionId: args.session_id,
|
|
@@ -4047,7 +4068,7 @@ async function meshSendTask(ctx, args) {
|
|
|
4047
4068
|
});
|
|
4048
4069
|
if (missionId) {
|
|
4049
4070
|
try {
|
|
4050
|
-
(0,
|
|
4071
|
+
(0, import_daemon_core2.recordDirectDispatchTask)(ctx.mesh.id, args.message, {
|
|
4051
4072
|
id: taskId,
|
|
4052
4073
|
missionId,
|
|
4053
4074
|
assignedNodeId: args.node_id,
|
|
@@ -4092,14 +4113,14 @@ async function meshSendTask(ctx, args) {
|
|
|
4092
4113
|
} : {}
|
|
4093
4114
|
});
|
|
4094
4115
|
}
|
|
4095
|
-
const task = (0,
|
|
4116
|
+
const task = (0, import_daemon_core2.enqueueTask)(ctx.mesh.id, args.message, {
|
|
4096
4117
|
targetNodeId: args.node_id,
|
|
4097
4118
|
targetSessionId: args.session_id,
|
|
4098
4119
|
taskMode,
|
|
4099
4120
|
...missionId ? { missionId } : {}
|
|
4100
4121
|
});
|
|
4101
4122
|
const queueTrigger = await triggerMeshQueueAndReport(ctx);
|
|
4102
|
-
const pendingEvents = (0,
|
|
4123
|
+
const pendingEvents = (0, import_daemon_core2.drainPendingMeshCoordinatorEvents)(ctx.mesh.id, ctx.localDaemonId);
|
|
4103
4124
|
const result = {
|
|
4104
4125
|
success: true,
|
|
4105
4126
|
source: "queue",
|
|
@@ -4132,7 +4153,7 @@ function classifyReadChatTransportCause(error) {
|
|
|
4132
4153
|
return "saturated";
|
|
4133
4154
|
}
|
|
4134
4155
|
function resolveCachedMeshSessionPreviewFromLedger(ctx, nodeId, sessionId) {
|
|
4135
|
-
const entries = (0,
|
|
4156
|
+
const entries = (0, import_daemon_core2.readLedgerEntries)(ctx.mesh.id, { tail: 200 });
|
|
4136
4157
|
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
4137
4158
|
const entry = entries[i];
|
|
4138
4159
|
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
@@ -4141,7 +4162,7 @@ function resolveCachedMeshSessionPreviewFromLedger(ctx, nodeId, sessionId) {
|
|
|
4141
4162
|
const entrySessionId = readString(entry.sessionId) || readString(payload.targetSessionId) || readString(payload.sessionId) || readString(payload.instanceId);
|
|
4142
4163
|
if (entrySessionId !== sessionId) continue;
|
|
4143
4164
|
const metadataEvent = payload.metadataEvent && typeof payload.metadataEvent === "object" && !Array.isArray(payload.metadataEvent) ? payload.metadataEvent : payload;
|
|
4144
|
-
const preview = (0,
|
|
4165
|
+
const preview = (0, import_daemon_core2.resolveMeshSurfacedSessionPreview)(metadataEvent);
|
|
4145
4166
|
if (preview) {
|
|
4146
4167
|
return { ...preview, ledgerKind: entry.kind, timestamp: entry.timestamp };
|
|
4147
4168
|
}
|
|
@@ -4149,7 +4170,7 @@ function resolveCachedMeshSessionPreviewFromLedger(ctx, nodeId, sessionId) {
|
|
|
4149
4170
|
return void 0;
|
|
4150
4171
|
}
|
|
4151
4172
|
function buildMeshReadChatCacheFallback(ctx, args, node, error) {
|
|
4152
|
-
const classification = (0,
|
|
4173
|
+
const classification = (0, import_daemon_core2.classifyP2pRelayFailure)(error, { command: "read_chat", targetDaemonId: node.daemonId });
|
|
4153
4174
|
const cause = classifyReadChatTransportCause(error);
|
|
4154
4175
|
const errorMessage = error instanceof Error ? error.message : String(error ?? "");
|
|
4155
4176
|
const causeNote = cause === "not_connected" ? "the worker daemon is not currently connected over P2P (no live channel)" : "the worker daemon is connected but saturated \u2014 it acknowledged the request but did not return the transcript within the deadline";
|
|
@@ -4219,7 +4240,7 @@ async function meshReadChat(ctx, args) {
|
|
|
4219
4240
|
tailLimit: args.tail ?? 10
|
|
4220
4241
|
});
|
|
4221
4242
|
} catch (e) {
|
|
4222
|
-
if (isLocalNode || !(0,
|
|
4243
|
+
if (isLocalNode || !(0, import_daemon_core2.isP2pRelayTransportFailure)(e)) throw e;
|
|
4223
4244
|
return buildMeshReadChatCacheFallback(ctx, args, node, e);
|
|
4224
4245
|
}
|
|
4225
4246
|
const payload = annotateRapidReadChatAdvisory(unwrapCommandPayload(result), {
|
|
@@ -4287,7 +4308,7 @@ async function meshLaunchSession(ctx, args) {
|
|
|
4287
4308
|
const coordinatorNode = resolveCoordinatorNode(ctx);
|
|
4288
4309
|
const coordinatorDaemonId = resolveCoordinatorDaemonId(ctx);
|
|
4289
4310
|
const spawnedSessionVisibility = readSpawnedSessionVisibility(ctx.mesh.policy);
|
|
4290
|
-
const delegatedWorkerAutoApprove = (0,
|
|
4311
|
+
const delegatedWorkerAutoApprove = (0, import_daemon_core2.resolveDelegatedWorkerAutoApprove)(ctx.mesh.policy, node.policy);
|
|
4291
4312
|
const isLocalNode = isLocalControlPlaneNode(ctx, node);
|
|
4292
4313
|
if (node.daemonId && !isLocalNode && !coordinatorDaemonId) {
|
|
4293
4314
|
return JSON.stringify(buildMissingCoordinatorDaemonIdFailure(ctx, node, resolvedProviderType), null, 2);
|
|
@@ -4334,7 +4355,7 @@ async function meshLaunchSession(ctx, args) {
|
|
|
4334
4355
|
});
|
|
4335
4356
|
}
|
|
4336
4357
|
try {
|
|
4337
|
-
(0,
|
|
4358
|
+
(0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
|
|
4338
4359
|
kind: "session_launched",
|
|
4339
4360
|
nodeId: args.node_id,
|
|
4340
4361
|
sessionId: runtimeSessionId || void 0,
|
|
@@ -4487,7 +4508,7 @@ async function meshCheckpoint(ctx, args) {
|
|
|
4487
4508
|
includeUntracked: true
|
|
4488
4509
|
});
|
|
4489
4510
|
try {
|
|
4490
|
-
(0,
|
|
4511
|
+
(0, import_daemon_core2.appendLedgerEntry)(ctx.mesh.id, {
|
|
4491
4512
|
kind: "checkpoint_created",
|
|
4492
4513
|
nodeId: args.node_id,
|
|
4493
4514
|
payload: {
|