@adhdev/daemon-standalone 1.0.56-rc.5 → 1.0.56-rc.7
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 +1203 -506
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/public/assets/{index-B43wNsru.js → index-keXYbbIn.js} +1 -1
- package/public/index.html +1 -1
- package/vendor/mcp-server/index.js +178 -11
- package/vendor/mcp-server/index.js.map +1 -1
package/dist/index.js
CHANGED
|
@@ -36620,10 +36620,10 @@ var require_dist3 = __commonJS({
|
|
|
36620
36620
|
}
|
|
36621
36621
|
function getDaemonBuildInfo() {
|
|
36622
36622
|
if (cached2) return cached2;
|
|
36623
|
-
const commit = readInjected(true ? "
|
|
36624
|
-
const commitShort = readInjected(true ? "
|
|
36625
|
-
const version2 = readInjected(true ? "1.0.56-rc.
|
|
36626
|
-
const builtAt = readInjected(true ? "2026-08-
|
|
36623
|
+
const commit = readInjected(true ? "13ea4fe8748ca3d97f0e4f9cc10a4c5969855fff" : void 0) ?? "unknown";
|
|
36624
|
+
const commitShort = readInjected(true ? "13ea4fe8" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
36625
|
+
const version2 = readInjected(true ? "1.0.56-rc.7" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
36626
|
+
const builtAt = readInjected(true ? "2026-08-19T14:43:40.002Z" : void 0);
|
|
36627
36627
|
cached2 = builtAt ? { commit, commitShort, version: version2, builtAt } : { commit, commitShort, version: version2 };
|
|
36628
36628
|
return cached2;
|
|
36629
36629
|
}
|
|
@@ -44261,6 +44261,13 @@ Next step: ${nextStep}`;
|
|
|
44261
44261
|
import_meta = {};
|
|
44262
44262
|
}
|
|
44263
44263
|
});
|
|
44264
|
+
function normalizeDeliveryMode(raw) {
|
|
44265
|
+
if (raw === void 0 || raw === null || raw === "") return { mode: DEFAULT_DELIVERY_MODE };
|
|
44266
|
+
const v = String(raw).trim().toLowerCase();
|
|
44267
|
+
if (v === "when_idle" || v === "whenidle") return { mode: "when_idle" };
|
|
44268
|
+
if (v === "interrupt") return { mode: "interrupt" };
|
|
44269
|
+
return { mode: DEFAULT_DELIVERY_MODE, unrecognized: String(raw) };
|
|
44270
|
+
}
|
|
44264
44271
|
function resolveDeliveryDecision(sessionStatus, opts) {
|
|
44265
44272
|
const status = (sessionStatus || "").trim().toLowerCase();
|
|
44266
44273
|
if (!status) {
|
|
@@ -44292,6 +44299,20 @@ Next step: ${nextStep}`;
|
|
|
44292
44299
|
message: "Session is waiting for approval \u2014 approval message delivered immediately."
|
|
44293
44300
|
};
|
|
44294
44301
|
}
|
|
44302
|
+
if (opts?.deliveryMode === "interrupt") {
|
|
44303
|
+
if (!opts.interruptSupported) {
|
|
44304
|
+
return {
|
|
44305
|
+
decision: "rejected",
|
|
44306
|
+
reason: "interrupt_unsupported_for_provider",
|
|
44307
|
+
message: opts.interruptUnsupportedMessage ?? `Session is ${status} and delivery mode 'interrupt' was requested, but this provider cannot interrupt a running turn. Refusing rather than silently queueing: queueing would let the current turn finish on the old instructions. Re-dispatch with delivery mode 'when_idle' if delivery-after-completion is acceptable.`
|
|
44308
|
+
};
|
|
44309
|
+
}
|
|
44310
|
+
return {
|
|
44311
|
+
decision: "interrupt",
|
|
44312
|
+
reason: `session_${status}_interrupt_requested`,
|
|
44313
|
+
message: `Session is ${status}. Aborting the in-flight turn via the provider's stop control, then delivering the task once it settles to idle. The work the session had not yet finished is discarded.`
|
|
44314
|
+
};
|
|
44315
|
+
}
|
|
44295
44316
|
return {
|
|
44296
44317
|
decision: "queued",
|
|
44297
44318
|
reason: `session_${status}_busy`,
|
|
@@ -44380,6 +44401,7 @@ Next step: ${nextStep}`;
|
|
|
44380
44401
|
}
|
|
44381
44402
|
}
|
|
44382
44403
|
var import_crypto4;
|
|
44404
|
+
var DEFAULT_DELIVERY_MODE;
|
|
44383
44405
|
var IMMEDIATE_DELIVERY_STATUSES;
|
|
44384
44406
|
var BUSY_DELIVERY_STATUSES;
|
|
44385
44407
|
var TERMINAL_DELIVERY_STATUSES;
|
|
@@ -44388,6 +44410,7 @@ Next step: ${nextStep}`;
|
|
|
44388
44410
|
"use strict";
|
|
44389
44411
|
import_crypto4 = require("crypto");
|
|
44390
44412
|
init_mesh_runtime_store();
|
|
44413
|
+
DEFAULT_DELIVERY_MODE = "when_idle";
|
|
44391
44414
|
IMMEDIATE_DELIVERY_STATUSES = /* @__PURE__ */ new Set([
|
|
44392
44415
|
"idle",
|
|
44393
44416
|
"waiting_input",
|
|
@@ -45688,6 +45711,25 @@ Next step: ${nextStep}`;
|
|
|
45688
45711
|
while (end > 0 && (buf[end] & 192) === 128) end -= 1;
|
|
45689
45712
|
return `${buf.subarray(0, end).toString("utf8")}${marker}`;
|
|
45690
45713
|
}
|
|
45714
|
+
function describeAvailablePointers(upstream) {
|
|
45715
|
+
if (!upstream) return " (no output is recorded for that ref yet)";
|
|
45716
|
+
const envelope = upstream.envelope;
|
|
45717
|
+
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) {
|
|
45718
|
+
return " (that ref has an output, but its envelope is not an object)";
|
|
45719
|
+
}
|
|
45720
|
+
const pointers = [];
|
|
45721
|
+
for (const key2 of Object.keys(envelope)) {
|
|
45722
|
+
pointers.push(`/${key2}`);
|
|
45723
|
+
if (key2 !== "worker_result") continue;
|
|
45724
|
+
const workerResult = envelope.worker_result;
|
|
45725
|
+
if (!workerResult || typeof workerResult !== "object" || Array.isArray(workerResult)) continue;
|
|
45726
|
+
for (const sub of Object.keys(workerResult)) {
|
|
45727
|
+
pointers.push(`/worker_result/${sub}`);
|
|
45728
|
+
}
|
|
45729
|
+
}
|
|
45730
|
+
if (pointers.length === 0) return " (that ref's envelope is empty)";
|
|
45731
|
+
return `. Available pointers on ref '${upstream.ref}': ${pointers.join(", ")}`;
|
|
45732
|
+
}
|
|
45691
45733
|
function resolveInputBindings(bindings, outputsByRef) {
|
|
45692
45734
|
const resolved = [];
|
|
45693
45735
|
let totalBytes = 0;
|
|
@@ -45698,7 +45740,7 @@ Next step: ${nextStep}`;
|
|
|
45698
45740
|
if (binding.required) {
|
|
45699
45741
|
throw new MeshMaterializationError(
|
|
45700
45742
|
"required_input_missing",
|
|
45701
|
-
`required binding '${binding.as}' found nothing at '${binding.select}' of ref '${binding.from}'
|
|
45743
|
+
`required binding '${binding.as}' found nothing at '${binding.select}' of ref '${binding.from}'` + describeAvailablePointers(upstream),
|
|
45702
45744
|
binding.as
|
|
45703
45745
|
);
|
|
45704
45746
|
}
|
|
@@ -47122,108 +47164,6 @@ ${blocks.join("\n\n")}`;
|
|
|
47122
47164
|
GIT_STASH_READONLY_SUBCOMMANDS = /* @__PURE__ */ new Set(["list", "show"]);
|
|
47123
47165
|
}
|
|
47124
47166
|
});
|
|
47125
|
-
function readPayloadTaskId(entry) {
|
|
47126
|
-
const value = entry.payload?.taskId;
|
|
47127
|
-
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
47128
|
-
}
|
|
47129
|
-
function parseTime(value) {
|
|
47130
|
-
if (!value) return null;
|
|
47131
|
-
const parsed = new Date(value).getTime();
|
|
47132
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
47133
|
-
}
|
|
47134
|
-
function computeMeshTaskStats(meshId, opts) {
|
|
47135
|
-
const queue = getQueue(meshId);
|
|
47136
|
-
const queueById = new Map(queue.map((task) => [task.id, task]));
|
|
47137
|
-
let targetIds;
|
|
47138
|
-
if (opts?.taskIds?.length) {
|
|
47139
|
-
targetIds = [...new Set(opts.taskIds)];
|
|
47140
|
-
} else if (opts?.missionId) {
|
|
47141
|
-
targetIds = queue.filter((task) => task.missionId === opts.missionId).map((task) => task.id);
|
|
47142
|
-
} else {
|
|
47143
|
-
targetIds = queue.map((task) => task.id);
|
|
47144
|
-
}
|
|
47145
|
-
if (targetIds.length === 0) return [];
|
|
47146
|
-
const targetSet = new Set(targetIds);
|
|
47147
|
-
const entries = readLedgerEntries(meshId, { tail: opts?.tail ?? 1e3 });
|
|
47148
|
-
const dispatches = /* @__PURE__ */ new Map();
|
|
47149
|
-
const terminals = /* @__PURE__ */ new Map();
|
|
47150
|
-
for (const entry of entries) {
|
|
47151
|
-
const taskId = readPayloadTaskId(entry);
|
|
47152
|
-
if (!taskId || !targetSet.has(taskId)) continue;
|
|
47153
|
-
if (entry.kind === "task_dispatched") {
|
|
47154
|
-
const existing = dispatches.get(taskId);
|
|
47155
|
-
if (existing) existing.count += 1;
|
|
47156
|
-
else dispatches.set(taskId, { first: entry.timestamp, count: 1 });
|
|
47157
|
-
} else if (entry.kind === "task_completed" || entry.kind === "task_failed") {
|
|
47158
|
-
terminals.set(taskId, { at: entry.timestamp, kind: entry.kind });
|
|
47159
|
-
}
|
|
47160
|
-
}
|
|
47161
|
-
return targetIds.map((taskId) => {
|
|
47162
|
-
const queueEntry = queueById.get(taskId);
|
|
47163
|
-
const dispatch = dispatches.get(taskId);
|
|
47164
|
-
const terminal = terminals.get(taskId);
|
|
47165
|
-
const status = queueEntry?.status ?? (terminal ? terminal.kind === "task_completed" ? "completed" : "failed" : "unknown");
|
|
47166
|
-
const isTerminalStatus = status === "completed" || status === "failed" || status === "cancelled";
|
|
47167
|
-
const dispatchTime = parseTime(dispatch?.first ?? queueEntry?.dispatchTimestamp);
|
|
47168
|
-
const terminalTime = parseTime(terminal?.at);
|
|
47169
|
-
const stats = {
|
|
47170
|
-
taskId,
|
|
47171
|
-
status,
|
|
47172
|
-
dispatchedAt: dispatch?.first ?? queueEntry?.dispatchTimestamp ?? null,
|
|
47173
|
-
terminalAt: terminal?.at ?? null,
|
|
47174
|
-
terminalKind: terminal?.kind ?? null,
|
|
47175
|
-
durationMs: null,
|
|
47176
|
-
dispatchCount: dispatch?.count ?? 0,
|
|
47177
|
-
requeueCount: queueEntry?.requeueCount ?? 0
|
|
47178
|
-
};
|
|
47179
|
-
if (dispatchTime !== null && terminalTime !== null && terminalTime >= dispatchTime) {
|
|
47180
|
-
stats.durationMs = terminalTime - dispatchTime;
|
|
47181
|
-
} else if (isTerminalStatus) {
|
|
47182
|
-
stats.incompleteEvidence = true;
|
|
47183
|
-
}
|
|
47184
|
-
return stats;
|
|
47185
|
-
});
|
|
47186
|
-
}
|
|
47187
|
-
function computeMeshMissionStats(meshId, missionId) {
|
|
47188
|
-
const tasks = computeMeshTaskStats(meshId, { missionId });
|
|
47189
|
-
const stats = {
|
|
47190
|
-
missionId,
|
|
47191
|
-
taskCount: tasks.length,
|
|
47192
|
-
completed: 0,
|
|
47193
|
-
failed: 0,
|
|
47194
|
-
totalDurationMs: 0,
|
|
47195
|
-
wallClockMs: null,
|
|
47196
|
-
retries: 0,
|
|
47197
|
-
incompleteTaskIds: []
|
|
47198
|
-
};
|
|
47199
|
-
let firstDispatch = null;
|
|
47200
|
-
let lastTerminal = null;
|
|
47201
|
-
for (const task of tasks) {
|
|
47202
|
-
if (task.status === "completed") stats.completed += 1;
|
|
47203
|
-
else if (task.status === "failed") stats.failed += 1;
|
|
47204
|
-
stats.retries += task.requeueCount;
|
|
47205
|
-
if (task.incompleteEvidence) {
|
|
47206
|
-
stats.incompleteTaskIds.push(task.taskId);
|
|
47207
|
-
continue;
|
|
47208
|
-
}
|
|
47209
|
-
if (task.durationMs !== null) stats.totalDurationMs += task.durationMs;
|
|
47210
|
-
const dispatchTime = parseTime(task.dispatchedAt);
|
|
47211
|
-
const terminalTime = parseTime(task.terminalAt);
|
|
47212
|
-
if (dispatchTime !== null && (firstDispatch === null || dispatchTime < firstDispatch)) firstDispatch = dispatchTime;
|
|
47213
|
-
if (terminalTime !== null && (lastTerminal === null || terminalTime > lastTerminal)) lastTerminal = terminalTime;
|
|
47214
|
-
}
|
|
47215
|
-
if (firstDispatch !== null && lastTerminal !== null && lastTerminal >= firstDispatch) {
|
|
47216
|
-
stats.wallClockMs = lastTerminal - firstDispatch;
|
|
47217
|
-
}
|
|
47218
|
-
return stats;
|
|
47219
|
-
}
|
|
47220
|
-
var init_mesh_task_stats = __esm2({
|
|
47221
|
-
"src/mesh/mesh-task-stats.ts"() {
|
|
47222
|
-
"use strict";
|
|
47223
|
-
init_mesh_ledger();
|
|
47224
|
-
init_mesh_work_queue();
|
|
47225
|
-
}
|
|
47226
|
-
});
|
|
47227
47167
|
function truncateString(value, maxChars) {
|
|
47228
47168
|
if (value.length <= maxChars) return value;
|
|
47229
47169
|
if (maxChars <= 12) return value.slice(0, Math.max(0, maxChars));
|
|
@@ -47943,6 +47883,7 @@ ${blocks.join("\n\n")}`;
|
|
|
47943
47883
|
const taskId = readNonEmptyString(metadata.taskId) || readNonEmptyString(readRecord2(metadata.payload)?.taskId);
|
|
47944
47884
|
const jobId = readRefineJobId2(event);
|
|
47945
47885
|
const timestamp2 = metadata.timestamp !== void 0 && metadata.timestamp !== null ? String(metadata.timestamp) : "";
|
|
47886
|
+
const alertReason = COORDINATOR_ALERT_EVENTS_WITH_REASON_FINGERPRINT.has(event.event) ? readNonEmptyString(metadata.reason) : void 0;
|
|
47946
47887
|
return [
|
|
47947
47888
|
event.meshId,
|
|
47948
47889
|
event.event,
|
|
@@ -47951,7 +47892,8 @@ ${blocks.join("\n\n")}`;
|
|
|
47951
47892
|
providerSessionId || "",
|
|
47952
47893
|
taskId || "",
|
|
47953
47894
|
jobId || "",
|
|
47954
|
-
timestamp2 || ""
|
|
47895
|
+
timestamp2 || "",
|
|
47896
|
+
...alertReason ? [alertReason] : []
|
|
47955
47897
|
].join("::");
|
|
47956
47898
|
}
|
|
47957
47899
|
function hasPendingCoordinatorEventDuplicate(event) {
|
|
@@ -48400,6 +48342,7 @@ ${blocks.join("\n\n")}`;
|
|
|
48400
48342
|
var warnedV2Violations;
|
|
48401
48343
|
var loggedSqlitePendingDrainFailure;
|
|
48402
48344
|
var TERMINAL_COMPLETION_EVENTS;
|
|
48345
|
+
var COORDINATOR_ALERT_EVENTS_WITH_REASON_FINGERPRINT;
|
|
48403
48346
|
var PENDING_EVENTS_DRAINED_RETENTION_MS;
|
|
48404
48347
|
var PENDING_EVENTS_UNDRAINED_RETENTION_MS;
|
|
48405
48348
|
var PENDING_RETENTION_EXPIRED_HOLD_REASON;
|
|
@@ -48450,6 +48393,9 @@ ${blocks.join("\n\n")}`;
|
|
|
48450
48393
|
warnedV2Violations = /* @__PURE__ */ new Set();
|
|
48451
48394
|
loggedSqlitePendingDrainFailure = false;
|
|
48452
48395
|
TERMINAL_COMPLETION_EVENTS = /* @__PURE__ */ new Set(["agent:generating_completed", "agent:stopped"]);
|
|
48396
|
+
COORDINATOR_ALERT_EVENTS_WITH_REASON_FINGERPRINT = /* @__PURE__ */ new Set([
|
|
48397
|
+
"mesh:dispatch_blocked"
|
|
48398
|
+
]);
|
|
48453
48399
|
PENDING_EVENTS_DRAINED_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
48454
48400
|
PENDING_EVENTS_UNDRAINED_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
48455
48401
|
PENDING_RETENTION_EXPIRED_HOLD_REASON = "pending_retention_expired";
|
|
@@ -48470,6 +48416,245 @@ ${blocks.join("\n\n")}`;
|
|
|
48470
48416
|
};
|
|
48471
48417
|
}
|
|
48472
48418
|
});
|
|
48419
|
+
function s(v) {
|
|
48420
|
+
return typeof v === "string" && v.trim() ? v.trim() : "";
|
|
48421
|
+
}
|
|
48422
|
+
function meshEventTraceKey(ctx) {
|
|
48423
|
+
const segs = [`task=${s(ctx.taskId) || "-"}`];
|
|
48424
|
+
const eventId = s(ctx.eventId);
|
|
48425
|
+
if (eventId) segs.push(`evt=${eventId}`);
|
|
48426
|
+
segs.push(`sess=${s(ctx.sessionId) || "-"}`);
|
|
48427
|
+
const nodeId = s(ctx.nodeId);
|
|
48428
|
+
if (nodeId) segs.push(`node=${nodeId}`);
|
|
48429
|
+
const meshId = s(ctx.meshId);
|
|
48430
|
+
if (meshId) segs.push(`mesh=${meshId}`);
|
|
48431
|
+
const event = s(ctx.event);
|
|
48432
|
+
if (event) segs.push(`event=${event}`);
|
|
48433
|
+
return segs.join(" ");
|
|
48434
|
+
}
|
|
48435
|
+
function traceMeshEventStage(stage, ctx, detail) {
|
|
48436
|
+
LOG2.info(CAT, `[stage:${stage}] ${meshEventTraceKey(ctx)}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
48437
|
+
}
|
|
48438
|
+
function traceMeshEventDrop(reason, ctx, detail) {
|
|
48439
|
+
LOG2.warn(CAT, `[drop:${reason}] ${meshEventTraceKey(ctx)}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
48440
|
+
}
|
|
48441
|
+
var CAT;
|
|
48442
|
+
var init_mesh_event_trace = __esm2({
|
|
48443
|
+
"src/mesh/mesh-event-trace.ts"() {
|
|
48444
|
+
"use strict";
|
|
48445
|
+
init_logger();
|
|
48446
|
+
CAT = "EvtTrace";
|
|
48447
|
+
}
|
|
48448
|
+
});
|
|
48449
|
+
function taskIsParked(task) {
|
|
48450
|
+
return !!readNonEmptyString(task?.parked?.reason);
|
|
48451
|
+
}
|
|
48452
|
+
function buildParkingRecord(task, reason, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
48453
|
+
return {
|
|
48454
|
+
reason,
|
|
48455
|
+
parkedAt: nowIso,
|
|
48456
|
+
// Mirror the ORIGINAL address. `targetSessionId` also stays on the row
|
|
48457
|
+
// (that is what keeps the row unclaimable), but a later re-target
|
|
48458
|
+
// overwrites it — this copy is what still answers "who was this delta
|
|
48459
|
+
// written for?" after the coordinator has moved it.
|
|
48460
|
+
...readNonEmptyString(task.targetSessionId) ? { targetSessionId: task.targetSessionId } : {},
|
|
48461
|
+
...readNonEmptyString(task.targetNodeId) ? { targetNodeId: task.targetNodeId } : {}
|
|
48462
|
+
};
|
|
48463
|
+
}
|
|
48464
|
+
function parkedAgeMs(task, nowMs = Date.now()) {
|
|
48465
|
+
const parkedAt = readNonEmptyString(task.parked?.parkedAt);
|
|
48466
|
+
if (!parkedAt) return null;
|
|
48467
|
+
const parsed = Date.parse(parkedAt);
|
|
48468
|
+
return Number.isFinite(parsed) ? nowMs - parsed : null;
|
|
48469
|
+
}
|
|
48470
|
+
function parkedTaskRetentionExpired(task, nowMs = Date.now(), retentionMs = PARKED_TASK_RETENTION_MS) {
|
|
48471
|
+
if (!taskIsParked(task)) return false;
|
|
48472
|
+
const age = parkedAgeMs(task, nowMs);
|
|
48473
|
+
return age !== null && age >= retentionMs;
|
|
48474
|
+
}
|
|
48475
|
+
function buildParkedTaskNotice(taskId, targetSessionId, reason) {
|
|
48476
|
+
const addressee = readNonEmptyString(targetSessionId);
|
|
48477
|
+
return `The delta was addressed to session '${addressee || "(unknown)"}' and that pin went stale (${reason || PARK_REASON_PIN_EXPIRED}), so the task is now PARKED \u2014 held for you, deliberately NOT re-homed onto another session. A message written for one session's context becomes a context-free instruction anywhere else, so the daemon will not choose a new addressee for you. The task is claimable by nobody until you act on it, and it is dropped (as a terminal failure, with another notification) if left parked for ${Math.round(PARKED_TASK_RETENTION_MS / 36e5)}h. Four ways out \u2014 inspect it with mesh_view_queue (parked rows are listed under parkedTasks with their original addressee), then either re-target it with mesh_queue_requeue(task_id='${taskId}', target_session_id='<live session>') \u2014 or clear_target_session to let any compatible session take it; REWRITE it first with mesh_queue_requeue(task_id='${taskId}', message='<updated instruction>') if the situation moved on while it waited (the common case: the worker already did the part your delta was about); or cancel it with mesh_queue_cancel(task_id='${taskId}') if it is no longer wanted. Any requeue unparks the task.`;
|
|
48478
|
+
}
|
|
48479
|
+
function notifyCoordinatorOfParkedTaskDropped(meshId, task) {
|
|
48480
|
+
const taskId = task.id;
|
|
48481
|
+
const addressee = readNonEmptyString(task.parked?.targetSessionId);
|
|
48482
|
+
const hours = Math.round(PARKED_TASK_RETENTION_MS / 36e5);
|
|
48483
|
+
const coordinatorMessage = `[System] A PARKED mesh task was dropped after ${hours}h with no coordinator decision.
|
|
48484
|
+
Task ${taskId} was parked because its delta was addressed to session '${addressee || "(unknown)"}' and that pin went stale. It was held \u2014 claimable by nobody \u2014 waiting for you to re-target, rewrite, or cancel it. That never happened, so it is now marked FAILED (${PARK_RETENTION_EXPIRED_REASON}) and any dependent tasks have been unblocked.
|
|
48485
|
+
The instruction it carried was never delivered to anyone. If it still matters, re-enqueue it (mesh_enqueue_task) against a live session; the failed row remains in the queue as the audit record. To avoid this next time, check parkedTasks in mesh_view_queue \u2014 parked rows are surfaced there from the moment they park.`;
|
|
48486
|
+
const nodeLabel = readNonEmptyString(task.targetNodeId) || readNonEmptyString(task.parked?.targetNodeId) || meshId;
|
|
48487
|
+
try {
|
|
48488
|
+
const targetCoordinatorDaemonId = readNonEmptyString(loadConfig2().machineId);
|
|
48489
|
+
const targetCoordinatorSessionId = readNonEmptyString(task.sourceCoordinatorSessionId);
|
|
48490
|
+
queuePendingMeshCoordinatorEvent({
|
|
48491
|
+
event: "mesh:dispatch_blocked",
|
|
48492
|
+
meshId,
|
|
48493
|
+
nodeLabel,
|
|
48494
|
+
metadataEvent: {
|
|
48495
|
+
source: "mesh_parked_task_retention_expired",
|
|
48496
|
+
taskId,
|
|
48497
|
+
reason: PARK_RETENTION_EXPIRED_REASON,
|
|
48498
|
+
...addressee ? { parkedTargetSessionId: addressee } : {},
|
|
48499
|
+
coordinatorMessage
|
|
48500
|
+
},
|
|
48501
|
+
coordinatorMessage,
|
|
48502
|
+
queuedAt: Date.now(),
|
|
48503
|
+
...targetCoordinatorDaemonId ? { targetCoordinatorDaemonId } : {},
|
|
48504
|
+
...targetCoordinatorSessionId ? { targetCoordinatorSessionId } : {}
|
|
48505
|
+
});
|
|
48506
|
+
} catch (e) {
|
|
48507
|
+
LOG2.warn("MeshQueue", `Failed to surface parked-task retention drop for task ${taskId} (mesh ${meshId}): ${e?.message || e}`);
|
|
48508
|
+
}
|
|
48509
|
+
}
|
|
48510
|
+
function parkExpiredTargetPin(meshId, task, verdict, ttlMs, park) {
|
|
48511
|
+
if (!verdict.expired) return false;
|
|
48512
|
+
const parked = park(meshId, task.id, PARK_REASON_PIN_EXPIRED);
|
|
48513
|
+
if (parked) {
|
|
48514
|
+
noteTargetPinCleared(PARK_REASON_PIN_EXPIRED);
|
|
48515
|
+
traceMeshEventDrop("target_session_pin_expired", {
|
|
48516
|
+
taskId: task.id,
|
|
48517
|
+
sessionId: readNonEmptyString(task.targetSessionId),
|
|
48518
|
+
nodeId: readNonEmptyString(task.targetNodeId),
|
|
48519
|
+
meshId,
|
|
48520
|
+
event: "agent:ready"
|
|
48521
|
+
}, `unproductive ${Math.round((verdict.ageMs ?? 0) / 1e3)}s \u2265 ttl ${Math.round(ttlMs / 1e3)}s \u2192 task PARKED (held for the coordinator, NOT re-homed)`);
|
|
48522
|
+
}
|
|
48523
|
+
return true;
|
|
48524
|
+
}
|
|
48525
|
+
function settleParkedQueueTask(meshId, task, markSkip, sweep) {
|
|
48526
|
+
const swept = sweep(meshId, task.id);
|
|
48527
|
+
if (swept) {
|
|
48528
|
+
notifyCoordinatorOfParkedTaskDropped(meshId, task);
|
|
48529
|
+
return "swept";
|
|
48530
|
+
}
|
|
48531
|
+
markSkip(PARKED_SKIP_REASON);
|
|
48532
|
+
return "held";
|
|
48533
|
+
}
|
|
48534
|
+
function logTaskParked(meshId, taskId, reason, targetSessionId) {
|
|
48535
|
+
LOG2.warn("MeshQueue", `PIN-PARKING: task ${taskId} (mesh ${meshId}) PARKED (${reason})${targetSessionId ? ` \u2014 original addressee session ${targetSessionId}` : ""}; it is claimable by nobody until the coordinator re-targets, edits, or cancels it.`);
|
|
48536
|
+
}
|
|
48537
|
+
var PARKED_TASK_RETENTION_MS;
|
|
48538
|
+
var PARK_REASON_PIN_EXPIRED;
|
|
48539
|
+
var PARKED_SKIP_REASON;
|
|
48540
|
+
var PARK_RETENTION_EXPIRED_REASON;
|
|
48541
|
+
var init_mesh_task_parking = __esm2({
|
|
48542
|
+
"src/mesh/mesh-task-parking.ts"() {
|
|
48543
|
+
"use strict";
|
|
48544
|
+
init_logger();
|
|
48545
|
+
init_mesh_events_utils();
|
|
48546
|
+
init_mesh_events_pending();
|
|
48547
|
+
init_mesh_turn_ledger();
|
|
48548
|
+
init_mesh_event_trace();
|
|
48549
|
+
init_config();
|
|
48550
|
+
PARKED_TASK_RETENTION_MS = 24 * 60 * 6e4;
|
|
48551
|
+
PARK_REASON_PIN_EXPIRED = "target_session_pin_expired_parked";
|
|
48552
|
+
PARKED_SKIP_REASON = "target_session_pin_parked";
|
|
48553
|
+
PARK_RETENTION_EXPIRED_REASON = "parked_task_retention_expired";
|
|
48554
|
+
}
|
|
48555
|
+
});
|
|
48556
|
+
function readPayloadTaskId(entry) {
|
|
48557
|
+
const value = entry.payload?.taskId;
|
|
48558
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
48559
|
+
}
|
|
48560
|
+
function parseTime(value) {
|
|
48561
|
+
if (!value) return null;
|
|
48562
|
+
const parsed = new Date(value).getTime();
|
|
48563
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
48564
|
+
}
|
|
48565
|
+
function computeMeshTaskStats(meshId, opts) {
|
|
48566
|
+
const queue = getQueue(meshId);
|
|
48567
|
+
const queueById = new Map(queue.map((task) => [task.id, task]));
|
|
48568
|
+
let targetIds;
|
|
48569
|
+
if (opts?.taskIds?.length) {
|
|
48570
|
+
targetIds = [...new Set(opts.taskIds)];
|
|
48571
|
+
} else if (opts?.missionId) {
|
|
48572
|
+
targetIds = queue.filter((task) => task.missionId === opts.missionId).map((task) => task.id);
|
|
48573
|
+
} else {
|
|
48574
|
+
targetIds = queue.map((task) => task.id);
|
|
48575
|
+
}
|
|
48576
|
+
if (targetIds.length === 0) return [];
|
|
48577
|
+
const targetSet = new Set(targetIds);
|
|
48578
|
+
const entries = readLedgerEntries(meshId, { tail: opts?.tail ?? 1e3 });
|
|
48579
|
+
const dispatches = /* @__PURE__ */ new Map();
|
|
48580
|
+
const terminals = /* @__PURE__ */ new Map();
|
|
48581
|
+
for (const entry of entries) {
|
|
48582
|
+
const taskId = readPayloadTaskId(entry);
|
|
48583
|
+
if (!taskId || !targetSet.has(taskId)) continue;
|
|
48584
|
+
if (entry.kind === "task_dispatched") {
|
|
48585
|
+
const existing = dispatches.get(taskId);
|
|
48586
|
+
if (existing) existing.count += 1;
|
|
48587
|
+
else dispatches.set(taskId, { first: entry.timestamp, count: 1 });
|
|
48588
|
+
} else if (entry.kind === "task_completed" || entry.kind === "task_failed") {
|
|
48589
|
+
terminals.set(taskId, { at: entry.timestamp, kind: entry.kind });
|
|
48590
|
+
}
|
|
48591
|
+
}
|
|
48592
|
+
return targetIds.map((taskId) => {
|
|
48593
|
+
const queueEntry = queueById.get(taskId);
|
|
48594
|
+
const dispatch = dispatches.get(taskId);
|
|
48595
|
+
const terminal = terminals.get(taskId);
|
|
48596
|
+
const status = queueEntry?.status ?? (terminal ? terminal.kind === "task_completed" ? "completed" : "failed" : "unknown");
|
|
48597
|
+
const isTerminalStatus = status === "completed" || status === "failed" || status === "cancelled";
|
|
48598
|
+
const dispatchTime = parseTime(dispatch?.first ?? queueEntry?.dispatchTimestamp);
|
|
48599
|
+
const terminalTime = parseTime(terminal?.at);
|
|
48600
|
+
const stats = {
|
|
48601
|
+
taskId,
|
|
48602
|
+
status,
|
|
48603
|
+
dispatchedAt: dispatch?.first ?? queueEntry?.dispatchTimestamp ?? null,
|
|
48604
|
+
terminalAt: terminal?.at ?? null,
|
|
48605
|
+
terminalKind: terminal?.kind ?? null,
|
|
48606
|
+
durationMs: null,
|
|
48607
|
+
dispatchCount: dispatch?.count ?? 0,
|
|
48608
|
+
requeueCount: queueEntry?.requeueCount ?? 0
|
|
48609
|
+
};
|
|
48610
|
+
if (dispatchTime !== null && terminalTime !== null && terminalTime >= dispatchTime) {
|
|
48611
|
+
stats.durationMs = terminalTime - dispatchTime;
|
|
48612
|
+
} else if (isTerminalStatus) {
|
|
48613
|
+
stats.incompleteEvidence = true;
|
|
48614
|
+
}
|
|
48615
|
+
return stats;
|
|
48616
|
+
});
|
|
48617
|
+
}
|
|
48618
|
+
function computeMeshMissionStats(meshId, missionId) {
|
|
48619
|
+
const tasks = computeMeshTaskStats(meshId, { missionId });
|
|
48620
|
+
const stats = {
|
|
48621
|
+
missionId,
|
|
48622
|
+
taskCount: tasks.length,
|
|
48623
|
+
completed: 0,
|
|
48624
|
+
failed: 0,
|
|
48625
|
+
totalDurationMs: 0,
|
|
48626
|
+
wallClockMs: null,
|
|
48627
|
+
retries: 0,
|
|
48628
|
+
incompleteTaskIds: []
|
|
48629
|
+
};
|
|
48630
|
+
let firstDispatch = null;
|
|
48631
|
+
let lastTerminal = null;
|
|
48632
|
+
for (const task of tasks) {
|
|
48633
|
+
if (task.status === "completed") stats.completed += 1;
|
|
48634
|
+
else if (task.status === "failed") stats.failed += 1;
|
|
48635
|
+
stats.retries += task.requeueCount;
|
|
48636
|
+
if (task.incompleteEvidence) {
|
|
48637
|
+
stats.incompleteTaskIds.push(task.taskId);
|
|
48638
|
+
continue;
|
|
48639
|
+
}
|
|
48640
|
+
if (task.durationMs !== null) stats.totalDurationMs += task.durationMs;
|
|
48641
|
+
const dispatchTime = parseTime(task.dispatchedAt);
|
|
48642
|
+
const terminalTime = parseTime(task.terminalAt);
|
|
48643
|
+
if (dispatchTime !== null && (firstDispatch === null || dispatchTime < firstDispatch)) firstDispatch = dispatchTime;
|
|
48644
|
+
if (terminalTime !== null && (lastTerminal === null || terminalTime > lastTerminal)) lastTerminal = terminalTime;
|
|
48645
|
+
}
|
|
48646
|
+
if (firstDispatch !== null && lastTerminal !== null && lastTerminal >= firstDispatch) {
|
|
48647
|
+
stats.wallClockMs = lastTerminal - firstDispatch;
|
|
48648
|
+
}
|
|
48649
|
+
return stats;
|
|
48650
|
+
}
|
|
48651
|
+
var init_mesh_task_stats = __esm2({
|
|
48652
|
+
"src/mesh/mesh-task-stats.ts"() {
|
|
48653
|
+
"use strict";
|
|
48654
|
+
init_mesh_ledger();
|
|
48655
|
+
init_mesh_work_queue();
|
|
48656
|
+
}
|
|
48657
|
+
});
|
|
48473
48658
|
var mesh_missions_exports = {};
|
|
48474
48659
|
__export2(mesh_missions_exports, {
|
|
48475
48660
|
COMPACT_STATUS_GOAL_PREVIEW_MAX: () => COMPACT_STATUS_GOAL_PREVIEW_MAX,
|
|
@@ -48855,11 +49040,12 @@ ${blocks.join("\n\n")}`;
|
|
|
48855
49040
|
describeTaskDependencyState: () => describeTaskDependencyState,
|
|
48856
49041
|
enqueueTask: () => enqueueTask,
|
|
48857
49042
|
enqueueTaskGraph: () => enqueueTaskGraph,
|
|
48858
|
-
|
|
49043
|
+
failRetentionExpiredParkedTask: () => failRetentionExpiredParkedTask,
|
|
48859
49044
|
formatMeshTaskModeViolations: () => formatMeshTaskModeViolations,
|
|
48860
49045
|
getActiveDirectDispatches: () => getActiveDirectDispatches,
|
|
48861
49046
|
getMeshQueueRevision: () => getMeshQueueRevision,
|
|
48862
49047
|
getMeshQueueStats: () => getMeshQueueStats,
|
|
49048
|
+
getParkedTasks: () => getParkedTasks,
|
|
48863
49049
|
getQueue: () => getQueue,
|
|
48864
49050
|
hasPendingDependents: () => hasPendingDependents,
|
|
48865
49051
|
insertDirectDispatch: () => insertDirectDispatch,
|
|
@@ -48871,6 +49057,7 @@ ${blocks.join("\n\n")}`;
|
|
|
48871
49057
|
normalizeMeshCapabilityTags: () => normalizeMeshCapabilityTags,
|
|
48872
49058
|
normalizeMeshTaskMode: () => normalizeMeshTaskMode,
|
|
48873
49059
|
normalizeMeshTaskPriority: () => normalizeMeshTaskPriority,
|
|
49060
|
+
parkTaskTargetPin: () => parkTaskTargetPin,
|
|
48874
49061
|
reclaimStrandedAssignedTask: () => reclaimStrandedAssignedTask,
|
|
48875
49062
|
recordDirectDispatchTask: () => recordDirectDispatchTask,
|
|
48876
49063
|
recordMeshToolCall: () => recordMeshToolCall,
|
|
@@ -49352,6 +49539,7 @@ ${blocks.join("\n\n")}`;
|
|
|
49352
49539
|
entry.status = "cancelled";
|
|
49353
49540
|
entry.cancelledAt = now;
|
|
49354
49541
|
if (opts?.reason) entry.cancelReason = opts.reason;
|
|
49542
|
+
delete entry.parked;
|
|
49355
49543
|
delete entry.assignedNodeId;
|
|
49356
49544
|
delete entry.assignedSessionId;
|
|
49357
49545
|
delete entry.assignedProviderType;
|
|
@@ -49384,6 +49572,12 @@ ${blocks.join("\n\n")}`;
|
|
|
49384
49572
|
if (value) lastCancelledTaskAssignment.delete(key2);
|
|
49385
49573
|
return value;
|
|
49386
49574
|
}
|
|
49575
|
+
function applyRequeueMessageEdit(entry, message) {
|
|
49576
|
+
if (typeof message !== "string") return;
|
|
49577
|
+
const next = message.trim();
|
|
49578
|
+
if (!next || next === entry.message) return;
|
|
49579
|
+
entry.message = next;
|
|
49580
|
+
}
|
|
49387
49581
|
function requeueTask(meshId, taskId, opts) {
|
|
49388
49582
|
requireMeshHostQueueOwner(opts);
|
|
49389
49583
|
const result = withQueueLock(meshId, () => {
|
|
@@ -49418,6 +49612,8 @@ ${blocks.join("\n\n")}`;
|
|
|
49418
49612
|
entry.requeuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
49419
49613
|
entry.dispatchFailureCount = dispatchFailures;
|
|
49420
49614
|
if (opts?.reason) entry.requeueReason = opts.reason;
|
|
49615
|
+
applyRequeueMessageEdit(entry, opts?.message);
|
|
49616
|
+
delete entry.parked;
|
|
49421
49617
|
const backoffMs = DISPATCH_RETRY_BACKOFF_BASE_MS * Math.pow(2, dispatchFailures - 1);
|
|
49422
49618
|
entry.notBefore = resolveNotBefore(Math.min(backoffMs, DISPATCH_RETRY_BACKOFF_MAX_MS));
|
|
49423
49619
|
MeshRuntimeStore.getInstance().updateQueueEntry(entry);
|
|
@@ -49446,6 +49642,8 @@ ${blocks.join("\n\n")}`;
|
|
|
49446
49642
|
entry.requeuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
49447
49643
|
entry.requeueCount = currentCount + 1;
|
|
49448
49644
|
if (opts?.reason) entry.requeueReason = opts.reason;
|
|
49645
|
+
applyRequeueMessageEdit(entry, opts?.message);
|
|
49646
|
+
delete entry.parked;
|
|
49449
49647
|
const notBefore = resolveNotBefore(opts?.notBefore);
|
|
49450
49648
|
if (notBefore) entry.notBefore = notBefore;
|
|
49451
49649
|
else delete entry.notBefore;
|
|
@@ -49455,24 +49653,46 @@ ${blocks.join("\n\n")}`;
|
|
|
49455
49653
|
if (result?.missionAffected) scheduleMissionCloseCandidateCheck(meshId, [result.entry, ...result.cascaded]);
|
|
49456
49654
|
return result ? result.entry : null;
|
|
49457
49655
|
}
|
|
49458
|
-
function
|
|
49656
|
+
function parkTaskTargetPin(meshId, taskId, opts) {
|
|
49459
49657
|
requireMeshHostQueueOwner(opts);
|
|
49460
49658
|
return withQueueLock(meshId, () => {
|
|
49461
49659
|
const entry = MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId);
|
|
49462
49660
|
if (!entry) return null;
|
|
49463
49661
|
if (entry.status !== "pending") return null;
|
|
49464
49662
|
if (!entry.targetSessionId && !entry.targetNodeId) return null;
|
|
49465
|
-
|
|
49466
|
-
const
|
|
49467
|
-
|
|
49468
|
-
|
|
49469
|
-
entry.updatedAt =
|
|
49470
|
-
|
|
49663
|
+
if (taskIsParked(entry)) return null;
|
|
49664
|
+
const reason = opts?.reason || PARK_REASON_PIN_EXPIRED;
|
|
49665
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
49666
|
+
entry.parked = buildParkingRecord(entry, reason, now);
|
|
49667
|
+
entry.updatedAt = now;
|
|
49668
|
+
entry.requeueReason = reason;
|
|
49471
49669
|
MeshRuntimeStore.getInstance().updateQueueEntry(entry);
|
|
49472
|
-
|
|
49670
|
+
logTaskParked(meshId, taskId, reason, entry.targetSessionId);
|
|
49473
49671
|
return entry;
|
|
49474
49672
|
});
|
|
49475
49673
|
}
|
|
49674
|
+
function failRetentionExpiredParkedTask(meshId, taskId, opts) {
|
|
49675
|
+
requireMeshHostQueueOwner(opts);
|
|
49676
|
+
const result = withQueueLock(meshId, () => {
|
|
49677
|
+
const entry = MeshRuntimeStore.getInstance().findQueueEntryById(meshId, taskId);
|
|
49678
|
+
if (!entry) return null;
|
|
49679
|
+
if (entry.status !== "pending" || !taskIsParked(entry)) return null;
|
|
49680
|
+
if (!parkedTaskRetentionExpired(entry, Date.now(), opts?.retentionMs)) return null;
|
|
49681
|
+
const hours = Math.round((opts?.retentionMs ?? PARKED_TASK_RETENTION_MS) / 36e5);
|
|
49682
|
+
entry.status = "failed";
|
|
49683
|
+
entry.cancelReason = `${PARK_RETENTION_EXPIRED_REASON}: parked for over ${hours}h (addressed to session ${entry.parked?.targetSessionId || "unknown"}) with no coordinator decision`;
|
|
49684
|
+
entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
49685
|
+
MeshRuntimeStore.getInstance().updateQueueEntry(entry);
|
|
49686
|
+
const cascaded = propagateDependencyFailure(meshId, taskId);
|
|
49687
|
+
LOG2.warn("MeshQueue", `PIN-PARKING retention: task ${taskId} (mesh ${meshId}) stayed parked past ${hours}h with no coordinator decision; failed it (dependents unblocked). This is reported to the coordinator, never a silent drop.`);
|
|
49688
|
+
return { entry, cascaded, missionAffected: true };
|
|
49689
|
+
});
|
|
49690
|
+
if (result?.missionAffected) scheduleMissionCloseCandidateCheck(meshId, [result.entry, ...result.cascaded]);
|
|
49691
|
+
return result ? result.entry : null;
|
|
49692
|
+
}
|
|
49693
|
+
function getParkedTasks(meshId) {
|
|
49694
|
+
return getQueue(meshId, { status: ["pending"] }).filter(taskIsParked);
|
|
49695
|
+
}
|
|
49476
49696
|
function reclaimStrandedAssignedTask(meshId, taskId, opts) {
|
|
49477
49697
|
requireMeshHostQueueOwner(opts);
|
|
49478
49698
|
const result = withQueueLock(meshId, () => {
|
|
@@ -49700,6 +49920,7 @@ ${blocks.join("\n\n")}`;
|
|
|
49700
49920
|
init_mesh_graph_derived_failure();
|
|
49701
49921
|
init_dist();
|
|
49702
49922
|
init_mesh_task_mode_guardrail();
|
|
49923
|
+
init_mesh_task_parking();
|
|
49703
49924
|
init_mesh_task_mode_guardrail();
|
|
49704
49925
|
ACTIVE_MESH_QUEUE_STATUSES = ["pending", "assigned"];
|
|
49705
49926
|
HISTORICAL_MESH_QUEUE_STATUSES = ["completed", "failed", "cancelled"];
|
|
@@ -52571,6 +52792,7 @@ ${blocks.join("\n\n")}`;
|
|
|
52571
52792
|
init_config();
|
|
52572
52793
|
init_mesh_ledger();
|
|
52573
52794
|
init_mesh_work_queue();
|
|
52795
|
+
init_mesh_task_parking();
|
|
52574
52796
|
init_mesh_graph_schema();
|
|
52575
52797
|
init_mesh_graph_store();
|
|
52576
52798
|
init_slot_model_enforcement();
|
|
@@ -53539,7 +53761,8 @@ ${blocks.join("\n\n")}`;
|
|
|
53539
53761
|
};
|
|
53540
53762
|
const allowedTaskDifficulties = opts?.allowedTaskDifficulties;
|
|
53541
53763
|
const difficultyAllows = (candidate) => !allowedTaskDifficulties || candidate.difficulty === "freeform" || !candidate.difficulty || allowedTaskDifficulties.includes(candidate.difficulty);
|
|
53542
|
-
const
|
|
53764
|
+
const notParked = (candidate) => !taskIsParked(candidate);
|
|
53765
|
+
const entry = candidates.find((candidate) => nodeSatisfiesRequiredTags(candidate.requiredTags, capabilityTags) && dependenciesSatisfied(candidate) && notBeforeReady(candidate) && notParked(candidate) && convergenceAllows(candidate) && targetMatches(candidate) && difficultyAllows(candidate) && parallelCapsAllow(candidate) && nodeConflictAllows(candidate));
|
|
53543
53766
|
if (!entry) return null;
|
|
53544
53767
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
53545
53768
|
entry.status = "assigned";
|
|
@@ -55255,6 +55478,7 @@ ${blocks.join("\n\n")}`;
|
|
|
55255
55478
|
compactLedger: () => compactLedger,
|
|
55256
55479
|
enforceAllLedgerRotationCaps: () => enforceAllLedgerRotationCaps,
|
|
55257
55480
|
enforceLedgerRotationCap: () => enforceLedgerRotationCap,
|
|
55481
|
+
extractJsonObjectFromSummary: () => extractJsonObjectFromSummary,
|
|
55258
55482
|
getLedgerDir: () => getLedgerDir2,
|
|
55259
55483
|
getLedgerSummary: () => getLedgerSummary,
|
|
55260
55484
|
getSessionRecoveryContext: () => getSessionRecoveryContext,
|
|
@@ -56873,11 +57097,12 @@ ${rules.join("\n")}`;
|
|
|
56873
57097
|
return `## Rules
|
|
56874
57098
|
|
|
56875
57099
|
- **Route, don't implement.** Delegate all code reading, analysis, and execution to node agents. Never read source files or run commands in the coordinator \u2014 keep context lean. See also: **Never use local sub-agents** below.${destructiveGitRule}
|
|
56876
|
-
- **Never use local sub-agents.** Do NOT spawn your runtime's own sub-agents (e.g. Claude Code's Task/Explore/Agent tools, or any equivalent in-process agent-spawning tool) to read code, investigate, run RCA, or implement. Such sub-agents execute on the coordinator's machine, outside the mesh \u2014 they escape mesh parallelism, the ledger/audit trail, node capability profiles, and worktree isolation, and leave no \`mesh_task_history\` record. ALL code reading, analysis, RCA, and implementation must be delegated through \`mesh_enqueue_batch\`
|
|
56877
|
-
- **Front-load immutable task instructions.** Include everything the agent needs (files, problem, expected fix) in \`mesh_enqueue_batch\`
|
|
57100
|
+
- **Never use local sub-agents.** Do NOT spawn your runtime's own sub-agents (e.g. Claude Code's Task/Explore/Agent tools, or any equivalent in-process agent-spawning tool) to read code, investigate, run RCA, or implement. Such sub-agents execute on the coordinator's machine, outside the mesh \u2014 they escape mesh parallelism, the ledger/audit trail, node capability profiles, and worktree isolation, and leave no \`mesh_task_history\` record. ALL code reading, analysis, RCA, and implementation must be delegated through \`mesh_enqueue_batch\` (the default \u2014 see Workflow 3.a), falling back to \`mesh_enqueue_task\` only for a terminal single step, to \`mesh_send_task\` for a same-session continuation (use \`task_mode: "live_debug_readonly"\` for read-only investigation), or cross-verified via \`mesh_magi_review\` for read-only fan-out. The coordinator's own actions are limited to \`mesh_*\` tool orchestration and synthesizing results.
|
|
57101
|
+
- **Front-load immutable task instructions.** Include everything the agent needs (files, problem, expected fix) in whichever dispatch surface Workflow 3.a selects (\`mesh_enqueue_batch\` by default; \`mesh_enqueue_task\` / \`mesh_send_task\` in their narrower cases). Put predecessor-produced data in explicit \`inputs_from\` bindings and coordinator decisions in gates; never copy untrusted worker output into a new instruction by hand when a binding can preserve provenance. Append a structured result request at the end: ask the worker to conclude with a JSON block containing \`status\`, \`changedFiles\`, \`gitStatus\`, \`validationResults\`, \`errors\`, \`nextAction\`. The daemon parses this automatically; you can read it from \`mesh_task_history\`.
|
|
56878
57102
|
- **Reuse idle sessions.** For follow-up, retry, commit/push, or cleanup on the same issue, send only the delta to the existing idle session. Start a fresh session only when: (a) branch/worktree isolation is required, (b) the existing session had a dispatch failure or provider mismatch, (c) the transcript/runtime is contaminated or interrupted, (d) the user explicitly asks for a different provider/session, or (e) **the delta is a genuinely NEW subject rather than a continuation** \u2014 a new topic appended to an existing session can be dropped or re-run as the previous task, so give it its own task even when a session sits idle. Continuation of the same issue in an already-idle session is allowed and preferred \u2014 this rule blocks concurrent unrelated work interleaved into a live (still-generating) session, not sequential same-issue follow-ups. The test is subject continuity, not timing: carrying an investigation forward into its own fix is the SAME subject and belongs in that session (Workflow 3f), while an unrelated bug is a new subject even if the same session just went idle.
|
|
56879
57103
|
- **Nodes are separate machines with separate checkouts \u2014 not interchangeable execution slots.** Each node is a different physical computer with its own clone of the repo. Work done on another node must be committed, pushed, and pulled back before this machine sees it, and since RELEASE/DEPLOY runs on the coordinator's own machine, sending a code change elsewhere buys a round trip out and another one back. So **default to this coordinator's own machine for code changes** \u2014 its local node (base or a worktree cloned from it). Routing to a DIFFERENT machine is the exception and needs a reason, of which there are exactly two: (a) **platform-specific verification** that cannot be done here \u2014 win32 PATH/registry, a clean install/uninstall on that OS, that machine's package-manager state; or (b) **parallelizing read-only investigation** across machines. "That node is idle" is not a reason. If you catch yourself dispatching a fix to another machine without (a) or (b), route it here instead.
|
|
56880
57104
|
- **Don't split investigation from the fix.** When a task will plainly end in a code change, dispatch it as \`code_change\` from the start \u2014 the in-session handoff and split criteria live in Workflow 3f. Split only when the fix genuinely belongs on another machine for reason (a) above; redoing an investigator's context in a fresh session (worse, on another machine) is pure loss.
|
|
57105
|
+
- **Batch is the default enqueue surface.** Apply Workflow 3.a: before every enqueue, ask whether you will read this task's result and then dispatch more work. If yes, that successor is declarable \u2014 submit the whole plan as one \`mesh_enqueue_batch\` with \`inputs_from\` / \`run_if\` / \`gated_by\`, rather than enqueueing a step, waiting, and enqueueing the next by hand. \`mesh_enqueue_task\` is the fallback for a genuinely terminal single step, and it takes an \`orchestration_decision\` stating which \`single_reason\` applies.
|
|
56881
57106
|
- **Base nodes are reserved for environment-specific testing.** Apply Workflow 3.b0: only work that verifies a machine's physical environment runs on a base node (pinned with \`required_tags\`/\`target_node_id\`); every ordinary \`code_change\` gets its own cloned worktree. Node availability is not branch isolation.
|
|
56882
57107
|
- **Worktree affinity.** Apply Workflow 3.b1: route a branch's follow-ups back to its own worktree node (\`required_tags: ["worktree=<branch>"]\` or \`target_node_id\`, taken from the \`mesh_clone_node\` result or a live \`mesh_status\`); only \`convergence\` (merge/push) runs base-side.
|
|
56883
57108
|
- **Classify task difficulty honestly.** Judge each task's real difficulty (\`easy\`/\`medium\`/\`difficult\`/\`freeform\`) per the Task difficulty section above \u2014 it is a routing hint, and the matched slot's own model/thinking is what launches. Never bend difficulty to chase a model; retune slots instead (\`mesh_node_slots_set\`).
|
|
@@ -56943,7 +57168,7 @@ When you compose the task message you dispatch to a node, include this requireme
|
|
|
56943
57168
|
| \`mesh_view_queue\` | Queue status \u2014 pending/assigned/completed/failed/cancelled |
|
|
56944
57169
|
| \`mesh_graph_view\` | Inspect orchestration graphs \u2014 node states, gates awaiting you, workspace sagas, why something is blocked |
|
|
56945
57170
|
| \`mesh_graph_gate_claim\` | Take the lease on a gate awaiting a coordinator; returns the fencing token + generation a release needs |
|
|
56946
|
-
| \`mesh_graph_gate_release\` | Pass a gate you hold \u2014 the ONLY way through one (no timeout ever passes a gate) |
|
|
57171
|
+
| \`mesh_graph_gate_release\` | Pass a gate you hold \u2014 the ONLY way through one (no timeout ever passes a gate). **A gate and its dependents are one unit**: a gate earns its keep only when some task names it in \`gated_by\`, because releasing it is what dispatches that task. A gate nothing depends on opens nothing and is pure claim/release overhead \u2014 declare the follower in the same batch, or skip the gate |
|
|
56947
57172
|
| \`mesh_queue_cancel\` | Cancel a queue task (audit history kept) |
|
|
56948
57173
|
| \`mesh_queue_requeue\` | Return a task to pending for retry |
|
|
56949
57174
|
| \`mesh_send_task\` | Push a task straight to a specific node/session |
|
|
@@ -57008,13 +57233,22 @@ Before doing any coordinator work, confirm that the actual callable tool list in
|
|
|
57008
57233
|
WORKFLOW_SECTION = `## Orchestration Workflow
|
|
57009
57234
|
|
|
57010
57235
|
1. **Assess** \u2014 Call \`mesh_status\` to see which nodes are healthy and available. Check \`mesh_task_history\` to understand what has already been done in this mesh \u2014 previous delegations, completions, and failures.
|
|
57011
|
-
2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist. If \`mesh_task_history\` shows a recent failure for a task, decide whether to retry or reassign. **For multi-task work, create a mission first**: call \`mesh_mission_upsert\` with a title and goal, then
|
|
57236
|
+
2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist. If \`mesh_task_history\` shows a recent failure for a task, decide whether to retry or reassign. **For multi-task work, create a mission first**: call \`mesh_mission_upsert\` with a title and goal, then submit the plan as one \`mesh_enqueue_batch\` carrying that \`mission_id\` (a top-level \`mission_id\` applies to every entry). Express "B after A" ordering with \`depends_on\` instead of waiting and polling \u2014 the system claims dependents automatically when their dependencies complete. When the mission's outcome is decided, update its status (\`completed\`/\`abandoned\`) via \`mesh_mission_upsert\`. If the prompt already shows an **Active Mission**, continue it from its current task state \u2014 do not re-enqueue tasks that already exist.
|
|
57012
57237
|
3. **Queue / Delegate** \u2014 The Mesh uses an autonomous pull-based Work Queue:
|
|
57013
|
-
a. **Batch-first rule
|
|
57238
|
+
a. **Batch-first rule.** Before enqueueing anything, answer ONE question: *when this task reports back, will I read its result and then dispatch more work \u2014 or does the result go to the user with nothing behind it?*
|
|
57239
|
+
- **You will act on the result \u2192 \`mesh_enqueue_batch\`.** If you already intend to read the output and dispatch a next step, that next step is a step you can declare. Declare it: bind the evidence with \`inputs_from\`, branch on it with \`run_if\`, or put your own decision behind a coordinator gate and point the follower at it with \`gated_by\`. "I need to see the result first" is HOW a graph edge is expressed, not a reason to skip the graph \u2014 that is exactly what \`inputs_from\` and gates are for. An investigation you plan to act on is, by this test, a declarable two-step plan.
|
|
57240
|
+
- **The result goes to the user and nothing follows \u2192 \`mesh_enqueue_task\`.** One task, no successor you intend to dispatch yourself. Same-subject continuation of an already-running session is \`mesh_send_task\`, not a new enqueue either way.
|
|
57241
|
+
- Submit the whole materializable plan in ONE batch. A batch may mix worker tasks, \`inputs_from\` bindings, \`run_if\` branches, delayed \`workspace_ref\` preparation, and coordinator gates for approval, Refinery, CI waiting, publish, or deployment. Do not enqueue one known step, wait for it, and then enqueue the next known step by hand.
|
|
57242
|
+
- **A mesh with idle nodes or a short plan does not remove this requirement.** "It is only two steps" and "I will just enqueue the next one when this finishes" are the two ways the graph gets skipped; neither is a reason. Two declarable steps is precisely the case the batch surface exists for.
|
|
57243
|
+
- **"The only thing I am sure of right now is this one task" is not evidence of a single.** That feeling is about your confidence, not about how many steps are settled; two steps are routinely settled at once while it still feels that way. Count the steps you would actually dispatch. If that count is two or more, it is a batch no matter how the plan feels.
|
|
57244
|
+
- **"I cannot choose the next step until I see this result" states that the next step EXISTS.** What the result decides is *which branch you take*, not *whether* there is a follow-up \u2014 and a branch that turns on a predecessor's output is what \`run_if\` selects, what \`inputs_from\` feeds, and what a coordinator gate holds until you decide. Needing to look at evidence before choosing is the ordinary case for a graph edge, not an exception to it.
|
|
57245
|
+
- **A gate or a batch is not overhead when something follows it.** The extra call buys the dispatch of everything behind it, so it costs one call and saves a whole round-trip through you. The only genuinely wasteful gate is one nothing depends on \u2014 a gate no task names in \`gated_by\` opens nothing when released, which the release response tells you about; that case is answered by declaring the follower, not by abandoning the batch.
|
|
57014
57246
|
|
|
57015
|
-
|
|
57247
|
+
When you do use \`mesh_enqueue_task\`, pass \`orchestration_decision\` recording why a single was right (\`single_reason\`: \`only_one_step_known\` / \`future_step_not_specifiable\` / \`same_session_continuation\` / \`legacy_client\` / \`operator_override\`). Omitting it is recorded as \`decision_missing\`.
|
|
57016
57248
|
|
|
57017
|
-
Do not invent speculative downstream instructions merely to form a batch.
|
|
57249
|
+
Do not invent speculative downstream instructions merely to form a batch \u2014 a step you would not actually dispatch does not belong in the graph. But check the three declaration surfaces before concluding a step is unstatable: \`inputs_from\` when the instruction is stable and only needs predecessor evidence, a coordinator gate when a human/coordinator decision or external side effect must come first, \`run_if\` when the branch turns on a structured result predicate. Only when none of the three can state the future step faithfully is single-task enqueue correct.
|
|
57250
|
+
|
|
57251
|
+
**The line between the two failures.** The rebuttals above are aimed at declining to declare a step you already intend to dispatch; this guard is aimed at fabricating one you do not. They never both apply to the same step: the test is whether the step exists in your plan, not whether its details are settled. A step you intend to run but whose exact branch depends on evidence is KNOWN and belongs in the batch \u2014 that dependency is what the three surfaces express. A step you would have to make up to fill the batch is UNKNOWN and belongs nowhere. When you genuinely cannot tell which one you are looking at, enqueue the single and record \`future_step_not_specifiable\`; that is the honest answer and it is measured.
|
|
57018
57252
|
|
|
57019
57253
|
Either way, idle node agents automatically pull tasks from the queue and begin working. A batch inserts atomically (a mid-batch error such as a cycle or bad difficulty rolls the whole set back, so no half-registered chain), and each task's \`depends_on\` may name sibling tasks by their batch-local \`ref\` label, forward references included.
|
|
57020
57254
|
b. **Node Preparation**: Reuse an existing idle session on the correct node/provider before launching a new chat/session. Call \`mesh_launch_session\` only when no suitable session exists, when the user explicitly asks for a fresh provider/session, or when branch/worktree isolation requires it. **A node is not limited to one live session for read-only work** \u2014 \`readonly\`/\`live_debug_readonly\` tasks are exempt from the one-active-per-node invariant, so the SAME node can auto-launch multiple concurrent read-only sessions with no worktree needed. Cloning a worktree costs roughly 10 seconds, so it is cheap enough to create one whenever write work needs a free node; use it for branch isolation, for parallel write tasks (one active write per node), or when a node's read-only queue is deep enough that a second node would clearly finish faster \u2014 call \`mesh_clone_node\` to create the worktree node first.
|
|
@@ -59510,6 +59744,7 @@ ${rendered}`, "utf-8");
|
|
|
59510
59744
|
void opts.registerNode;
|
|
59511
59745
|
return {
|
|
59512
59746
|
nowMs: () => Date.now(),
|
|
59747
|
+
resolveBaseRevision: (req) => defaultResolveBaseRevision(req),
|
|
59513
59748
|
createWorktree: (req) => defaultCreateWorktree(req),
|
|
59514
59749
|
findOwnedWorktree: (req) => defaultFindOwnedWorktree(req),
|
|
59515
59750
|
inspectWorktree: (req) => defaultInspectWorktree(req),
|
|
@@ -59555,6 +59790,40 @@ ${rendered}`, "utf-8");
|
|
|
59555
59790
|
env: gitChildEnv()
|
|
59556
59791
|
});
|
|
59557
59792
|
}
|
|
59793
|
+
async function defaultResolveBaseRevision(req) {
|
|
59794
|
+
let repoRoot;
|
|
59795
|
+
try {
|
|
59796
|
+
({ repoRoot } = resolveSourceRepo(req.meshId, req.sourceNodeId));
|
|
59797
|
+
} catch {
|
|
59798
|
+
return void 0;
|
|
59799
|
+
}
|
|
59800
|
+
try {
|
|
59801
|
+
const { stdout } = await execFileAsync4("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
59802
|
+
cwd: repoRoot,
|
|
59803
|
+
encoding: "utf8",
|
|
59804
|
+
timeout: GIT_TIMEOUT_MS3,
|
|
59805
|
+
maxBuffer: GIT_MAX_BUFFER2,
|
|
59806
|
+
windowsHide: true,
|
|
59807
|
+
env: gitChildEnv()
|
|
59808
|
+
});
|
|
59809
|
+
const branch = stdout.trim();
|
|
59810
|
+
if (branch && branch !== "HEAD") return branch;
|
|
59811
|
+
} catch {
|
|
59812
|
+
}
|
|
59813
|
+
try {
|
|
59814
|
+
const { stdout } = await execFileAsync4("git", ["rev-parse", "HEAD"], {
|
|
59815
|
+
cwd: repoRoot,
|
|
59816
|
+
encoding: "utf8",
|
|
59817
|
+
timeout: GIT_TIMEOUT_MS3,
|
|
59818
|
+
maxBuffer: GIT_MAX_BUFFER2,
|
|
59819
|
+
windowsHide: true,
|
|
59820
|
+
env: gitChildEnv()
|
|
59821
|
+
});
|
|
59822
|
+
return stdout.trim() || void 0;
|
|
59823
|
+
} catch {
|
|
59824
|
+
return void 0;
|
|
59825
|
+
}
|
|
59826
|
+
}
|
|
59558
59827
|
async function defaultCreateWorktree(req) {
|
|
59559
59828
|
const existing = await defaultFindOwnedWorktree({
|
|
59560
59829
|
meshId: req.meshId,
|
|
@@ -59900,8 +60169,26 @@ ${rendered}`, "utf-8");
|
|
|
59900
60169
|
}
|
|
59901
60170
|
return compensateClaimedIntent({ ...intent, ownerTag }, ports, generation);
|
|
59902
60171
|
}
|
|
59903
|
-
|
|
59904
|
-
|
|
60172
|
+
let baseRevision = intent.baseRevision;
|
|
60173
|
+
if (intent.sagaState === "declared" && !baseRevision) {
|
|
60174
|
+
const derived = await ports.resolveBaseRevision({
|
|
60175
|
+
meshId: intent.meshId,
|
|
60176
|
+
graphId,
|
|
60177
|
+
workspaceRef,
|
|
60178
|
+
sourceNodeId: intent.sourceNodeId
|
|
60179
|
+
}).catch(() => void 0);
|
|
60180
|
+
if (derived) {
|
|
60181
|
+
store.transaction(() => {
|
|
60182
|
+
store.graphStore().patchWorkspaceIntent(graphId, workspaceRef, {
|
|
60183
|
+
baseRevision: derived
|
|
60184
|
+
}, nowIso, { leaseGeneration: generation });
|
|
60185
|
+
});
|
|
60186
|
+
baseRevision = derived;
|
|
60187
|
+
LOG2.info("MeshGraphWorkspace", `derived base revision '${derived}' for ${graphId}/${workspaceRef} from source node ${intent.sourceNodeId ?? "base"}`);
|
|
60188
|
+
} else {
|
|
60189
|
+
LOG2.debug("MeshGraphWorkspace", `${graphId}/${workspaceRef} stays declared: no base_revision and none derivable from source node ${intent.sourceNodeId ?? "(none declared)"}`);
|
|
60190
|
+
return { graphId, workspaceRef, sagaState: "declared", action: "skipped_no_base" };
|
|
60191
|
+
}
|
|
59905
60192
|
}
|
|
59906
60193
|
store.transaction(() => {
|
|
59907
60194
|
store.graphStore().patchWorkspaceIntent(graphId, workspaceRef, {
|
|
@@ -59909,7 +60196,7 @@ ${rendered}`, "utf-8");
|
|
|
59909
60196
|
ownerTag
|
|
59910
60197
|
}, nowIso, { leaseGeneration: generation });
|
|
59911
60198
|
});
|
|
59912
|
-
return prepareClaimedIntent({ ...intent, sagaState: "preparing", ownerTag }, ports, generation, faults);
|
|
60199
|
+
return prepareClaimedIntent({ ...intent, baseRevision, sagaState: "preparing", ownerTag }, ports, generation, faults);
|
|
59913
60200
|
}
|
|
59914
60201
|
async function prepareClaimedIntent(intent, ports, generation, faults) {
|
|
59915
60202
|
const store = MeshRuntimeStore.getInstance();
|
|
@@ -60537,9 +60824,11 @@ ${rendered}`, "utf-8");
|
|
|
60537
60824
|
const materializedNodeIds = advanceReleasedGateDownstream(store, gate, node, nowIso);
|
|
60538
60825
|
const freshNodes = graphStore.listNodes(gate.graphId);
|
|
60539
60826
|
const graph = graphStore.getGraph(gate.graphId);
|
|
60827
|
+
let graphCompleted = false;
|
|
60540
60828
|
if (graph && freshNodes.every(isTerminalEquivalent)) {
|
|
60541
60829
|
graphStore.updateGraphStatus(gate.graphId, "completed", nowIso, true);
|
|
60542
60830
|
insertGateOutbox(graphStore, gate.meshId, gate.graphId, "graph_completed", { graphId: gate.graphId }, nowIso);
|
|
60831
|
+
graphCompleted = true;
|
|
60543
60832
|
} else if (graph?.status === "waiting_gate") {
|
|
60544
60833
|
const stillWaiting = graphStore.listGatesByGraph(gate.graphId).some((g) => g.gateId !== gate.gateId && (g.state === "awaiting_coordinator" || g.state === "claimed"));
|
|
60545
60834
|
if (!stillWaiting) graphStore.updateGraphStatus(gate.graphId, "active", nowIso);
|
|
@@ -60563,7 +60852,16 @@ ${rendered}`, "utf-8");
|
|
|
60563
60852
|
gateId: gate.gateId
|
|
60564
60853
|
}, nowIso);
|
|
60565
60854
|
}
|
|
60566
|
-
return {
|
|
60855
|
+
return {
|
|
60856
|
+
released: true,
|
|
60857
|
+
duplicate: false,
|
|
60858
|
+
gate: graphStore.getGate(gate.gateId),
|
|
60859
|
+
materializedNodeIds,
|
|
60860
|
+
// Read from the edge set this release already walked — a gate with an empty
|
|
60861
|
+
// directDownstream is terminal, and terminal gates materialize nothing by design.
|
|
60862
|
+
downstreamNodeCount: directDownstream.size,
|
|
60863
|
+
graphCompleted
|
|
60864
|
+
};
|
|
60567
60865
|
});
|
|
60568
60866
|
try {
|
|
60569
60867
|
drainMeshGraphOutbox(input.meshId);
|
|
@@ -60824,6 +61122,18 @@ ${rendered}`, "utf-8");
|
|
|
60824
61122
|
...p.replayed ? { replayed: true } : {}
|
|
60825
61123
|
});
|
|
60826
61124
|
}
|
|
61125
|
+
function recordSingleEnqueueDecision(meshId, p) {
|
|
61126
|
+
safeAppend(meshId, "single_enqueue_decision", {
|
|
61127
|
+
taskId: p.taskId,
|
|
61128
|
+
enqueueSurface: "single",
|
|
61129
|
+
...p.missionId ? { missionId: p.missionId } : {},
|
|
61130
|
+
...p.coordinatorSessionId ? { coordinatorSessionId: p.coordinatorSessionId } : {},
|
|
61131
|
+
orchestrationDecision: p.decision,
|
|
61132
|
+
...p.decisionMissing ? { decisionMissing: true } : {},
|
|
61133
|
+
...p.declaredEligibleSingle ? { declaredEligibleSingle: true } : {},
|
|
61134
|
+
...p.batchCapabilityAvailable ? { batchCapabilityAvailable: p.batchCapabilityAvailable } : {}
|
|
61135
|
+
}, p.taskId);
|
|
61136
|
+
}
|
|
60827
61137
|
function recordGraphEnqueueValidationFailed(meshId, p) {
|
|
60828
61138
|
safeAppend(meshId, "graph_enqueue_validation_failed", {
|
|
60829
61139
|
code: p.code,
|
|
@@ -60881,6 +61191,7 @@ ${rendered}`, "utf-8");
|
|
|
60881
61191
|
}
|
|
60882
61192
|
var MESH_VALID_SINGLE_REASONS;
|
|
60883
61193
|
var MESH_SUPERSEDED_SINGLE_REASONS;
|
|
61194
|
+
var MESH_DECLARED_ELIGIBLE_SINGLE_HINT;
|
|
60884
61195
|
var init_mesh_graph_provenance = __esm2({
|
|
60885
61196
|
"src/mesh/mesh-graph-provenance.ts"() {
|
|
60886
61197
|
"use strict";
|
|
@@ -60898,6 +61209,7 @@ ${rendered}`, "utf-8");
|
|
|
60898
61209
|
"workspace_unresolved",
|
|
60899
61210
|
"coordinator_action_between"
|
|
60900
61211
|
];
|
|
61212
|
+
MESH_DECLARED_ELIGIBLE_SINGLE_HINT = "You declared known_graph_steps >= 2 but used the single-task surface. Submit the known steps as one mesh_enqueue_batch \u2014 its input bindings, conditions and coordinator gates declare the ones that need predecessor evidence, a condition, or a coordinator action \u2014 instead of enqueueing them one at a time.";
|
|
60901
61213
|
}
|
|
60902
61214
|
});
|
|
60903
61215
|
function turnProjectionActiveWorkStatus(meshId, taskId) {
|
|
@@ -61748,8 +62060,28 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
61748
62060
|
}
|
|
61749
62061
|
};
|
|
61750
62062
|
}
|
|
61751
|
-
function
|
|
61752
|
-
|
|
62063
|
+
function liveLocalProviderEnablementForRouting(loader2, isLocalNode) {
|
|
62064
|
+
if (!loader2) return null;
|
|
62065
|
+
const verdicts = /* @__PURE__ */ new Map();
|
|
62066
|
+
return {
|
|
62067
|
+
isMachineProviderEnabled: (providerType) => loader2.isMachineProviderEnabled(providerType),
|
|
62068
|
+
isMachineQuotaEnabled: (providerType) => loader2.isMachineQuotaEnabled ? loader2.isMachineQuotaEnabled(providerType) : true,
|
|
62069
|
+
isLocalNode(node) {
|
|
62070
|
+
let verdict = verdicts.get(node);
|
|
62071
|
+
if (verdict === void 0) {
|
|
62072
|
+
verdict = !!node && isLocalNode(node) === true;
|
|
62073
|
+
verdicts.set(node, verdict);
|
|
62074
|
+
}
|
|
62075
|
+
return verdict;
|
|
62076
|
+
}
|
|
62077
|
+
};
|
|
62078
|
+
}
|
|
62079
|
+
function quotaFactsContextForLiveRouting(mesh, isLocalNode, providerLoader) {
|
|
62080
|
+
return {
|
|
62081
|
+
nodes: mesh?.nodes,
|
|
62082
|
+
liveLocalQuota: liveLocalQuotaForRouting(isLocalNode),
|
|
62083
|
+
providerEnablement: liveLocalProviderEnablementForRouting(providerLoader, isLocalNode)
|
|
62084
|
+
};
|
|
61753
62085
|
}
|
|
61754
62086
|
function directQuotaEntryFor(node, providerType) {
|
|
61755
62087
|
const facts = node?.nodeFacts;
|
|
@@ -61806,6 +62138,28 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
61806
62138
|
const ageText = Number.isFinite(ageMs2) ? `${Math.round(ageMs2 / 6e4)}m` : "unparseable";
|
|
61807
62139
|
LOG2.info("MeshQuota", `QUOTA GATE: provider '${providerType}' on node ${nodeId} fails open \u2014 snapshot age ${ageText} exceeds the ${Math.round(staleAfterMs / 6e4)}m freshness threshold`);
|
|
61808
62140
|
}
|
|
62141
|
+
function classifyAbsentQuotaReason(node, providerType, context) {
|
|
62142
|
+
const enablement = context?.providerEnablement;
|
|
62143
|
+
if (!enablement) return "unclassified_remote";
|
|
62144
|
+
const sourceNode = cloneSourceNodeFor(node, context);
|
|
62145
|
+
if (!enablement.isLocalNode(node) && !(sourceNode !== void 0 && enablement.isLocalNode(sourceNode))) {
|
|
62146
|
+
return "unclassified_remote";
|
|
62147
|
+
}
|
|
62148
|
+
if (!enablement.isMachineProviderEnabled(providerType)) return "provider_disabled";
|
|
62149
|
+
if (!enablement.isMachineQuotaEnabled(providerType)) return "probe_disabled";
|
|
62150
|
+
return "not_measured";
|
|
62151
|
+
}
|
|
62152
|
+
function logAbsentQuotaFailOpen(node, providerType, policy, now, context) {
|
|
62153
|
+
const staleAfterMs = resolveQuotaRoutingPolicy(policy).staleAfterMs;
|
|
62154
|
+
const nodeId = typeof node?.nodeId === "string" && node.nodeId ? node.nodeId : typeof node?.id === "string" && node.id ? node.id : "unknown";
|
|
62155
|
+
const key2 = `${nodeId} ${providerType}`;
|
|
62156
|
+
const last = absentFailOpenLoggedAt.get(key2);
|
|
62157
|
+
if (last !== void 0 && now - last < staleAfterMs) return;
|
|
62158
|
+
absentFailOpenLoggedAt.set(key2, now);
|
|
62159
|
+
const reason = classifyAbsentQuotaReason(node, providerType, context);
|
|
62160
|
+
const reasonText = reason === "probe_disabled" ? "quota probing is disabled for this provider on this machine (quotaEnabled: false \u2014 an intended opt-out, not a fault)" : reason === "provider_disabled" ? "this provider is not enabled on this machine" : reason === "not_measured" ? "no snapshot has been measured yet" : "this daemon cannot read that node's machineProviders config (remote node, or its own daemon has not injected one) \u2014 cannot tell not-yet-measured from an opt-out; check the owning node's config directly";
|
|
62161
|
+
LOG2.info("MeshQuota", `QUOTA GATE: provider '${providerType}' on node ${nodeId} fails open \u2014 no quota entry (${reason}: ${reasonText})`);
|
|
62162
|
+
}
|
|
61809
62163
|
function isSessionResetImminent(session, facts, quota, imminentMs, now) {
|
|
61810
62164
|
const resetsAt = Number(session?.resetsAt);
|
|
61811
62165
|
if (!Number.isFinite(resetsAt) || resetsAt <= 0) return false;
|
|
@@ -61827,7 +62181,10 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
61827
62181
|
}
|
|
61828
62182
|
function evaluateProviderQuotaGate(node, providerType, policy, now = Date.now(), context) {
|
|
61829
62183
|
const entry = quotaEntryFor(node, providerType, context, now);
|
|
61830
|
-
if (!entry)
|
|
62184
|
+
if (!entry) {
|
|
62185
|
+
logAbsentQuotaFailOpen(node, providerType, policy, now, context);
|
|
62186
|
+
return null;
|
|
62187
|
+
}
|
|
61831
62188
|
const { facts, quota } = entry;
|
|
61832
62189
|
let sessionTrustworthy = true;
|
|
61833
62190
|
let weeklyTrustworthy = true;
|
|
@@ -61989,6 +62346,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
61989
62346
|
var PROVIDER_QUOTA_EXHAUSTED_SKIP_REASON;
|
|
61990
62347
|
var ALL_PROVIDERS_QUOTA_GATED_SKIP_REASON;
|
|
61991
62348
|
var staleFailOpenLoggedAt;
|
|
62349
|
+
var absentFailOpenLoggedAt;
|
|
61992
62350
|
var DEFAULT_WEEKLY_WINDOW_MINUTES;
|
|
61993
62351
|
var DEFAULT_SESSION_WINDOW_MINUTES;
|
|
61994
62352
|
var lastQuotaRankingByNode;
|
|
@@ -62004,6 +62362,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
62004
62362
|
PROVIDER_QUOTA_EXHAUSTED_SKIP_REASON = "provider_quota_exhausted";
|
|
62005
62363
|
ALL_PROVIDERS_QUOTA_GATED_SKIP_REASON = "all_providers_quota_gated";
|
|
62006
62364
|
staleFailOpenLoggedAt = /* @__PURE__ */ new Map();
|
|
62365
|
+
absentFailOpenLoggedAt = /* @__PURE__ */ new Map();
|
|
62007
62366
|
DEFAULT_WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
|
|
62008
62367
|
DEFAULT_SESSION_WINDOW_MINUTES = 5 * 60;
|
|
62009
62368
|
lastQuotaRankingByNode = /* @__PURE__ */ new Map();
|
|
@@ -64991,36 +65350,6 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
64991
65350
|
init_chat_history();
|
|
64992
65351
|
}
|
|
64993
65352
|
});
|
|
64994
|
-
function s(v) {
|
|
64995
|
-
return typeof v === "string" && v.trim() ? v.trim() : "";
|
|
64996
|
-
}
|
|
64997
|
-
function meshEventTraceKey(ctx) {
|
|
64998
|
-
const segs = [`task=${s(ctx.taskId) || "-"}`];
|
|
64999
|
-
const eventId = s(ctx.eventId);
|
|
65000
|
-
if (eventId) segs.push(`evt=${eventId}`);
|
|
65001
|
-
segs.push(`sess=${s(ctx.sessionId) || "-"}`);
|
|
65002
|
-
const nodeId = s(ctx.nodeId);
|
|
65003
|
-
if (nodeId) segs.push(`node=${nodeId}`);
|
|
65004
|
-
const meshId = s(ctx.meshId);
|
|
65005
|
-
if (meshId) segs.push(`mesh=${meshId}`);
|
|
65006
|
-
const event = s(ctx.event);
|
|
65007
|
-
if (event) segs.push(`event=${event}`);
|
|
65008
|
-
return segs.join(" ");
|
|
65009
|
-
}
|
|
65010
|
-
function traceMeshEventStage(stage, ctx, detail) {
|
|
65011
|
-
LOG2.info(CAT, `[stage:${stage}] ${meshEventTraceKey(ctx)}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
65012
|
-
}
|
|
65013
|
-
function traceMeshEventDrop(reason, ctx, detail) {
|
|
65014
|
-
LOG2.warn(CAT, `[drop:${reason}] ${meshEventTraceKey(ctx)}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
65015
|
-
}
|
|
65016
|
-
var CAT;
|
|
65017
|
-
var init_mesh_event_trace = __esm2({
|
|
65018
|
-
"src/mesh/mesh-event-trace.ts"() {
|
|
65019
|
-
"use strict";
|
|
65020
|
-
init_logger();
|
|
65021
|
-
CAT = "EvtTrace";
|
|
65022
|
-
}
|
|
65023
|
-
});
|
|
65024
65353
|
function isAnthropicProvider(providerType) {
|
|
65025
65354
|
const p = typeof providerType === "string" ? providerType.trim().toLowerCase() : "";
|
|
65026
65355
|
return p.length > 0 && ANTHROPIC_PROVIDER_TYPES.has(p);
|
|
@@ -65495,6 +65824,43 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
65495
65824
|
const anchorMs = Date.parse(task.requeuedAt || task.createdAt || "");
|
|
65496
65825
|
return Number.isFinite(anchorMs) ? nowMs - anchorMs : null;
|
|
65497
65826
|
}
|
|
65827
|
+
function resolveTargetPinTtlVerdict(components, task, nowMs = Date.now()) {
|
|
65828
|
+
const wallAgeMs = targetPinAgeMs(task, nowMs);
|
|
65829
|
+
if (wallAgeMs === null) return { expired: false, ageMs: null, suspended: false };
|
|
65830
|
+
const targetSessionId = readNonEmptyString(task.targetSessionId);
|
|
65831
|
+
const key2 = `${task.meshId}::${task.id}`;
|
|
65832
|
+
const prior = targetPinGeneratingCreditMs.get(key2);
|
|
65833
|
+
const generating = !!targetSessionId && resolveSessionBusyVerdict(components, targetSessionId) === "GENERATING";
|
|
65834
|
+
let creditMs = prior?.creditMs ?? 0;
|
|
65835
|
+
if (generating && prior) {
|
|
65836
|
+
creditMs += Math.max(0, nowMs - prior.lastSeenMs);
|
|
65837
|
+
}
|
|
65838
|
+
targetPinGeneratingCreditMs.set(key2, { creditMs, lastSeenMs: nowMs });
|
|
65839
|
+
const unproductiveAgeMs = Math.max(0, wallAgeMs - creditMs);
|
|
65840
|
+
return {
|
|
65841
|
+
// A tick on which the addressee is OBSERVABLY GENERATING never expires the pin,
|
|
65842
|
+
// independently of the accumulated credit.
|
|
65843
|
+
//
|
|
65844
|
+
// The credit ledger alone is not sufficient here, and the difference is the
|
|
65845
|
+
// whole live defect. Credit only accrues from the SECOND observation onward
|
|
65846
|
+
// (the first has no prior interval to bank), and it is in-memory — so a pin
|
|
65847
|
+
// that crossed the wall-clock TTL while the daemon was not watching, or before
|
|
65848
|
+
// a restart, would arrive at its very first post-restart observation with zero
|
|
65849
|
+
// credit and expire on the spot, while the session it is addressed to is
|
|
65850
|
+
// visibly mid-turn. That is exactly the observed failure (`unclaimed 902s`
|
|
65851
|
+
// logged on an `agent:ready`, i.e. at the end of real work) reproduced by a
|
|
65852
|
+
// different route.
|
|
65853
|
+
//
|
|
65854
|
+
// Gating on the live verdict makes "is the addressee working right now?" the
|
|
65855
|
+
// decisive question and leaves the credit ledger as what it should be: an
|
|
65856
|
+
// optimisation that stops intermittent work from silently burning the budget.
|
|
65857
|
+
// It cannot make a pin immortal — the verdict is re-evaluated every tick from
|
|
65858
|
+
// live state and only a LOCAL, observably-generating session can produce it.
|
|
65859
|
+
expired: !generating && unproductiveAgeMs >= TARGET_SESSION_PIN_TTL_MS,
|
|
65860
|
+
ageMs: unproductiveAgeMs,
|
|
65861
|
+
suspended: generating
|
|
65862
|
+
};
|
|
65863
|
+
}
|
|
65498
65864
|
function resolveDeadTargetVerdict(components, meshId, mesh, task) {
|
|
65499
65865
|
const NOT_DEAD = { dead: false, nodeDead: false, reason: "" };
|
|
65500
65866
|
const targetSessionId = readNonEmptyString(task.targetSessionId);
|
|
@@ -65570,6 +65936,21 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
65570
65936
|
summary: "the node's workspace is dirty, so auto-launch is blocked to avoid clobbering uncommitted changes",
|
|
65571
65937
|
nextAction: "Clean or commit the node's working tree (or fast-forward it); the task will then auto-assign."
|
|
65572
65938
|
};
|
|
65939
|
+
if (reason === PARKED_SKIP_REASON) {
|
|
65940
|
+
const base = "it was pinned to a specific session, that pin went stale, and the task is now PARKED \u2014 deliberately held for you rather than re-homed onto another session, because a delta written for one session's context becomes a context-free instruction anywhere else";
|
|
65941
|
+
if (evidence === "consumed") return {
|
|
65942
|
+
summary: `${base}. The delivery record shows the addressed session DID receive and start acting on this message, so the parked row is a bookkeeping remnant, not a lost delta`,
|
|
65943
|
+
nextAction: "Do NOT re-send it. Confirm with mesh_read_chat / mesh_read_terminal that the work is under way, then clear the park with mesh_queue_cancel (or requeue it only if you genuinely want it run again)."
|
|
65944
|
+
};
|
|
65945
|
+
if (evidence === "delivered") return {
|
|
65946
|
+
summary: `${base}. The message WAS handed to that session's transport but no turn start was echoed, so whether it acted on it is unconfirmed`,
|
|
65947
|
+
nextAction: "Check the session (mesh_read_chat / mesh_read_terminal) before acting. If it is already handling it, cancel the parked row; if not, mesh_queue_requeue with target_session_id=<live session> \u2014 and pass message=<rewritten instruction> if the situation moved on while it waited."
|
|
65948
|
+
};
|
|
65949
|
+
return {
|
|
65950
|
+
summary: `${base}. No delivery to that session was ever recorded, so the message did not reach it and the worker is still acting on its previous instructions`,
|
|
65951
|
+
nextAction: "Re-target it with mesh_queue_requeue(target_session_id=<live session>), or drop the pin with clear_target_session to let any compatible session take it. Re-read the work produced meanwhile and pass message=<rewritten instruction> if the delta is now partly stale; mesh_queue_cancel if it is moot."
|
|
65952
|
+
};
|
|
65953
|
+
}
|
|
65573
65954
|
if (reason === "target_session_pin_expired") {
|
|
65574
65955
|
if (evidence === "consumed") return {
|
|
65575
65956
|
summary: "it was pinned to a specific session and the pin TTL expired before the queue row was claimed \u2014 but the delivery record shows this session DID receive and start acting on the message (a turn was started for it), so the queue row lagging is a bookkeeping gap, not a lost delta",
|
|
@@ -65616,11 +65997,11 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
65616
65997
|
const targetCoordinatorDaemonId = readNonEmptyString(loadConfig2().machineId);
|
|
65617
65998
|
const targetCoordinatorSessionId = readNonEmptyString(task?.sourceCoordinatorSessionId);
|
|
65618
65999
|
const nodeLabel = readNonEmptyString(nodeId) || readNonEmptyString(task?.targetNodeId);
|
|
65619
|
-
const evidence = reason === "target_session_pin_expired" ? resolveTaskDeliveryEvidence(meshId, taskId) : void 0;
|
|
66000
|
+
const evidence = reason === "target_session_pin_expired" || reason === PARKED_SKIP_REASON ? resolveTaskDeliveryEvidence(meshId, taskId) : void 0;
|
|
65620
66001
|
const { summary, nextAction } = actionableSkipGuidance(reason, evidence);
|
|
65621
66002
|
const providerAvailabilityResult = reason.startsWith("provider") || reason === "missing_provider_priority";
|
|
65622
66003
|
const reachabilityResult = reason.startsWith("remote_auto_launch");
|
|
65623
|
-
const closing = reason === "target_session_pin_expired" ? "The stale pin has already been cleared, so the task is now claimable by any compatible session \u2014 the action above is about the session it was originally addressed to." : providerAvailabilityResult ? "This result needs action if it persists: a later provider-status refresh or an already-starting usable session can clear it, but a genuinely missing, disabled, or misconfigured provider will keep the task pending until you fix that configuration." : reachabilityResult ? "This result needs action if it persists: the node reconnecting (or re-registering its daemon id) clears it on its own, but a node that stays unreachable will keep the task pending until you bring it back or re-target the task." : "This is an actionable blocker \u2014 it will NOT clear on its own; the task stays pending until you resolve it.";
|
|
66004
|
+
const closing = reason === PARKED_SKIP_REASON ? `This task is claimable by NOBODY until you act on it \u2014 no session will pick it up and no timer will re-home it. It is held for ${Math.round(PARKED_TASK_RETENTION_MS / 36e5)}h and then failed (with another notification), so it is never silently discarded. Parked rows are listed under parkedTasks in mesh_view_queue, and any mesh_queue_requeue unparks it \u2014 including one that only rewrites its message.` : reason === "target_session_pin_expired" ? "The stale pin has already been cleared, so the task is now claimable by any compatible session \u2014 the action above is about the session it was originally addressed to." : providerAvailabilityResult ? "This result needs action if it persists: a later provider-status refresh or an already-starting usable session can clear it, but a genuinely missing, disabled, or misconfigured provider will keep the task pending until you fix that configuration." : reachabilityResult ? "This result needs action if it persists: the node reconnecting (or re-registering its daemon id) clears it on its own, but a node that stays unreachable will keep the task pending until you bring it back or re-target the task." : "This is an actionable blocker \u2014 it will NOT clear on its own; the task stays pending until you resolve it.";
|
|
65624
66005
|
const coordinatorMessage = `[System] A queued mesh task${nodeLabel ? ` for node ${nodeLabel}` : ""} is not being dispatched because ${summary}. ${nextAction} ${closing}`;
|
|
65625
66006
|
try {
|
|
65626
66007
|
queuePendingMeshCoordinatorEvent({
|
|
@@ -65649,6 +66030,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
65649
66030
|
var lastActionableSkipNotified;
|
|
65650
66031
|
var DEAD_TARGET_GRACE_MS;
|
|
65651
66032
|
var TARGET_SESSION_PIN_TTL_MS;
|
|
66033
|
+
var targetPinGeneratingCreditMs;
|
|
65652
66034
|
var init_mesh_skip_notify = __esm2({
|
|
65653
66035
|
"src/mesh/mesh-skip-notify.ts"() {
|
|
65654
66036
|
"use strict";
|
|
@@ -65664,6 +66046,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
65664
66046
|
init_slot_model_enforcement();
|
|
65665
66047
|
init_mesh_queue_assignment();
|
|
65666
66048
|
init_mesh_queue_observability();
|
|
66049
|
+
init_mesh_task_parking();
|
|
65667
66050
|
ACTIONABLE_SKIP_REASON_PREFIXES = [
|
|
65668
66051
|
"target_node_id_unmatched",
|
|
65669
66052
|
"no_node_satisfies_required_tags",
|
|
@@ -65686,6 +66069,14 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
65686
66069
|
// premise the delta was meant to correct. The coordinator must know its
|
|
65687
66070
|
// addressed message lost its address.
|
|
65688
66071
|
"target_session_pin_expired",
|
|
66072
|
+
// PIN-PARKING: the reason above's successor. The pin no longer CLEARS on expiry
|
|
66073
|
+
// (that silently re-homed a context-bound delta onto an arbitrary session); the
|
|
66074
|
+
// task PARKS instead — held, still addressed, claimable by nobody. That state is
|
|
66075
|
+
// by construction actionable and terminal-until-touched: nothing in the daemon
|
|
66076
|
+
// will ever move a parked task, so if the coordinator is not told, the work is
|
|
66077
|
+
// lost exactly as surely as if it had been dropped. Both reasons stay listed —
|
|
66078
|
+
// the old one so a version-skewed daemon's rows still page.
|
|
66079
|
+
PARKED_SKIP_REASON,
|
|
65689
66080
|
// SLOT MODEL GUARD (absent): no slot on the node declares the task's model.
|
|
65690
66081
|
// Permanent — no amount of waiting produces a slot, so the coordinator must
|
|
65691
66082
|
// re-drive (adjust difficulty, target another node, ask the owner). Its
|
|
@@ -65704,6 +66095,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
65704
66095
|
lastActionableSkipNotified = /* @__PURE__ */ new Map();
|
|
65705
66096
|
DEAD_TARGET_GRACE_MS = 6e4;
|
|
65706
66097
|
TARGET_SESSION_PIN_TTL_MS = 15 * 6e4;
|
|
66098
|
+
targetPinGeneratingCreditMs = /* @__PURE__ */ new Map();
|
|
65707
66099
|
}
|
|
65708
66100
|
});
|
|
65709
66101
|
function nodesForMesh(meshId) {
|
|
@@ -66112,6 +66504,173 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66112
66504
|
INTRA_NODE_LOSERS_MAX = 2;
|
|
66113
66505
|
}
|
|
66114
66506
|
});
|
|
66507
|
+
function localCoordinatorDaemonId() {
|
|
66508
|
+
return canonicalDaemonId(readNonEmptyString(loadConfig2().machineId));
|
|
66509
|
+
}
|
|
66510
|
+
function isIdleSessionState2(state) {
|
|
66511
|
+
const status = readNonEmptyString(state?.status).toLowerCase();
|
|
66512
|
+
if (TERMINAL_SESSION_STATUSES.includes(status)) return false;
|
|
66513
|
+
return status === "idle" || state?.activeChat?.status === "waiting_input";
|
|
66514
|
+
}
|
|
66515
|
+
function sweepAutoLaunchOrphanSessions(components, meshId) {
|
|
66516
|
+
let sessions;
|
|
66517
|
+
try {
|
|
66518
|
+
sessions = components.instanceManager?.getByCategory?.("cli") || [];
|
|
66519
|
+
} catch {
|
|
66520
|
+
return;
|
|
66521
|
+
}
|
|
66522
|
+
if (!sessions.length) return;
|
|
66523
|
+
let queue;
|
|
66524
|
+
try {
|
|
66525
|
+
queue = getQueue(meshId);
|
|
66526
|
+
} catch {
|
|
66527
|
+
return;
|
|
66528
|
+
}
|
|
66529
|
+
const byId = new Map(queue.map((t) => [t.id, t]));
|
|
66530
|
+
for (const inst of sessions) {
|
|
66531
|
+
let state;
|
|
66532
|
+
try {
|
|
66533
|
+
state = inst.getState();
|
|
66534
|
+
} catch {
|
|
66535
|
+
continue;
|
|
66536
|
+
}
|
|
66537
|
+
const settings = state?.settings || {};
|
|
66538
|
+
if (readNonEmptyString(settings.meshNodeFor) !== meshId) continue;
|
|
66539
|
+
const originTaskId = readNonEmptyString(settings.autoLaunchedForQueueTaskId);
|
|
66540
|
+
if (!originTaskId) continue;
|
|
66541
|
+
const sessionId = readNonEmptyString(state?.instanceId);
|
|
66542
|
+
if (!sessionId) continue;
|
|
66543
|
+
if (!isIdleSessionState2(state)) continue;
|
|
66544
|
+
if (sessionHasActiveAssignment(meshId, sessionId)) continue;
|
|
66545
|
+
const originTask = byId.get(originTaskId);
|
|
66546
|
+
const assignedElsewhere = !!originTask && originTask.status === "assigned" && !!readNonEmptyString(originTask.assignedSessionId) && !sessionIdsEquivalent(readNonEmptyString(originTask.assignedSessionId), sessionId);
|
|
66547
|
+
const originGone = !originTask || originTask.status !== "pending" && originTask.status !== "assigned";
|
|
66548
|
+
const dedupKey = `${meshId}::${sessionId}`;
|
|
66549
|
+
if (!assignedElsewhere && !originGone) {
|
|
66550
|
+
autoLaunchOrphanFirstSeenAtMs.delete(dedupKey);
|
|
66551
|
+
continue;
|
|
66552
|
+
}
|
|
66553
|
+
const nowMs = Date.now();
|
|
66554
|
+
const firstSeenAtMs = autoLaunchOrphanFirstSeenAtMs.get(dedupKey);
|
|
66555
|
+
if (firstSeenAtMs === void 0) {
|
|
66556
|
+
autoLaunchOrphanFirstSeenAtMs.set(dedupKey, nowMs);
|
|
66557
|
+
if (autoLaunchOrphanFirstSeenAtMs.size > AUTO_LAUNCH_LEDGER_DEDUP_MAX) {
|
|
66558
|
+
const oldest = autoLaunchOrphanFirstSeenAtMs.keys().next().value;
|
|
66559
|
+
if (oldest !== void 0) autoLaunchOrphanFirstSeenAtMs.delete(oldest);
|
|
66560
|
+
}
|
|
66561
|
+
continue;
|
|
66562
|
+
}
|
|
66563
|
+
if (nowMs - firstSeenAtMs < AUTO_LAUNCH_ORPHAN_GRACE_MS) continue;
|
|
66564
|
+
if (autoLaunchOrphanNotified.has(dedupKey)) continue;
|
|
66565
|
+
autoLaunchOrphanNotified.add(dedupKey);
|
|
66566
|
+
if (autoLaunchOrphanNotified.size > AUTO_LAUNCH_LEDGER_DEDUP_MAX) {
|
|
66567
|
+
const oldest = autoLaunchOrphanNotified.values().next().value;
|
|
66568
|
+
if (oldest !== void 0) autoLaunchOrphanNotified.delete(oldest);
|
|
66569
|
+
}
|
|
66570
|
+
const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
66571
|
+
const detail = assignedElsewhere ? `task ${originTaskId} is assigned to a different session (${readNonEmptyString(originTask.assignedSessionId)})` : `task ${originTaskId} is no longer active (${originTask ? originTask.status : "absent from the queue"})`;
|
|
66572
|
+
recordAutoLaunchEvent(meshId, {
|
|
66573
|
+
phase: "skipped",
|
|
66574
|
+
taskId: originTaskId,
|
|
66575
|
+
reason: "auto_launch_orphan_session_detected",
|
|
66576
|
+
nodeId,
|
|
66577
|
+
sessionId
|
|
66578
|
+
});
|
|
66579
|
+
LOG2.warn("MeshQueue", `AUTOLAUNCH-ORPHAN-SWEEP: session ${sessionId}${nodeId ? ` on node ${nodeId}` : ""} (mesh ${meshId}) was auto-launched for task ${originTaskId} but ${detail}; it is idle with no work and will not self-recover.`);
|
|
66580
|
+
try {
|
|
66581
|
+
queuePendingMeshCoordinatorEvent({
|
|
66582
|
+
event: "mesh:dispatch_blocked",
|
|
66583
|
+
meshId,
|
|
66584
|
+
nodeLabel: nodeId || meshId,
|
|
66585
|
+
...nodeId ? { nodeId } : {},
|
|
66586
|
+
metadataEvent: {
|
|
66587
|
+
source: "mesh_auto_launch_orphan_session",
|
|
66588
|
+
taskId: originTaskId,
|
|
66589
|
+
sessionId,
|
|
66590
|
+
reason: "auto_launch_orphan_session_detected"
|
|
66591
|
+
},
|
|
66592
|
+
coordinatorMessage: `[System] Mesh session ${sessionId}${nodeId ? ` on node ${nodeId}` : ""} was auto-launched for task ${originTaskId}, but ${detail}. The session is idle with no work assigned and nothing reclaims it on its own. Reuse it for other queued work or stop it.`,
|
|
66593
|
+
queuedAt: Date.now(),
|
|
66594
|
+
...readNonEmptyString(originTask?.sourceCoordinatorSessionId) ? { targetCoordinatorSessionId: readNonEmptyString(originTask.sourceCoordinatorSessionId) } : {},
|
|
66595
|
+
...localCoordinatorDaemonId() ? { targetCoordinatorDaemonId: localCoordinatorDaemonId() } : {}
|
|
66596
|
+
});
|
|
66597
|
+
} catch (e) {
|
|
66598
|
+
LOG2.warn("MeshQueue", `AUTOLAUNCH-ORPHAN-SWEEP: failed to queue the coordinator notification for session ${sessionId}: ${e?.message || e}`);
|
|
66599
|
+
}
|
|
66600
|
+
}
|
|
66601
|
+
}
|
|
66602
|
+
function autoLaunchWriteWouldClobberWinner(meshId, taskId, args, awaitClaimWindowMs2) {
|
|
66603
|
+
if (args.status === "completed" && readNonEmptyString(args.sessionId)) return false;
|
|
66604
|
+
let existing;
|
|
66605
|
+
try {
|
|
66606
|
+
existing = getQueue(meshId).find((t) => t.id === taskId)?.autoLaunch;
|
|
66607
|
+
} catch {
|
|
66608
|
+
return false;
|
|
66609
|
+
}
|
|
66610
|
+
const heldSessionId = existing ? readNonEmptyString(existing.sessionId) : "";
|
|
66611
|
+
if (!existing || existing.status !== "completed" || !heldSessionId) return false;
|
|
66612
|
+
const heldAtMs = Date.parse(existing.updatedAt);
|
|
66613
|
+
if (!Number.isFinite(heldAtMs) || Date.now() - heldAtMs >= awaitClaimWindowMs2) return false;
|
|
66614
|
+
if (sessionIdsEquivalent(readNonEmptyString(args.sessionId), heldSessionId)) return false;
|
|
66615
|
+
LOG2.info("MeshQueue", `AUTOLAUNCH-WINNER-CLOBBER: suppressed a '${args.status}' autoLaunch write for task ${taskId} (mesh ${meshId}) that would have overwritten the in-window launch record for session ${heldSessionId}; the field keeps pointing at the actually-launched session.`);
|
|
66616
|
+
return true;
|
|
66617
|
+
}
|
|
66618
|
+
function awaitClaimWindowMs(cycles) {
|
|
66619
|
+
return AUTO_LAUNCH_AWAIT_CLAIM_MS * Math.pow(2, Math.min(cycles, AUTO_LAUNCH_AWAIT_CLAIM_BACKOFF_CAP_CYCLES));
|
|
66620
|
+
}
|
|
66621
|
+
function remoteSessionAppearsLive(meshId, sessionId) {
|
|
66622
|
+
if (!sessionId) return false;
|
|
66623
|
+
try {
|
|
66624
|
+
return MeshRuntimeStore.getInstance().getRemoteIdleSessions(meshId).some((s2) => sessionIdsEquivalent(s2.sessionId, sessionId));
|
|
66625
|
+
} catch {
|
|
66626
|
+
return false;
|
|
66627
|
+
}
|
|
66628
|
+
}
|
|
66629
|
+
function inWindowAutoLaunchSessionIdsForNode(meshId, nodeId) {
|
|
66630
|
+
const nowMs = Date.now();
|
|
66631
|
+
const out = [];
|
|
66632
|
+
for (const task of getQueue(meshId, { status: ["pending"] })) {
|
|
66633
|
+
const al = task.autoLaunch;
|
|
66634
|
+
const sid = al ? readNonEmptyString(al.sessionId) : "";
|
|
66635
|
+
if (!al || al.status !== "started" && al.status !== "completed" || !sid) continue;
|
|
66636
|
+
if (!daemonIdsEquivalent(al.nodeId, nodeId)) continue;
|
|
66637
|
+
const launchedAtMs = Date.parse(al.updatedAt);
|
|
66638
|
+
const inBaseWindow = Number.isFinite(launchedAtMs) && nowMs - launchedAtMs < AUTO_LAUNCH_AWAIT_CLAIM_MS;
|
|
66639
|
+
const inBackoff = autoLaunchAwaitClaimBackoff.has(`${meshId}::${task.id}`);
|
|
66640
|
+
if (inBaseWindow || inBackoff) out.push(sid);
|
|
66641
|
+
}
|
|
66642
|
+
return out;
|
|
66643
|
+
}
|
|
66644
|
+
var AUTO_LAUNCH_AWAIT_CLAIM_MS;
|
|
66645
|
+
var AUTO_LAUNCH_AWAIT_CLAIM_BACKOFF_CAP_CYCLES;
|
|
66646
|
+
var AUTO_LAUNCH_REMOTE_IDLE_TTL_MS;
|
|
66647
|
+
var TERMINAL_SESSION_STATUSES;
|
|
66648
|
+
var autoLaunchOrphanNotified;
|
|
66649
|
+
var autoLaunchOrphanFirstSeenAtMs;
|
|
66650
|
+
var AUTO_LAUNCH_ORPHAN_GRACE_MS;
|
|
66651
|
+
var autoLaunchAwaitClaimBackoff;
|
|
66652
|
+
var init_mesh_autolaunch_integrity = __esm2({
|
|
66653
|
+
"src/mesh/mesh-autolaunch-integrity.ts"() {
|
|
66654
|
+
"use strict";
|
|
66655
|
+
init_config();
|
|
66656
|
+
init_logger();
|
|
66657
|
+
init_dist();
|
|
66658
|
+
init_mesh_work_queue();
|
|
66659
|
+
init_mesh_events_utils();
|
|
66660
|
+
init_mesh_events_pending();
|
|
66661
|
+
init_mesh_queue_observability();
|
|
66662
|
+
init_mesh_scheduling_fitness();
|
|
66663
|
+
init_mesh_runtime_store();
|
|
66664
|
+
AUTO_LAUNCH_AWAIT_CLAIM_MS = 9e4;
|
|
66665
|
+
AUTO_LAUNCH_AWAIT_CLAIM_BACKOFF_CAP_CYCLES = 2;
|
|
66666
|
+
AUTO_LAUNCH_REMOTE_IDLE_TTL_MS = 5 * 60 * 1e3;
|
|
66667
|
+
TERMINAL_SESSION_STATUSES = ["stopped", "failed", "terminated", "exited", "closed"];
|
|
66668
|
+
autoLaunchOrphanNotified = /* @__PURE__ */ new Set();
|
|
66669
|
+
autoLaunchOrphanFirstSeenAtMs = /* @__PURE__ */ new Map();
|
|
66670
|
+
AUTO_LAUNCH_ORPHAN_GRACE_MS = 3e4;
|
|
66671
|
+
autoLaunchAwaitClaimBackoff = /* @__PURE__ */ new Map();
|
|
66672
|
+
}
|
|
66673
|
+
});
|
|
66115
66674
|
function classifiedDifficultiesForSlot(slot) {
|
|
66116
66675
|
const highest = slot.difficulty?.reduce((rank, value) => {
|
|
66117
66676
|
const candidate = CLASSIFIED_DIFFICULTIES.indexOf(value);
|
|
@@ -66216,7 +66775,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66216
66775
|
CLASSIFIED_DIFFICULTIES = ["easy", "medium", "difficult"];
|
|
66217
66776
|
}
|
|
66218
66777
|
});
|
|
66219
|
-
function
|
|
66778
|
+
function localCoordinatorDaemonId2() {
|
|
66220
66779
|
return canonicalDaemonId(readNonEmptyString(loadConfig2().machineId));
|
|
66221
66780
|
}
|
|
66222
66781
|
function loadRepoConfigForNode(node) {
|
|
@@ -66325,7 +66884,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66325
66884
|
const routingDecision = {
|
|
66326
66885
|
source: routing?.source ?? "queue",
|
|
66327
66886
|
selectedNodeId: ctx.nodeId,
|
|
66328
|
-
...
|
|
66887
|
+
...localCoordinatorDaemonId2() ? { daemonId: localCoordinatorDaemonId2() } : {},
|
|
66329
66888
|
transport: ctx.transport,
|
|
66330
66889
|
// D: resolved execution profile — prefer the caller's resolved values, fall back
|
|
66331
66890
|
// to what the claimed task row carries (queue/idle drains carry it on the task).
|
|
@@ -66540,7 +67099,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66540
67099
|
LOG2.info("MeshQueue", `Deferring queue claim for node ${nodeId} (${sessionId}): an auto fast-forward is mutating its workspace \u2014 task left pending, claim re-fires next tick`);
|
|
66541
67100
|
return false;
|
|
66542
67101
|
}
|
|
66543
|
-
const quotaClaimBlock = evaluateProviderQuotaGate(node, providerType, mesh?.policy?.quotaRouting ?? null, Date.now(), quotaFactsContextForLiveRouting(mesh, isLocalAutoLaunchNode));
|
|
67102
|
+
const quotaClaimBlock = evaluateProviderQuotaGate(node, providerType, mesh?.policy?.quotaRouting ?? null, Date.now(), quotaFactsContextForLiveRouting(mesh, isLocalAutoLaunchNode, components.providerLoader));
|
|
66544
67103
|
if (quotaClaimTrace) quotaClaimTrace.evaluated += 1;
|
|
66545
67104
|
if (quotaClaimBlock) {
|
|
66546
67105
|
const observation = { nodeId, sessionId, providerType, block: quotaClaimBlock };
|
|
@@ -66645,7 +67204,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66645
67204
|
nodeId,
|
|
66646
67205
|
sessionId,
|
|
66647
67206
|
providerType,
|
|
66648
|
-
coordinatorDaemonId:
|
|
67207
|
+
coordinatorDaemonId: localCoordinatorDaemonId2(),
|
|
66649
67208
|
coordinatorSessionId: readNonEmptyString(task.sourceCoordinatorSessionId) || void 0
|
|
66650
67209
|
});
|
|
66651
67210
|
dispatchAttemptId = attempt.attemptId;
|
|
@@ -66689,7 +67248,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66689
67248
|
if (remoteDaemonId && components.dispatchMeshCommand) {
|
|
66690
67249
|
const isLocalNode = isLocalAutoLaunchNode(node) || components.cliManager.adapters.has(sessionId);
|
|
66691
67250
|
if (!isLocalNode) {
|
|
66692
|
-
const localDaemonIdForDispatch =
|
|
67251
|
+
const localDaemonIdForDispatch = localCoordinatorDaemonId2();
|
|
66693
67252
|
const sourceCoordinatorSessionId = readNonEmptyString(task.sourceCoordinatorSessionId) || void 0;
|
|
66694
67253
|
const dispatchMeshCommand = components.dispatchMeshCommand;
|
|
66695
67254
|
deliverTaskToSession(
|
|
@@ -66739,7 +67298,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66739
67298
|
try {
|
|
66740
67299
|
const inst = components.instanceManager.getInstance(sessionId);
|
|
66741
67300
|
if (inst && typeof inst.updateSettings === "function") {
|
|
66742
|
-
const localDaemonId =
|
|
67301
|
+
const localDaemonId = localCoordinatorDaemonId2();
|
|
66743
67302
|
const localSourceCoordinatorSessionId = readNonEmptyString(task.sourceCoordinatorSessionId);
|
|
66744
67303
|
inst.updateSettings({
|
|
66745
67304
|
meshNodeFor: meshId,
|
|
@@ -66783,7 +67342,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66783
67342
|
...typeof task.dispatchNonce === "number" ? { dispatchNonce: task.dispatchNonce } : {},
|
|
66784
67343
|
// TURN-LEDGER (Stage 5): the opaque attempt identity (see remote branch above).
|
|
66785
67344
|
...dispatchAttemptId ? { attemptId: dispatchAttemptId } : {},
|
|
66786
|
-
...
|
|
67345
|
+
...localCoordinatorDaemonId2() ? { coordinatorDaemonId: localCoordinatorDaemonId2() } : {},
|
|
66787
67346
|
...readNonEmptyString(task.sourceCoordinatorSessionId) ? { coordinatorSessionId: readNonEmptyString(task.sourceCoordinatorSessionId) } : {},
|
|
66788
67347
|
...silentIdlePushOnDispatch ? { silentIdlePush: true } : {}
|
|
66789
67348
|
}
|
|
@@ -66796,38 +67355,12 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66796
67355
|
task,
|
|
66797
67356
|
transport: "local",
|
|
66798
67357
|
...readNonEmptyString(task.sourceCoordinatorSessionId) ? { sourceCoordinatorSessionId: readNonEmptyString(task.sourceCoordinatorSessionId) } : {},
|
|
66799
|
-
...
|
|
67358
|
+
...localCoordinatorDaemonId2() ? { sourceCoordinatorDaemonId: localCoordinatorDaemonId2() } : {},
|
|
66800
67359
|
...routingDecision ? { routingDecision } : {}
|
|
66801
67360
|
}
|
|
66802
67361
|
);
|
|
66803
67362
|
return true;
|
|
66804
67363
|
}
|
|
66805
|
-
function awaitClaimWindowMs(cycles) {
|
|
66806
|
-
return AUTO_LAUNCH_AWAIT_CLAIM_MS * Math.pow(2, Math.min(cycles, AUTO_LAUNCH_AWAIT_CLAIM_BACKOFF_CAP_CYCLES));
|
|
66807
|
-
}
|
|
66808
|
-
function remoteSessionAppearsLive(meshId, sessionId) {
|
|
66809
|
-
if (!sessionId) return false;
|
|
66810
|
-
try {
|
|
66811
|
-
return MeshRuntimeStore.getInstance().getRemoteIdleSessions(meshId).some((s2) => sessionIdsEquivalent(s2.sessionId, sessionId));
|
|
66812
|
-
} catch {
|
|
66813
|
-
return false;
|
|
66814
|
-
}
|
|
66815
|
-
}
|
|
66816
|
-
function inWindowAutoLaunchSessionIdsForNode(meshId, nodeId) {
|
|
66817
|
-
const nowMs = Date.now();
|
|
66818
|
-
const out = [];
|
|
66819
|
-
for (const task of getQueue(meshId, { status: ["pending"] })) {
|
|
66820
|
-
const al = task.autoLaunch;
|
|
66821
|
-
const sid = al ? readNonEmptyString(al.sessionId) : "";
|
|
66822
|
-
if (!al || al.status !== "started" && al.status !== "completed" || !sid) continue;
|
|
66823
|
-
if (!daemonIdsEquivalent(al.nodeId, nodeId)) continue;
|
|
66824
|
-
const launchedAtMs = Date.parse(al.updatedAt);
|
|
66825
|
-
const inBaseWindow = Number.isFinite(launchedAtMs) && nowMs - launchedAtMs < AUTO_LAUNCH_AWAIT_CLAIM_MS;
|
|
66826
|
-
const inBackoff = autoLaunchAwaitClaimBackoff.has(`${meshId}::${task.id}`);
|
|
66827
|
-
if (inBaseWindow || inBackoff) out.push(sid);
|
|
66828
|
-
}
|
|
66829
|
-
return out;
|
|
66830
|
-
}
|
|
66831
67364
|
function sweepExpiredCooldowns() {
|
|
66832
67365
|
const now = Date.now();
|
|
66833
67366
|
for (const [key2, until] of autoLaunchCooldownUntil) {
|
|
@@ -66847,7 +67380,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66847
67380
|
function isTerminalSessionStatus(status) {
|
|
66848
67381
|
return ["stopped", "failed", "terminated", "exited", "closed"].includes(status);
|
|
66849
67382
|
}
|
|
66850
|
-
function
|
|
67383
|
+
function isIdleSessionState3(state) {
|
|
66851
67384
|
const status = readNonEmptyString(state?.status).toLowerCase();
|
|
66852
67385
|
if (isTerminalSessionStatus(status)) return false;
|
|
66853
67386
|
return status === "idle" || state?.activeChat?.status === "waiting_input";
|
|
@@ -66867,7 +67400,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66867
67400
|
const instNodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
66868
67401
|
if (!daemonIdsEquivalent(instNodeId, nodeId)) return false;
|
|
66869
67402
|
const sessionId = readNonEmptyString(state.instanceId);
|
|
66870
|
-
if (currentSessionId && sessionIdsEquivalent(sessionId, currentSessionId) &&
|
|
67403
|
+
if (currentSessionId && sessionIdsEquivalent(sessionId, currentSessionId) && isIdleSessionState3(state)) return false;
|
|
66871
67404
|
return sessionStateLooksActive(state);
|
|
66872
67405
|
});
|
|
66873
67406
|
}
|
|
@@ -66898,7 +67431,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66898
67431
|
const daemonId = readMeshNodeDaemonId(node ?? {});
|
|
66899
67432
|
if (!daemonId) return { mode: "skip", reason: "remote_auto_launch_unsupported" };
|
|
66900
67433
|
if (!components.dispatchMeshCommand) return { mode: "skip", reason: "remote_auto_launch_unsupported" };
|
|
66901
|
-
const coordinatorDaemonId =
|
|
67434
|
+
const coordinatorDaemonId = localCoordinatorDaemonId2();
|
|
66902
67435
|
if (!coordinatorDaemonId) return { mode: "skip", reason: "remote_auto_launch_no_coordinator_daemon_id" };
|
|
66903
67436
|
return { mode: "remote", daemonId, coordinatorDaemonId };
|
|
66904
67437
|
}
|
|
@@ -66972,8 +67505,8 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
66972
67505
|
const reason = args.reason || args.error;
|
|
66973
67506
|
const difficultyFloorSkip = args.status === "skipped" && isDifficultyFloorWaitReason(reason);
|
|
66974
67507
|
if (difficultyFloorSkip) {
|
|
66975
|
-
handleDifficultyFloorSkip({ meshId, taskId, reason, nodeId: args.nodeId, coordinatorDaemonId:
|
|
66976
|
-
} else {
|
|
67508
|
+
handleDifficultyFloorSkip({ meshId, taskId, reason, nodeId: args.nodeId, coordinatorDaemonId: localCoordinatorDaemonId2() });
|
|
67509
|
+
} else if (!autoLaunchWriteWouldClobberWinner(meshId, taskId, args, AUTO_LAUNCH_AWAIT_CLAIM_MS)) {
|
|
66977
67510
|
recordTaskAutoLaunch(meshId, taskId, {
|
|
66978
67511
|
status: args.status,
|
|
66979
67512
|
reason,
|
|
@@ -67125,7 +67658,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
67125
67658
|
const nowMs = Date.now();
|
|
67126
67659
|
const state = autoLaunchAwaitClaimBackoff.get(backoffKey) || { cycles: 0, nextAttemptAtMs: 0 };
|
|
67127
67660
|
if (state.nextAttemptAtMs && nowMs < state.nextAttemptAtMs) return "backoff";
|
|
67128
|
-
const atCap = state.cycles >=
|
|
67661
|
+
const atCap = state.cycles >= AUTO_LAUNCH_AWAIT_CLAIM_BACKOFF_CAP_CYCLES2;
|
|
67129
67662
|
const live = remoteSessionAppearsLive(meshId, sessionId);
|
|
67130
67663
|
if ((live || atCap) && nodeId && providerType) {
|
|
67131
67664
|
try {
|
|
@@ -67151,7 +67684,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
67151
67684
|
return "respawn";
|
|
67152
67685
|
}
|
|
67153
67686
|
}
|
|
67154
|
-
const cycles = Math.min(state.cycles + 1,
|
|
67687
|
+
const cycles = Math.min(state.cycles + 1, AUTO_LAUNCH_AWAIT_CLAIM_BACKOFF_CAP_CYCLES2);
|
|
67155
67688
|
autoLaunchAwaitClaimBackoff.set(backoffKey, { cycles, nextAttemptAtMs: nowMs + awaitClaimWindowMs(cycles) });
|
|
67156
67689
|
recordAutoLaunchEvent(meshId, { phase: "skipped", taskId: task.id, reason: "awaiting_launched_session_claim_backoff", nodeId, sessionId });
|
|
67157
67690
|
return "backoff";
|
|
@@ -67172,333 +67705,339 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
67172
67705
|
const maxParallelTasks = resolveMaxParallelTasks(mesh?.policy?.maxParallelTasks);
|
|
67173
67706
|
const maxReadonlyParallelTasks = resolveMaxReadonlyParallelTasks(maxParallelTasks);
|
|
67174
67707
|
for (const task of pending) {
|
|
67175
|
-
|
|
67176
|
-
|
|
67708
|
+
const taskLaunchKey = `${meshId}::${task.id}`;
|
|
67709
|
+
if (autoLaunchTaskInProgress.has(taskLaunchKey)) {
|
|
67710
|
+
recordAutoLaunchEvent(meshId, { phase: "skipped", taskId: task.id, reason: "auto_launch_task_in_progress" });
|
|
67177
67711
|
continue;
|
|
67178
67712
|
}
|
|
67179
|
-
|
|
67180
|
-
|
|
67181
|
-
|
|
67182
|
-
|
|
67183
|
-
const isReadonly = isTaskReadonly(task);
|
|
67184
|
-
if (isReadonly) {
|
|
67185
|
-
if (activeReadonlyAssignedCount(meshId) >= maxReadonlyParallelTasks) {
|
|
67186
|
-
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_readonly_parallel_tasks_reached" });
|
|
67713
|
+
autoLaunchTaskInProgress.add(taskLaunchKey);
|
|
67714
|
+
try {
|
|
67715
|
+
if (!taskDependenciesSatisfied(task, statusById)) {
|
|
67716
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "dependencies_unsatisfied" });
|
|
67187
67717
|
continue;
|
|
67188
67718
|
}
|
|
67189
|
-
|
|
67190
|
-
|
|
67191
|
-
continue;
|
|
67192
|
-
}
|
|
67193
|
-
if (task.targetSessionId) {
|
|
67194
|
-
const deadTarget = resolveDeadTargetVerdict(components, meshId, mesh, task);
|
|
67195
|
-
if (deadTarget.dead) {
|
|
67196
|
-
const requeued = requeueTask(meshId, task.id, {
|
|
67197
|
-
reason: deadTarget.reason,
|
|
67198
|
-
clearTargetSession: true,
|
|
67199
|
-
// Keep the node pin if only the SESSION died on a still-live node; clear it
|
|
67200
|
-
// when the NODE itself is absent (nothing to pin to).
|
|
67201
|
-
clearTargetNode: deadTarget.nodeDead
|
|
67202
|
-
});
|
|
67203
|
-
if (requeued) {
|
|
67204
|
-
noteTargetPinCleared(deadTarget.reason);
|
|
67205
|
-
LOG2.warn("MeshQueue", `DEAD-TARGET-SELFHEAL: task ${task.id} (mesh ${meshId}) was pinned to a dead target (${deadTarget.reason}); requeued${deadTarget.nodeDead ? " and unpinned node" : ""} (requeueCount=${requeued.requeueCount ?? "?"}, status=${requeued.status}).`);
|
|
67206
|
-
}
|
|
67207
|
-
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_dead_requeued" });
|
|
67719
|
+
if (!meshTaskNotBeforeReady(task)) {
|
|
67720
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "not_before_delayed" });
|
|
67208
67721
|
continue;
|
|
67209
67722
|
}
|
|
67210
|
-
const
|
|
67211
|
-
if (
|
|
67212
|
-
|
|
67213
|
-
|
|
67214
|
-
|
|
67215
|
-
traceMeshEventDrop("target_session_pin_expired", {
|
|
67216
|
-
taskId: task.id,
|
|
67217
|
-
sessionId: readNonEmptyString(task.targetSessionId),
|
|
67218
|
-
nodeId: readNonEmptyString(task.targetNodeId),
|
|
67219
|
-
meshId,
|
|
67220
|
-
event: "agent:ready"
|
|
67221
|
-
}, `unclaimed ${Math.round(pinAgeMs / 1e3)}s \u2265 ttl ${Math.round(TARGET_SESSION_PIN_TTL_MS / 1e3)}s \u2192 pin cleared, claimable`);
|
|
67222
|
-
LOG2.warn("MeshQueue", `TARGET-PIN-TTL: task ${task.id} (mesh ${meshId}) stayed pinned-but-unclaimed for ${Math.round(pinAgeMs / 1e3)}s (ttl ${Math.round(TARGET_SESSION_PIN_TTL_MS / 1e3)}s); expired the stale target pin so a compatible session can claim it.`);
|
|
67723
|
+
const isReadonly = isTaskReadonly(task);
|
|
67724
|
+
if (isReadonly) {
|
|
67725
|
+
if (activeReadonlyAssignedCount(meshId) >= maxReadonlyParallelTasks) {
|
|
67726
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_readonly_parallel_tasks_reached" });
|
|
67727
|
+
continue;
|
|
67223
67728
|
}
|
|
67224
|
-
|
|
67729
|
+
} else if (activeWriteAssignedCount(meshId) >= maxParallelTasks) {
|
|
67730
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "max_parallel_tasks_reached" });
|
|
67225
67731
|
continue;
|
|
67226
67732
|
}
|
|
67227
|
-
|
|
67228
|
-
|
|
67229
|
-
|
|
67230
|
-
|
|
67231
|
-
|
|
67232
|
-
|
|
67233
|
-
const alNodeId = readNonEmptyString(task.autoLaunch.nodeId);
|
|
67234
|
-
const alProvider = readNonEmptyString(task.autoLaunch.providerType);
|
|
67235
|
-
if (Number.isFinite(launchedAtMs) && Date.now() - launchedAtMs < AUTO_LAUNCH_AWAIT_CLAIM_MS) {
|
|
67236
|
-
recordAutoLaunchEvent(meshId, { phase: "skipped", taskId: task.id, reason: "awaiting_launched_session_claim", nodeId: alNodeId, sessionId: alSessionId });
|
|
67237
|
-
continue;
|
|
67238
|
-
}
|
|
67239
|
-
if (Number.isFinite(launchedAtMs) && alSessionId && alNodeId) {
|
|
67240
|
-
const outcome = driveExpiredAwaitClaim(components, meshId, task, { sessionId: alSessionId, nodeId: alNodeId, providerType: alProvider });
|
|
67241
|
-
if (outcome === "claimed" || outcome === "fallback") return true;
|
|
67242
|
-
if (outcome === "backoff") continue;
|
|
67243
|
-
}
|
|
67244
|
-
}
|
|
67245
|
-
const candidateNodes = Array.isArray(mesh?.nodes) ? mesh.nodes.filter((node) => {
|
|
67246
|
-
if (task.targetNodeId && !meshNodeIdMatches(node, task.targetNodeId)) return false;
|
|
67247
|
-
if (task.taskMode === "convergence" && node?.isLocalWorktree === true) return false;
|
|
67248
|
-
if (task.requiredTags?.length) {
|
|
67249
|
-
const slotProviders = resolveNodeCapabilitySlots(node, meshId).map((s2) => s2.provider).filter(Boolean);
|
|
67250
|
-
const priorities = slotProviders.length ? slotProviders : normalizeProviderPriority2(node?.policy);
|
|
67251
|
-
const providerCandidates = priorities.length ? priorities : [void 0];
|
|
67252
|
-
return providerCandidates.some(
|
|
67253
|
-
(p) => nodeSatisfiesRequiredTags(task.requiredTags, buildMeshNodeCapabilityTags(node, p))
|
|
67733
|
+
if (taskIsParked(task)) {
|
|
67734
|
+
settleParkedQueueTask(
|
|
67735
|
+
meshId,
|
|
67736
|
+
task,
|
|
67737
|
+
(reason) => markAutoLaunch(meshId, task.id, { status: "skipped", reason }),
|
|
67738
|
+
failRetentionExpiredParkedTask
|
|
67254
67739
|
);
|
|
67255
|
-
}
|
|
67256
|
-
return true;
|
|
67257
|
-
}) : [];
|
|
67258
|
-
if (!candidateNodes.length) {
|
|
67259
|
-
const targetPinUnmatched = !!task.targetNodeId && !(Array.isArray(mesh?.nodes) && mesh.nodes.some((n) => meshNodeIdMatches(n, task.targetNodeId)));
|
|
67260
|
-
const convergenceOntoWorktree = task.taskMode === "convergence" && Array.isArray(mesh?.nodes) && (() => {
|
|
67261
|
-
const matched = mesh.nodes.filter((n) => !task.targetNodeId || meshNodeIdMatches(n, task.targetNodeId));
|
|
67262
|
-
return matched.length > 0 && matched.every((n) => n?.isLocalWorktree === true);
|
|
67263
|
-
})();
|
|
67264
|
-
const targetTransientlyUnresolved = targetPinUnmatched && isTargetNodeTransientlyUnresolved(mesh, task);
|
|
67265
|
-
markAutoLaunch(meshId, task.id, {
|
|
67266
|
-
status: "skipped",
|
|
67267
|
-
reason: convergenceOntoWorktree ? "mesh_convergence_target_is_worktree" : targetTransientlyUnresolved ? TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON : targetPinUnmatched ? "target_node_id_unmatched" : "no_node_satisfies_required_tags",
|
|
67268
|
-
nodeId: task.targetNodeId
|
|
67269
|
-
});
|
|
67270
|
-
continue;
|
|
67271
|
-
}
|
|
67272
|
-
const strategy = resolveSchedulingStrategy(mesh);
|
|
67273
|
-
const orderedCandidateNodes = strategy === "first_eligible" ? candidateNodes : orderEligibleNodes(
|
|
67274
|
-
meshId,
|
|
67275
|
-
strategy,
|
|
67276
|
-
candidateNodes.map((node, index) => ({ nodeId: readMeshNodeId(node), node, index })).filter((c) => c.nodeId),
|
|
67277
|
-
// Auto-launch drains one task at a time, so the task IS in scope here —
|
|
67278
|
-
// pass it through for the 'fitness' strategy's task→slot ranking. The
|
|
67279
|
-
// mesh's quotaRouting thresholds ride along so the fitness score can
|
|
67280
|
-
// include the quota-headroom spread bonus (fail-open when unset).
|
|
67281
|
-
{ bumpCursor: true, task: { difficulty: task.difficulty, requiredTags: task.requiredTags }, quotaRouting: mesh?.policy?.quotaRouting ?? null, quotaFactsContext: quotaFactsContextForLiveRouting(mesh, isLocalAutoLaunchNode) }
|
|
67282
|
-
).map((c) => c.node);
|
|
67283
|
-
const skippedCandidates = [];
|
|
67284
|
-
const SKIPPED_CANDIDATES_MAX = 5;
|
|
67285
|
-
const markSkip = (nodeIdForSkip, reason, extra) => {
|
|
67286
|
-
markAutoLaunch(meshId, task.id, { status: "skipped", reason, nodeId: nodeIdForSkip, ...extra || {} });
|
|
67287
|
-
if (nodeIdForSkip && skippedCandidates.length < SKIPPED_CANDIDATES_MAX) {
|
|
67288
|
-
skippedCandidates.push({ nodeId: nodeIdForSkip, reason });
|
|
67289
|
-
}
|
|
67290
|
-
};
|
|
67291
|
-
for (const node of orderedCandidateNodes) {
|
|
67292
|
-
const nodeId = readMeshNodeId(node);
|
|
67293
|
-
if (!nodeId) continue;
|
|
67294
|
-
const launchKey = `${meshId}:${nodeId}`;
|
|
67295
|
-
const now = Date.now();
|
|
67296
|
-
const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
|
|
67297
|
-
if (cooldownUntil > 0 && now >= cooldownUntil) autoLaunchCooldownUntil.delete(launchKey);
|
|
67298
|
-
if (autoLaunchInProgress.has(launchKey)) {
|
|
67299
|
-
markSkip(nodeId, "auto_launch_in_progress");
|
|
67300
|
-
continue;
|
|
67301
|
-
}
|
|
67302
|
-
if (now < cooldownUntil) {
|
|
67303
|
-
markSkip(nodeId, "auto_launch_cooldown");
|
|
67304
|
-
continue;
|
|
67305
|
-
}
|
|
67306
|
-
if (isDirtyNode(node)) {
|
|
67307
|
-
markSkip(nodeId, "dirty_workspace");
|
|
67308
|
-
continue;
|
|
67309
|
-
}
|
|
67310
|
-
if (!isLaunchableNode(node)) {
|
|
67311
|
-
markSkip(nodeId, "node_health_not_launchable");
|
|
67312
|
-
continue;
|
|
67313
|
-
}
|
|
67314
|
-
if (!isMeshNodeFreshEnoughToLaunch(node, freshnessGate)) {
|
|
67315
|
-
markSkip(nodeId, "node_stale_behind_upstream");
|
|
67316
|
-
continue;
|
|
67317
|
-
}
|
|
67318
|
-
const launchTarget = resolveAutoLaunchTarget(components, node);
|
|
67319
|
-
if (launchTarget.mode === "skip") {
|
|
67320
|
-
markSkip(nodeId, launchTarget.reason || "auto_launch_unavailable");
|
|
67321
|
-
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
67322
|
-
sweepExpiredCooldowns();
|
|
67323
67740
|
continue;
|
|
67324
67741
|
}
|
|
67325
|
-
if (
|
|
67326
|
-
|
|
67327
|
-
|
|
67328
|
-
|
|
67329
|
-
|
|
67330
|
-
|
|
67742
|
+
if (task.targetSessionId) {
|
|
67743
|
+
const deadTarget = resolveDeadTargetVerdict(components, meshId, mesh, task);
|
|
67744
|
+
if (deadTarget.dead) {
|
|
67745
|
+
const requeued = requeueTask(meshId, task.id, {
|
|
67746
|
+
reason: deadTarget.reason,
|
|
67747
|
+
clearTargetSession: true,
|
|
67748
|
+
// Keep the node pin if only the SESSION died on a still-live node; clear it
|
|
67749
|
+
// when the NODE itself is absent (nothing to pin to).
|
|
67750
|
+
clearTargetNode: deadTarget.nodeDead
|
|
67751
|
+
});
|
|
67752
|
+
if (requeued) {
|
|
67753
|
+
noteTargetPinCleared(deadTarget.reason);
|
|
67754
|
+
LOG2.warn("MeshQueue", `DEAD-TARGET-SELFHEAL: task ${task.id} (mesh ${meshId}) was pinned to a dead target (${deadTarget.reason}); requeued${deadTarget.nodeDead ? " and unpinned node" : ""} (requeueCount=${requeued.requeueCount ?? "?"}, status=${requeued.status}).`);
|
|
67755
|
+
}
|
|
67756
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_dead_requeued" });
|
|
67757
|
+
continue;
|
|
67758
|
+
}
|
|
67759
|
+
if (parkExpiredTargetPin(meshId, task, resolveTargetPinTtlVerdict(components, task), TARGET_SESSION_PIN_TTL_MS, (m, t, r) => parkTaskTargetPin(m, t, { reason: r }))) {
|
|
67760
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: PARKED_SKIP_REASON });
|
|
67761
|
+
continue;
|
|
67762
|
+
}
|
|
67763
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason: "target_session_constraint" });
|
|
67331
67764
|
continue;
|
|
67332
67765
|
}
|
|
67333
|
-
|
|
67334
|
-
|
|
67335
|
-
|
|
67766
|
+
if (task.autoLaunch?.status === "completed" && task.autoLaunch.sessionId) {
|
|
67767
|
+
const launchedAtMs = Date.parse(task.autoLaunch.updatedAt);
|
|
67768
|
+
const alSessionId = readNonEmptyString(task.autoLaunch.sessionId);
|
|
67769
|
+
const alNodeId = readNonEmptyString(task.autoLaunch.nodeId);
|
|
67770
|
+
const alProvider = readNonEmptyString(task.autoLaunch.providerType);
|
|
67771
|
+
if (Number.isFinite(launchedAtMs) && Date.now() - launchedAtMs < AUTO_LAUNCH_AWAIT_CLAIM_MS) {
|
|
67772
|
+
recordAutoLaunchEvent(meshId, { phase: "skipped", taskId: task.id, reason: "awaiting_launched_session_claim", nodeId: alNodeId, sessionId: alSessionId });
|
|
67773
|
+
continue;
|
|
67774
|
+
}
|
|
67775
|
+
if (Number.isFinite(launchedAtMs) && alSessionId && alNodeId) {
|
|
67776
|
+
const outcome = driveExpiredAwaitClaim(components, meshId, task, { sessionId: alSessionId, nodeId: alNodeId, providerType: alProvider });
|
|
67777
|
+
if (outcome === "claimed" || outcome === "fallback") return true;
|
|
67778
|
+
if (outcome === "backoff") continue;
|
|
67779
|
+
}
|
|
67780
|
+
}
|
|
67781
|
+
const candidateNodes = Array.isArray(mesh?.nodes) ? mesh.nodes.filter((node) => {
|
|
67782
|
+
if (task.targetNodeId && !meshNodeIdMatches(node, task.targetNodeId)) return false;
|
|
67783
|
+
if (task.taskMode === "convergence" && node?.isLocalWorktree === true) return false;
|
|
67784
|
+
if (task.requiredTags?.length) {
|
|
67785
|
+
const slotProviders = resolveNodeCapabilitySlots(node, meshId).map((s2) => s2.provider).filter(Boolean);
|
|
67786
|
+
const priorities = slotProviders.length ? slotProviders : normalizeProviderPriority2(node?.policy);
|
|
67787
|
+
const providerCandidates = priorities.length ? priorities : [void 0];
|
|
67788
|
+
return providerCandidates.some(
|
|
67789
|
+
(p) => nodeSatisfiesRequiredTags(task.requiredTags, buildMeshNodeCapabilityTags(node, p))
|
|
67790
|
+
);
|
|
67791
|
+
}
|
|
67792
|
+
return true;
|
|
67793
|
+
}) : [];
|
|
67794
|
+
if (!candidateNodes.length) {
|
|
67795
|
+
const targetPinUnmatched = !!task.targetNodeId && !(Array.isArray(mesh?.nodes) && mesh.nodes.some((n) => meshNodeIdMatches(n, task.targetNodeId)));
|
|
67796
|
+
const convergenceOntoWorktree = task.taskMode === "convergence" && Array.isArray(mesh?.nodes) && (() => {
|
|
67797
|
+
const matched = mesh.nodes.filter((n) => !task.targetNodeId || meshNodeIdMatches(n, task.targetNodeId));
|
|
67798
|
+
return matched.length > 0 && matched.every((n) => n?.isLocalWorktree === true);
|
|
67799
|
+
})();
|
|
67800
|
+
const targetTransientlyUnresolved = targetPinUnmatched && isTargetNodeTransientlyUnresolved(mesh, task);
|
|
67801
|
+
markAutoLaunch(meshId, task.id, {
|
|
67802
|
+
status: "skipped",
|
|
67803
|
+
reason: convergenceOntoWorktree ? "mesh_convergence_target_is_worktree" : targetTransientlyUnresolved ? TRANSIENT_TARGET_NODE_BOOTSTRAP_PENDING_REASON : targetPinUnmatched ? "target_node_id_unmatched" : "no_node_satisfies_required_tags",
|
|
67804
|
+
nodeId: task.targetNodeId
|
|
67805
|
+
});
|
|
67336
67806
|
continue;
|
|
67337
67807
|
}
|
|
67338
|
-
|
|
67339
|
-
|
|
67340
|
-
|
|
67341
|
-
|
|
67342
|
-
|
|
67808
|
+
const strategy = resolveSchedulingStrategy(mesh);
|
|
67809
|
+
const orderedCandidateNodes = strategy === "first_eligible" ? candidateNodes : orderEligibleNodes(
|
|
67810
|
+
meshId,
|
|
67811
|
+
strategy,
|
|
67812
|
+
candidateNodes.map((node, index) => ({ nodeId: readMeshNodeId(node), node, index })).filter((c) => c.nodeId),
|
|
67813
|
+
// Auto-launch drains one task at a time, so the task IS in scope here —
|
|
67814
|
+
// pass it through for the 'fitness' strategy's task→slot ranking. The
|
|
67815
|
+
// mesh's quotaRouting thresholds ride along so the fitness score can
|
|
67816
|
+
// include the quota-headroom spread bonus (fail-open when unset).
|
|
67817
|
+
{ bumpCursor: true, task: { difficulty: task.difficulty, requiredTags: task.requiredTags }, quotaRouting: mesh?.policy?.quotaRouting ?? null, quotaFactsContext: quotaFactsContextForLiveRouting(mesh, isLocalAutoLaunchNode, components.providerLoader) }
|
|
67818
|
+
).map((c) => c.node);
|
|
67819
|
+
const skippedCandidates = [];
|
|
67820
|
+
const SKIPPED_CANDIDATES_MAX = 5;
|
|
67821
|
+
const markSkip = (nodeIdForSkip, reason, extra) => {
|
|
67822
|
+
markAutoLaunch(meshId, task.id, { status: "skipped", reason, nodeId: nodeIdForSkip, ...extra || {} });
|
|
67823
|
+
if (nodeIdForSkip && skippedCandidates.length < SKIPPED_CANDIDATES_MAX) {
|
|
67824
|
+
skippedCandidates.push({ nodeId: nodeIdForSkip, reason });
|
|
67825
|
+
}
|
|
67826
|
+
};
|
|
67827
|
+
for (const node of orderedCandidateNodes) {
|
|
67828
|
+
const nodeId = readMeshNodeId(node);
|
|
67829
|
+
if (!nodeId) continue;
|
|
67830
|
+
const launchKey = `${meshId}:${nodeId}`;
|
|
67831
|
+
const now = Date.now();
|
|
67832
|
+
const cooldownUntil = autoLaunchCooldownUntil.get(launchKey) || 0;
|
|
67833
|
+
if (cooldownUntil > 0 && now >= cooldownUntil) autoLaunchCooldownUntil.delete(launchKey);
|
|
67834
|
+
if (autoLaunchInProgress.has(launchKey)) {
|
|
67835
|
+
markSkip(nodeId, "auto_launch_in_progress");
|
|
67343
67836
|
continue;
|
|
67344
67837
|
}
|
|
67345
|
-
|
|
67346
|
-
|
|
67347
|
-
const effectiveThinkingLevel = resolveLaunchAxis(task.thinkingLevel, task.thinkingLevelSource, resolved.thinkingLevel, slotCoversDifficulty);
|
|
67348
|
-
const slotDecision = decideSlotForModel({
|
|
67349
|
-
requestedModel,
|
|
67350
|
-
slots: resolveNodeCapabilitySlots(node, meshId).map((slot) => ({
|
|
67351
|
-
slot,
|
|
67352
|
-
available: slotHasCapacity(meshId, nodeId, node, slot, mesh?.nodes, isReadonly)
|
|
67353
|
-
}))
|
|
67354
|
-
});
|
|
67355
|
-
if (slotDecision.outcome === "wait") {
|
|
67356
|
-
LOG2.info("MeshQueue", `SLOT MODEL GUARD: model '${requestedModel}' is declared on node ${nodeId} but every matching slot is at its maxParallel cap (task ${task.id}); leaving the task queued until a slot goes idle`);
|
|
67357
|
-
markSkip(nodeId, slotDecision.reason, { providerType: resolved.providerType });
|
|
67838
|
+
if (now < cooldownUntil) {
|
|
67839
|
+
markSkip(nodeId, "auto_launch_cooldown");
|
|
67358
67840
|
continue;
|
|
67359
67841
|
}
|
|
67360
|
-
if (
|
|
67361
|
-
|
|
67362
|
-
markSkip(nodeId, slotDecision.reason, { providerType: resolved.providerType });
|
|
67842
|
+
if (isDirtyNode(node)) {
|
|
67843
|
+
markSkip(nodeId, "dirty_workspace");
|
|
67363
67844
|
continue;
|
|
67364
67845
|
}
|
|
67365
|
-
|
|
67366
|
-
|
|
67367
|
-
const finalizedModel = slotDecision.slot.model?.trim() || void 0;
|
|
67368
|
-
const slotWasDemoted = resolved.slot?.provider !== slotDecision.slot.provider || winningModel !== finalizedModel;
|
|
67369
|
-
const demotionReason = slotWasDemoted ? slotHasCapacity(meshId, nodeId, node, resolved.slot, mesh?.nodes, isReadonly) ? "slot_reselected_during_launch" : "winning_slot_capacity_exhausted" : void 0;
|
|
67370
|
-
const effectiveModel = isModelCompatibleWithProvider(rawEffectiveModel, resolved.providerType) ? rawEffectiveModel : void 0;
|
|
67371
|
-
if (rawEffectiveModel && effectiveModel === void 0) {
|
|
67372
|
-
LOG2.info("MeshQueue", `CODEX-400 GUARD: dropped incompatible launch model '${rawEffectiveModel}' for non-Anthropic provider '${resolved.providerType}' on node ${nodeId} (task ${task.id}); provider will use its own default model`);
|
|
67373
|
-
}
|
|
67374
|
-
const providerCap = effectiveSlotCap(
|
|
67375
|
-
resolveProviderMaxParallel(resolveNodeCapabilitySlots(node, meshId), resolved.providerType),
|
|
67376
|
-
isReadonly
|
|
67377
|
-
);
|
|
67378
|
-
if (providerCap !== void 0 && activeProviderAssignedCount(
|
|
67379
|
-
meshId,
|
|
67380
|
-
nodeId,
|
|
67381
|
-
resolved.providerType,
|
|
67382
|
-
resolveDaemonSiblingNodeIds(nodeId, mesh?.nodes)
|
|
67383
|
-
) >= providerCap) {
|
|
67384
|
-
markSkip(nodeId, "max_provider_parallel_reached", { providerType: resolved.providerType });
|
|
67846
|
+
if (!isLaunchableNode(node)) {
|
|
67847
|
+
markSkip(nodeId, "node_health_not_launchable");
|
|
67385
67848
|
continue;
|
|
67386
67849
|
}
|
|
67387
|
-
|
|
67388
|
-
|
|
67389
|
-
|
|
67390
|
-
|
|
67391
|
-
|
|
67392
|
-
|
|
67393
|
-
|
|
67394
|
-
|
|
67395
|
-
|
|
67396
|
-
|
|
67397
|
-
|
|
67398
|
-
|
|
67399
|
-
|
|
67400
|
-
|
|
67401
|
-
|
|
67402
|
-
|
|
67403
|
-
|
|
67404
|
-
|
|
67405
|
-
|
|
67406
|
-
|
|
67407
|
-
|
|
67408
|
-
|
|
67409
|
-
|
|
67850
|
+
if (!isMeshNodeFreshEnoughToLaunch(node, freshnessGate)) {
|
|
67851
|
+
markSkip(nodeId, "node_stale_behind_upstream");
|
|
67852
|
+
continue;
|
|
67853
|
+
}
|
|
67854
|
+
const launchTarget = resolveAutoLaunchTarget(components, node);
|
|
67855
|
+
if (launchTarget.mode === "skip") {
|
|
67856
|
+
markSkip(nodeId, launchTarget.reason || "auto_launch_unavailable");
|
|
67857
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
67858
|
+
sweepExpiredCooldowns();
|
|
67859
|
+
continue;
|
|
67860
|
+
}
|
|
67861
|
+
if (nodeHasLiveSessionPendingClaim(components, meshId, nodeId, task, node)) {
|
|
67862
|
+
markSkip(nodeId, "node_has_live_session_pending_claim");
|
|
67863
|
+
continue;
|
|
67864
|
+
}
|
|
67865
|
+
if (!isTaskReadonly(task) && nodeHasActiveAssignment(meshId, nodeId)) {
|
|
67866
|
+
markSkip(nodeId, "node_has_active_assignment");
|
|
67867
|
+
continue;
|
|
67868
|
+
}
|
|
67869
|
+
const maxConcurrentSessions = Number(node?.policy?.maxConcurrentSessions);
|
|
67870
|
+
if (Number.isFinite(maxConcurrentSessions) && maxConcurrentSessions >= 0 && liveSessionCountForNode(components, meshId, nodeId) >= maxConcurrentSessions) {
|
|
67871
|
+
markSkip(nodeId, "max_concurrent_sessions_reached");
|
|
67872
|
+
continue;
|
|
67873
|
+
}
|
|
67874
|
+
autoLaunchInProgress.add(launchKey);
|
|
67875
|
+
try {
|
|
67876
|
+
const resolved = await resolveUsableProvider(components, nodeId, node, meshId, task.requiredTags, { difficulty: task.difficulty, requiredTags: task.requiredTags }, mesh?.policy?.quotaRouting ?? null, quotaFactsContextForLiveRouting(mesh, isLocalAutoLaunchNode, components.providerLoader), task.id);
|
|
67877
|
+
if (!resolved.providerType) {
|
|
67878
|
+
markSkip(nodeId, resolved.reason || "provider_unusable");
|
|
67879
|
+
continue;
|
|
67880
|
+
}
|
|
67881
|
+
const slotCoversDifficulty = slotCoversTaskDifficulty(resolved.slot, task.difficulty);
|
|
67882
|
+
const requestedModel = resolveLaunchAxis(task.model, task.modelSource, resolved.model, slotCoversDifficulty);
|
|
67883
|
+
const effectiveThinkingLevel = resolveLaunchAxis(task.thinkingLevel, task.thinkingLevelSource, resolved.thinkingLevel, slotCoversDifficulty);
|
|
67884
|
+
const slotDecision = decideSlotForModel({
|
|
67885
|
+
requestedModel,
|
|
67886
|
+
slots: resolveNodeCapabilitySlots(node, meshId).map((slot) => ({
|
|
67887
|
+
slot,
|
|
67888
|
+
available: slotHasCapacity(meshId, nodeId, node, slot, mesh?.nodes, isReadonly)
|
|
67889
|
+
}))
|
|
67890
|
+
});
|
|
67891
|
+
if (slotDecision.outcome === "wait") {
|
|
67892
|
+
LOG2.info("MeshQueue", `SLOT MODEL GUARD: model '${requestedModel}' is declared on node ${nodeId} but every matching slot is at its maxParallel cap (task ${task.id}); leaving the task queued until a slot goes idle`);
|
|
67893
|
+
markSkip(nodeId, slotDecision.reason, { providerType: resolved.providerType });
|
|
67894
|
+
continue;
|
|
67895
|
+
}
|
|
67896
|
+
if (slotDecision.outcome === "notify") {
|
|
67897
|
+
LOG2.warn("MeshQueue", `SLOT MODEL GUARD: no slot on node ${nodeId} declares model '${requestedModel}' (declared: ${slotDecision.declaredModels.join(", ") || "none"}) for task ${task.id}; not launching \u2014 surfacing to the coordinator to re-drive`);
|
|
67898
|
+
markSkip(nodeId, slotDecision.reason, { providerType: resolved.providerType });
|
|
67899
|
+
continue;
|
|
67900
|
+
}
|
|
67901
|
+
const rawEffectiveModel = slotDecision.model;
|
|
67902
|
+
const winningModel = resolved.slot?.model?.trim() || void 0;
|
|
67903
|
+
const finalizedModel = slotDecision.slot.model?.trim() || void 0;
|
|
67904
|
+
const slotWasDemoted = resolved.slot?.provider !== slotDecision.slot.provider || winningModel !== finalizedModel;
|
|
67905
|
+
const demotionReason = slotWasDemoted ? slotHasCapacity(meshId, nodeId, node, resolved.slot, mesh?.nodes, isReadonly) ? "slot_reselected_during_launch" : "winning_slot_capacity_exhausted" : void 0;
|
|
67906
|
+
const effectiveModel = isModelCompatibleWithProvider(rawEffectiveModel, resolved.providerType) ? rawEffectiveModel : void 0;
|
|
67907
|
+
if (rawEffectiveModel && effectiveModel === void 0) {
|
|
67908
|
+
LOG2.info("MeshQueue", `CODEX-400 GUARD: dropped incompatible launch model '${rawEffectiveModel}' for non-Anthropic provider '${resolved.providerType}' on node ${nodeId} (task ${task.id}); provider will use its own default model`);
|
|
67909
|
+
}
|
|
67910
|
+
const providerCap = effectiveSlotCap(
|
|
67911
|
+
resolveProviderMaxParallel(resolveNodeCapabilitySlots(node, meshId), resolved.providerType),
|
|
67912
|
+
isReadonly
|
|
67913
|
+
);
|
|
67914
|
+
if (providerCap !== void 0 && activeProviderAssignedCount(
|
|
67915
|
+
meshId,
|
|
67916
|
+
nodeId,
|
|
67917
|
+
resolved.providerType,
|
|
67918
|
+
resolveDaemonSiblingNodeIds(nodeId, mesh?.nodes)
|
|
67919
|
+
) >= providerCap) {
|
|
67920
|
+
markSkip(nodeId, "max_provider_parallel_reached", { providerType: resolved.providerType });
|
|
67921
|
+
continue;
|
|
67922
|
+
}
|
|
67923
|
+
const launchSettings = {
|
|
67924
|
+
// Worker launch envelope: role + mesh context so worker can route completion events.
|
|
67925
|
+
role: "worker",
|
|
67926
|
+
meshNodeFor: meshId,
|
|
67927
|
+
meshNodeId: nodeId,
|
|
67928
|
+
spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || "hidden",
|
|
67929
|
+
// Coordinator-dispatched worker: auto-approve unless mesh/node policy
|
|
67930
|
+
// opts out (default true). Lands in settingsOverride and beats the
|
|
67931
|
+
// global per-provider-type boolean/mode through explicit opposite-key clearing.
|
|
67932
|
+
...delegatedWorkerAutoApproveSettingsForNode(
|
|
67933
|
+
mesh,
|
|
67934
|
+
node,
|
|
67935
|
+
components.providerLoader?.getMeta(resolved.providerType),
|
|
67936
|
+
resolved.providerType
|
|
67937
|
+
),
|
|
67938
|
+
launchedByCoordinator: true,
|
|
67939
|
+
autoLaunchedForQueueTaskId: task.id
|
|
67410
67940
|
};
|
|
67941
|
+
if (launchTarget.mode === "remote") {
|
|
67942
|
+
const remoteSettings = {
|
|
67943
|
+
...launchSettings,
|
|
67944
|
+
meshCoordinatorDaemonId: launchTarget.coordinatorDaemonId,
|
|
67945
|
+
meshCoordinatorNodeId: nodeId
|
|
67946
|
+
};
|
|
67947
|
+
markAutoLaunch(meshId, task.id, { status: "started", nodeId, providerType: resolved.providerType, ...effectiveModel ? { model: effectiveModel } : {}, ...effectiveThinkingLevel ? { thinkingLevel: effectiveThinkingLevel } : {} });
|
|
67948
|
+
let launchResult2;
|
|
67949
|
+
try {
|
|
67950
|
+
launchResult2 = await components.dispatchMeshCommand(launchTarget.daemonId, "launch_cli", withStatusProbeMarker({
|
|
67951
|
+
cliType: resolved.providerType,
|
|
67952
|
+
dir: node.workspace,
|
|
67953
|
+
settings: remoteSettings,
|
|
67954
|
+
// MAGI-KIND-PANEL model axis: forward the task's model override so the
|
|
67955
|
+
// remote worker session launches with it (initialModel). Best-effort.
|
|
67956
|
+
// Slot-aware: task override wins, else the matched slot's model.
|
|
67957
|
+
...effectiveModel ? { initialModel: effectiveModel } : {},
|
|
67958
|
+
// BRAIN-ROUTING thinking axis: forward the effective thinking level (initialThinkingLevel).
|
|
67959
|
+
...effectiveThinkingLevel ? { initialThinkingLevel: effectiveThinkingLevel } : {}
|
|
67960
|
+
}));
|
|
67961
|
+
} catch (e) {
|
|
67962
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason: `remote_launch_dispatch_failed: ${e?.message || String(e)}`, nodeId, providerType: resolved.providerType });
|
|
67963
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
67964
|
+
sweepExpiredCooldowns();
|
|
67965
|
+
return false;
|
|
67966
|
+
}
|
|
67967
|
+
const payload = launchResult2 && typeof launchResult2 === "object" && "payload" in launchResult2 && launchResult2.payload && typeof launchResult2.payload === "object" ? launchResult2.payload : launchResult2;
|
|
67968
|
+
if (!payload?.success) {
|
|
67969
|
+
const reason = readNonEmptyString(payload?.error) || "remote_launch_cli_failed";
|
|
67970
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
|
|
67971
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
67972
|
+
sweepExpiredCooldowns();
|
|
67973
|
+
return false;
|
|
67974
|
+
}
|
|
67975
|
+
const remoteSessionId = readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.id) || readNonEmptyString(payload.runtimeSessionId);
|
|
67976
|
+
markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId: remoteSessionId || void 0, ...effectiveModel ? { model: effectiveModel } : {}, ...effectiveThinkingLevel ? { thinkingLevel: effectiveThinkingLevel } : {} });
|
|
67977
|
+
logAutoLaunchQuotaFallbackSuccess(resolved, task.id, nodeId, remoteSessionId || void 0);
|
|
67978
|
+
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
67979
|
+
sweepExpiredCooldowns();
|
|
67980
|
+
return true;
|
|
67981
|
+
}
|
|
67411
67982
|
markAutoLaunch(meshId, task.id, { status: "started", nodeId, providerType: resolved.providerType, ...effectiveModel ? { model: effectiveModel } : {}, ...effectiveThinkingLevel ? { thinkingLevel: effectiveThinkingLevel } : {} });
|
|
67412
|
-
|
|
67413
|
-
|
|
67414
|
-
|
|
67415
|
-
|
|
67416
|
-
|
|
67417
|
-
|
|
67418
|
-
|
|
67419
|
-
|
|
67420
|
-
|
|
67421
|
-
|
|
67422
|
-
|
|
67423
|
-
|
|
67424
|
-
})
|
|
67425
|
-
} catch (e) {
|
|
67426
|
-
markAutoLaunch(meshId, task.id, { status: "failed", reason: `remote_launch_dispatch_failed: ${e?.message || String(e)}`, nodeId, providerType: resolved.providerType });
|
|
67983
|
+
const launchResult = await components.cliManager.handleCliCommand("launch_cli", {
|
|
67984
|
+
cliType: resolved.providerType,
|
|
67985
|
+
dir: node.workspace,
|
|
67986
|
+
settings: launchSettings,
|
|
67987
|
+
// MAGI-KIND-PANEL model axis: local launch forwards the effective model
|
|
67988
|
+
// (task override, else matched slot) as initialModel (CLI → modelLaunchArgs; ACP → setConfigOption).
|
|
67989
|
+
...effectiveModel ? { initialModel: effectiveModel } : {},
|
|
67990
|
+
// BRAIN-ROUTING thinking axis: forward the effective thinking level (initialThinkingLevel).
|
|
67991
|
+
...effectiveThinkingLevel ? { initialThinkingLevel: effectiveThinkingLevel } : {}
|
|
67992
|
+
});
|
|
67993
|
+
if (!launchResult?.success) {
|
|
67994
|
+
const reason = launchResult?.error || "launch_cli_failed";
|
|
67995
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
|
|
67427
67996
|
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
67428
67997
|
sweepExpiredCooldowns();
|
|
67429
67998
|
return false;
|
|
67430
67999
|
}
|
|
67431
|
-
const
|
|
67432
|
-
if (!
|
|
67433
|
-
|
|
67434
|
-
markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
|
|
68000
|
+
const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
|
|
68001
|
+
if (!sessionId) {
|
|
68002
|
+
markAutoLaunch(meshId, task.id, { status: "failed", reason: "launch_missing_session_id", nodeId, providerType: resolved.providerType });
|
|
67435
68003
|
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
67436
68004
|
sweepExpiredCooldowns();
|
|
67437
68005
|
return false;
|
|
67438
68006
|
}
|
|
67439
|
-
|
|
67440
|
-
|
|
67441
|
-
|
|
67442
|
-
|
|
67443
|
-
|
|
68007
|
+
markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId, ...effectiveModel ? { model: effectiveModel } : {}, ...effectiveThinkingLevel ? { thinkingLevel: effectiveThinkingLevel } : {} });
|
|
68008
|
+
logAutoLaunchQuotaFallbackSuccess(resolved, task.id, nodeId, sessionId);
|
|
68009
|
+
await waitForLocalSessionReady(components, sessionId);
|
|
68010
|
+
const requiredTags = Array.isArray(task.requiredTags) ? task.requiredTags.filter((t) => !!t) : [];
|
|
68011
|
+
const routingDecision = buildAutoLaunchRoutingDecision({
|
|
68012
|
+
node,
|
|
68013
|
+
meshId,
|
|
68014
|
+
task: { difficulty: task.difficulty, requiredTags: task.requiredTags },
|
|
68015
|
+
resolved,
|
|
68016
|
+
quotaRouting: mesh?.policy?.quotaRouting ?? null,
|
|
68017
|
+
quotaFactsContext: quotaFactsContextForLiveRouting(mesh, isLocalAutoLaunchNode, components.providerLoader),
|
|
68018
|
+
skippedCandidates,
|
|
68019
|
+
requiredTagsResult: {
|
|
68020
|
+
required: requiredTags,
|
|
68021
|
+
satisfied: !requiredTags.length || nodeSatisfiesRequiredTags(requiredTags, buildMeshNodeCapabilityTags(node, resolved.providerType)),
|
|
68022
|
+
missing: requiredTags.filter((t) => !buildMeshNodeCapabilityTags(node, resolved.providerType).includes(t))
|
|
68023
|
+
},
|
|
68024
|
+
effectiveModel,
|
|
68025
|
+
effectiveThinkingLevel,
|
|
68026
|
+
executedSlot: slotDecision.slot,
|
|
68027
|
+
demotionReason
|
|
68028
|
+
});
|
|
68029
|
+
tryAssignQueueTask(components, meshId, nodeId, sessionId, resolved.providerType, routingDecision);
|
|
67444
68030
|
return true;
|
|
67445
|
-
}
|
|
67446
|
-
|
|
67447
|
-
const launchResult = await components.cliManager.handleCliCommand("launch_cli", {
|
|
67448
|
-
cliType: resolved.providerType,
|
|
67449
|
-
dir: node.workspace,
|
|
67450
|
-
settings: launchSettings,
|
|
67451
|
-
// MAGI-KIND-PANEL model axis: local launch forwards the effective model
|
|
67452
|
-
// (task override, else matched slot) as initialModel (CLI → modelLaunchArgs; ACP → setConfigOption).
|
|
67453
|
-
...effectiveModel ? { initialModel: effectiveModel } : {},
|
|
67454
|
-
// BRAIN-ROUTING thinking axis: forward the effective thinking level (initialThinkingLevel).
|
|
67455
|
-
...effectiveThinkingLevel ? { initialThinkingLevel: effectiveThinkingLevel } : {}
|
|
67456
|
-
});
|
|
67457
|
-
if (!launchResult?.success) {
|
|
67458
|
-
const reason = launchResult?.error || "launch_cli_failed";
|
|
67459
|
-
markAutoLaunch(meshId, task.id, { status: "failed", reason, nodeId, providerType: resolved.providerType });
|
|
67460
|
-
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
67461
|
-
sweepExpiredCooldowns();
|
|
67462
|
-
return false;
|
|
67463
|
-
}
|
|
67464
|
-
const sessionId = readNonEmptyString(launchResult.sessionId) || readNonEmptyString(launchResult.id) || readNonEmptyString(launchResult.runtimeSessionId);
|
|
67465
|
-
if (!sessionId) {
|
|
67466
|
-
markAutoLaunch(meshId, task.id, { status: "failed", reason: "launch_missing_session_id", nodeId, providerType: resolved.providerType });
|
|
68031
|
+
} catch (e) {
|
|
68032
|
+
markAutoLaunch(meshId, task.id, { status: "failed", error: e?.message || String(e), nodeId });
|
|
67467
68033
|
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
67468
|
-
sweepExpiredCooldowns();
|
|
67469
68034
|
return false;
|
|
68035
|
+
} finally {
|
|
68036
|
+
autoLaunchInProgress.delete(launchKey);
|
|
67470
68037
|
}
|
|
67471
|
-
markAutoLaunch(meshId, task.id, { status: "completed", nodeId, providerType: resolved.providerType, sessionId, ...effectiveModel ? { model: effectiveModel } : {}, ...effectiveThinkingLevel ? { thinkingLevel: effectiveThinkingLevel } : {} });
|
|
67472
|
-
logAutoLaunchQuotaFallbackSuccess(resolved, task.id, nodeId, sessionId);
|
|
67473
|
-
await waitForLocalSessionReady(components, sessionId);
|
|
67474
|
-
const requiredTags = Array.isArray(task.requiredTags) ? task.requiredTags.filter((t) => !!t) : [];
|
|
67475
|
-
const routingDecision = buildAutoLaunchRoutingDecision({
|
|
67476
|
-
node,
|
|
67477
|
-
meshId,
|
|
67478
|
-
task: { difficulty: task.difficulty, requiredTags: task.requiredTags },
|
|
67479
|
-
resolved,
|
|
67480
|
-
quotaRouting: mesh?.policy?.quotaRouting ?? null,
|
|
67481
|
-
quotaFactsContext: quotaFactsContextForLiveRouting(mesh, isLocalAutoLaunchNode),
|
|
67482
|
-
skippedCandidates,
|
|
67483
|
-
requiredTagsResult: {
|
|
67484
|
-
required: requiredTags,
|
|
67485
|
-
satisfied: !requiredTags.length || nodeSatisfiesRequiredTags(requiredTags, buildMeshNodeCapabilityTags(node, resolved.providerType)),
|
|
67486
|
-
missing: requiredTags.filter((t) => !buildMeshNodeCapabilityTags(node, resolved.providerType).includes(t))
|
|
67487
|
-
},
|
|
67488
|
-
effectiveModel,
|
|
67489
|
-
effectiveThinkingLevel,
|
|
67490
|
-
executedSlot: slotDecision.slot,
|
|
67491
|
-
demotionReason
|
|
67492
|
-
});
|
|
67493
|
-
tryAssignQueueTask(components, meshId, nodeId, sessionId, resolved.providerType, routingDecision);
|
|
67494
|
-
return true;
|
|
67495
|
-
} catch (e) {
|
|
67496
|
-
markAutoLaunch(meshId, task.id, { status: "failed", error: e?.message || String(e), nodeId });
|
|
67497
|
-
autoLaunchCooldownUntil.set(launchKey, Date.now() + AUTO_LAUNCH_COOLDOWN_MS);
|
|
67498
|
-
return false;
|
|
67499
|
-
} finally {
|
|
67500
|
-
autoLaunchInProgress.delete(launchKey);
|
|
67501
68038
|
}
|
|
68039
|
+
} finally {
|
|
68040
|
+
autoLaunchTaskInProgress.delete(taskLaunchKey);
|
|
67502
68041
|
}
|
|
67503
68042
|
}
|
|
67504
68043
|
return false;
|
|
@@ -67545,7 +68084,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
67545
68084
|
if (instMeshId !== meshId) continue;
|
|
67546
68085
|
const nodeId = readNonEmptyString(settings.meshNodeId) || readNonEmptyString(settings.nodeId);
|
|
67547
68086
|
if (!nodeId) continue;
|
|
67548
|
-
if (!
|
|
68087
|
+
if (!isIdleSessionState3(state)) {
|
|
67549
68088
|
const status = readNonEmptyString(state.status).toLowerCase();
|
|
67550
68089
|
skippedSessions.push({
|
|
67551
68090
|
nodeId,
|
|
@@ -67618,6 +68157,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
67618
68157
|
}
|
|
67619
68158
|
}
|
|
67620
68159
|
autoLaunchStarted = await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
|
|
68160
|
+
sweepAutoLaunchOrphanSessions(components, meshId);
|
|
67621
68161
|
const afterQueue = getQueue(meshId);
|
|
67622
68162
|
const pendingAfter = afterQueue.filter((task) => task.status === "pending").length;
|
|
67623
68163
|
const assignedAfter = afterQueue.filter((task) => task.status === "assigned").length;
|
|
@@ -67676,12 +68216,10 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
67676
68216
|
var LOCAL_LAUNCH_READY_TIMEOUT_MS;
|
|
67677
68217
|
var LOCAL_LAUNCH_READY_POLL_MS;
|
|
67678
68218
|
var autoLaunchInProgress;
|
|
68219
|
+
var autoLaunchTaskInProgress;
|
|
67679
68220
|
var autoLaunchCooldownUntil;
|
|
67680
68221
|
var AUTO_LAUNCH_COOLDOWN_MS;
|
|
67681
|
-
var
|
|
67682
|
-
var AUTO_LAUNCH_AWAIT_CLAIM_BACKOFF_CAP_CYCLES;
|
|
67683
|
-
var AUTO_LAUNCH_REMOTE_IDLE_TTL_MS;
|
|
67684
|
-
var autoLaunchAwaitClaimBackoff;
|
|
68222
|
+
var AUTO_LAUNCH_AWAIT_CLAIM_BACKOFF_CAP_CYCLES2;
|
|
67685
68223
|
var init_mesh_queue_assignment = __esm2({
|
|
67686
68224
|
"src/mesh/mesh-queue-assignment.ts"() {
|
|
67687
68225
|
"use strict";
|
|
@@ -67714,14 +68252,17 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
67714
68252
|
init_mesh_duplicate_dispatch();
|
|
67715
68253
|
init_mesh_auto_fast_forward();
|
|
67716
68254
|
init_mesh_skip_notify();
|
|
68255
|
+
init_mesh_task_parking();
|
|
67717
68256
|
init_mesh_scheduling_fitness();
|
|
67718
68257
|
init_mesh_queue_observability();
|
|
67719
68258
|
init_mesh_routing_decision();
|
|
68259
|
+
init_mesh_autolaunch_integrity();
|
|
67720
68260
|
init_mesh_difficulty_floor();
|
|
67721
68261
|
init_mesh_difficulty_floor();
|
|
67722
68262
|
init_mesh_auto_fast_forward();
|
|
67723
68263
|
init_mesh_skip_notify();
|
|
67724
68264
|
init_mesh_scheduling_fitness();
|
|
68265
|
+
init_mesh_autolaunch_integrity();
|
|
67725
68266
|
BOOTSTRAP_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["complete", "failed"]);
|
|
67726
68267
|
DISPATCH_CONFIRM_TIMEOUT_MS = 12e4;
|
|
67727
68268
|
DISPATCH_CONNECT_TIMEOUT_MS = MESH_CONNECT_TIMEOUT_MS2;
|
|
@@ -67729,12 +68270,10 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
67729
68270
|
LOCAL_LAUNCH_READY_TIMEOUT_MS = 15e3;
|
|
67730
68271
|
LOCAL_LAUNCH_READY_POLL_MS = 100;
|
|
67731
68272
|
autoLaunchInProgress = /* @__PURE__ */ new Set();
|
|
68273
|
+
autoLaunchTaskInProgress = /* @__PURE__ */ new Set();
|
|
67732
68274
|
autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
|
|
67733
68275
|
AUTO_LAUNCH_COOLDOWN_MS = 5e3;
|
|
67734
|
-
|
|
67735
|
-
AUTO_LAUNCH_AWAIT_CLAIM_BACKOFF_CAP_CYCLES = 2;
|
|
67736
|
-
AUTO_LAUNCH_REMOTE_IDLE_TTL_MS = 5 * 60 * 1e3;
|
|
67737
|
-
autoLaunchAwaitClaimBackoff = /* @__PURE__ */ new Map();
|
|
68276
|
+
AUTO_LAUNCH_AWAIT_CLAIM_BACKOFF_CAP_CYCLES2 = 2;
|
|
67738
68277
|
}
|
|
67739
68278
|
});
|
|
67740
68279
|
function readSettings(state) {
|
|
@@ -70468,6 +71007,11 @@ ${cleanBody}`;
|
|
|
70468
71007
|
if (isFalseIdleCompletion(metadataEvent)) return false;
|
|
70469
71008
|
return !!readWorkerResultMetadata(metadataEvent) || !!readNonEmptyString(metadataEvent.finalSummary);
|
|
70470
71009
|
}
|
|
71010
|
+
function resolveGraphEnvelopeWorkerResult(metadataEvent) {
|
|
71011
|
+
const explicit = readWorkerResultMetadata(metadataEvent);
|
|
71012
|
+
if (explicit) return explicit;
|
|
71013
|
+
return extractJsonObjectFromSummary(readNonEmptyString(metadataEvent.finalSummary) || void 0);
|
|
71014
|
+
}
|
|
70471
71015
|
function supersedesTruncatedTerminalSummary(args) {
|
|
70472
71016
|
if (!args.terminalTaskId || !args.eventTaskId || args.terminalTaskId !== args.eventTaskId) return false;
|
|
70473
71017
|
if (!isGenuineCompletionEvidence(args.metadataEvent)) return false;
|
|
@@ -71105,7 +71649,7 @@ ${cleanBody}`;
|
|
|
71105
71649
|
// as the terminal flip (design :317-327 step 2).
|
|
71106
71650
|
envelope: {
|
|
71107
71651
|
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
|
|
71108
|
-
workerResult:
|
|
71652
|
+
workerResult: resolveGraphEnvelopeWorkerResult(args.metadataEvent),
|
|
71109
71653
|
nodeId: readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0,
|
|
71110
71654
|
providerType: readNonEmptyString(args.metadataEvent.providerType) || void 0
|
|
71111
71655
|
}
|
|
@@ -71531,7 +72075,7 @@ ${cleanBody}`;
|
|
|
71531
72075
|
// daemon's live quota cache, not the git_status-stamped
|
|
71532
72076
|
// nodeFacts copy (mesh-quota-routing). Remote nodes keep
|
|
71533
72077
|
// reading their reported nodeFacts.
|
|
71534
|
-
quotaFactsContextForLiveRouting(mesh, isLocalAutoLaunchNode)
|
|
72078
|
+
quotaFactsContextForLiveRouting(mesh, isLocalAutoLaunchNode, components.providerLoader)
|
|
71535
72079
|
) : { action: "block" };
|
|
71536
72080
|
const relaunchProviderType = relaunch.action === "block" ? null : relaunch.providerType;
|
|
71537
72081
|
if (node) {
|
|
@@ -82384,6 +82928,7 @@ ${e?.stderr || ""}`;
|
|
|
82384
82928
|
CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES: () => CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES,
|
|
82385
82929
|
CHAT_MESSAGE_VISIBILITIES: () => CHAT_MESSAGE_VISIBILITIES,
|
|
82386
82930
|
COMPACT_STATUS_GOAL_PREVIEW_MAX: () => COMPACT_STATUS_GOAL_PREVIEW_MAX,
|
|
82931
|
+
CTRL_C: () => CTRL_C,
|
|
82387
82932
|
CdpDomHandlers: () => CdpDomHandlers,
|
|
82388
82933
|
CliProviderInstance: () => CliProviderInstance,
|
|
82389
82934
|
DAEMON_WS_PATH: () => DAEMON_WS_PATH2,
|
|
@@ -82393,6 +82938,7 @@ ${e?.stderr || ""}`;
|
|
|
82393
82938
|
DEFAULT_CHAT_TAIL_MISSING_SESSION_POLICY: () => DEFAULT_CHAT_TAIL_MISSING_SESSION_POLICY,
|
|
82394
82939
|
DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS: () => DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS2,
|
|
82395
82940
|
DEFAULT_DAEMON_PORT: () => DEFAULT_DAEMON_PORT2,
|
|
82941
|
+
DEFAULT_DELIVERY_MODE: () => DEFAULT_DELIVERY_MODE,
|
|
82396
82942
|
DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS: () => DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS,
|
|
82397
82943
|
DEFAULT_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS: () => DEFAULT_MACHINE_RUNTIME_SUBSCRIPTION_INTERVAL_MS2,
|
|
82398
82944
|
DEFAULT_MESH_POLICY: () => DEFAULT_MESH_POLICY,
|
|
@@ -82418,6 +82964,7 @@ ${e?.stderr || ""}`;
|
|
|
82418
82964
|
DaemonStatusReporter: () => DaemonStatusReporter,
|
|
82419
82965
|
DevServer: () => DevServer,
|
|
82420
82966
|
DuplicateMeshDispatchError: () => DuplicateMeshDispatchError,
|
|
82967
|
+
ESC: () => ESC,
|
|
82421
82968
|
FsmDriver: () => FsmDriver,
|
|
82422
82969
|
GOAL_PREVIEW_MAX: () => GOAL_PREVIEW_MAX,
|
|
82423
82970
|
GitCommandError: () => GitCommandError,
|
|
@@ -82437,6 +82984,7 @@ ${e?.stderr || ""}`;
|
|
|
82437
82984
|
MAX_SIZE_ROTATION_GENERATIONS: () => MAX_SIZE_ROTATION_GENERATIONS,
|
|
82438
82985
|
MESH_CONVERGE_FAST_FORWARD_TAG: () => MESH_CONVERGE_FAST_FORWARD_TAG,
|
|
82439
82986
|
MESH_CONVERGE_REFINE_TAG: () => MESH_CONVERGE_REFINE_TAG,
|
|
82987
|
+
MESH_DECLARED_ELIGIBLE_SINGLE_HINT: () => MESH_DECLARED_ELIGIBLE_SINGLE_HINT,
|
|
82440
82988
|
MESH_GATE_DEFAULT_LEASE_SECONDS: () => MESH_GATE_DEFAULT_LEASE_SECONDS,
|
|
82441
82989
|
MESH_GATE_NAMED_OUTCOMES: () => MESH_GATE_NAMED_OUTCOMES,
|
|
82442
82990
|
MESH_GATE_RELEASE_PATCH_KEYS: () => MESH_GATE_RELEASE_PATCH_KEYS,
|
|
@@ -82471,6 +83019,10 @@ ${e?.stderr || ""}`;
|
|
|
82471
83019
|
OPERATING_NOTE_KIND: () => OPERATING_NOTE_KIND,
|
|
82472
83020
|
OPERATING_NOTE_TOMBSTONE_KIND: () => OPERATING_NOTE_TOMBSTONE_KIND,
|
|
82473
83021
|
P2pRelayFailureError: () => P2pRelayFailureError,
|
|
83022
|
+
PARKED_SKIP_REASON: () => PARKED_SKIP_REASON,
|
|
83023
|
+
PARKED_TASK_RETENTION_MS: () => PARKED_TASK_RETENTION_MS,
|
|
83024
|
+
PARK_REASON_PIN_EXPIRED: () => PARK_REASON_PIN_EXPIRED,
|
|
83025
|
+
PARK_RETENTION_EXPIRED_REASON: () => PARK_RETENTION_EXPIRED_REASON,
|
|
82474
83026
|
PROVIDER_CHANNEL_ENV_VAR: () => PROVIDER_CHANNEL_ENV_VAR,
|
|
82475
83027
|
PRUNABLE_ORPHAN_STALE_REASONS: () => PRUNABLE_ORPHAN_STALE_REASONS,
|
|
82476
83028
|
ProviderChannelError: () => ProviderChannelError,
|
|
@@ -82484,6 +83036,7 @@ ${e?.stderr || ""}`;
|
|
|
82484
83036
|
STALE_MAGI_WINDOW_MS: () => STALE_MAGI_WINDOW_MS,
|
|
82485
83037
|
STALE_TERMINAL_REFINE_WINDOW_MS: () => STALE_TERMINAL_REFINE_WINDOW_MS,
|
|
82486
83038
|
STANDALONE_CDP_SCAN_INTERVAL_MS: () => STANDALONE_CDP_SCAN_INTERVAL_MS2,
|
|
83039
|
+
STOP_CONTROL_ID: () => STOP_CONTROL_ID,
|
|
82487
83040
|
SYM: () => SYM,
|
|
82488
83041
|
SessionHostPtyTransportFactory: () => SessionHostPtyTransportFactory2,
|
|
82489
83042
|
StatuslineInstallError: () => StatuslineInstallError2,
|
|
@@ -82534,6 +83087,7 @@ ${e?.stderr || ""}`;
|
|
|
82534
83087
|
buildMissionPromptSection: () => buildMissionPromptSection,
|
|
82535
83088
|
buildOrphanedPinNotice: () => buildOrphanedPinNotice,
|
|
82536
83089
|
buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
|
|
83090
|
+
buildParkedTaskNotice: () => buildParkedTaskNotice,
|
|
82537
83091
|
buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
|
|
82538
83092
|
buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
|
|
82539
83093
|
buildSessionEntries: () => buildSessionEntries,
|
|
@@ -82613,6 +83167,7 @@ ${e?.stderr || ""}`;
|
|
|
82613
83167
|
evaluateProviderQuotaGate: () => evaluateProviderQuotaGate,
|
|
82614
83168
|
execNpmCommandSync: () => execNpmCommandSync,
|
|
82615
83169
|
expandDaemonIdForms: () => expandDaemonIdForms,
|
|
83170
|
+
failRetentionExpiredParkedTask: () => failRetentionExpiredParkedTask,
|
|
82616
83171
|
fastForwardMeshNode: () => fastForwardMeshNode,
|
|
82617
83172
|
fetchAntigravityQuota: () => fetchAntigravityQuota2,
|
|
82618
83173
|
fetchClaudeQuota: () => fetchClaudeQuota2,
|
|
@@ -82665,6 +83220,7 @@ ${e?.stderr || ""}`;
|
|
|
82665
83220
|
getMeshStatusMissionSummaries: () => getMeshStatusMissionSummaries,
|
|
82666
83221
|
getMeshStatusMissionsCompact: () => getMeshStatusMissionsCompact,
|
|
82667
83222
|
getNpmExecOptions: () => getNpmExecOptions,
|
|
83223
|
+
getParkedTasks: () => getParkedTasks,
|
|
82668
83224
|
getPendingMeshCoordinatorEvents: () => getPendingMeshCoordinatorEvents,
|
|
82669
83225
|
getProcessInstanceContext: () => getProcessInstanceContext,
|
|
82670
83226
|
getQueue: () => getQueue,
|
|
@@ -82764,6 +83320,7 @@ ${e?.stderr || ""}`;
|
|
|
82764
83320
|
normalizeChatMessageKind: () => normalizeChatMessageKind,
|
|
82765
83321
|
normalizeChatMessages: () => normalizeChatMessages,
|
|
82766
83322
|
normalizeChatTailActiveModal: () => normalizeChatTailActiveModal,
|
|
83323
|
+
normalizeDeliveryMode: () => normalizeDeliveryMode,
|
|
82767
83324
|
normalizeGitOutput: () => normalizeGitOutput,
|
|
82768
83325
|
normalizeGitWorkspaceSubscriptionParams: () => normalizeGitWorkspaceSubscriptionParams2,
|
|
82769
83326
|
normalizeInputEnvelope: () => normalizeInputEnvelope,
|
|
@@ -82784,7 +83341,11 @@ ${e?.stderr || ""}`;
|
|
|
82784
83341
|
normalizeRepoMeshDeclarativeConfig: () => normalizeRepoMeshDeclarativeConfig,
|
|
82785
83342
|
normalizeSessionModalFields: () => normalizeSessionModalFields,
|
|
82786
83343
|
notifyCoordinatorOfOrphanedPins: () => notifyCoordinatorOfOrphanedPins,
|
|
83344
|
+
notifyCoordinatorOfParkedTaskDropped: () => notifyCoordinatorOfParkedTaskDropped,
|
|
82787
83345
|
otherTrackConfigDir: () => otherTrackConfigDir,
|
|
83346
|
+
parkTaskTargetPin: () => parkTaskTargetPin,
|
|
83347
|
+
parkedAgeMs: () => parkedAgeMs,
|
|
83348
|
+
parkedTaskRetentionExpired: () => parkedTaskRetentionExpired,
|
|
82788
83349
|
parseOnDependencyFailurePolicy: () => parseOnDependencyFailurePolicy,
|
|
82789
83350
|
parsePorcelainV2Status: () => parsePorcelainV2Status,
|
|
82790
83351
|
parseProviderSourceConfigUpdate: () => parseProviderSourceConfigUpdate,
|
|
@@ -82835,6 +83396,7 @@ ${e?.stderr || ""}`;
|
|
|
82835
83396
|
recordMeshToolCall: () => recordMeshToolCall,
|
|
82836
83397
|
recordMissingSessionAttempt: () => recordMissingSessionAttempt2,
|
|
82837
83398
|
recordSessionUsage: () => recordSessionUsage,
|
|
83399
|
+
recordSingleEnqueueDecision: () => recordSingleEnqueueDecision,
|
|
82838
83400
|
recoverExpiredWorkspaceSagas: () => recoverExpiredWorkspaceSagas,
|
|
82839
83401
|
registerExtensionProviders: () => registerExtensionProviders,
|
|
82840
83402
|
registerMeshCoordinator: () => registerMeshCoordinator,
|
|
@@ -82867,6 +83429,7 @@ ${e?.stderr || ""}`;
|
|
|
82867
83429
|
resolveGitRepository: () => resolveGitRepository,
|
|
82868
83430
|
resolveInstanceContext: () => resolveInstanceContext,
|
|
82869
83431
|
resolveInstanceDir: () => resolveInstanceDir,
|
|
83432
|
+
resolveInterruptCapability: () => resolveInterruptCapability,
|
|
82870
83433
|
resolveMagiSessionCleanupMode: () => resolveMagiSessionCleanupMode,
|
|
82871
83434
|
resolveMaxParallelTasks: () => resolveMaxParallelTasks,
|
|
82872
83435
|
resolveMeshHostStatus: () => resolveMeshHostStatus,
|
|
@@ -82924,6 +83487,7 @@ ${e?.stderr || ""}`;
|
|
|
82924
83487
|
supportsUnicodeSymbols: () => supportsUnicodeSymbols,
|
|
82925
83488
|
sweepMeshGraphGateTimeouts: () => sweepMeshGraphGateTimeouts,
|
|
82926
83489
|
taskDependenciesSatisfied: () => taskDependenciesSatisfied,
|
|
83490
|
+
taskIsParked: () => taskIsParked,
|
|
82927
83491
|
tombstoneOperatingNote: () => tombstoneOperatingNote,
|
|
82928
83492
|
totalTokens: () => totalTokens,
|
|
82929
83493
|
triggerMeshQueue: () => triggerMeshQueue,
|
|
@@ -85421,6 +85985,8 @@ ${e?.stderr || ""}`;
|
|
|
85421
85985
|
};
|
|
85422
85986
|
}
|
|
85423
85987
|
init_mesh_work_queue();
|
|
85988
|
+
init_mesh_work_queue();
|
|
85989
|
+
init_mesh_task_parking();
|
|
85424
85990
|
init_mesh_graph_derived_failure();
|
|
85425
85991
|
init_mesh_graph_workspace_saga();
|
|
85426
85992
|
init_mesh_graph_workspace_safety();
|
|
@@ -86020,6 +86586,12 @@ ${e?.stderr || ""}`;
|
|
|
86020
86586
|
workspaceRef: ws.workspaceRef,
|
|
86021
86587
|
detail: `Workspace '${ws.workspaceRef}' needs manual compensation: ${ws.lastError ?? "compensation refused"}.`
|
|
86022
86588
|
});
|
|
86589
|
+
} else if (ws.sagaState === "declared" && !ws.baseRevision) {
|
|
86590
|
+
actions.push({
|
|
86591
|
+
kind: "workspace_declared_no_base",
|
|
86592
|
+
workspaceRef: ws.workspaceRef,
|
|
86593
|
+
detail: `Workspace '${ws.workspaceRef}' is stuck in 'declared': no base_revision was given and none could be derived from ${ws.sourceNodeId ? `source node '${ws.sourceNodeId}' (its repo is unreachable on this daemon, or git could not resolve its HEAD)` : "a source node, because the declaration named none"}. The workspace will never clone and every task bound to it stays blocked. Re-declare the workspace with an explicit base_revision (e.g. 'main')${ws.sourceNodeId ? "" : " and a source_node_id"}.`
|
|
86594
|
+
});
|
|
86023
86595
|
}
|
|
86024
86596
|
}
|
|
86025
86597
|
return {
|
|
@@ -86186,6 +86758,47 @@ The pin is NOT cleared automatically: a pin often encodes required context conti
|
|
|
86186
86758
|
init_contracts();
|
|
86187
86759
|
init_mesh_events_utils();
|
|
86188
86760
|
init_mesh_delivery_policy();
|
|
86761
|
+
var CTRL_C = "";
|
|
86762
|
+
var ESC = "\x1B";
|
|
86763
|
+
var STOP_CONTROL_ID = "stop";
|
|
86764
|
+
var LIVE_VERIFIED_PROVIDERS = /* @__PURE__ */ new Set(["claude-cli"]);
|
|
86765
|
+
function nameForKeys(keys) {
|
|
86766
|
+
if (keys === CTRL_C) return "Ctrl-C";
|
|
86767
|
+
if (keys === ESC) return "ESC";
|
|
86768
|
+
return "custom";
|
|
86769
|
+
}
|
|
86770
|
+
function resolveInterruptCapability(providerType, controlBar) {
|
|
86771
|
+
const stop = (controlBar ?? []).find((c) => c.id === STOP_CONTROL_ID);
|
|
86772
|
+
if (!stop) {
|
|
86773
|
+
return {
|
|
86774
|
+
supported: false,
|
|
86775
|
+
reason: "no_stop_control",
|
|
86776
|
+
message: `Provider '${providerType}' declares no '${STOP_CONTROL_ID}' control, so a running turn cannot be interrupted. Dispatch with delivery mode when_idle and the task will be delivered once the session finishes on its own.`
|
|
86777
|
+
};
|
|
86778
|
+
}
|
|
86779
|
+
if (stop.action?.type !== "send_keys") {
|
|
86780
|
+
return {
|
|
86781
|
+
supported: false,
|
|
86782
|
+
reason: "stop_control_not_send_keys",
|
|
86783
|
+
message: `Provider '${providerType}' declares a '${STOP_CONTROL_ID}' control of type '${stop.action?.type}', not a key write, so it cannot be used to abort a turn. Dispatch with delivery mode when_idle instead.`
|
|
86784
|
+
};
|
|
86785
|
+
}
|
|
86786
|
+
const keys = stop.action.keys;
|
|
86787
|
+
if (typeof keys !== "string" || keys.length === 0) {
|
|
86788
|
+
return {
|
|
86789
|
+
supported: false,
|
|
86790
|
+
reason: "stop_keys_empty",
|
|
86791
|
+
message: `Provider '${providerType}' declares a '${STOP_CONTROL_ID}' control with an EMPTY key sequence, so pressing it would write nothing to the terminal while still reporting success. Treating this as unsupported rather than silently doing nothing. Dispatch with delivery mode when_idle, or fix the provider spec to declare a real stop key.`
|
|
86792
|
+
};
|
|
86793
|
+
}
|
|
86794
|
+
return {
|
|
86795
|
+
supported: true,
|
|
86796
|
+
keys,
|
|
86797
|
+
keyName: nameForKeys(keys),
|
|
86798
|
+
confidence: LIVE_VERIFIED_PROVIDERS.has(providerType) ? "proven" : "declared",
|
|
86799
|
+
...stop.visible_when_state ? { visibleWhenState: stop.visible_when_state } : {}
|
|
86800
|
+
};
|
|
86801
|
+
}
|
|
86189
86802
|
var NO_FALLBACK_REASON = "This mesh operation needs a live peer-to-peer connection to the node, which is not open right now. This is often transient \u2014 the peer may be connecting, slow via TURN relay, or briefly offline. Wait a moment and retry, or re-establish the node's connection. Mesh commands use P2P only by design \u2014 there is no cloud/WS relay fallback.";
|
|
86190
86803
|
var P2P_NEXT_ACTION = "The peer connection is recoverable. Wait a moment for the connection to establish (especially over TURN relay), then retry. If the node remains unreachable, check that the target daemon is running and online, then re-establish its connection.";
|
|
86191
86804
|
var NON_P2P_NEXT_ACTION = "Inspect the provider/command error and fix the underlying logic or configuration before retrying.";
|
|
@@ -101993,6 +102606,52 @@ ${marker}`,
|
|
|
101993
102606
|
hash: hash2
|
|
101994
102607
|
};
|
|
101995
102608
|
}
|
|
102609
|
+
/**
|
|
102610
|
+
* Report whether this provider can have its in-flight turn interrupted,
|
|
102611
|
+
* resolved from the spec THIS session actually loaded (never a hardcoded
|
|
102612
|
+
* per-provider table — the stop key varies by spec version; hermes-cli
|
|
102613
|
+
* ships Ctrl-C in specs/0.14.json and an EMPTY key in specs/4.0.json).
|
|
102614
|
+
*/
|
|
102615
|
+
getInterruptCapability() {
|
|
102616
|
+
return resolveInterruptCapability(this.cliType, this.spec.control_bar);
|
|
102617
|
+
}
|
|
102618
|
+
/**
|
|
102619
|
+
* Abort the turn currently in flight by writing the provider's own stop
|
|
102620
|
+
* key to the PTY. Used by delivery mode 'interrupt': the caller then waits
|
|
102621
|
+
* for the FSM to report idle and lets the ordinary queued-send drain
|
|
102622
|
+
* deliver the new prompt as a genuine new turn.
|
|
102623
|
+
*
|
|
102624
|
+
* ★ Deliberately NOT routed through invokeScript('stop'). That path calls
|
|
102625
|
+
* FsmDriver.handleClickControl, which silently returns when the control's
|
|
102626
|
+
* `visible_when_state` does not include the current state, and calls
|
|
102627
|
+
* send_keys("") for a provider whose stop key is empty — while
|
|
102628
|
+
* invokeScript unconditionally returns `{ ok: true, effects:[sent_keys] }`
|
|
102629
|
+
* either way. Reporting a successful interrupt that wrote nothing is
|
|
102630
|
+
* exactly the failure this feature exists to remove, so capability is
|
|
102631
|
+
* validated HERE, before any write, and the outcome is reported honestly.
|
|
102632
|
+
*/
|
|
102633
|
+
async interruptTurn() {
|
|
102634
|
+
if (!this.spawned || this.exited) {
|
|
102635
|
+
return { ok: false, reason: "not_running", message: `${this.cliName} is not running.` };
|
|
102636
|
+
}
|
|
102637
|
+
const cap = this.getInterruptCapability();
|
|
102638
|
+
if (!cap.supported) {
|
|
102639
|
+
LOG2.warn("SpecAdapter", `[${this.cliType}] interrupt refused: ${cap.reason}`);
|
|
102640
|
+
return { ok: false, reason: cap.reason, message: cap.message };
|
|
102641
|
+
}
|
|
102642
|
+
const status = this.latestState?.status;
|
|
102643
|
+
if (status !== "generating") {
|
|
102644
|
+
return {
|
|
102645
|
+
ok: false,
|
|
102646
|
+
reason: "not_busy",
|
|
102647
|
+
message: `Session is '${status ?? "unknown"}', not generating \u2014 nothing to interrupt.`
|
|
102648
|
+
};
|
|
102649
|
+
}
|
|
102650
|
+
this.driver.dispatch({ kind: "pty_write", data: cap.keys });
|
|
102651
|
+
const bytes = Buffer.byteLength(cap.keys, "utf8");
|
|
102652
|
+
LOG2.info("SpecAdapter", `[${this.cliType}] turn interrupted via ${cap.keyName} (bytes=${bytes}, confidence=${cap.confidence})`);
|
|
102653
|
+
return { ok: true, keyName: cap.keyName, bytes, confidence: cap.confidence };
|
|
102654
|
+
}
|
|
101996
102655
|
/**
|
|
101997
102656
|
* MESH-SEND-KEYS (feature 3: key injection). Inject a STRUCTURED key
|
|
101998
102657
|
* sequence into the spec-driven PTY for mesh_send_keys. Mirrors
|
|
@@ -110608,6 +111267,39 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
110608
111267
|
} else if (action === "stop") {
|
|
110609
111268
|
await this.stopSession(key2);
|
|
110610
111269
|
return { success: true, stopped: true };
|
|
111270
|
+
} else if (action === "interrupt_capability") {
|
|
111271
|
+
const probe = adapter;
|
|
111272
|
+
if (typeof probe.getInterruptCapability !== "function") {
|
|
111273
|
+
return {
|
|
111274
|
+
success: true,
|
|
111275
|
+
supported: false,
|
|
111276
|
+
reason: "interrupt_not_implemented",
|
|
111277
|
+
message: `Provider '${agentType}' runs on an adapter with no interrupt support.`
|
|
111278
|
+
};
|
|
111279
|
+
}
|
|
111280
|
+
const cap = probe.getInterruptCapability();
|
|
111281
|
+
return { success: true, ...cap };
|
|
111282
|
+
} else if (action === "interrupt_turn") {
|
|
111283
|
+
const interruptible = adapter;
|
|
111284
|
+
if (typeof interruptible.interruptTurn !== "function") {
|
|
111285
|
+
return {
|
|
111286
|
+
success: false,
|
|
111287
|
+
interrupted: false,
|
|
111288
|
+
reason: "interrupt_not_implemented",
|
|
111289
|
+
error: `Provider '${agentType}' runs on an adapter that cannot interrupt a turn.`
|
|
111290
|
+
};
|
|
111291
|
+
}
|
|
111292
|
+
const outcome = await interruptible.interruptTurn();
|
|
111293
|
+
if (!outcome.ok) {
|
|
111294
|
+
return { success: false, interrupted: false, reason: outcome.reason, error: outcome.message };
|
|
111295
|
+
}
|
|
111296
|
+
return {
|
|
111297
|
+
success: true,
|
|
111298
|
+
interrupted: true,
|
|
111299
|
+
keyName: outcome.keyName,
|
|
111300
|
+
bytes: outcome.bytes,
|
|
111301
|
+
confidence: outcome.confidence
|
|
111302
|
+
};
|
|
110611
111303
|
}
|
|
110612
111304
|
throw new Error(`Unknown action: ${action}`);
|
|
110613
111305
|
}
|
|
@@ -110770,7 +111462,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
110770
111462
|
const { shouldDeferDispatchForBootstrap: shouldDeferDispatchForBootstrap2 } = await Promise.resolve().then(() => (init_worktree_bootstrap_config(), worktree_bootstrap_config_exports));
|
|
110771
111463
|
if (shouldDeferDispatchForBootstrap2(nodeObj)) {
|
|
110772
111464
|
const dispatchSessionState = dispatchSessionId ? ctx.deps.instanceManager?.getInstance?.(dispatchSessionId)?.getState?.() : void 0;
|
|
110773
|
-
const sessionConfirmedReady = !!dispatchSessionState &&
|
|
111465
|
+
const sessionConfirmedReady = !!dispatchSessionState && isIdleSessionState3(dispatchSessionState);
|
|
110774
111466
|
if (!sessionConfirmedReady) {
|
|
110775
111467
|
return {
|
|
110776
111468
|
success: false,
|
|
@@ -118391,7 +119083,12 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
|
|
|
118391
119083
|
// CANON-IDENTITY: an in-flight (actively-generating) task is refused by
|
|
118392
119084
|
// default to avoid a duplicate second dispatch; an explicit operator
|
|
118393
119085
|
// force overrides that guard (and the retry cap).
|
|
118394
|
-
force: args?.force === true
|
|
119086
|
+
force: args?.force === true,
|
|
119087
|
+
// PIN-PARKING (edit): optional instruction rewrite, so a coordinator in
|
|
119088
|
+
// IPC/cloud mode has the same exit from parking as the in-process path.
|
|
119089
|
+
// Without this the field would be silently dropped exactly where the
|
|
119090
|
+
// multi-coordinator topology needs it most.
|
|
119091
|
+
message: typeof args?.message === "string" ? args.message : void 0
|
|
118395
119092
|
});
|
|
118396
119093
|
if (!task) return { success: false, error: `Queue task '${taskId}' not found` };
|
|
118397
119094
|
if (task.status === "assigned" && args?.force !== true) {
|