@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 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 ? "f2c2218bbcbabe7c7d59bf055c0a3acce18cce18" : void 0) ?? "unknown";
36915
- const commitShort = readInjected(true ? "f2c2218b" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
36916
- const version2 = readInjected(true ? "1.0.58-rc.33" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
36917
- const builtAt = readInjected(true ? "2026-08-28T11:56:21.824Z" : void 0);
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(sessionKey, fn) {
47481
- const prior = sessionOrderChains.get(sessionKey) ?? Promise.resolve();
47480
+ function enqueueSessionOrdered(sessionKey2, fn) {
47481
+ const prior = sessionOrderChains.get(sessionKey2) ?? Promise.resolve();
47482
47482
  const next = prior.then(fn, fn);
47483
- sessionOrderChains.set(sessionKey, next.catch(() => void 0));
47483
+ sessionOrderChains.set(sessionKey2, next.catch(() => void 0));
47484
47484
  return next;
47485
47485
  }
47486
- function runSessionEvidenceCollection(sessionKey, collect) {
47487
- return enqueueSessionOrdered(sessionKey, collect);
47486
+ function runSessionEvidenceCollection(sessionKey2, collect) {
47487
+ return enqueueSessionOrdered(sessionKey2, collect);
47488
47488
  }
47489
- function runSessionDestructiveAction(sessionKey, act) {
47490
- return enqueueSessionOrdered(sessionKey, act);
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
- // The token rides INSIDE the config, not in the worker's process
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
- notes.push(`worker MCP config written to ${result.configPath}`);
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
- const rows = this.db.prepare(`
66370
- SELECT * FROM mesh_turn_events WHERE mesh_id = ? AND task_id = ? ORDER BY recorded_at ASC, event_id ASC
66371
- `).all(meshId, taskId);
66372
- return rows.map((r) => ({
66373
- eventId: r.event_id,
66374
- attemptId: r.attempt_id,
66375
- kind: r.kind,
66376
- dedupeKey: r.dedupe_key ?? "",
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 elapsedFraction = Math.min(1, Math.max(0, 1 - (resetsAt - reporterNowMs) / windowMs));
74460
- return { remainingPercent: remaining, risk: remaining * elapsedFraction };
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
- message: task.message,
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
- message: task.message,
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, import_crypto15.randomUUID)(),
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 import_crypto15;
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
- import_crypto15 = require("crypto");
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 = readLedgerEntries(meshId, {
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 sessionKey = chatSourceSessionKey(args.providerType, args.sessionId);
106289
- const priorSnapshot = CHAT_SOURCE_REGISTRY.snapshotRecord(sessionKey);
106290
- const priorState = priorSnapshot?.state ?? CHAT_SOURCE_REGISTRY.getState(sessionKey);
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(sessionKey, observation);
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(sessionKey, priorSnapshot);
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(sessionKey);
106338
- decision = CHAT_SOURCE_REGISTRY.observe(sessionKey, observation);
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
- assertFocusedClaudeTuiReview(prompt) {
119280
- const screenText = this.readClaudeTuiSnapshotForAnswer();
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 A writes an EMPTY worker config (no servers). The worker tool
126135
- // surface and its server entry arrive in Phase B; until then the
126136
- // isolation win is simply that the coordinator's 60-tool entry is not
126137
- // in the file the worker reads.
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(sessionKey, taskId, content) {
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 = `${sessionKey}:${taskId}:${shortHash(content, 24)}`;
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 import_crypto16 = require("crypto");
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, import_crypto16.createHash)("sha256");
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: randomUUID20 } = await import("crypto");
135631
+ const { randomUUID: randomUUID21 } = await import("crypto");
134662
135632
  const clonedPolicy = { ...sourceNode.policy || {} };
134663
135633
  migrateProviderRolesToSlots2(clonedPolicy);
134664
135634
  node = {
134665
- id: `node_${randomUUID20().replace(/-/g, "")}`,
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 import_crypto17 = require("crypto");
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, import_crypto17.randomUUID)().slice(0, 8)}`;
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 import_crypto18 = require("crypto");
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, import_crypto18.randomUUID)(),
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);