@adhdev/daemon-standalone 1.0.58-rc.33 → 1.0.58-rc.34
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 +1054 -72
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/vendor/mcp-server/index.js +258 -41
- package/vendor/mcp-server/index.js.map +1 -1
package/dist/index.js
CHANGED
|
@@ -36911,10 +36911,10 @@ var require_dist3 = __commonJS({
|
|
|
36911
36911
|
}
|
|
36912
36912
|
function getDaemonBuildInfo() {
|
|
36913
36913
|
if (cached2) return cached2;
|
|
36914
|
-
const commit = readInjected(true ? "
|
|
36915
|
-
const commitShort = readInjected(true ? "
|
|
36916
|
-
const version2 = readInjected(true ? "1.0.58-rc.
|
|
36917
|
-
const builtAt = readInjected(true ? "2026-08-
|
|
36914
|
+
const commit = readInjected(true ? "c56a70dfe6a9124c996b31104841e9838acb3a7e" : void 0) ?? "unknown";
|
|
36915
|
+
const commitShort = readInjected(true ? "c56a70df" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
36916
|
+
const version2 = readInjected(true ? "1.0.58-rc.34" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
36917
|
+
const builtAt = readInjected(true ? "2026-08-28T18:02:34.248Z" : void 0);
|
|
36918
36918
|
cached2 = builtAt ? { commit, commitShort, version: version2, builtAt } : { commit, commitShort, version: version2 };
|
|
36919
36919
|
return cached2;
|
|
36920
36920
|
}
|
|
@@ -47477,17 +47477,17 @@ Next step: ${nextStep}`;
|
|
|
47477
47477
|
}
|
|
47478
47478
|
return { delivered, failed, rescheduled };
|
|
47479
47479
|
}
|
|
47480
|
-
function enqueueSessionOrdered(
|
|
47481
|
-
const prior = sessionOrderChains.get(
|
|
47480
|
+
function enqueueSessionOrdered(sessionKey2, fn) {
|
|
47481
|
+
const prior = sessionOrderChains.get(sessionKey2) ?? Promise.resolve();
|
|
47482
47482
|
const next = prior.then(fn, fn);
|
|
47483
|
-
sessionOrderChains.set(
|
|
47483
|
+
sessionOrderChains.set(sessionKey2, next.catch(() => void 0));
|
|
47484
47484
|
return next;
|
|
47485
47485
|
}
|
|
47486
|
-
function runSessionEvidenceCollection(
|
|
47487
|
-
return enqueueSessionOrdered(
|
|
47486
|
+
function runSessionEvidenceCollection(sessionKey2, collect) {
|
|
47487
|
+
return enqueueSessionOrdered(sessionKey2, collect);
|
|
47488
47488
|
}
|
|
47489
|
-
function runSessionDestructiveAction(
|
|
47490
|
-
return enqueueSessionOrdered(
|
|
47489
|
+
function runSessionDestructiveAction(sessionKey2, act) {
|
|
47490
|
+
return enqueueSessionOrdered(sessionKey2, act);
|
|
47491
47491
|
}
|
|
47492
47492
|
function safeEvidenceJson(evidence) {
|
|
47493
47493
|
const sanitized = {};
|
|
@@ -48299,6 +48299,10 @@ ${blocks.join("\n\n")}`;
|
|
|
48299
48299
|
set3.add(minted.token);
|
|
48300
48300
|
return minted;
|
|
48301
48301
|
}
|
|
48302
|
+
function verifyWorkerTaskToken(token) {
|
|
48303
|
+
if (typeof token !== "string" || !token.trim()) return null;
|
|
48304
|
+
return LIVE_TOKENS.get(token.trim()) || null;
|
|
48305
|
+
}
|
|
48302
48306
|
function tokensForTask(meshId, taskId) {
|
|
48303
48307
|
const set3 = TOKENS_BY_TASK.get(taskKey(meshId, taskId));
|
|
48304
48308
|
if (!set3) return [];
|
|
@@ -48329,6 +48333,67 @@ ${blocks.join("\n\n")}`;
|
|
|
48329
48333
|
}
|
|
48330
48334
|
return removed;
|
|
48331
48335
|
}
|
|
48336
|
+
function sessionKey(meshId, sessionId) {
|
|
48337
|
+
return `${meshId}\0${sessionId}`;
|
|
48338
|
+
}
|
|
48339
|
+
function mintWorkerSessionBind(input) {
|
|
48340
|
+
const meshId = String(input.meshId || "").trim();
|
|
48341
|
+
const sessionId = String(input.sessionId || "").trim();
|
|
48342
|
+
if (!meshId || !sessionId) {
|
|
48343
|
+
throw new Error("mintWorkerSessionBind requires both meshId and sessionId");
|
|
48344
|
+
}
|
|
48345
|
+
for (const existing of bindsForSession(meshId, sessionId)) revokeWorkerSessionBind(existing.bind);
|
|
48346
|
+
const minted = {
|
|
48347
|
+
bind: `${WORKER_BIND_CANARY_PREFIX}${crypto2.randomBytes(32).toString("base64url")}`,
|
|
48348
|
+
meshId,
|
|
48349
|
+
sessionId,
|
|
48350
|
+
...input.nodeId ? { nodeId: String(input.nodeId).trim() } : {},
|
|
48351
|
+
...input.spawnedForTaskId ? { spawnedForTaskId: String(input.spawnedForTaskId).trim() } : {},
|
|
48352
|
+
mintedAtMs: Date.now()
|
|
48353
|
+
};
|
|
48354
|
+
LIVE_BINDS.set(minted.bind, minted);
|
|
48355
|
+
const key2 = sessionKey(meshId, sessionId);
|
|
48356
|
+
let set3 = BINDS_BY_SESSION.get(key2);
|
|
48357
|
+
if (!set3) {
|
|
48358
|
+
set3 = /* @__PURE__ */ new Set();
|
|
48359
|
+
BINDS_BY_SESSION.set(key2, set3);
|
|
48360
|
+
}
|
|
48361
|
+
set3.add(minted.bind);
|
|
48362
|
+
return minted;
|
|
48363
|
+
}
|
|
48364
|
+
function bindsForSession(meshId, sessionId) {
|
|
48365
|
+
const set3 = BINDS_BY_SESSION.get(sessionKey(meshId, sessionId));
|
|
48366
|
+
if (!set3) return [];
|
|
48367
|
+
const out = [];
|
|
48368
|
+
for (const secret of set3) {
|
|
48369
|
+
const found = LIVE_BINDS.get(secret);
|
|
48370
|
+
if (found) out.push(found);
|
|
48371
|
+
}
|
|
48372
|
+
return out;
|
|
48373
|
+
}
|
|
48374
|
+
function verifyWorkerSessionBind(bind) {
|
|
48375
|
+
if (typeof bind !== "string" || !bind.trim()) return null;
|
|
48376
|
+
return LIVE_BINDS.get(bind.trim()) || null;
|
|
48377
|
+
}
|
|
48378
|
+
function revokeWorkerSessionBind(bind) {
|
|
48379
|
+
const found = LIVE_BINDS.get(bind);
|
|
48380
|
+
if (!found) return false;
|
|
48381
|
+
LIVE_BINDS.delete(bind);
|
|
48382
|
+
const key2 = sessionKey(found.meshId, found.sessionId);
|
|
48383
|
+
const set3 = BINDS_BY_SESSION.get(key2);
|
|
48384
|
+
if (set3) {
|
|
48385
|
+
set3.delete(bind);
|
|
48386
|
+
if (set3.size === 0) BINDS_BY_SESSION.delete(key2);
|
|
48387
|
+
}
|
|
48388
|
+
return true;
|
|
48389
|
+
}
|
|
48390
|
+
function findWorkerTaskTokenForSession(meshId, taskId, sessionId) {
|
|
48391
|
+
const wanted = String(sessionId || "").trim();
|
|
48392
|
+
for (const candidate of tokensForTask(meshId, taskId)) {
|
|
48393
|
+
if (!candidate.sessionId || candidate.sessionId === wanted) return candidate;
|
|
48394
|
+
}
|
|
48395
|
+
return null;
|
|
48396
|
+
}
|
|
48332
48397
|
function findWorkerPrivateHomeSpec(providerType) {
|
|
48333
48398
|
const type2 = String(providerType || "").trim();
|
|
48334
48399
|
if (!type2) return null;
|
|
@@ -48405,15 +48470,13 @@ ${blocks.join("\n\n")}`;
|
|
|
48405
48470
|
}
|
|
48406
48471
|
const servers = {};
|
|
48407
48472
|
if (input.server) {
|
|
48473
|
+
const entryEnv = {};
|
|
48474
|
+
if (input.token) entryEnv.ADHDEV_WORKER_TASK_TOKEN = input.token;
|
|
48475
|
+
if (input.bind) entryEnv.ADHDEV_WORKER_SESSION_BIND = input.bind;
|
|
48408
48476
|
servers[input.serverName] = buildMeshCoordinatorMcpServerEntry(input.format, {
|
|
48409
48477
|
command: input.server.command,
|
|
48410
48478
|
args: input.server.args,
|
|
48411
|
-
|
|
48412
|
-
// env. Both are readable by the worker either way (§3 "남는
|
|
48413
|
-
// 리스크"), but keeping it here means the value travels only to the
|
|
48414
|
-
// process that reads this file, and never lands in a spawn env that
|
|
48415
|
-
// gets inherited further down a process tree.
|
|
48416
|
-
...input.token ? { env: { ADHDEV_WORKER_TASK_TOKEN: input.token } } : {}
|
|
48479
|
+
...Object.keys(entryEnv).length ? { env: entryEnv } : {}
|
|
48417
48480
|
});
|
|
48418
48481
|
}
|
|
48419
48482
|
const config2 = { [getMcpServersKey(input.format)]: servers };
|
|
@@ -48463,6 +48526,14 @@ ${blocks.join("\n\n")}`;
|
|
|
48463
48526
|
notes.push(`${declaredPath} is home-rooted but ${input.providerType} has no private HOME \u2014 refusing to overwrite the coordinator config`);
|
|
48464
48527
|
return result;
|
|
48465
48528
|
}
|
|
48529
|
+
let pendingBind = null;
|
|
48530
|
+
if (input.server && input.bindContext?.meshId && input.bindContext?.sessionId) {
|
|
48531
|
+
try {
|
|
48532
|
+
pendingBind = mintWorkerSessionBind(input.bindContext);
|
|
48533
|
+
} catch (err) {
|
|
48534
|
+
notes.push(`worker session bind mint failed (${err?.message || err})`);
|
|
48535
|
+
}
|
|
48536
|
+
}
|
|
48466
48537
|
try {
|
|
48467
48538
|
result.configPath = writeWorkerMcpConfig({
|
|
48468
48539
|
declaredPath,
|
|
@@ -48471,21 +48542,50 @@ ${blocks.join("\n\n")}`;
|
|
|
48471
48542
|
workspace: input.workspace,
|
|
48472
48543
|
workerHome: result.workerHome,
|
|
48473
48544
|
server: input.server,
|
|
48474
|
-
token: input.token
|
|
48545
|
+
token: input.token,
|
|
48546
|
+
...pendingBind ? { bind: pendingBind.bind } : {}
|
|
48475
48547
|
});
|
|
48476
|
-
|
|
48548
|
+
if (pendingBind) result.bind = pendingBind.bind;
|
|
48549
|
+
notes.push(
|
|
48550
|
+
`worker MCP config written to ${result.configPath}` + (pendingBind ? ` (session bind issued for ${pendingBind.sessionId})` : "")
|
|
48551
|
+
);
|
|
48477
48552
|
} catch (err) {
|
|
48553
|
+
if (pendingBind) revokeWorkerSessionBind(pendingBind.bind);
|
|
48478
48554
|
notes.push(`worker MCP config write failed (${err?.message || err})`);
|
|
48479
48555
|
LOG2.warn("WorkerMcp", `config write failed for ${input.providerType}: ${err?.message || err}`);
|
|
48480
48556
|
}
|
|
48481
48557
|
return result;
|
|
48482
48558
|
}
|
|
48559
|
+
function exchangeWorkerSessionBind(bind, resolveCurrentTask) {
|
|
48560
|
+
const binding = verifyWorkerSessionBind(bind);
|
|
48561
|
+
if (!binding) return null;
|
|
48562
|
+
let current = null;
|
|
48563
|
+
try {
|
|
48564
|
+
current = resolveCurrentTask(binding.meshId, binding.sessionId);
|
|
48565
|
+
} catch {
|
|
48566
|
+
return null;
|
|
48567
|
+
}
|
|
48568
|
+
if (!current?.taskId) return null;
|
|
48569
|
+
const token = findWorkerTaskTokenForSession(binding.meshId, current.taskId, binding.sessionId);
|
|
48570
|
+
if (!token) return null;
|
|
48571
|
+
return {
|
|
48572
|
+
token: token.token,
|
|
48573
|
+
meshId: binding.meshId,
|
|
48574
|
+
taskId: current.taskId,
|
|
48575
|
+
...token.attemptId ? { attemptId: token.attemptId } : {},
|
|
48576
|
+
sessionId: binding.sessionId,
|
|
48577
|
+
...binding.nodeId ? { nodeId: binding.nodeId } : {}
|
|
48578
|
+
};
|
|
48579
|
+
}
|
|
48483
48580
|
var crypto2;
|
|
48484
48581
|
var os13;
|
|
48485
48582
|
var path222;
|
|
48486
48583
|
var import_fs6;
|
|
48487
48584
|
var LIVE_TOKENS;
|
|
48488
48585
|
var TOKENS_BY_TASK;
|
|
48586
|
+
var LIVE_BINDS;
|
|
48587
|
+
var BINDS_BY_SESSION;
|
|
48588
|
+
var WORKER_BIND_CANARY_PREFIX;
|
|
48489
48589
|
var WORKER_PRIVATE_HOME_SPECS;
|
|
48490
48590
|
var WORKER_HOME_PLACEHOLDER;
|
|
48491
48591
|
var init_worker_mcp_isolation = __esm2({
|
|
@@ -48500,6 +48600,9 @@ ${blocks.join("\n\n")}`;
|
|
|
48500
48600
|
init_mesh_coordinator_config();
|
|
48501
48601
|
LIVE_TOKENS = /* @__PURE__ */ new Map();
|
|
48502
48602
|
TOKENS_BY_TASK = /* @__PURE__ */ new Map();
|
|
48603
|
+
LIVE_BINDS = /* @__PURE__ */ new Map();
|
|
48604
|
+
BINDS_BY_SESSION = /* @__PURE__ */ new Map();
|
|
48605
|
+
WORKER_BIND_CANARY_PREFIX = "wsb_";
|
|
48503
48606
|
WORKER_PRIVATE_HOME_SPECS = [
|
|
48504
48607
|
{
|
|
48505
48608
|
providerType: "antigravity-cli",
|
|
@@ -58229,6 +58332,26 @@ CREATE TABLE IF NOT EXISTS sq_archive (
|
|
|
58229
58332
|
"outcome",
|
|
58230
58333
|
"terminalKind",
|
|
58231
58334
|
"event",
|
|
58335
|
+
// ── Stage 4B additions (2026-08-28) ────────────────────────────────────
|
|
58336
|
+
// Added for the intentional-cleanup-stop consumer (mesh-event-suppression
|
|
58337
|
+
// `hasRecentIntentionalCleanupStop`). Each was verified against EVERY
|
|
58338
|
+
// producer in the tree before being asserted non-content:
|
|
58339
|
+
//
|
|
58340
|
+
// `source` — a closed set of snake_case slugs naming the code
|
|
58341
|
+
// path that wrote the entry ('direct',
|
|
58342
|
+
// 'mesh_cleanup_sessions', 'operator_cleanup', …).
|
|
58343
|
+
// Machine-authored discriminators, never prose.
|
|
58344
|
+
// `intentionalStopReason` — one literal producer
|
|
58345
|
+
// (`router-worktree-cleanup.ts`), value
|
|
58346
|
+
// `'operator_cleanup'`.
|
|
58347
|
+
//
|
|
58348
|
+
// ★ `source` is the widest of the two — it is written at many call sites,
|
|
58349
|
+
// so the MAX_PROJECTED_STRING cap is doing real work here rather than being
|
|
58350
|
+
// a formality. A caller who ever assigns prose to `source` gets truncated,
|
|
58351
|
+
// not leaked wholesale; but the per-key assertion above is what keeps it
|
|
58352
|
+
// out, and it must be re-checked if a producer starts composing this value.
|
|
58353
|
+
"source",
|
|
58354
|
+
"intentionalStopReason",
|
|
58232
58355
|
// Booleans
|
|
58233
58356
|
"retryable",
|
|
58234
58357
|
"rebound",
|
|
@@ -58238,6 +58361,9 @@ CREATE TABLE IF NOT EXISTS sq_archive (
|
|
|
58238
58361
|
"requestedForce",
|
|
58239
58362
|
"removedByRemoteDaemon",
|
|
58240
58363
|
"completedViaReady",
|
|
58364
|
+
// Stage 4B: strictly boolean at every producer (mesh-termination-bridge,
|
|
58365
|
+
// router-worktree-cleanup) — the intentional/accidental stop discriminator.
|
|
58366
|
+
"intentional",
|
|
58241
58367
|
// Counters
|
|
58242
58368
|
"attempt",
|
|
58243
58369
|
"attemptCount",
|
|
@@ -58315,6 +58441,9 @@ CREATE TABLE IF NOT EXISTS sq_archive (
|
|
|
58315
58441
|
function sessionTranscriptTopic(sessionId) {
|
|
58316
58442
|
return `session.${safeSessionId(sessionId)}.transcript`;
|
|
58317
58443
|
}
|
|
58444
|
+
function meshHandoffTopic(meshId) {
|
|
58445
|
+
return `mesh.${safeMeshId(meshId)}.handoff`;
|
|
58446
|
+
}
|
|
58318
58447
|
function meshEventsPolicy() {
|
|
58319
58448
|
return {
|
|
58320
58449
|
kind: "append",
|
|
@@ -58335,6 +58464,15 @@ CREATE TABLE IF NOT EXISTS sq_archive (
|
|
|
58335
58464
|
finalityAuthority: ADHDEV_AUTHORITY_ID
|
|
58336
58465
|
};
|
|
58337
58466
|
}
|
|
58467
|
+
function meshHandoffPolicy() {
|
|
58468
|
+
return {
|
|
58469
|
+
kind: "append",
|
|
58470
|
+
retention: { mode: "full" },
|
|
58471
|
+
replication: "full-sync",
|
|
58472
|
+
access: "content",
|
|
58473
|
+
finalityAuthority: ADHDEV_AUTHORITY_ID
|
|
58474
|
+
};
|
|
58475
|
+
}
|
|
58338
58476
|
function sessionTranscriptPolicy(ringSize = SESSION_TRANSCRIPT_RING) {
|
|
58339
58477
|
return {
|
|
58340
58478
|
kind: "append",
|
|
@@ -58383,6 +58521,7 @@ CREATE TABLE IF NOT EXISTS sq_archive (
|
|
|
58383
58521
|
if (seen.has(topic)) continue;
|
|
58384
58522
|
seen.add(topic);
|
|
58385
58523
|
defs.push({ topic, policy: meshEventsPolicy() });
|
|
58524
|
+
defs.push({ topic: meshHandoffTopic(meshId), policy: meshHandoffPolicy() });
|
|
58386
58525
|
}
|
|
58387
58526
|
return defs;
|
|
58388
58527
|
}
|
|
@@ -59350,6 +59489,12 @@ CREATE TABLE IF NOT EXISTS sq_archive (
|
|
|
59350
59489
|
}
|
|
59351
59490
|
return false;
|
|
59352
59491
|
}
|
|
59492
|
+
function readIntentionalCleanupStopEntries(meshId, since) {
|
|
59493
|
+
return readProjectedEntries(meshId, {
|
|
59494
|
+
kind: ["session_stopped", "task_failed", "task_stalled"],
|
|
59495
|
+
since
|
|
59496
|
+
});
|
|
59497
|
+
}
|
|
59353
59498
|
function meshReplicaDiagnostics(meshId) {
|
|
59354
59499
|
const servingFromReplica = isMeshReadModelReady(meshId);
|
|
59355
59500
|
const events = servingFromReplica ? queryMeshReadModel(meshId) : [];
|
|
@@ -63745,6 +63890,49 @@ CREATE TABLE IF NOT EXISTS sq_archive (
|
|
|
63745
63890
|
MESH_TERMINAL_QUEUE_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
63746
63891
|
}
|
|
63747
63892
|
});
|
|
63893
|
+
function selectTurnEventsForTask(db, meshId, taskId) {
|
|
63894
|
+
const rows = db.prepare(`
|
|
63895
|
+
SELECT * FROM mesh_turn_events WHERE mesh_id = ? AND task_id = ? ORDER BY recorded_at ASC, event_id ASC
|
|
63896
|
+
`).all(meshId, taskId);
|
|
63897
|
+
return rows.map((r) => ({
|
|
63898
|
+
eventId: r.event_id,
|
|
63899
|
+
attemptId: r.attempt_id,
|
|
63900
|
+
kind: r.kind,
|
|
63901
|
+
dedupeKey: r.dedupe_key ?? "",
|
|
63902
|
+
payload: r.payload,
|
|
63903
|
+
occurredAtMs: r.occurred_at_ms,
|
|
63904
|
+
recordedAt: r.recorded_at
|
|
63905
|
+
}));
|
|
63906
|
+
}
|
|
63907
|
+
function selectTurnEventsByKind(db, meshId, kind, limit) {
|
|
63908
|
+
const rows = db.prepare(`
|
|
63909
|
+
SELECT * FROM mesh_turn_events WHERE mesh_id = ? AND kind = ?
|
|
63910
|
+
ORDER BY recorded_at DESC, event_id DESC LIMIT ?
|
|
63911
|
+
`).all(meshId, kind, Math.max(1, Math.floor(limit)));
|
|
63912
|
+
return rows.map((r) => ({
|
|
63913
|
+
eventId: r.event_id,
|
|
63914
|
+
attemptId: r.attempt_id,
|
|
63915
|
+
taskId: r.task_id,
|
|
63916
|
+
kind: r.kind,
|
|
63917
|
+
dedupeKey: r.dedupe_key ?? "",
|
|
63918
|
+
payload: r.payload,
|
|
63919
|
+
occurredAtMs: r.occurred_at_ms,
|
|
63920
|
+
recordedAt: r.recorded_at
|
|
63921
|
+
}));
|
|
63922
|
+
}
|
|
63923
|
+
function deleteTurnEventsByKindOlderThan(db, kind, cutoffIso, meshId) {
|
|
63924
|
+
const info = meshId ? db.prepare(`
|
|
63925
|
+
DELETE FROM mesh_turn_events WHERE mesh_id = ? AND kind = ? AND recorded_at < ?
|
|
63926
|
+
`).run(meshId, kind, cutoffIso) : db.prepare(`
|
|
63927
|
+
DELETE FROM mesh_turn_events WHERE kind = ? AND recorded_at < ?
|
|
63928
|
+
`).run(kind, cutoffIso);
|
|
63929
|
+
return info.changes ?? 0;
|
|
63930
|
+
}
|
|
63931
|
+
var init_mesh_turn_event_queries = __esm2({
|
|
63932
|
+
"src/mesh/mesh-turn-event-queries.ts"() {
|
|
63933
|
+
"use strict";
|
|
63934
|
+
}
|
|
63935
|
+
});
|
|
63748
63936
|
function loadDatabaseCtor() {
|
|
63749
63937
|
if (DatabaseCtor) return DatabaseCtor;
|
|
63750
63938
|
DatabaseCtor = loadBetterSqlite3();
|
|
@@ -63825,6 +64013,7 @@ CREATE TABLE IF NOT EXISTS sq_archive (
|
|
|
63825
64013
|
init_dist();
|
|
63826
64014
|
init_mesh_claim_refusal();
|
|
63827
64015
|
init_mesh_runtime_store_turn_rows();
|
|
64016
|
+
init_mesh_turn_event_queries();
|
|
63828
64017
|
init_mesh_runtime_store_turn_rows();
|
|
63829
64018
|
loggedMigrationFailure = false;
|
|
63830
64019
|
loggedStrayCleanup = false;
|
|
@@ -64231,6 +64420,10 @@ CREATE TABLE IF NOT EXISTS sq_archive (
|
|
|
64231
64420
|
CREATE INDEX IF NOT EXISTS idx_mesh_turn_events_task
|
|
64232
64421
|
ON mesh_turn_events(mesh_id, task_id, kind);
|
|
64233
64422
|
|
|
64423
|
+
-- WORKER-MCP (design \xA75): by-kind probes; see mesh-turn-event-queries.ts.
|
|
64424
|
+
CREATE INDEX IF NOT EXISTS idx_mesh_turn_events_kind
|
|
64425
|
+
ON mesh_turn_events(mesh_id, kind, recorded_at);
|
|
64426
|
+
|
|
64234
64427
|
-- TURN-LEDGER (Stage 5): durable outbound delivery state (coordinator-bound
|
|
64235
64428
|
-- completion / ACK notifications) for restart recovery. A row is enqueued in
|
|
64236
64429
|
-- the SAME transaction as the reducer's terminal commit, so a crash between
|
|
@@ -66365,19 +66558,16 @@ CREATE TABLE IF NOT EXISTS sq_archive (
|
|
|
66365
66558
|
).get(attemptId, kind, dedupeKey);
|
|
66366
66559
|
return row !== void 0;
|
|
66367
66560
|
}
|
|
66561
|
+
/** Turn events for one task, oldest first. SQL: mesh-turn-event-queries.ts. */
|
|
66368
66562
|
listTurnEventsForTask(meshId, taskId) {
|
|
66369
|
-
|
|
66370
|
-
|
|
66371
|
-
|
|
66372
|
-
|
|
66373
|
-
|
|
66374
|
-
|
|
66375
|
-
|
|
66376
|
-
|
|
66377
|
-
payload: r.payload,
|
|
66378
|
-
occurredAtMs: r.occurred_at_ms,
|
|
66379
|
-
recordedAt: r.recorded_at
|
|
66380
|
-
}));
|
|
66563
|
+
return selectTurnEventsForTask(this.db, meshId, taskId);
|
|
66564
|
+
}
|
|
66565
|
+
/** By-KIND turn-event queries. SQL + index rationale: mesh-turn-event-queries.ts. */
|
|
66566
|
+
listTurnEventsByKind(meshId, kind, limit = 200) {
|
|
66567
|
+
return selectTurnEventsByKind(this.db, meshId, kind, limit);
|
|
66568
|
+
}
|
|
66569
|
+
deleteTurnEventsByKindOlderThan(kind, cutoffIso, meshId) {
|
|
66570
|
+
return deleteTurnEventsByKindOlderThan(this.db, kind, cutoffIso, meshId);
|
|
66381
66571
|
}
|
|
66382
66572
|
// ── TURN-LEDGER (Stage 5): durable outbound delivery (outbox) ────────────
|
|
66383
66573
|
/** Enqueue an outbound notification. INSERT OR IGNORE on the row id = exactly-once. */
|
|
@@ -70310,6 +70500,7 @@ When the user asks to **set up / configure / onboard** this repo for Repo Mesh (
|
|
|
70310
70500
|
execUnderPty: () => execUnderPty,
|
|
70311
70501
|
inspectMeshCoordinatorMcpServerPaths: () => inspectMeshCoordinatorMcpServerPaths,
|
|
70312
70502
|
resolveMeshCoordinatorSetup: () => resolveMeshCoordinatorSetup,
|
|
70503
|
+
resolveWorkerMcpServerLaunch: () => resolveWorkerMcpServerLaunch,
|
|
70313
70504
|
stripCoordinatorWrapperFile: () => stripCoordinatorWrapperFile
|
|
70314
70505
|
});
|
|
70315
70506
|
function isHermesProvider(provider, cliType) {
|
|
@@ -70513,6 +70704,21 @@ ${(0, import_node_path3.resolve)(workspace || os14.tmpdir())}`;
|
|
|
70513
70704
|
function resolveAdhdevCommand(explicitCommand) {
|
|
70514
70705
|
return explicitCommand?.trim() || process.env.ADHDEV_COORDINATOR_MCP_COMMAND?.trim() || DEFAULT_ADHDEV_MCP_COMMAND;
|
|
70515
70706
|
}
|
|
70707
|
+
function resolveWorkerMcpServerLaunch(options) {
|
|
70708
|
+
const transport = resolveMcpTransport(options?.adhdevMcpTransport);
|
|
70709
|
+
const port = resolveMcpPort(options?.adhdevMcpPort);
|
|
70710
|
+
const directEntryPath = resolveAdhdevMcpEntryPath(options?.adhdevMcpEntryPath);
|
|
70711
|
+
if (directEntryPath) {
|
|
70712
|
+
const args2 = [directEntryPath, "--mode", transport, "--worker"];
|
|
70713
|
+
if (port !== void 0) args2.push("--port", String(port));
|
|
70714
|
+
return { command: resolveNodeExecutable(options?.nodeExecutable), args: args2 };
|
|
70715
|
+
}
|
|
70716
|
+
const command = resolveAdhdevCommand(options?.adhdevMcpCommand);
|
|
70717
|
+
const directMcpEntrypoint = (0, import_node_path3.basename)(command).startsWith("adhdev-mcp") || command.includes("/vendor/mcp-server/") || command.includes("\\vendor\\mcp-server\\");
|
|
70718
|
+
const args = [...directMcpEntrypoint ? [] : ["mcp"], "--mode", transport, "--worker"];
|
|
70719
|
+
if (port !== void 0) args.push("--port", String(port));
|
|
70720
|
+
return { command, args };
|
|
70721
|
+
}
|
|
70516
70722
|
function inspectMeshCoordinatorMcpServerPaths(mcpServer, options = {}) {
|
|
70517
70723
|
const embeddedPaths = [mcpServer.command, ...mcpServer.args].filter((value) => (0, import_node_path3.isAbsolute)(value));
|
|
70518
70724
|
if (embeddedPaths.length === 0) {
|
|
@@ -70831,6 +71037,16 @@ ${rendered}`, "utf-8");
|
|
|
70831
71037
|
function getCoordinatorForSession(sessionId) {
|
|
70832
71038
|
return _registry.get(sessionId);
|
|
70833
71039
|
}
|
|
71040
|
+
function pruneDeadMeshCoordinators(liveSessionIds) {
|
|
71041
|
+
const pruned = [];
|
|
71042
|
+
for (const entry of [..._registry.values()]) {
|
|
71043
|
+
if (liveSessionIds.has(entry.sessionId)) continue;
|
|
71044
|
+
_registry.delete(entry.sessionId);
|
|
71045
|
+
pruned.push(entry);
|
|
71046
|
+
}
|
|
71047
|
+
if (pruned.length > 0) saveRegistry();
|
|
71048
|
+
return pruned;
|
|
71049
|
+
}
|
|
70834
71050
|
function listCoordinatorsForWorkspace(workspace) {
|
|
70835
71051
|
return [..._registry.values()].filter((e) => e.workspace === workspace);
|
|
70836
71052
|
}
|
|
@@ -74440,6 +74656,10 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
74440
74656
|
}
|
|
74441
74657
|
return null;
|
|
74442
74658
|
}
|
|
74659
|
+
function expiryRiskScore(remainingPercent2, timeLeftFraction) {
|
|
74660
|
+
if (!(remainingPercent2 > 0)) return 0;
|
|
74661
|
+
return remainingPercent2 * remainingPercent2 / (remainingPercent2 + 100 * timeLeftFraction);
|
|
74662
|
+
}
|
|
74443
74663
|
function expiryRiskForRanking(node, providerType, axis, policy, now = Date.now(), context) {
|
|
74444
74664
|
const entry = quotaEntryFor(node, providerType, context, now);
|
|
74445
74665
|
if (!entry) return void 0;
|
|
@@ -74456,8 +74676,8 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
|
|
|
74456
74676
|
const windowMinutes3 = Number(window?.windowMinutes);
|
|
74457
74677
|
const fallbackMinutes = axis === "session" ? DEFAULT_SESSION_WINDOW_MINUTES : DEFAULT_WEEKLY_WINDOW_MINUTES;
|
|
74458
74678
|
const windowMs = (Number.isFinite(windowMinutes3) && windowMinutes3 > 0 ? windowMinutes3 : fallbackMinutes) * 60 * 1e3;
|
|
74459
|
-
const
|
|
74460
|
-
return { remainingPercent: remaining, risk: remaining
|
|
74679
|
+
const timeLeftFraction = Math.min(1, Math.max(0, (resetsAt - reporterNowMs) / windowMs));
|
|
74680
|
+
return { remainingPercent: remaining, risk: expiryRiskScore(remaining, timeLeftFraction) };
|
|
74461
74681
|
}
|
|
74462
74682
|
function rankProvidersByQuotaGate(node, orderedProviderTypes, policy, now = Date.now(), context) {
|
|
74463
74683
|
const clear = [];
|
|
@@ -79706,6 +79926,592 @@ Re-target now if the pin is stale: mesh_queue_requeue(task_id='${taskId}', targe
|
|
|
79706
79926
|
"use strict";
|
|
79707
79927
|
}
|
|
79708
79928
|
});
|
|
79929
|
+
var worker_report_exports = {};
|
|
79930
|
+
__export2(worker_report_exports, {
|
|
79931
|
+
WORKER_BLOCKERS_MAX: () => WORKER_BLOCKERS_MAX,
|
|
79932
|
+
WORKER_BRANCH_STATES: () => WORKER_BRANCH_STATES,
|
|
79933
|
+
WORKER_FOLLOW_UPS_MAX: () => WORKER_FOLLOW_UPS_MAX,
|
|
79934
|
+
WORKER_GUIDANCE_MAX_CHARS: () => WORKER_GUIDANCE_MAX_CHARS,
|
|
79935
|
+
WORKER_HANDOFF_EVENT_KIND: () => WORKER_HANDOFF_EVENT_KIND,
|
|
79936
|
+
WORKER_INTENT_MAX_CHARS: () => WORKER_INTENT_MAX_CHARS,
|
|
79937
|
+
WORKER_LIST_ITEM_MAX_CHARS: () => WORKER_LIST_ITEM_MAX_CHARS,
|
|
79938
|
+
WORKER_PROGRESS_EVENT_KIND: () => WORKER_PROGRESS_EVENT_KIND,
|
|
79939
|
+
WORKER_REPORT_EVENT_KIND: () => WORKER_REPORT_EVENT_KIND,
|
|
79940
|
+
WORKER_SUMMARY_MAX_CHARS: () => WORKER_SUMMARY_MAX_CHARS,
|
|
79941
|
+
WORKER_TOUCHED_FILES_MAX: () => WORKER_TOUCHED_FILES_MAX,
|
|
79942
|
+
acceptWorkerCompletionReport: () => acceptWorkerCompletionReport,
|
|
79943
|
+
acceptWorkerProgressUpdate: () => acceptWorkerProgressUpdate,
|
|
79944
|
+
configureHandoffNoteSink: () => configureHandoffNoteSink,
|
|
79945
|
+
resolveWorkerIdentity: () => resolveWorkerIdentity,
|
|
79946
|
+
validateWorkerCompletionReport: () => validateWorkerCompletionReport
|
|
79947
|
+
});
|
|
79948
|
+
function validateWorkerCompletionReport(raw) {
|
|
79949
|
+
const errors = [];
|
|
79950
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
79951
|
+
return { errors: [{ field: "", message: "report must be an object" }] };
|
|
79952
|
+
}
|
|
79953
|
+
const input = raw;
|
|
79954
|
+
const KNOWN = /* @__PURE__ */ new Set(["outcome", "summary", "handoffNotes", "touchedFiles", "branchState", "blockers"]);
|
|
79955
|
+
for (const key2 of Object.keys(input)) {
|
|
79956
|
+
if (!KNOWN.has(key2)) {
|
|
79957
|
+
errors.push({ field: key2, message: `unknown field '${key2}' (expected one of: ${[...KNOWN].join(", ")})` });
|
|
79958
|
+
}
|
|
79959
|
+
}
|
|
79960
|
+
const outcome = input.outcome;
|
|
79961
|
+
if (outcome !== "completed" && outcome !== "blocked" && outcome !== "failed") {
|
|
79962
|
+
errors.push({ field: "outcome", message: "outcome must be one of 'completed' | 'blocked' | 'failed'" });
|
|
79963
|
+
}
|
|
79964
|
+
const summary = typeof input.summary === "string" ? input.summary.trim() : "";
|
|
79965
|
+
if (!summary) {
|
|
79966
|
+
errors.push({ field: "summary", message: "summary is required and must be a non-empty string" });
|
|
79967
|
+
} else if (summary.length > WORKER_SUMMARY_MAX_CHARS) {
|
|
79968
|
+
errors.push({
|
|
79969
|
+
field: "summary",
|
|
79970
|
+
message: `summary is ${summary.length} chars, over the ${WORKER_SUMMARY_MAX_CHARS} limit \u2014 shorten it rather than relying on truncation`
|
|
79971
|
+
});
|
|
79972
|
+
}
|
|
79973
|
+
const touchedFiles = validateStringList(input.touchedFiles, "touchedFiles", WORKER_TOUCHED_FILES_MAX, errors);
|
|
79974
|
+
const blockers = validateStringList(input.blockers, "blockers", WORKER_BLOCKERS_MAX, errors);
|
|
79975
|
+
if (input.branchState !== void 0 && !WORKER_BRANCH_STATES.includes(input.branchState)) {
|
|
79976
|
+
errors.push({
|
|
79977
|
+
field: "branchState",
|
|
79978
|
+
message: `branchState must be one of: ${WORKER_BRANCH_STATES.join(", ")}`
|
|
79979
|
+
});
|
|
79980
|
+
}
|
|
79981
|
+
let handoffNotes;
|
|
79982
|
+
if (input.handoffNotes !== void 0) {
|
|
79983
|
+
const notes = input.handoffNotes;
|
|
79984
|
+
if (!notes || typeof notes !== "object" || Array.isArray(notes)) {
|
|
79985
|
+
errors.push({ field: "handoffNotes", message: "handoffNotes must be an object" });
|
|
79986
|
+
} else {
|
|
79987
|
+
const n = notes;
|
|
79988
|
+
const KNOWN_NOTES = /* @__PURE__ */ new Set(["intent", "conflictGuidance", "touchedFiles", "followUps"]);
|
|
79989
|
+
for (const key2 of Object.keys(n)) {
|
|
79990
|
+
if (!KNOWN_NOTES.has(key2)) {
|
|
79991
|
+
errors.push({ field: `handoffNotes.${key2}`, message: `unknown field '${key2}'` });
|
|
79992
|
+
}
|
|
79993
|
+
}
|
|
79994
|
+
const intent = typeof n.intent === "string" ? n.intent.trim() : "";
|
|
79995
|
+
if (!intent) {
|
|
79996
|
+
errors.push({ field: "handoffNotes.intent", message: "intent is required \u2014 it is the part a diff cannot convey" });
|
|
79997
|
+
} else if (intent.length > WORKER_INTENT_MAX_CHARS) {
|
|
79998
|
+
errors.push({ field: "handoffNotes.intent", message: `intent is over the ${WORKER_INTENT_MAX_CHARS} char limit` });
|
|
79999
|
+
}
|
|
80000
|
+
let guidance;
|
|
80001
|
+
if (n.conflictGuidance !== void 0) {
|
|
80002
|
+
if (typeof n.conflictGuidance !== "string") {
|
|
80003
|
+
errors.push({ field: "handoffNotes.conflictGuidance", message: "conflictGuidance must be a string" });
|
|
80004
|
+
} else if (n.conflictGuidance.trim().length > WORKER_GUIDANCE_MAX_CHARS) {
|
|
80005
|
+
errors.push({ field: "handoffNotes.conflictGuidance", message: `conflictGuidance is over the ${WORKER_GUIDANCE_MAX_CHARS} char limit` });
|
|
80006
|
+
} else {
|
|
80007
|
+
guidance = n.conflictGuidance.trim() || void 0;
|
|
80008
|
+
}
|
|
80009
|
+
}
|
|
80010
|
+
const noteFiles = validateStringList(n.touchedFiles, "handoffNotes.touchedFiles", WORKER_TOUCHED_FILES_MAX, errors);
|
|
80011
|
+
if (n.touchedFiles === void 0 || !noteFiles?.length) {
|
|
80012
|
+
errors.push({
|
|
80013
|
+
field: "handoffNotes.touchedFiles",
|
|
80014
|
+
message: "touchedFiles is required and must be non-empty \u2014 it is what matches this note to future work"
|
|
80015
|
+
});
|
|
80016
|
+
}
|
|
80017
|
+
const followUps = validateStringList(n.followUps, "handoffNotes.followUps", WORKER_FOLLOW_UPS_MAX, errors);
|
|
80018
|
+
if (intent && noteFiles?.length) {
|
|
80019
|
+
handoffNotes = {
|
|
80020
|
+
intent,
|
|
80021
|
+
...guidance ? { conflictGuidance: guidance } : {},
|
|
80022
|
+
touchedFiles: noteFiles,
|
|
80023
|
+
...followUps?.length ? { followUps } : {}
|
|
80024
|
+
};
|
|
80025
|
+
}
|
|
80026
|
+
}
|
|
80027
|
+
}
|
|
80028
|
+
if (errors.length) return { errors };
|
|
80029
|
+
return {
|
|
80030
|
+
report: {
|
|
80031
|
+
outcome,
|
|
80032
|
+
summary,
|
|
80033
|
+
...handoffNotes ? { handoffNotes } : {},
|
|
80034
|
+
...touchedFiles?.length ? { touchedFiles } : {},
|
|
80035
|
+
...input.branchState ? { branchState: input.branchState } : {},
|
|
80036
|
+
...blockers?.length ? { blockers } : {}
|
|
80037
|
+
},
|
|
80038
|
+
errors: []
|
|
80039
|
+
};
|
|
80040
|
+
}
|
|
80041
|
+
function validateStringList(value, field2, max, errors) {
|
|
80042
|
+
if (value === void 0) return void 0;
|
|
80043
|
+
if (!Array.isArray(value)) {
|
|
80044
|
+
errors.push({ field: field2, message: `${field2} must be an array of strings` });
|
|
80045
|
+
return void 0;
|
|
80046
|
+
}
|
|
80047
|
+
if (value.length > max) {
|
|
80048
|
+
errors.push({ field: field2, message: `${field2} has ${value.length} entries, over the ${max} limit` });
|
|
80049
|
+
return void 0;
|
|
80050
|
+
}
|
|
80051
|
+
const out = [];
|
|
80052
|
+
for (const item of value) {
|
|
80053
|
+
if (typeof item !== "string") {
|
|
80054
|
+
errors.push({ field: field2, message: `${field2} must contain only strings` });
|
|
80055
|
+
return void 0;
|
|
80056
|
+
}
|
|
80057
|
+
const trimmed = item.trim();
|
|
80058
|
+
if (!trimmed) continue;
|
|
80059
|
+
if (trimmed.length > WORKER_LIST_ITEM_MAX_CHARS) {
|
|
80060
|
+
errors.push({ field: field2, message: `${field2} contains an entry over the ${WORKER_LIST_ITEM_MAX_CHARS} char limit` });
|
|
80061
|
+
return void 0;
|
|
80062
|
+
}
|
|
80063
|
+
out.push(trimmed);
|
|
80064
|
+
}
|
|
80065
|
+
return out;
|
|
80066
|
+
}
|
|
80067
|
+
function resolveWorkerIdentity(credential) {
|
|
80068
|
+
const direct = verifyWorkerTaskToken(credential.token);
|
|
80069
|
+
if (direct) {
|
|
80070
|
+
return {
|
|
80071
|
+
token: direct.token,
|
|
80072
|
+
meshId: direct.meshId,
|
|
80073
|
+
taskId: direct.taskId,
|
|
80074
|
+
...direct.attemptId ? { attemptId: direct.attemptId } : {},
|
|
80075
|
+
sessionId: direct.sessionId || "",
|
|
80076
|
+
...direct.nodeId ? { nodeId: direct.nodeId } : {}
|
|
80077
|
+
};
|
|
80078
|
+
}
|
|
80079
|
+
return exchangeWorkerSessionBind(credential.bind, resolveCurrentTaskForSession);
|
|
80080
|
+
}
|
|
80081
|
+
function resolveCurrentTaskForSession(meshId, sessionId) {
|
|
80082
|
+
try {
|
|
80083
|
+
const entry = MeshRuntimeStore.getInstance().findAssignedBySession(meshId, sessionId);
|
|
80084
|
+
if (!entry?.id) return null;
|
|
80085
|
+
return {
|
|
80086
|
+
taskId: entry.id,
|
|
80087
|
+
...entry.attemptId ? { attemptId: entry.attemptId } : {}
|
|
80088
|
+
};
|
|
80089
|
+
} catch {
|
|
80090
|
+
return null;
|
|
80091
|
+
}
|
|
80092
|
+
}
|
|
80093
|
+
function configureHandoffNoteSink(sink) {
|
|
80094
|
+
handoffSink = sink;
|
|
80095
|
+
}
|
|
80096
|
+
function acceptWorkerCompletionReport(credential, report, opts = {}) {
|
|
80097
|
+
const identity = resolveWorkerIdentity(credential);
|
|
80098
|
+
if (!identity) return { accepted: false, refusal: "unauthenticated" };
|
|
80099
|
+
const nowMs = opts.nowMs ?? Date.now();
|
|
80100
|
+
const nowIso = new Date(nowMs).toISOString();
|
|
80101
|
+
const store = MeshRuntimeStore.getInstance();
|
|
80102
|
+
if (identity.attemptId) {
|
|
80103
|
+
try {
|
|
80104
|
+
store.insertTurnEvent({
|
|
80105
|
+
eventId: (0, import_crypto15.randomUUID)(),
|
|
80106
|
+
meshId: identity.meshId,
|
|
80107
|
+
attemptId: identity.attemptId,
|
|
80108
|
+
taskId: identity.taskId,
|
|
80109
|
+
kind: WORKER_REPORT_EVENT_KIND,
|
|
80110
|
+
// UNIQUE(attempt_id, kind, dedupe_key) makes a re-call for the
|
|
80111
|
+
// same outcome insert-once, matching the reducer's own idempotency.
|
|
80112
|
+
dedupeKey: report.outcome,
|
|
80113
|
+
payload: JSON.stringify({
|
|
80114
|
+
outcome: report.outcome,
|
|
80115
|
+
summaryLength: report.summary.length,
|
|
80116
|
+
touchedFileCount: report.touchedFiles?.length ?? 0,
|
|
80117
|
+
blockerCount: report.blockers?.length ?? 0,
|
|
80118
|
+
hasHandoffNotes: !!report.handoffNotes,
|
|
80119
|
+
...report.branchState ? { branchState: report.branchState } : {}
|
|
80120
|
+
}),
|
|
80121
|
+
occurredAtMs: nowMs,
|
|
80122
|
+
recordedAt: nowIso
|
|
80123
|
+
});
|
|
80124
|
+
} catch (e) {
|
|
80125
|
+
LOG2.warn("WorkerReport", `Failed to record report evidence for task ${identity.taskId}: ${e?.message || e}`);
|
|
80126
|
+
}
|
|
80127
|
+
}
|
|
80128
|
+
let handoffNoteRecorded = false;
|
|
80129
|
+
if (report.handoffNotes) {
|
|
80130
|
+
handoffNoteRecorded = recordHandoffNote(identity, report.handoffNotes, nowMs, nowIso);
|
|
80131
|
+
}
|
|
80132
|
+
const terminalStatus = report.outcome === "completed" ? "completed" : "failed";
|
|
80133
|
+
let commit;
|
|
80134
|
+
try {
|
|
80135
|
+
commit = commitTaskTerminalAndAdvanceGraph({
|
|
80136
|
+
meshId: identity.meshId,
|
|
80137
|
+
taskId: identity.taskId,
|
|
80138
|
+
status: terminalStatus,
|
|
80139
|
+
...identity.sessionId ? { sessionId: identity.sessionId } : {},
|
|
80140
|
+
...identity.attemptId ? { attemptId: identity.attemptId } : {},
|
|
80141
|
+
occurredAtMs: nowMs,
|
|
80142
|
+
// ★A NEW proposal source. The reducer's causal checks apply to it
|
|
80143
|
+
// exactly as they do to a provider event — a valid token does not
|
|
80144
|
+
// buy an exemption from stale-attempt or session-mismatch.
|
|
80145
|
+
source: "worker_tool_report",
|
|
80146
|
+
reason: report.outcome === "blocked" ? `worker_reported_blocked:${(report.blockers || []).length}` : `worker_reported:${report.outcome}`,
|
|
80147
|
+
envelope: {
|
|
80148
|
+
finalSummary: report.summary,
|
|
80149
|
+
...report.touchedFiles?.length ? { artifacts: { touchedFiles: report.touchedFiles } } : {},
|
|
80150
|
+
evidence: {
|
|
80151
|
+
// The grade this report earns (design §4 등급표): declared by
|
|
80152
|
+
// the worker, so it is complete by construction and carries
|
|
80153
|
+
// no truncation risk the way a screen scrape does.
|
|
80154
|
+
summarySource: "tool_report",
|
|
80155
|
+
mayBeTruncated: false,
|
|
80156
|
+
reportedOutcome: report.outcome,
|
|
80157
|
+
...report.branchState ? { branchState: report.branchState } : {},
|
|
80158
|
+
...report.blockers?.length ? { blockers: report.blockers.join("; ") } : {}
|
|
80159
|
+
},
|
|
80160
|
+
...identity.nodeId ? { nodeId: identity.nodeId } : {},
|
|
80161
|
+
completedAt: nowIso
|
|
80162
|
+
}
|
|
80163
|
+
});
|
|
80164
|
+
} catch (e) {
|
|
80165
|
+
LOG2.warn("WorkerReport", `Terminal commit threw for task ${identity.taskId}: ${e?.message || e}`);
|
|
80166
|
+
return { accepted: false, refusal: "rejected_by_reducer", detail: e?.message || String(e) };
|
|
80167
|
+
}
|
|
80168
|
+
if (!commit.committed) {
|
|
80169
|
+
return { accepted: false, refusal: "unknown_task", detail: `no queue row for task ${identity.taskId}` };
|
|
80170
|
+
}
|
|
80171
|
+
LOG2.info(
|
|
80172
|
+
"WorkerReport",
|
|
80173
|
+
`Accepted ${report.outcome} report for task ${identity.taskId}` + (identity.attemptId ? ` attempt ${identity.attemptId}` : "") + (commit.duplicate ? " (duplicate replay)" : "") + (handoffNoteRecorded ? " with handoff note" : "")
|
|
80174
|
+
);
|
|
80175
|
+
return {
|
|
80176
|
+
accepted: true,
|
|
80177
|
+
taskId: identity.taskId,
|
|
80178
|
+
...identity.attemptId ? { attemptId: identity.attemptId } : {},
|
|
80179
|
+
outcome: report.outcome,
|
|
80180
|
+
duplicate: commit.duplicate,
|
|
80181
|
+
handoffNoteRecorded
|
|
80182
|
+
};
|
|
80183
|
+
}
|
|
80184
|
+
function acceptWorkerProgressUpdate(credential, note, opts = {}) {
|
|
80185
|
+
const identity = resolveWorkerIdentity(credential);
|
|
80186
|
+
if (!identity) return { accepted: false, refusal: "unauthenticated" };
|
|
80187
|
+
if (!identity.attemptId) return { accepted: true, taskId: identity.taskId };
|
|
80188
|
+
const nowMs = opts.nowMs ?? Date.now();
|
|
80189
|
+
try {
|
|
80190
|
+
MeshRuntimeStore.getInstance().insertTurnEvent({
|
|
80191
|
+
eventId: (0, import_crypto15.randomUUID)(),
|
|
80192
|
+
meshId: identity.meshId,
|
|
80193
|
+
attemptId: identity.attemptId,
|
|
80194
|
+
taskId: identity.taskId,
|
|
80195
|
+
kind: WORKER_PROGRESS_EVENT_KIND,
|
|
80196
|
+
// Distinct per call — progress updates are a SEQUENCE, unlike the
|
|
80197
|
+
// completion report where the UNIQUE constraint provides idempotency.
|
|
80198
|
+
dedupeKey: `${nowMs}`,
|
|
80199
|
+
payload: JSON.stringify({ noteLength: note.length }),
|
|
80200
|
+
occurredAtMs: nowMs,
|
|
80201
|
+
recordedAt: new Date(nowMs).toISOString()
|
|
80202
|
+
});
|
|
80203
|
+
} catch (e) {
|
|
80204
|
+
LOG2.warn("WorkerReport", `Failed to record progress update for task ${identity.taskId}: ${e?.message || e}`);
|
|
80205
|
+
}
|
|
80206
|
+
return { accepted: true, taskId: identity.taskId };
|
|
80207
|
+
}
|
|
80208
|
+
function recordHandoffNote(identity, notes, nowMs, nowIso) {
|
|
80209
|
+
if (identity.attemptId) {
|
|
80210
|
+
try {
|
|
80211
|
+
MeshRuntimeStore.getInstance().insertTurnEvent({
|
|
80212
|
+
eventId: (0, import_crypto15.randomUUID)(),
|
|
80213
|
+
meshId: identity.meshId,
|
|
80214
|
+
attemptId: identity.attemptId,
|
|
80215
|
+
taskId: identity.taskId,
|
|
80216
|
+
kind: WORKER_HANDOFF_EVENT_KIND,
|
|
80217
|
+
dedupeKey: "",
|
|
80218
|
+
payload: JSON.stringify({
|
|
80219
|
+
touchedFiles: notes.touchedFiles,
|
|
80220
|
+
intentLength: notes.intent.length,
|
|
80221
|
+
hasConflictGuidance: !!notes.conflictGuidance,
|
|
80222
|
+
followUpCount: notes.followUps?.length ?? 0,
|
|
80223
|
+
...identity.sessionId ? { sessionId: identity.sessionId } : {},
|
|
80224
|
+
...identity.nodeId ? { nodeId: identity.nodeId } : {}
|
|
80225
|
+
}),
|
|
80226
|
+
occurredAtMs: nowMs,
|
|
80227
|
+
recordedAt: nowIso
|
|
80228
|
+
});
|
|
80229
|
+
} catch (e) {
|
|
80230
|
+
LOG2.warn("WorkerReport", `Failed to index handoff note for task ${identity.taskId}: ${e?.message || e}`);
|
|
80231
|
+
return false;
|
|
80232
|
+
}
|
|
80233
|
+
}
|
|
80234
|
+
if (handoffSink) {
|
|
80235
|
+
try {
|
|
80236
|
+
handoffSink({
|
|
80237
|
+
meshId: identity.meshId,
|
|
80238
|
+
taskId: identity.taskId,
|
|
80239
|
+
...identity.attemptId ? { attemptId: identity.attemptId } : {},
|
|
80240
|
+
...identity.sessionId ? { sessionId: identity.sessionId } : {},
|
|
80241
|
+
...identity.nodeId ? { nodeId: identity.nodeId } : {},
|
|
80242
|
+
notes,
|
|
80243
|
+
recordedAtIso: nowIso
|
|
80244
|
+
});
|
|
80245
|
+
} catch (e) {
|
|
80246
|
+
LOG2.warn("WorkerReport", `Handoff note sink threw for task ${identity.taskId}: ${e?.message || e}`);
|
|
80247
|
+
}
|
|
80248
|
+
}
|
|
80249
|
+
return true;
|
|
80250
|
+
}
|
|
80251
|
+
var import_crypto15;
|
|
80252
|
+
var WORKER_BRANCH_STATES;
|
|
80253
|
+
var WORKER_REPORT_EVENT_KIND;
|
|
80254
|
+
var WORKER_PROGRESS_EVENT_KIND;
|
|
80255
|
+
var WORKER_HANDOFF_EVENT_KIND;
|
|
80256
|
+
var WORKER_SUMMARY_MAX_CHARS;
|
|
80257
|
+
var WORKER_INTENT_MAX_CHARS;
|
|
80258
|
+
var WORKER_GUIDANCE_MAX_CHARS;
|
|
80259
|
+
var WORKER_TOUCHED_FILES_MAX;
|
|
80260
|
+
var WORKER_LIST_ITEM_MAX_CHARS;
|
|
80261
|
+
var WORKER_BLOCKERS_MAX;
|
|
80262
|
+
var WORKER_FOLLOW_UPS_MAX;
|
|
80263
|
+
var handoffSink;
|
|
80264
|
+
var init_worker_report = __esm2({
|
|
80265
|
+
"src/mesh/worker-report.ts"() {
|
|
80266
|
+
"use strict";
|
|
80267
|
+
import_crypto15 = require("crypto");
|
|
80268
|
+
init_logger();
|
|
80269
|
+
init_mesh_runtime_store();
|
|
80270
|
+
init_mesh_graph_transition_runner();
|
|
80271
|
+
init_worker_mcp_isolation();
|
|
80272
|
+
WORKER_BRANCH_STATES = [
|
|
80273
|
+
"merged_to_main",
|
|
80274
|
+
"pushed_feature_branch_needs_merge",
|
|
80275
|
+
"blocked_review",
|
|
80276
|
+
"cleanup_candidate",
|
|
80277
|
+
"not_mergeable"
|
|
80278
|
+
];
|
|
80279
|
+
WORKER_REPORT_EVENT_KIND = "worker_tool_report";
|
|
80280
|
+
WORKER_PROGRESS_EVENT_KIND = "worker_progress_update";
|
|
80281
|
+
WORKER_HANDOFF_EVENT_KIND = "worker_handoff_note";
|
|
80282
|
+
WORKER_SUMMARY_MAX_CHARS = 8e3;
|
|
80283
|
+
WORKER_INTENT_MAX_CHARS = 4e3;
|
|
80284
|
+
WORKER_GUIDANCE_MAX_CHARS = 4e3;
|
|
80285
|
+
WORKER_TOUCHED_FILES_MAX = 200;
|
|
80286
|
+
WORKER_LIST_ITEM_MAX_CHARS = 500;
|
|
80287
|
+
WORKER_BLOCKERS_MAX = 50;
|
|
80288
|
+
WORKER_FOLLOW_UPS_MAX = 50;
|
|
80289
|
+
handoffSink = null;
|
|
80290
|
+
}
|
|
80291
|
+
});
|
|
80292
|
+
function noteKey(meshId, taskId) {
|
|
80293
|
+
return `${meshId}\0${taskId}`;
|
|
80294
|
+
}
|
|
80295
|
+
function configureHandoffNotesSeqscribe(handle) {
|
|
80296
|
+
seqscribeHandle = handle;
|
|
80297
|
+
}
|
|
80298
|
+
function storeHandoffNote(note) {
|
|
80299
|
+
NOTE_TEXT_STORE.set(noteKey(note.meshId, note.taskId), note);
|
|
80300
|
+
const handle = seqscribeHandle;
|
|
80301
|
+
if (!handle) return;
|
|
80302
|
+
const topic = meshHandoffTopic(note.meshId);
|
|
80303
|
+
if (!handle.topics.some((d) => d.topic === topic)) {
|
|
80304
|
+
return;
|
|
80305
|
+
}
|
|
80306
|
+
void handle.node.log(topic).append(HANDOFF_ENTRY_KIND, {
|
|
80307
|
+
taskId: note.taskId,
|
|
80308
|
+
...note.attemptId ? { attemptId: note.attemptId } : {},
|
|
80309
|
+
...note.nodeId ? { nodeId: note.nodeId } : {},
|
|
80310
|
+
recordedAt: note.recordedAtIso,
|
|
80311
|
+
intent: note.notes.intent,
|
|
80312
|
+
...note.notes.conflictGuidance ? { conflictGuidance: note.notes.conflictGuidance } : {},
|
|
80313
|
+
touchedFiles: note.notes.touchedFiles,
|
|
80314
|
+
...note.notes.followUps?.length ? { followUps: note.notes.followUps } : {}
|
|
80315
|
+
}).catch((e) => {
|
|
80316
|
+
LOG2.warn("HandoffNotes", `Failed to append handoff note for task ${note.taskId}: ${e?.message || e}`);
|
|
80317
|
+
});
|
|
80318
|
+
}
|
|
80319
|
+
function selectRelevantHandoffNotes(input) {
|
|
80320
|
+
const store = MeshRuntimeStore.getInstance();
|
|
80321
|
+
let rows;
|
|
80322
|
+
try {
|
|
80323
|
+
rows = store.listTurnEventsByKind(input.meshId, WORKER_HANDOFF_EVENT_KIND, 200);
|
|
80324
|
+
} catch (e) {
|
|
80325
|
+
LOG2.warn("HandoffNotes", `Handoff note lookup failed for mesh ${input.meshId}: ${e?.message || e}`);
|
|
80326
|
+
return [];
|
|
80327
|
+
}
|
|
80328
|
+
const wantedFiles = new Set((input.touchedFiles || []).map(normalizeFilePath).filter(Boolean));
|
|
80329
|
+
const cutoffMs = (input.nowMs ?? Date.now()) - HANDOFF_RETENTION_MS;
|
|
80330
|
+
const byFiles = [];
|
|
80331
|
+
const byMission = [];
|
|
80332
|
+
const byBranch = [];
|
|
80333
|
+
const seenTasks = /* @__PURE__ */ new Set([input.taskId]);
|
|
80334
|
+
for (const row of rows) {
|
|
80335
|
+
if (seenTasks.has(row.taskId)) continue;
|
|
80336
|
+
const recordedMs = Date.parse(row.recordedAt);
|
|
80337
|
+
if (Number.isFinite(recordedMs) && recordedMs < cutoffMs) continue;
|
|
80338
|
+
const stored = NOTE_TEXT_STORE.get(noteKey(input.meshId, row.taskId));
|
|
80339
|
+
if (!stored) continue;
|
|
80340
|
+
let meta3 = {};
|
|
80341
|
+
try {
|
|
80342
|
+
meta3 = JSON.parse(row.payload);
|
|
80343
|
+
} catch {
|
|
80344
|
+
}
|
|
80345
|
+
const noteFiles = Array.isArray(meta3.touchedFiles) ? meta3.touchedFiles.filter((f) => typeof f === "string") : stored.notes.touchedFiles;
|
|
80346
|
+
const overlap = wantedFiles.size ? noteFiles.filter((f) => wantedFiles.has(normalizeFilePath(f))) : [];
|
|
80347
|
+
if (overlap.length) {
|
|
80348
|
+
seenTasks.add(row.taskId);
|
|
80349
|
+
byFiles.push({ ...stored, reason: "touched_files", overlap });
|
|
80350
|
+
continue;
|
|
80351
|
+
}
|
|
80352
|
+
if (input.missionId && input.lookupMissionId?.(row.taskId) === input.missionId) {
|
|
80353
|
+
seenTasks.add(row.taskId);
|
|
80354
|
+
byMission.push({ ...stored, reason: "same_mission" });
|
|
80355
|
+
continue;
|
|
80356
|
+
}
|
|
80357
|
+
if (input.branch && input.lookupBranch?.(row.taskId) === input.branch) {
|
|
80358
|
+
seenTasks.add(row.taskId);
|
|
80359
|
+
byBranch.push({ ...stored, reason: "same_branch" });
|
|
80360
|
+
}
|
|
80361
|
+
}
|
|
80362
|
+
return [...byFiles, ...byMission, ...byBranch];
|
|
80363
|
+
}
|
|
80364
|
+
function normalizeFilePath(value) {
|
|
80365
|
+
return String(value || "").trim().replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
|
|
80366
|
+
}
|
|
80367
|
+
function renderHandoffNotesBlock(notes, opts = {}) {
|
|
80368
|
+
if (!notes.length) return null;
|
|
80369
|
+
const maxNotes = opts.maxNotes ?? HANDOFF_ENCLOSE_MAX_NOTES;
|
|
80370
|
+
const maxBytes = opts.maxBytes ?? HANDOFF_ENCLOSE_MAX_BYTES;
|
|
80371
|
+
const header = "## Handoff notes from related work\n\n_Left by earlier agents whose changes overlap yours. They cannot be asked follow-up questions._\n";
|
|
80372
|
+
const parts = [];
|
|
80373
|
+
let used = Buffer.byteLength(header, "utf8");
|
|
80374
|
+
let included = 0;
|
|
80375
|
+
for (const note of notes) {
|
|
80376
|
+
if (included >= maxNotes) break;
|
|
80377
|
+
const rendered = renderOneNote(note);
|
|
80378
|
+
const cost = Buffer.byteLength(rendered, "utf8");
|
|
80379
|
+
if (included > 0 && used + cost > maxBytes) break;
|
|
80380
|
+
parts.push(rendered);
|
|
80381
|
+
used += cost;
|
|
80382
|
+
included += 1;
|
|
80383
|
+
}
|
|
80384
|
+
const omitted = notes.length - included;
|
|
80385
|
+
const omissionLine = omitted > 0 ? `
|
|
80386
|
+
_${omitted} further related note(s) omitted to fit the enclosure budget \u2014 the most relevant are shown first._
|
|
80387
|
+
` : "";
|
|
80388
|
+
return { text: `${header}${parts.join("")}${omissionLine}`, included, omitted };
|
|
80389
|
+
}
|
|
80390
|
+
function renderOneNote(note) {
|
|
80391
|
+
const why = note.reason === "touched_files" ? `overlapping files: ${(note.overlap || []).join(", ")}` : note.reason === "same_mission" ? "same mission" : "same branch";
|
|
80392
|
+
const lines = [`
|
|
80393
|
+
### From task ${note.taskId} (${why})
|
|
80394
|
+
`];
|
|
80395
|
+
lines.push(`- **Intent:** ${note.notes.intent}
|
|
80396
|
+
`);
|
|
80397
|
+
if (note.notes.conflictGuidance) {
|
|
80398
|
+
lines.push(`- **If you conflict with this:** ${note.notes.conflictGuidance}
|
|
80399
|
+
`);
|
|
80400
|
+
}
|
|
80401
|
+
if (note.notes.touchedFiles.length) {
|
|
80402
|
+
lines.push(`- **Touched:** ${note.notes.touchedFiles.join(", ")}
|
|
80403
|
+
`);
|
|
80404
|
+
}
|
|
80405
|
+
if (note.notes.followUps?.length) {
|
|
80406
|
+
lines.push(`- **Left undone:** ${note.notes.followUps.join("; ")}
|
|
80407
|
+
`);
|
|
80408
|
+
}
|
|
80409
|
+
return lines.join("");
|
|
80410
|
+
}
|
|
80411
|
+
function composeTaskDispatchBody(message, input) {
|
|
80412
|
+
let selected = [];
|
|
80413
|
+
try {
|
|
80414
|
+
selected = selectRelevantHandoffNotes(input);
|
|
80415
|
+
} catch (e) {
|
|
80416
|
+
LOG2.warn("HandoffNotes", `Handoff selection failed for task ${input.taskId}: ${e?.message || e}`);
|
|
80417
|
+
return { body: message, enclosedNotes: 0, omittedNotes: 0 };
|
|
80418
|
+
}
|
|
80419
|
+
const block2 = renderHandoffNotesBlock(selected);
|
|
80420
|
+
if (!block2) return { body: message, enclosedNotes: 0, omittedNotes: 0 };
|
|
80421
|
+
return {
|
|
80422
|
+
body: `${message}
|
|
80423
|
+
|
|
80424
|
+
---
|
|
80425
|
+
|
|
80426
|
+
${block2.text}`,
|
|
80427
|
+
enclosedNotes: block2.included,
|
|
80428
|
+
omittedNotes: block2.omitted
|
|
80429
|
+
};
|
|
80430
|
+
}
|
|
80431
|
+
function pruneExpiredHandoffNotes(nowMs = Date.now()) {
|
|
80432
|
+
const cutoffIso = new Date(nowMs - HANDOFF_RETENTION_MS).toISOString();
|
|
80433
|
+
let removed = 0;
|
|
80434
|
+
try {
|
|
80435
|
+
removed = MeshRuntimeStore.getInstance().deleteTurnEventsByKindOlderThan(WORKER_HANDOFF_EVENT_KIND, cutoffIso);
|
|
80436
|
+
} catch (e) {
|
|
80437
|
+
LOG2.warn("HandoffNotes", `Handoff retention sweep failed: ${e?.message || e}`);
|
|
80438
|
+
}
|
|
80439
|
+
const cutoffMs = nowMs - HANDOFF_RETENTION_MS;
|
|
80440
|
+
for (const [key2, note] of NOTE_TEXT_STORE) {
|
|
80441
|
+
const recordedMs = Date.parse(note.recordedAtIso);
|
|
80442
|
+
if (Number.isFinite(recordedMs) && recordedMs < cutoffMs) NOTE_TEXT_STORE.delete(key2);
|
|
80443
|
+
}
|
|
80444
|
+
return removed;
|
|
80445
|
+
}
|
|
80446
|
+
var HANDOFF_ENTRY_KIND;
|
|
80447
|
+
var HANDOFF_RETENTION_DAYS;
|
|
80448
|
+
var HANDOFF_RETENTION_MS;
|
|
80449
|
+
var HANDOFF_ENCLOSE_MAX_NOTES;
|
|
80450
|
+
var HANDOFF_ENCLOSE_MAX_BYTES;
|
|
80451
|
+
var NOTE_TEXT_STORE;
|
|
80452
|
+
var seqscribeHandle;
|
|
80453
|
+
var init_worker_handoff_notes = __esm2({
|
|
80454
|
+
"src/mesh/worker-handoff-notes.ts"() {
|
|
80455
|
+
"use strict";
|
|
80456
|
+
init_logger();
|
|
80457
|
+
init_mesh_runtime_store();
|
|
80458
|
+
init_worker_report();
|
|
80459
|
+
init_topics2();
|
|
80460
|
+
HANDOFF_ENTRY_KIND = "worker.handoff";
|
|
80461
|
+
HANDOFF_RETENTION_DAYS = 30;
|
|
80462
|
+
HANDOFF_RETENTION_MS = HANDOFF_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
80463
|
+
HANDOFF_ENCLOSE_MAX_NOTES = 5;
|
|
80464
|
+
HANDOFF_ENCLOSE_MAX_BYTES = 8 * 1024;
|
|
80465
|
+
NOTE_TEXT_STORE = /* @__PURE__ */ new Map();
|
|
80466
|
+
seqscribeHandle = null;
|
|
80467
|
+
}
|
|
80468
|
+
});
|
|
80469
|
+
function readNonEmpty2(value) {
|
|
80470
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
80471
|
+
}
|
|
80472
|
+
function resolveDispatchMessage(task, meshId, node) {
|
|
80473
|
+
if (!isWorkerMcpEnabled()) return task.message;
|
|
80474
|
+
try {
|
|
80475
|
+
const touchedFiles = task.touchedFiles;
|
|
80476
|
+
const branch = readNonEmpty2(node?.worktreeBranch);
|
|
80477
|
+
const composed = composeTaskDispatchBody(task.message, {
|
|
80478
|
+
meshId,
|
|
80479
|
+
taskId: task.id,
|
|
80480
|
+
...Array.isArray(touchedFiles) ? { touchedFiles } : {},
|
|
80481
|
+
...task.missionId ? { missionId: task.missionId } : {},
|
|
80482
|
+
...branch ? { branch } : {},
|
|
80483
|
+
// Injected rather than imported by the note module: resolving a
|
|
80484
|
+
// note's mission means reading a QUEUE row, and the note module
|
|
80485
|
+
// must not depend on the queue (it is read by the queue).
|
|
80486
|
+
lookupMissionId: (candidateTaskId) => {
|
|
80487
|
+
try {
|
|
80488
|
+
return MeshRuntimeStore.getInstance().findQueueEntryById(meshId, candidateTaskId)?.missionId;
|
|
80489
|
+
} catch {
|
|
80490
|
+
return void 0;
|
|
80491
|
+
}
|
|
80492
|
+
}
|
|
80493
|
+
});
|
|
80494
|
+
if (composed.enclosedNotes > 0) {
|
|
80495
|
+
LOG2.info(
|
|
80496
|
+
"HandoffNotes",
|
|
80497
|
+
`Enclosed ${composed.enclosedNotes} handoff note(s) with task ${task.id}` + (composed.omittedNotes ? ` (${composed.omittedNotes} omitted to fit)` : "")
|
|
80498
|
+
);
|
|
80499
|
+
}
|
|
80500
|
+
return composed.body;
|
|
80501
|
+
} catch (e) {
|
|
80502
|
+
LOG2.warn("HandoffNotes", `Failed to compose dispatch body for ${task.id}: ${e?.message || e}`);
|
|
80503
|
+
return task.message;
|
|
80504
|
+
}
|
|
80505
|
+
}
|
|
80506
|
+
var init_worker_handoff_dispatch = __esm2({
|
|
80507
|
+
"src/mesh/worker-handoff-dispatch.ts"() {
|
|
80508
|
+
"use strict";
|
|
80509
|
+
init_logger();
|
|
80510
|
+
init_mesh_runtime_store();
|
|
80511
|
+
init_worker_mcp_isolation();
|
|
80512
|
+
init_worker_handoff_notes();
|
|
80513
|
+
}
|
|
80514
|
+
});
|
|
79709
80515
|
function localCoordinatorDaemonId2() {
|
|
79710
80516
|
return canonicalDaemonId(readNonEmptyString(loadConfig2().machineId));
|
|
79711
80517
|
}
|
|
@@ -80217,6 +81023,7 @@ Re-target now if the pin is stale: mesh_queue_requeue(task_id='${taskId}', targe
|
|
|
80217
81023
|
LOG2.warn("WorkerMcp", `Failed to mint worker task token for ${task.id}: ${e?.message || e}`);
|
|
80218
81024
|
}
|
|
80219
81025
|
}
|
|
81026
|
+
const dispatchMessage = resolveDispatchMessage(task, meshId, node);
|
|
80220
81027
|
if (dispatchAttemptId) {
|
|
80221
81028
|
try {
|
|
80222
81029
|
const attemptRow = MeshRuntimeStore.getInstance().getTurnAttempt(dispatchAttemptId);
|
|
@@ -80260,7 +81067,9 @@ Re-target now if the pin is stale: mesh_queue_requeue(task_id='${taskId}', targe
|
|
|
80260
81067
|
targetSessionId: sessionId,
|
|
80261
81068
|
cliType: providerType,
|
|
80262
81069
|
action: "send_chat",
|
|
80263
|
-
|
|
81070
|
+
// Handoff-note enclosure applies to the DISPATCHED body only;
|
|
81071
|
+
// task.message stays the authored text (see composition above).
|
|
81072
|
+
message: dispatchMessage,
|
|
80264
81073
|
// DISPATCH-SOURCE-TRACE: call-site tag echoed in the worker daemon log.
|
|
80265
81074
|
dispatchSource: "mesh-queue-assignment:tryAssignQueueTask:remote",
|
|
80266
81075
|
meshContext: {
|
|
@@ -80336,7 +81145,8 @@ Re-target now if the pin is stale: mesh_queue_requeue(task_id='${taskId}', targe
|
|
|
80336
81145
|
targetSessionId: sessionId,
|
|
80337
81146
|
cliType: providerType,
|
|
80338
81147
|
action: "send_chat",
|
|
80339
|
-
|
|
81148
|
+
// Same enclosure as the remote arm — one composed body, both paths.
|
|
81149
|
+
message: dispatchMessage,
|
|
80340
81150
|
// DISPATCH-SOURCE-TRACE: call-site tag echoed in the daemon log.
|
|
80341
81151
|
dispatchSource: "mesh-queue-assignment:tryAssignQueueTask:local",
|
|
80342
81152
|
meshContext: {
|
|
@@ -81169,6 +81979,7 @@ Re-target now if the pin is stale: mesh_queue_requeue(task_id='${taskId}', targe
|
|
|
81169
81979
|
init_mesh_autolaunch_integrity();
|
|
81170
81980
|
init_mesh_difficulty_floor();
|
|
81171
81981
|
init_worker_mcp_isolation();
|
|
81982
|
+
init_worker_handoff_dispatch();
|
|
81172
81983
|
init_mesh_candidacy_predicates();
|
|
81173
81984
|
init_mesh_difficulty_floor();
|
|
81174
81985
|
init_mesh_auto_fast_forward();
|
|
@@ -81361,7 +82172,7 @@ Re-target now if the pin is stale: mesh_queue_requeue(task_id='${taskId}', targe
|
|
|
81361
82172
|
const fingerprint = `${target}::${buildPendingEventFingerprint(fingerprintSource)}`;
|
|
81362
82173
|
try {
|
|
81363
82174
|
const inserted = store.insertPendingEvent({
|
|
81364
|
-
id: (0,
|
|
82175
|
+
id: (0, import_crypto16.randomUUID)(),
|
|
81365
82176
|
meshId: UNRESOLVED_FORWARD_OUTBOX_MESH_ID,
|
|
81366
82177
|
coordinatorDaemonId: target,
|
|
81367
82178
|
event,
|
|
@@ -81424,14 +82235,14 @@ Re-target now if the pin is stale: mesh_queue_requeue(task_id='${taskId}', targe
|
|
|
81424
82235
|
return 0;
|
|
81425
82236
|
}
|
|
81426
82237
|
}
|
|
81427
|
-
var
|
|
82238
|
+
var import_crypto16;
|
|
81428
82239
|
var UNRESOLVED_FORWARD_OUTBOX_MESH_ID;
|
|
81429
82240
|
var UNRESOLVED_FORWARD_MAX_AGE_MS;
|
|
81430
82241
|
var retryNudgeHandler;
|
|
81431
82242
|
var init_mesh_unresolved_forward_outbox = __esm2({
|
|
81432
82243
|
"src/mesh/mesh-unresolved-forward-outbox.ts"() {
|
|
81433
82244
|
"use strict";
|
|
81434
|
-
|
|
82245
|
+
import_crypto16 = require("crypto");
|
|
81435
82246
|
init_logger();
|
|
81436
82247
|
init_mesh_runtime_store();
|
|
81437
82248
|
init_mesh_events_pending();
|
|
@@ -83740,10 +84551,7 @@ ${cleanBody}`;
|
|
|
83740
84551
|
function hasRecentIntentionalCleanupStop(meshId, sessionId, nodeId) {
|
|
83741
84552
|
if (!sessionId && !nodeId) return false;
|
|
83742
84553
|
const cutoff = Date.now() - INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS;
|
|
83743
|
-
const entries =
|
|
83744
|
-
kind: ["session_stopped", "task_failed", "task_stalled"],
|
|
83745
|
-
since: new Date(cutoff).toISOString()
|
|
83746
|
-
});
|
|
84554
|
+
const entries = readIntentionalCleanupStopEntries(meshId, new Date(cutoff).toISOString());
|
|
83747
84555
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
83748
84556
|
const entry = entries[i];
|
|
83749
84557
|
if (!isIntentionalCleanupStopEntry(entry)) continue;
|
|
@@ -86044,6 +86852,7 @@ ${cleanBody}`;
|
|
|
86044
86852
|
let sessionHostRuntimes = 0;
|
|
86045
86853
|
let rotationEvicted = 0;
|
|
86046
86854
|
let rotationEvictedBytes = 0;
|
|
86855
|
+
let handoffNotes = 0;
|
|
86047
86856
|
try {
|
|
86048
86857
|
ledgerJsonl = pruneExpiredLedgerJsonl(now);
|
|
86049
86858
|
} catch (e) {
|
|
@@ -86069,6 +86878,14 @@ ${cleanBody}`;
|
|
|
86069
86878
|
} catch (e) {
|
|
86070
86879
|
LOG2.warn("DiskRetention", `Ledger rotation cap sweep failed: ${e?.message || e}`);
|
|
86071
86880
|
}
|
|
86881
|
+
try {
|
|
86882
|
+
handoffNotes = pruneExpiredHandoffNotes(now);
|
|
86883
|
+
if (handoffNotes > 0) {
|
|
86884
|
+
LOG2.info("DiskRetention", `Pruned ${handoffNotes} handoff note row(s) older than ${HANDOFF_RETENTION_DAYS}d`);
|
|
86885
|
+
}
|
|
86886
|
+
} catch (e) {
|
|
86887
|
+
LOG2.warn("DiskRetention", `Handoff note prune failed: ${e?.message || e}`);
|
|
86888
|
+
}
|
|
86072
86889
|
let diskLevel = "ok";
|
|
86073
86890
|
try {
|
|
86074
86891
|
const status = checkDiskSpace(getConfigDir2());
|
|
@@ -86077,7 +86894,7 @@ ${cleanBody}`;
|
|
|
86077
86894
|
} catch (e) {
|
|
86078
86895
|
LOG2.warn("DiskRetention", `Disk space check failed: ${e?.message || e}`);
|
|
86079
86896
|
}
|
|
86080
|
-
return { ledgerJsonl, dbBackups, sessionHostRuntimes, rotationEvicted, rotationEvictedBytes, diskLevel };
|
|
86897
|
+
return { ledgerJsonl, dbBackups, sessionHostRuntimes, rotationEvicted, rotationEvictedBytes, handoffNotes, diskLevel };
|
|
86081
86898
|
}
|
|
86082
86899
|
async function detectAndSignalOrphanWorktrees(mesh, now = Date.now()) {
|
|
86083
86900
|
const nodes = Array.isArray(mesh.nodes) ? mesh.nodes : [];
|
|
@@ -86146,6 +86963,7 @@ ${cleanBody}`;
|
|
|
86146
86963
|
init_git_worktree();
|
|
86147
86964
|
init_logger();
|
|
86148
86965
|
init_disk_space_preflight();
|
|
86966
|
+
init_worker_handoff_notes();
|
|
86149
86967
|
DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
86150
86968
|
LEDGER_JSONL_MAX_AGE_MS = 30 * DAY_MS2;
|
|
86151
86969
|
SESSION_HOST_RUNTIME_MAX_AGE_MS = 14 * DAY_MS2;
|
|
@@ -98818,6 +99636,7 @@ ${e?.stderr || ""}`;
|
|
|
98818
99636
|
isExtensionInstalled: () => isExtensionInstalled,
|
|
98819
99637
|
isGitCommandName: () => isGitCommandName,
|
|
98820
99638
|
isIdeRunning: () => isIdeRunning,
|
|
99639
|
+
isIntentionalCleanupStopEntry: () => isIntentionalCleanupStopEntry,
|
|
98821
99640
|
isInternalChatMessage: () => isInternalChatMessage,
|
|
98822
99641
|
isManagedStatusWaiting: () => isManagedStatusWaiting,
|
|
98823
99642
|
isManagedStatusWorking: () => isManagedStatusWorking,
|
|
@@ -98954,6 +99773,7 @@ ${e?.stderr || ""}`;
|
|
|
98954
99773
|
projectFleetStatusEntry: () => projectFleetStatusEntry,
|
|
98955
99774
|
projectGraphPublicPolicy: () => projectGraphPublicPolicy,
|
|
98956
99775
|
projectMeshLedgerEntry: () => projectMeshLedgerEntry,
|
|
99776
|
+
pruneDeadMeshCoordinators: () => pruneDeadMeshCoordinators,
|
|
98957
99777
|
pruneOperatingNotes: () => pruneOperatingNotes,
|
|
98958
99778
|
pruneStaleDirectDispatches: () => pruneStaleDirectDispatches,
|
|
98959
99779
|
queryMeshReadModel: () => queryMeshReadModel,
|
|
@@ -98969,6 +99789,7 @@ ${e?.stderr || ""}`;
|
|
|
98969
99789
|
readCodexCliSession: () => readSession2,
|
|
98970
99790
|
readDiskSpace: () => readDiskSpace,
|
|
98971
99791
|
readHermesCliSession: () => readSession4,
|
|
99792
|
+
readIntentionalCleanupStopEntries: () => readIntentionalCleanupStopEntries,
|
|
98972
99793
|
readKeyStaleAdvisory: () => readKeyStaleAdvisory,
|
|
98973
99794
|
readLatestCodexRateLimits: () => readLatestCodexRateLimits,
|
|
98974
99795
|
readLedgerEntries: () => readLedgerEntries,
|
|
@@ -99425,12 +100246,17 @@ ${e?.stderr || ""}`;
|
|
|
99425
100246
|
}
|
|
99426
100247
|
if (navIndex < 0) return parseClaudeHeaderlessInteractiveTuiQuestion(page, index);
|
|
99427
100248
|
if (!page.screenText.includes("Enter to select")) return null;
|
|
100249
|
+
for (let i = navIndex + 1; i < lines.length; i += 1) {
|
|
100250
|
+
const candidate = lines[i].trim();
|
|
100251
|
+
if (!candidate || /^─+$/.test(candidate)) continue;
|
|
100252
|
+
if (candidate === "Review your answers" || candidate === "Ready to submit your answers?") return null;
|
|
100253
|
+
if (CLAUDE_TUI_OPTION_PATTERN.test(lines[i])) break;
|
|
100254
|
+
}
|
|
99428
100255
|
let question = "";
|
|
99429
100256
|
let questionLineIndex = -1;
|
|
99430
100257
|
for (let i = navIndex + 1; i < lines.length; i += 1) {
|
|
99431
100258
|
const candidate = lines[i].trim();
|
|
99432
100259
|
if (!candidate || /^─+$/.test(candidate)) continue;
|
|
99433
|
-
if (candidate === "Review your answers" || candidate === "Ready to submit your answers?") return null;
|
|
99434
100260
|
question = candidate;
|
|
99435
100261
|
questionLineIndex = i;
|
|
99436
100262
|
break;
|
|
@@ -106285,15 +107111,15 @@ ${effect.notification.body || ""}`.trim();
|
|
|
106285
107111
|
function decideCliReadChatSource(args) {
|
|
106286
107112
|
const supportsNative = supportsCliNativeTranscript(args.providerType, args.provider);
|
|
106287
107113
|
const observation = buildObservationForCli(args, supportsNative);
|
|
106288
|
-
const
|
|
106289
|
-
const priorSnapshot = CHAT_SOURCE_REGISTRY.snapshotRecord(
|
|
106290
|
-
const priorState = priorSnapshot?.state ?? CHAT_SOURCE_REGISTRY.getState(
|
|
107114
|
+
const sessionKey2 = chatSourceSessionKey(args.providerType, args.sessionId);
|
|
107115
|
+
const priorSnapshot = CHAT_SOURCE_REGISTRY.snapshotRecord(sessionKey2);
|
|
107116
|
+
const priorState = priorSnapshot?.state ?? CHAT_SOURCE_REGISTRY.getState(sessionKey2);
|
|
106291
107117
|
const eligibleForStickyHold = args.trustedExactNativeIdentity === true && (priorState.name === "NativeLocked" || priorState.name === "Recovering");
|
|
106292
|
-
let decision = CHAT_SOURCE_REGISTRY.observe(
|
|
107118
|
+
let decision = CHAT_SOURCE_REGISTRY.observe(sessionKey2, observation);
|
|
106293
107119
|
const heldNativeMessages = observation.kind === "native_present" ? extractNativeMessagesFromResult(args.providerType, args.nativeHistoryResult) : [];
|
|
106294
107120
|
const mayHoldNativeSlice = heldNativeMessages.length > 0 && args.safeMapping === true;
|
|
106295
107121
|
if (eligibleForStickyHold && decision.selected === "pty-parser" && mayHoldNativeSlice) {
|
|
106296
|
-
CHAT_SOURCE_REGISTRY.restoreRecord(
|
|
107122
|
+
CHAT_SOURCE_REGISTRY.restoreRecord(sessionKey2, priorSnapshot);
|
|
106297
107123
|
const messageSource2 = buildCliMessageSourceProvenance({
|
|
106298
107124
|
selected: "native-history",
|
|
106299
107125
|
provider: args.providerType,
|
|
@@ -106334,8 +107160,8 @@ ${effect.notification.body || ""}`.trim();
|
|
|
106334
107160
|
};
|
|
106335
107161
|
}
|
|
106336
107162
|
if (decision.selected === "pty-parser" && args.trustedExactNativeIdentity === true && args.safeMapping && args.ptyMessages.length === 0 && observation.kind === "native_present" && observation.coverage !== "partial" && observation.messages.length > 0) {
|
|
106337
|
-
CHAT_SOURCE_REGISTRY.clear(
|
|
106338
|
-
decision = CHAT_SOURCE_REGISTRY.observe(
|
|
107163
|
+
CHAT_SOURCE_REGISTRY.clear(sessionKey2);
|
|
107164
|
+
decision = CHAT_SOURCE_REGISTRY.observe(sessionKey2, observation);
|
|
106339
107165
|
}
|
|
106340
107166
|
const nativeMessages = observation.kind === "native_present" ? extractNativeMessagesFromResult(args.providerType, args.nativeHistoryResult) : [];
|
|
106341
107167
|
const nativeSource = typeof args.nativeHistoryResult?.source === "string" ? args.nativeHistoryResult.source : void 0;
|
|
@@ -114390,6 +115216,93 @@ ${marker}`,
|
|
|
114390
115216
|
};
|
|
114391
115217
|
}
|
|
114392
115218
|
};
|
|
115219
|
+
var workerReportHandlers = {
|
|
115220
|
+
/**
|
|
115221
|
+
* Exchange a session bind for the caller's current task identity.
|
|
115222
|
+
*
|
|
115223
|
+
* Exposed as its own command so the MCP server can answer "do I have a task
|
|
115224
|
+
* right now" — which is what decides whether it publishes the reporting
|
|
115225
|
+
* tools at all — without having to submit a report to find out.
|
|
115226
|
+
*
|
|
115227
|
+
* ★The task TOKEN is deliberately NOT returned. The caller does not need it:
|
|
115228
|
+
* it presents the bind again on the actual report, and the daemon re-resolves.
|
|
115229
|
+
* Returning the token would put a second live secret on the wire for no gain.
|
|
115230
|
+
*/
|
|
115231
|
+
worker_resolve_task: async (_ctx, args) => {
|
|
115232
|
+
try {
|
|
115233
|
+
const { resolveWorkerIdentity: resolveWorkerIdentity2 } = await Promise.resolve().then(() => (init_worker_report(), worker_report_exports));
|
|
115234
|
+
const identity = resolveWorkerIdentity2({ token: args?.token, bind: args?.bind });
|
|
115235
|
+
if (!identity) {
|
|
115236
|
+
return {
|
|
115237
|
+
success: false,
|
|
115238
|
+
error: "worker_not_bound",
|
|
115239
|
+
hint: "No live task is bound to this worker session. A report is only possible while a task is assigned."
|
|
115240
|
+
};
|
|
115241
|
+
}
|
|
115242
|
+
return {
|
|
115243
|
+
success: true,
|
|
115244
|
+
meshId: identity.meshId,
|
|
115245
|
+
taskId: identity.taskId,
|
|
115246
|
+
...identity.attemptId ? { attemptId: identity.attemptId } : {},
|
|
115247
|
+
...identity.sessionId ? { sessionId: identity.sessionId } : {},
|
|
115248
|
+
...identity.nodeId ? { nodeId: identity.nodeId } : {}
|
|
115249
|
+
};
|
|
115250
|
+
} catch (e) {
|
|
115251
|
+
return { success: false, error: e?.message || String(e) };
|
|
115252
|
+
}
|
|
115253
|
+
},
|
|
115254
|
+
/**
|
|
115255
|
+
* Accept a structured completion report.
|
|
115256
|
+
*
|
|
115257
|
+
* Validation errors come back as a STRUCTURED list rather than a single
|
|
115258
|
+
* string so the MCP layer can hand the worker something it can act on
|
|
115259
|
+
* field-by-field — the retry loop is the whole reason the schema is enforced
|
|
115260
|
+
* at the tool layer instead of being parsed out of prose.
|
|
115261
|
+
*/
|
|
115262
|
+
worker_report_completion: async (_ctx, args) => {
|
|
115263
|
+
try {
|
|
115264
|
+
const { validateWorkerCompletionReport: validateWorkerCompletionReport2, acceptWorkerCompletionReport: acceptWorkerCompletionReport2 } = await Promise.resolve().then(() => (init_worker_report(), worker_report_exports));
|
|
115265
|
+
const { report, errors } = validateWorkerCompletionReport2(args?.report);
|
|
115266
|
+
if (!report) {
|
|
115267
|
+
return { success: false, error: "invalid_report", validationErrors: errors };
|
|
115268
|
+
}
|
|
115269
|
+
const result = acceptWorkerCompletionReport2({ token: args?.token, bind: args?.bind }, report);
|
|
115270
|
+
if (!result.accepted) {
|
|
115271
|
+
return {
|
|
115272
|
+
success: false,
|
|
115273
|
+
error: result.refusal,
|
|
115274
|
+
...result.detail ? { detail: result.detail } : {},
|
|
115275
|
+
hint: result.refusal === "unauthenticated" ? "No live task is bound to this worker session \u2014 the task may already be terminal or reassigned." : "The completion was refused by the turn ledger; the task state is authoritative."
|
|
115276
|
+
};
|
|
115277
|
+
}
|
|
115278
|
+
return {
|
|
115279
|
+
success: true,
|
|
115280
|
+
taskId: result.taskId,
|
|
115281
|
+
...result.attemptId ? { attemptId: result.attemptId } : {},
|
|
115282
|
+
outcome: result.outcome,
|
|
115283
|
+
duplicate: result.duplicate,
|
|
115284
|
+
handoffNoteRecorded: result.handoffNoteRecorded
|
|
115285
|
+
};
|
|
115286
|
+
} catch (e) {
|
|
115287
|
+
return { success: false, error: e?.message || String(e) };
|
|
115288
|
+
}
|
|
115289
|
+
},
|
|
115290
|
+
/** Record a mid-task progress note. Never terminal. */
|
|
115291
|
+
worker_progress_update: async (_ctx, args) => {
|
|
115292
|
+
const note = typeof args?.note === "string" ? args.note.trim() : "";
|
|
115293
|
+
if (!note) return { success: false, error: "note required" };
|
|
115294
|
+
try {
|
|
115295
|
+
const { acceptWorkerProgressUpdate: acceptWorkerProgressUpdate2 } = await Promise.resolve().then(() => (init_worker_report(), worker_report_exports));
|
|
115296
|
+
const result = acceptWorkerProgressUpdate2({ token: args?.token, bind: args?.bind }, note);
|
|
115297
|
+
if (!result.accepted) {
|
|
115298
|
+
return { success: false, error: result.refusal || "unauthenticated" };
|
|
115299
|
+
}
|
|
115300
|
+
return { success: true, ...result.taskId ? { taskId: result.taskId } : {} };
|
|
115301
|
+
} catch (e) {
|
|
115302
|
+
return { success: false, error: e?.message || String(e) };
|
|
115303
|
+
}
|
|
115304
|
+
}
|
|
115305
|
+
};
|
|
114393
115306
|
var lowFamilyRegistry = new Map(
|
|
114394
115307
|
Object.entries({
|
|
114395
115308
|
...sessionHostHandlers,
|
|
@@ -114401,7 +115314,8 @@ ${marker}`,
|
|
|
114401
115314
|
...notificationHandlers,
|
|
114402
115315
|
...daemonLifecycleHandlers,
|
|
114403
115316
|
...meshLedgerHandlers,
|
|
114404
|
-
...meshNodeLogsHandlers
|
|
115317
|
+
...meshNodeLogsHandlers,
|
|
115318
|
+
...workerReportHandlers
|
|
114405
115319
|
})
|
|
114406
115320
|
);
|
|
114407
115321
|
init_dist();
|
|
@@ -118642,7 +119556,7 @@ ${marker}`,
|
|
|
118642
119556
|
await new Promise((resolve32) => setTimeout(resolve32, 180));
|
|
118643
119557
|
}
|
|
118644
119558
|
}
|
|
118645
|
-
this.assertFocusedClaudeTuiReview(prompt);
|
|
119559
|
+
await this.assertFocusedClaudeTuiReview(prompt);
|
|
118646
119560
|
this.driver.dispatch({ kind: "pty_write", data: "\r" });
|
|
118647
119561
|
await new Promise((resolve32) => setTimeout(resolve32, 180));
|
|
118648
119562
|
} else {
|
|
@@ -119164,6 +120078,7 @@ ${marker}`,
|
|
|
119164
120078
|
}
|
|
119165
120079
|
maybeCaptureClaudeTuiPrompt() {
|
|
119166
120080
|
if (this.interactivePromptScheme() !== "claude_tui" || this.activeInteractivePrompt || this.claudeTuiPromptCaptureInFlight) return;
|
|
120081
|
+
if (this.latestState?.status === "generating") return;
|
|
119167
120082
|
const screenText = this.driver.snapshot();
|
|
119168
120083
|
if (!screenText.includes("Enter to select")) {
|
|
119169
120084
|
const now = Date.now();
|
|
@@ -119276,8 +120191,31 @@ ${marker}`,
|
|
|
119276
120191
|
const observed = focused?.question ? `; focused question is "${focused.question}"` : "";
|
|
119277
120192
|
throw new Error(`Claude TUI focused question does not match the active interactive prompt (expected "${expected.question}"${observed})`);
|
|
119278
120193
|
}
|
|
119279
|
-
|
|
119280
|
-
|
|
120194
|
+
/**
|
|
120195
|
+
* Wait for the review page to actually be on screen before the final Enter.
|
|
120196
|
+
*
|
|
120197
|
+
* The last answer keystroke is what navigates the picker onto its review
|
|
120198
|
+
* page, and the TUI repaint is not instantaneous. Gating on a single
|
|
120199
|
+
* snapshot taken a fixed delay after that keypress races the repaint: on a
|
|
120200
|
+
* slow frame the assertion still sees the previous question page and fails
|
|
120201
|
+
* closed, refusing an answer that was in fact correct (live defect
|
|
120202
|
+
* 2026-08-28). Poll on the same bounded budget the capture path already
|
|
120203
|
+
* uses (snapshotSettledClaudeTuiPage) and accept the first frame that reads
|
|
120204
|
+
* as the review page; on timeout fall through to the last frame so a
|
|
120205
|
+
* genuinely wrong screen still fails closed with its real content.
|
|
120206
|
+
*/
|
|
120207
|
+
async snapshotSettledClaudeTuiReview() {
|
|
120208
|
+
let screenText = this.readClaudeTuiSnapshotForAnswer();
|
|
120209
|
+
const deadline = Date.now() + _SpecCliAdapter.CLAUDE_TUI_PAGE_SETTLE_TIMEOUT_MS;
|
|
120210
|
+
while (Date.now() < deadline) {
|
|
120211
|
+
if (!readFocusedClaudeTuiQuestion(screenText) && isClaudeTuiReviewScreen(screenText)) return screenText;
|
|
120212
|
+
await new Promise((resolve32) => setTimeout(resolve32, _SpecCliAdapter.CLAUDE_TUI_PAGE_POLL_INTERVAL_MS));
|
|
120213
|
+
screenText = this.readClaudeTuiSnapshotForAnswer();
|
|
120214
|
+
}
|
|
120215
|
+
return screenText;
|
|
120216
|
+
}
|
|
120217
|
+
async assertFocusedClaudeTuiReview(prompt) {
|
|
120218
|
+
const screenText = await this.snapshotSettledClaudeTuiReview();
|
|
119281
120219
|
const focused = readFocusedClaudeTuiQuestion(screenText);
|
|
119282
120220
|
if (focused || !isClaudeTuiReviewScreen(screenText)) {
|
|
119283
120221
|
const observed = focused?.question ? `; focused question is "${focused.question}"` : "";
|
|
@@ -119613,6 +120551,7 @@ ${marker}`,
|
|
|
119613
120551
|
}
|
|
119614
120552
|
}
|
|
119615
120553
|
init_worker_mcp_isolation();
|
|
120554
|
+
init_mesh_coordinator();
|
|
119616
120555
|
init_summary_metadata();
|
|
119617
120556
|
var os26 = __toESM2(require("os"));
|
|
119618
120557
|
var crypto6 = __toESM2(require("crypto"));
|
|
@@ -126130,11 +127069,13 @@ ${rawInput}` : rawInput;
|
|
|
126130
127069
|
providerType: input.cliType,
|
|
126131
127070
|
workspace: input.workspace,
|
|
126132
127071
|
sessionKey: input.sessionKey || input.workspace,
|
|
126133
|
-
mcpConfig: input.mcpConfig
|
|
126134
|
-
// Phase
|
|
126135
|
-
//
|
|
126136
|
-
//
|
|
126137
|
-
//
|
|
127072
|
+
mcpConfig: input.mcpConfig,
|
|
127073
|
+
// Phase B: with a mesh+session to bind, the worker gets a MINIMAL MCP
|
|
127074
|
+
// server (`--mode worker`) plus the bind it exchanges for its task
|
|
127075
|
+
// token. Without one, Phase A's shape stands — a config with no servers
|
|
127076
|
+
// at all, whose isolation win is that the coordinator's 60-tool entry is
|
|
127077
|
+
// absent from the file the worker reads.
|
|
127078
|
+
...input.bindContext ? { bindContext: input.bindContext, server: resolveWorkerMcpServerLaunch() } : {}
|
|
126138
127079
|
});
|
|
126139
127080
|
const envSet = input.isolation?.env?.set;
|
|
126140
127081
|
if (workerIsolation && envSet && typeof envSet === "object") {
|
|
@@ -126367,12 +127308,12 @@ ${rawInput}` : rawInput;
|
|
|
126367
127308
|
* Returns the guard key on admission, or null when the submission is a
|
|
126368
127309
|
* duplicate and must be suppressed.
|
|
126369
127310
|
*/
|
|
126370
|
-
beginMeshDispatchSubmission(
|
|
127311
|
+
beginMeshDispatchSubmission(sessionKey2, taskId, content) {
|
|
126371
127312
|
const now = Date.now();
|
|
126372
127313
|
for (const [k, at] of this.meshDispatchSubmissions) {
|
|
126373
127314
|
if (now - at > MESH_DISPATCH_SUBMIT_DEDUP_WINDOW_MS) this.meshDispatchSubmissions.delete(k);
|
|
126374
127315
|
}
|
|
126375
|
-
const guardKey = `${
|
|
127316
|
+
const guardKey = `${sessionKey2}:${taskId}:${shortHash(content, 24)}`;
|
|
126376
127317
|
if (this.meshDispatchSubmissions.has(guardKey)) return null;
|
|
126377
127318
|
this.meshDispatchSubmissions.set(guardKey, now);
|
|
126378
127319
|
return guardKey;
|
|
@@ -126580,7 +127521,7 @@ ${rawInput}` : rawInput;
|
|
|
126580
127521
|
Enable and detect this provider from the Machine Providers page before starting a runtime.`
|
|
126581
127522
|
);
|
|
126582
127523
|
}
|
|
126583
|
-
const key2 = crypto7.randomUUID();
|
|
127524
|
+
const key2 = options?.presetSessionKey?.trim() || crypto7.randomUUID();
|
|
126584
127525
|
{
|
|
126585
127526
|
const coordinatorMeshId = options?.settingsOverride?.meshCoordinatorFor;
|
|
126586
127527
|
if (typeof coordinatorMeshId === "string" && coordinatorMeshId.trim()) {
|
|
@@ -126874,6 +127815,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
126874
127815
|
const key2 = `${r.workspace}::${r.cliType}`;
|
|
126875
127816
|
workspaceTypeCounts.set(key2, (workspaceTypeCounts.get(key2) || 0) + 1);
|
|
126876
127817
|
}
|
|
127818
|
+
const rebindAdoptedSessionIds = /* @__PURE__ */ new Set();
|
|
126877
127819
|
for (const record2 of sessions) {
|
|
126878
127820
|
if (!record2?.runtimeId || !record2?.cliType || !record2?.workspace) continue;
|
|
126879
127821
|
if (!shouldRestoreHostedRuntime(record2, managerTag)) {
|
|
@@ -126916,6 +127858,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
126916
127858
|
const siblingCount = workspaceTypeCounts.get(`${record2.workspace}::${record2.cliType}`) || 1;
|
|
126917
127859
|
if (!coordinatorPresentById && siblingCount === 1) {
|
|
126918
127860
|
coordinatorEntry = candidate;
|
|
127861
|
+
if (candidate.sessionId) rebindAdoptedSessionIds.add(candidate.sessionId);
|
|
126919
127862
|
LOG2.info(
|
|
126920
127863
|
"CLI",
|
|
126921
127864
|
`\u21BB Rebound coordinator mark by workspace for ${record2.runtimeKey || record2.runtimeId} (mesh ${candidate.meshId} @ ${record2.workspace}); registry key did not match runtimeId`
|
|
@@ -126972,6 +127915,19 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
126972
127915
|
LOG2.warn("CLI", `Failed to restore hosted runtime ${record2.runtimeId}: ${error48?.message || error48}`);
|
|
126973
127916
|
}
|
|
126974
127917
|
}
|
|
127918
|
+
if (!records && typeof this.deps.listHostedCliRuntimes === "function") {
|
|
127919
|
+
const liveSessionIds = new Set(restoredRuntimeIds);
|
|
127920
|
+
for (const r of sessions) {
|
|
127921
|
+
if (r?.runtimeId) liveSessionIds.add(r.runtimeId);
|
|
127922
|
+
}
|
|
127923
|
+
for (const sessionId of rebindAdoptedSessionIds) liveSessionIds.add(sessionId);
|
|
127924
|
+
for (const entry of pruneDeadMeshCoordinators(liveSessionIds)) {
|
|
127925
|
+
LOG2.info(
|
|
127926
|
+
"CLI",
|
|
127927
|
+
`\u{1F9F9} Pruned stale mesh coordinator entry ${entry.sessionId} (mesh ${entry.meshId}${entry.workspace ? ` @ ${entry.workspace}` : ""}, started ${new Date(entry.startedAt || 0).toISOString()}): session is not among the ${liveSessionIds.size} live hosted runtime(s) after daemon restart`
|
|
127928
|
+
);
|
|
127929
|
+
}
|
|
127930
|
+
}
|
|
126975
127931
|
if (restored > 0) {
|
|
126976
127932
|
this.deps.onStatusChange();
|
|
126977
127933
|
}
|
|
@@ -127096,6 +128052,8 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
127096
128052
|
};
|
|
127097
128053
|
}
|
|
127098
128054
|
}
|
|
128055
|
+
const delegatedSessionKey = settingsOverride?.launchedByCoordinator === true ? crypto7.randomUUID() : void 0;
|
|
128056
|
+
const delegatedMeshId = typeof settingsOverride?.meshNodeFor === "string" ? settingsOverride.meshNodeFor.trim() : "";
|
|
127099
128057
|
const delegatedLaunch = settingsOverride?.launchedByCoordinator === true ? buildCoordinatorDelegatedCliLaunchOptions({
|
|
127100
128058
|
cliType,
|
|
127101
128059
|
workspace: dir,
|
|
@@ -127109,7 +128067,18 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
127109
128067
|
// Prefer the auto-launch task binding so two workers on
|
|
127110
128068
|
// one workspace get distinct private HOMEs; fall back to
|
|
127111
128069
|
// the mesh node, then the workspace alone.
|
|
127112
|
-
sessionKey: (typeof settingsOverride?.autoLaunchedForQueueTaskId === "string" ? settingsOverride.autoLaunchedForQueueTaskId.trim() : "") || (typeof settingsOverride?.meshNodeId === "string" ? settingsOverride.meshNodeId.trim() : "") || dir
|
|
128070
|
+
sessionKey: (typeof settingsOverride?.autoLaunchedForQueueTaskId === "string" ? settingsOverride.autoLaunchedForQueueTaskId.trim() : "") || (typeof settingsOverride?.meshNodeId === "string" ? settingsOverride.meshNodeId.trim() : "") || dir,
|
|
128071
|
+
// Present ⇒ the worker gets a reporting surface (Phase B).
|
|
128072
|
+
// Absent (a delegated launch with no mesh context) ⇒ the
|
|
128073
|
+
// Phase A shape: isolation only, no worker server.
|
|
128074
|
+
...delegatedMeshId && delegatedSessionKey ? {
|
|
128075
|
+
bindContext: {
|
|
128076
|
+
meshId: delegatedMeshId,
|
|
128077
|
+
sessionId: delegatedSessionKey,
|
|
128078
|
+
...typeof settingsOverride?.meshNodeId === "string" && settingsOverride.meshNodeId.trim() ? { nodeId: settingsOverride.meshNodeId.trim() } : {},
|
|
128079
|
+
...typeof settingsOverride?.autoLaunchedForQueueTaskId === "string" && settingsOverride.autoLaunchedForQueueTaskId.trim() ? { spawnedForTaskId: settingsOverride.autoLaunchedForQueueTaskId.trim() } : {}
|
|
128080
|
+
}
|
|
128081
|
+
} : {}
|
|
127113
128082
|
}) : null;
|
|
127114
128083
|
if (delegatedLaunch?.workerIsolation?.notes.length) {
|
|
127115
128084
|
LOG2.info("WorkerMcp", `[${cliType}] ${delegatedLaunch.workerIsolation.notes.join("; ")}`);
|
|
@@ -127137,6 +128106,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
127137
128106
|
resumeSessionId: args?.resumeSessionId,
|
|
127138
128107
|
settingsOverride,
|
|
127139
128108
|
extraEnv: delegatedLaunch ? delegatedLaunch.env : args?.env,
|
|
128109
|
+
...delegatedSessionKey ? { presetSessionKey: delegatedSessionKey } : {},
|
|
127140
128110
|
...typeof args?.initialThinkingLevel === "string" && args.initialThinkingLevel.trim() ? { initialThinkingLevel: args.initialThinkingLevel.trim() } : {}
|
|
127141
128111
|
}
|
|
127142
128112
|
);
|
|
@@ -130487,7 +131457,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
130487
131457
|
init_contract();
|
|
130488
131458
|
var fs47 = __toESM2(require("fs"));
|
|
130489
131459
|
var path522 = __toESM2(require("path"));
|
|
130490
|
-
var
|
|
131460
|
+
var import_crypto17 = require("crypto");
|
|
130491
131461
|
init_contract();
|
|
130492
131462
|
var TREE_DIGEST_ALGORITHM = "adhdev-provider-tree-sha256-v1";
|
|
130493
131463
|
function computeProviderTreeDigest(rootDir, providerType) {
|
|
@@ -130501,7 +131471,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
|
|
|
130501
131471
|
);
|
|
130502
131472
|
}
|
|
130503
131473
|
relPaths.sort();
|
|
130504
|
-
const hash2 = (0,
|
|
131474
|
+
const hash2 = (0, import_crypto17.createHash)("sha256");
|
|
130505
131475
|
for (const relPath of relPaths) {
|
|
130506
131476
|
const absPath = path522.join(rootDir, ...relPath.split("/"));
|
|
130507
131477
|
const bytes = fs47.readFileSync(absPath);
|
|
@@ -134658,11 +135628,11 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
|
|
|
134658
135628
|
let node;
|
|
134659
135629
|
const { migrateProviderRolesToSlots: migrateProviderRolesToSlots2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
|
|
134660
135630
|
if (meshRecord.inline) {
|
|
134661
|
-
const { randomUUID:
|
|
135631
|
+
const { randomUUID: randomUUID21 } = await import("crypto");
|
|
134662
135632
|
const clonedPolicy = { ...sourceNode.policy || {} };
|
|
134663
135633
|
migrateProviderRolesToSlots2(clonedPolicy);
|
|
134664
135634
|
node = {
|
|
134665
|
-
id: `node_${
|
|
135635
|
+
id: `node_${randomUUID21().replace(/-/g, "")}`,
|
|
134666
135636
|
workspace: result.worktreePath,
|
|
134667
135637
|
repoRoot: result.worktreePath,
|
|
134668
135638
|
daemonId: sourceNode.daemonId,
|
|
@@ -147548,7 +148518,7 @@ data: ${JSON.stringify(msg.data)}
|
|
|
147548
148518
|
});
|
|
147549
148519
|
}
|
|
147550
148520
|
};
|
|
147551
|
-
var
|
|
148521
|
+
var import_crypto18 = require("crypto");
|
|
147552
148522
|
var import_session_host_core12 = require_dist();
|
|
147553
148523
|
var BASE_KEY_SEQUENCES = {
|
|
147554
148524
|
enter: "\r",
|
|
@@ -147646,7 +148616,7 @@ data: ${JSON.stringify(msg.data)}
|
|
|
147646
148616
|
const sessionId = String(options.sessionId || "").trim();
|
|
147647
148617
|
if (!sessionId) throw new Error("sessionId is required");
|
|
147648
148618
|
const mode = options.mode || "read";
|
|
147649
|
-
const clientId = options.clientId || `raw-terminal-${process.pid}-${(0,
|
|
148619
|
+
const clientId = options.clientId || `raw-terminal-${process.pid}-${(0, import_crypto18.randomUUID)().slice(0, 8)}`;
|
|
147650
148620
|
const client = options.client || new import_session_host_core12.SessionHostClient({ endpoint: options.endpoint });
|
|
147651
148621
|
await client.connect();
|
|
147652
148622
|
const attachResponse = await client.request({
|
|
@@ -148426,7 +149396,7 @@ data: ${JSON.stringify(msg.data)}
|
|
|
148426
149396
|
init_mesh_reconcile_loop();
|
|
148427
149397
|
var import_fs23 = require("fs");
|
|
148428
149398
|
var import_path24 = require("path");
|
|
148429
|
-
var
|
|
149399
|
+
var import_crypto19 = require("crypto");
|
|
148430
149400
|
init_logger();
|
|
148431
149401
|
init_mesh_ledger();
|
|
148432
149402
|
init_mesh_runtime_store();
|
|
@@ -148460,7 +149430,7 @@ data: ${JSON.stringify(msg.data)}
|
|
|
148460
149430
|
try {
|
|
148461
149431
|
const fingerprint = buildPendingEventFingerprint(event);
|
|
148462
149432
|
MeshRuntimeStore.getInstance().insertPendingEvent({
|
|
148463
|
-
id: (0,
|
|
149433
|
+
id: (0, import_crypto19.randomUUID)(),
|
|
148464
149434
|
meshId: event.meshId,
|
|
148465
149435
|
coordinatorDaemonId: event.targetCoordinatorDaemonId ?? null,
|
|
148466
149436
|
event: event.event,
|
|
@@ -149401,6 +150371,8 @@ data: ${JSON.stringify(msg.data)}
|
|
|
149401
150371
|
}
|
|
149402
150372
|
init_mesh_read_readiness();
|
|
149403
150373
|
init_mesh_dual_write();
|
|
150374
|
+
init_worker_handoff_notes();
|
|
150375
|
+
init_worker_report();
|
|
149404
150376
|
init_dist();
|
|
149405
150377
|
init_topics2();
|
|
149406
150378
|
var FLEET_STATUS_SUB_VIEW = "tail";
|
|
@@ -150123,6 +151095,8 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
|
|
|
150123
151095
|
configureMeshReadModel(components.seqscribeNode ?? null);
|
|
150124
151096
|
configureFleetStatusShadow(components.seqscribeNode ?? null);
|
|
150125
151097
|
configureFleetStatusParity(components.seqscribeNode ?? null);
|
|
151098
|
+
configureHandoffNotesSeqscribe(components.seqscribeNode ?? null);
|
|
151099
|
+
configureHandoffNoteSink(storeHandoffNote);
|
|
150126
151100
|
if (components.seqscribeNode) {
|
|
150127
151101
|
pruneStaleConsumersAtBoot();
|
|
150128
151102
|
components.seqscribeParityLoop = startMeshParityLoop(components.seqscribeNode);
|
|
@@ -150247,6 +151221,14 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
|
|
|
150247
151221
|
configureMeshReadModel(null);
|
|
150248
151222
|
} catch {
|
|
150249
151223
|
}
|
|
151224
|
+
try {
|
|
151225
|
+
configureHandoffNotesSeqscribe(null);
|
|
151226
|
+
} catch {
|
|
151227
|
+
}
|
|
151228
|
+
try {
|
|
151229
|
+
configureHandoffNoteSink(null);
|
|
151230
|
+
} catch {
|
|
151231
|
+
}
|
|
150250
151232
|
try {
|
|
150251
151233
|
if (agentStreamManager) {
|
|
150252
151234
|
await agentStreamManager.dispose(cdpManagers);
|