@algosuite/vo-mcp 0.2.0-beta.49 → 0.2.0-beta.51

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
@@ -502,7 +502,7 @@ __export(memory_knowledge_bridge_exports, {
502
502
  extractMemoryTitle: () => extractMemoryTitle,
503
503
  upsertMemoryFilesAsKnowledge: () => upsertMemoryFilesAsKnowledge
504
504
  });
505
- import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "node:fs";
505
+ import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "node:fs";
506
506
  function extractMemoryTitle(fileName, content) {
507
507
  const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
508
508
  if (frontmatter) {
@@ -517,7 +517,7 @@ async function upsertMemoryFilesAsKnowledge(options) {
517
517
  const { controlPlaneUrl, token, memoryDir, fetchFn, cache, deadline } = options;
518
518
  let files;
519
519
  try {
520
- if (!existsSync7(memoryDir)) {
520
+ if (!existsSync8(memoryDir)) {
521
521
  return { attempted: 0, upserted: 0, failed: 0, skipped: 0, failures: [] };
522
522
  }
523
523
  files = readdirSync5(memoryDir).filter(
@@ -807,24 +807,24 @@ function defaultOverridePath() {
807
807
  return join2(homedir(), ".claude", "vo-arch-defaults.local.json");
808
808
  }
809
809
  function loadTenantOverride(opts = {}) {
810
- const path3 = opts.path ?? defaultOverridePath();
811
- if (!existsSync2(path3)) {
810
+ const path4 = opts.path ?? defaultOverridePath();
811
+ if (!existsSync2(path4)) {
812
812
  return { override: null, source_path: null };
813
813
  }
814
- const raw = readFileSync2(path3, "utf8");
814
+ const raw = readFileSync2(path4, "utf8");
815
815
  let parsed;
816
816
  try {
817
817
  parsed = JSON.parse(raw);
818
818
  } catch (err) {
819
819
  const m = err instanceof Error ? err.message : String(err);
820
- throw new Error(`vo-arch-defaults: invalid JSON in override ${path3}: ${m}`, { cause: err });
820
+ throw new Error(`vo-arch-defaults: invalid JSON in override ${path4}: ${m}`, { cause: err });
821
821
  }
822
822
  try {
823
823
  const override = parseOverride(parsed);
824
- return { override, source_path: path3 };
824
+ return { override, source_path: path4 };
825
825
  } catch (err) {
826
826
  const m = err instanceof Error ? err.message : String(err);
827
- throw new Error(`vo-arch-defaults: override schema validation failed for ${path3}: ${m}`, { cause: err });
827
+ throw new Error(`vo-arch-defaults: override schema validation failed for ${path4}: ${m}`, { cause: err });
828
828
  }
829
829
  }
830
830
 
@@ -954,9 +954,9 @@ function globToRegExp(glob) {
954
954
  }
955
955
  return new RegExp("^" + out + "$");
956
956
  }
957
- function matchesAnyGlob(path3, globs) {
957
+ function matchesAnyGlob(path4, globs) {
958
958
  for (const g of globs) {
959
- if (globToRegExp(g).test(path3)) return true;
959
+ if (globToRegExp(g).test(path4)) return true;
960
960
  }
961
961
  return false;
962
962
  }
@@ -1490,7 +1490,17 @@ function assertWithinByteCap(toolName, fieldName, value, maxBytes) {
1490
1490
  );
1491
1491
  }
1492
1492
  }
1493
+ var REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
1494
+ function subjectFromEnv(env = process.env) {
1495
+ const code_task_id = (env["VO_CODE_TASK_ID"] ?? "").trim().slice(0, 120);
1496
+ const repo = (env["VO_CODE_TASK_REPO"] ?? "").trim();
1497
+ const subject = {};
1498
+ if (code_task_id) subject.code_task_id = code_task_id;
1499
+ if (repo && REPO_RE.test(repo) && repo.length <= 200) subject.repo = repo;
1500
+ return subject.code_task_id || subject.repo ? subject : null;
1501
+ }
1493
1502
  function buildBaseEvent(args) {
1503
+ const subject = args.subject === void 0 ? subjectFromEnv() : args.subject;
1494
1504
  return {
1495
1505
  schema_version: 1,
1496
1506
  event_id: args.eventId ?? randomUUID(),
@@ -1516,7 +1526,12 @@ function buildBaseEvent(args) {
1516
1526
  downstream_outcome: null,
1517
1527
  vo_mcp_version: VO_MCP_VERSION,
1518
1528
  consensus_engine_version: null,
1519
- cache_hit: false
1529
+ cache_hit: false,
1530
+ // OMIT the key when there is no subject (rather than `subject: null`): the
1531
+ // ingest schema is `.strict()`, so an event with no subject stays valid on a
1532
+ // sink that has not learned the field yet — only subject-carrying events
1533
+ // depend on the sink being current (deploy ordering: sink before producer).
1534
+ ...subject ? { subject } : {}
1520
1535
  };
1521
1536
  }
1522
1537
  function jsonContent(value) {
@@ -1582,6 +1597,16 @@ function aggregateEventTokenUsage(src, engineUsage) {
1582
1597
  total_cost_usd: anyCost ? costMicroUsd / 1e6 : null
1583
1598
  };
1584
1599
  }
1600
+ function trajectoryFromEngine(result, known = {}) {
1601
+ const rounds = typeof result.token_usage?.rounds_counted === "number" && Number.isInteger(result.token_usage.rounds_counted) && result.token_usage.rounds_counted >= 0 ? result.token_usage.rounds_counted : null;
1602
+ const called = result.fan_out_diagnostics?.models_called;
1603
+ const turns = typeof called === "number" && Number.isInteger(called) && called >= 0 ? called : rounds !== null && rounds > 0 ? result.per_model_verdicts.length * rounds : null;
1604
+ const sourceGrounded = result.citation_grade !== void 0 || result.low_confidence_sources !== void 0;
1605
+ const tool_calls = typeof known.tool_calls === "number" && Number.isInteger(known.tool_calls) && known.tool_calls >= 0 ? known.tool_calls : sourceGrounded ? null : 0;
1606
+ if (rounds === null && turns === null && tool_calls === null) return {};
1607
+ const trajectory = { rounds, turns, tool_calls };
1608
+ return { trajectory };
1609
+ }
1585
1610
  function toEventSynthesizedVerdict(src) {
1586
1611
  return {
1587
1612
  verdict: src.verdict,
@@ -1954,7 +1979,8 @@ async function handleCheckHollowTest(deps, rawInput, signal) {
1954
1979
  consensus_confidence: engineResult.synthesized_verdict.confidence,
1955
1980
  duration_ms: engineResult.duration_ms,
1956
1981
  consensus_engine_version: engineResult.engine_version,
1957
- ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
1982
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
1983
+ ...trajectoryFromEngine(engineResult)
1958
1984
  };
1959
1985
  const payload = {
1960
1986
  verdict: engineResult.synthesized_verdict.verdict,
@@ -2137,7 +2163,8 @@ async function handleVerifyAnswer(deps, rawInput, signal) {
2137
2163
  consensus_confidence: engineResult.synthesized_verdict.confidence,
2138
2164
  duration_ms: engineResult.duration_ms,
2139
2165
  consensus_engine_version: engineResult.engine_version,
2140
- ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
2166
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
2167
+ ...trajectoryFromEngine(engineResult)
2141
2168
  };
2142
2169
  const payload = {
2143
2170
  verdict: engineResult.synthesized_verdict.verdict,
@@ -2403,7 +2430,8 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
2403
2430
  consensus_engine_version: engineResult.engine_version,
2404
2431
  per_model_verdicts: perModelForEvent,
2405
2432
  synthesized_verdict: synthForEvent,
2406
- ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
2433
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
2434
+ ...trajectoryFromEngine(engineResult)
2407
2435
  };
2408
2436
  const payload = {
2409
2437
  verdict: engineResult.synthesized_verdict.verdict,
@@ -2616,7 +2644,8 @@ async function handleArchitectureReview(deps, rawInput, signal) {
2616
2644
  consensus_confidence: engineResult.synthesized_verdict.confidence,
2617
2645
  duration_ms: engineResult.duration_ms,
2618
2646
  consensus_engine_version: engineResult.engine_version,
2619
- ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
2647
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
2648
+ ...trajectoryFromEngine(engineResult)
2620
2649
  };
2621
2650
  const escalationRequired = engineResult.escalation_required === true || engineResult.escalation_required === void 0 && engineResult.synthesized_verdict.dissent_summary !== null;
2622
2651
  const escalationReason = engineResult.escalation_reason ?? engineResult.synthesized_verdict.dissent_summary ?? "";
@@ -4102,7 +4131,8 @@ Produce the JSON dispatch plan now.`;
4102
4131
  consensus_engine_version: engineResult.engine_version,
4103
4132
  per_model_verdicts: toEventPerModelVerdicts(engineResult.per_model_verdicts),
4104
4133
  synthesized_verdict: toEventSynthesizedVerdict(engineResult.synthesized_verdict),
4105
- ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
4134
+ ...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
4135
+ ...trajectoryFromEngine(engineResult)
4106
4136
  };
4107
4137
  deps.events.append(enrichedEvent);
4108
4138
  return jsonContent(envelope);
@@ -4112,10 +4142,10 @@ Produce the JSON dispatch plan now.`;
4112
4142
  init_auth_token_source();
4113
4143
  init_credential_store();
4114
4144
  var AdminCallableError = class extends Error {
4115
- constructor(status, path3, message) {
4145
+ constructor(status, path4, message) {
4116
4146
  super(message);
4117
4147
  this.status = status;
4118
- this.path = path3;
4148
+ this.path = path4;
4119
4149
  this.name = "AdminCallableError";
4120
4150
  }
4121
4151
  status;
@@ -4672,6 +4702,7 @@ async function handleRejectAndRetry(deps, rawInput, _signal) {
4672
4702
  // src/tools/pr/review-merge.ts
4673
4703
  var TOOL_NAME18 = "vo_review_merge";
4674
4704
  var LIST_PATH = "/api/v1/admin/pr/list";
4705
+ var DEFAULT_REVIEW_REPO = "Algosuite-ai/Nexus";
4675
4706
  var ENGINE_GATE = "final-deep-verify";
4676
4707
  var EVENT_GATE = "merge-review";
4677
4708
  var UNAVAILABLE_REASON = "vo_review_merge needs cloud mode to fetch PR context \u2014 set VO_CONTROL_PLANE_URL + VO_CONTROL_PLANE_ADMIN_TOKEN in the MCP env. (It is read-only; it never merges.)";
@@ -4736,6 +4767,11 @@ async function handleReviewMerge(deps, rawInput, signal) {
4736
4767
  if (rawInput.notes !== void 0) normalizedInput.notes = rawInput.notes;
4737
4768
  const inputJson = JSON.stringify(normalizedInput);
4738
4769
  const key = deps.cache.keyFor(TOOL_NAME18, normalizedInput);
4770
+ const subject = {
4771
+ ...subjectFromEnv() ?? {},
4772
+ repo: subjectFromEnv()?.repo ?? DEFAULT_REVIEW_REPO,
4773
+ pr_number: prNumber
4774
+ };
4739
4775
  const baseEvent = buildBaseEvent({
4740
4776
  tool: TOOL_NAME18,
4741
4777
  gateType: EVENT_GATE,
@@ -4743,7 +4779,8 @@ async function handleReviewMerge(deps, rawInput, signal) {
4743
4779
  inputExcerpt: inputJson.slice(0, 300),
4744
4780
  inputSizeBytes: bytesOf(inputJson),
4745
4781
  session: deps.session,
4746
- now: deps.now()
4782
+ now: deps.now(),
4783
+ subject
4747
4784
  });
4748
4785
  const emit = (payload2, eventExtra) => {
4749
4786
  deps.events.append(eventExtra ? { ...baseEvent, ...eventExtra } : baseEvent);
@@ -4845,7 +4882,8 @@ async function handleReviewMerge(deps, rawInput, signal) {
4845
4882
  consensus_engine_version: result.engine_version,
4846
4883
  per_model_verdicts: perModel,
4847
4884
  synthesized_verdict: synth,
4848
- ...aggregateEventTokenUsage(result.per_model_verdicts, result.token_usage)
4885
+ ...aggregateEventTokenUsage(result.per_model_verdicts, result.token_usage),
4886
+ ...trajectoryFromEngine(result)
4849
4887
  });
4850
4888
  }
4851
4889
 
@@ -5102,7 +5140,7 @@ async function handleReportSessionState(deps, rawInput, _signal) {
5102
5140
  import { spawn } from "node:child_process";
5103
5141
  import { homedir as homedir5 } from "node:os";
5104
5142
  import { join as join7 } from "node:path";
5105
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
5143
+ import { closeSync as closeSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
5106
5144
 
5107
5145
  // src/swarm/tier-binding.ts
5108
5146
  var SWARM_TIERS = Object.freeze([
@@ -5308,10 +5346,10 @@ function sanitizeSwarmId(raw) {
5308
5346
  return id;
5309
5347
  }
5310
5348
  var CEILING_FILE = "ceiling.json";
5311
- function createExclusive(path3, contents) {
5349
+ function createExclusive(path4, contents) {
5312
5350
  let fd;
5313
5351
  try {
5314
- fd = openSync(path3, "wx");
5352
+ fd = openSync(path4, "wx");
5315
5353
  } catch {
5316
5354
  return false;
5317
5355
  }
@@ -5326,18 +5364,18 @@ function capToCents(cap) {
5326
5364
  return isPositiveCap(cap) ? Math.round(cap * 100) : 0;
5327
5365
  }
5328
5366
  function readOrRecordLedgerHead(swarmDir, proposedCeiling, proposedCapCents, nowIso) {
5329
- const path3 = join6(swarmDir, CEILING_FILE);
5367
+ const path4 = join6(swarmDir, CEILING_FILE);
5330
5368
  const head = JSON.stringify({
5331
5369
  ceiling: proposedCeiling,
5332
5370
  cap_cents: proposedCapCents,
5333
5371
  recorded_at: nowIso
5334
5372
  });
5335
- if (createExclusive(path3, head)) {
5373
+ if (createExclusive(path4, head)) {
5336
5374
  return { ceiling: proposedCeiling, capCents: proposedCapCents };
5337
5375
  }
5338
5376
  let parsed;
5339
5377
  try {
5340
- parsed = JSON.parse(readFileSync6(path3, "utf8"));
5378
+ parsed = JSON.parse(readFileSync6(path4, "utf8"));
5341
5379
  } catch {
5342
5380
  return null;
5343
5381
  }
@@ -5417,6 +5455,288 @@ var claimSpawnSlot = ({ swarmId, proposedCeiling, proposedCapUsd, dir, nowIso })
5417
5455
  };
5418
5456
  };
5419
5457
 
5458
+ // src/swarm/spawn-plan.ts
5459
+ function resolveSpawnPlan(env, input, nowIso, platform = process.platform, claim = claimSpawnSlot) {
5460
+ const rawBinding = env[SWARM_TIER_BINDING_ENV];
5461
+ const hasBinding = typeof rawBinding === "string" && rawBinding.trim().length > 0;
5462
+ if (!hasBinding) {
5463
+ const explicit = input.agent?.trim();
5464
+ const resolved2 = resolveSuccessorLaunch({ agent: explicit || "claude", maxTurns: input.max_turns, platform });
5465
+ if (!resolved2.ok) return { ok: false, reason: resolved2.reason, tier: "unbound" };
5466
+ return {
5467
+ ok: true,
5468
+ bin: resolved2.bin,
5469
+ args: resolved2.args,
5470
+ agent: resolved2.agent,
5471
+ tier: "unbound",
5472
+ bound: false,
5473
+ env: {},
5474
+ slot: null,
5475
+ capUsd: null,
5476
+ capRemainingUsd: null
5477
+ };
5478
+ }
5479
+ const binding = inheritSwarmTierBinding(env, nowIso);
5480
+ const admission = admitSubagentSpawn(binding);
5481
+ if (!admission.allowed) {
5482
+ return { ok: false, reason: admission.reason, tier: binding.tier };
5483
+ }
5484
+ const agentRefusal = agentBindingRefusal(binding, input.agent);
5485
+ if (agentRefusal !== null) return { ok: false, reason: agentRefusal, tier: binding.tier };
5486
+ const resolved = resolveSuccessorLaunch({
5487
+ agent: binding.agent,
5488
+ maxTurns: input.max_turns,
5489
+ platform
5490
+ });
5491
+ if (!resolved.ok) return { ok: false, reason: resolved.reason, tier: binding.tier };
5492
+ const slot = claim({
5493
+ swarmId: binding.swarm_id,
5494
+ proposedCeiling: binding.subagent_budget,
5495
+ // The spend-cap POOL, recorded once per swarm exactly like the ceiling. The
5496
+ // child's cap is DEBITED from it below, not recomputed from this binding.
5497
+ proposedCapUsd: binding.spend_cap_usd,
5498
+ dir: resolveLedgerDir(env),
5499
+ nowIso
5500
+ });
5501
+ if (!slot.ok) return { ok: false, reason: slot.reason, tier: binding.tier };
5502
+ return {
5503
+ ok: true,
5504
+ bin: resolved.bin,
5505
+ args: resolved.args,
5506
+ agent: resolved.agent,
5507
+ tier: binding.tier,
5508
+ bound: true,
5509
+ // Re-export the same TIER with a DECREMENTED budget and the spend cap the
5510
+ // ledger just DEBITED. Exporting the binding verbatim (what this did before
5511
+ // #9312) meant the child re-read the full budget and every generation
5512
+ // restarted at zero. Recomputing the cap from THIS binding (what #9312 did)
5513
+ // bounded a chain but not a tree: three siblings each re-halved the parent's
5514
+ // untouched $50 and walked away with $75 between them.
5515
+ env: childBindingEnvFragment(binding, slot.capUsd),
5516
+ slot: slot.slot,
5517
+ capUsd: slot.capUsd,
5518
+ capRemainingUsd: slot.capRemainingUsd
5519
+ };
5520
+ }
5521
+
5522
+ // ../../scripts/virtual-office/code-runner/windows-claude-launch.mjs
5523
+ import { existsSync as existsSync4, realpathSync } from "node:fs";
5524
+ import { win32 as path3 } from "node:path";
5525
+ import { spawnSync as spawnSync2 } from "node:child_process";
5526
+ var NATIVE_CLAUDE_PARTS = [
5527
+ "node_modules",
5528
+ "@anthropic-ai",
5529
+ "claude-code",
5530
+ "bin",
5531
+ "claude.exe"
5532
+ ];
5533
+ function pathValue(env) {
5534
+ for (const key of ["Path", "PATH", "path"]) {
5535
+ if (typeof env?.[key] === "string") return env[key];
5536
+ }
5537
+ return "";
5538
+ }
5539
+ function cleanPathSegment(value) {
5540
+ const trimmed = String(value || "").trim();
5541
+ return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
5542
+ }
5543
+ function envValue(env, name) {
5544
+ const exact = env?.[name];
5545
+ if (typeof exact === "string") return exact.trim();
5546
+ const key = Object.keys(env || {}).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
5547
+ return typeof env?.[key] === "string" ? env[key].trim() : "";
5548
+ }
5549
+ function userClaudeCandidates(bin, env) {
5550
+ if (!/^claude(?:\.(?:exe|cmd|ps1))?$/iu.test(bin)) return [];
5551
+ const userProfile = envValue(env, "USERPROFILE");
5552
+ const appData = envValue(env, "APPDATA") || (userProfile ? path3.join(userProfile, "AppData", "Roaming") : "");
5553
+ const localAppData = envValue(env, "LOCALAPPDATA") || (userProfile ? path3.join(userProfile, "AppData", "Local") : "");
5554
+ const candidates = [];
5555
+ if (appData) {
5556
+ const npmBin = path3.join(appData, "npm");
5557
+ candidates.push(
5558
+ path3.join(npmBin, "claude.exe"),
5559
+ path3.join(npmBin, "claude.cmd"),
5560
+ path3.join(npmBin, "claude.ps1"),
5561
+ path3.join(npmBin, "claude"),
5562
+ path3.join(npmBin, ...NATIVE_CLAUDE_PARTS)
5563
+ );
5564
+ }
5565
+ if (userProfile) candidates.push(path3.join(userProfile, ".local", "bin", "claude.exe"));
5566
+ if (localAppData) {
5567
+ candidates.push(
5568
+ path3.join(localAppData, "Microsoft", "WinGet", "Links", "claude.exe"),
5569
+ path3.join(localAppData, "Microsoft", "WindowsApps", "claude.exe")
5570
+ );
5571
+ }
5572
+ return candidates;
5573
+ }
5574
+ function pathCandidates(bin, env) {
5575
+ if (path3.isAbsolute(bin) || /[\\/]/u.test(bin)) {
5576
+ return [path3.resolve(bin)];
5577
+ }
5578
+ const extension = path3.extname(bin);
5579
+ const fromPath = pathValue(env).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path3.join(directory, bin)] : [
5580
+ path3.join(directory, `${bin}.exe`),
5581
+ path3.join(directory, `${bin}.cmd`),
5582
+ path3.join(directory, `${bin}.ps1`),
5583
+ path3.join(directory, bin)
5584
+ ]);
5585
+ const seen = /* @__PURE__ */ new Set();
5586
+ return [...fromPath, ...userClaudeCandidates(bin, env)].filter((candidate) => {
5587
+ const key = candidate.toLowerCase();
5588
+ if (seen.has(key)) return false;
5589
+ seen.add(key);
5590
+ return true;
5591
+ });
5592
+ }
5593
+ function canonicalExistingPath(candidate, exists, canonicalize2) {
5594
+ if (!exists(candidate)) return null;
5595
+ try {
5596
+ return canonicalize2(candidate);
5597
+ } catch {
5598
+ return null;
5599
+ }
5600
+ }
5601
+ function resolveWindowsClaudeExecutable({
5602
+ bin = "claude",
5603
+ env = process.env,
5604
+ exists = existsSync4,
5605
+ canonicalize: canonicalize2 = realpathSync
5606
+ } = {}) {
5607
+ const requested = String(bin || "").trim();
5608
+ if (!requested || requested.includes("\0")) {
5609
+ throw new TypeError("Claude executable must be a non-empty path without NUL bytes");
5610
+ }
5611
+ for (const candidate of pathCandidates(requested, env)) {
5612
+ const found = canonicalExistingPath(candidate, exists, canonicalize2);
5613
+ if (!found) continue;
5614
+ if (path3.extname(found).toLowerCase() === ".exe") return found;
5615
+ const native = path3.join(path3.dirname(found), ...NATIVE_CLAUDE_PARTS);
5616
+ const resolvedNative = canonicalExistingPath(native, exists, canonicalize2);
5617
+ if (resolvedNative) return resolvedNative;
5618
+ }
5619
+ const error = new Error(
5620
+ `Could not resolve a native claude.exe for "${requested}". Install or update Claude Code with the native Windows installer (recommended) or npm install -g @anthropic-ai/claude-code; the HQ runner will not execute a shell-only .cmd/.ps1 shim.`
5621
+ );
5622
+ error.code = "ENOENT";
5623
+ throw error;
5624
+ }
5625
+
5626
+ // src/swarm/successor-windows-exe.ts
5627
+ var resolveWindowsClaudeExe = (bin, env) => resolveWindowsClaudeExecutable({ bin, env });
5628
+ function resolveNativeWindowsExecutable(bin, env = process.env, resolve3 = resolveWindowsClaudeExe) {
5629
+ try {
5630
+ return { ok: true, bin: resolve3(bin, env) };
5631
+ } catch (error) {
5632
+ const message = error instanceof Error ? error.message : String(error);
5633
+ return {
5634
+ ok: false,
5635
+ reason: `could not resolve a native Windows executable for '${bin}': ${message}`
5636
+ };
5637
+ }
5638
+ }
5639
+
5640
+ // src/swarm/successor-liveness.ts
5641
+ import { statSync as statSync3 } from "node:fs";
5642
+ var DEFAULT_EARLY_EXIT_SEC = 10;
5643
+ var DEFAULT_NO_OUTPUT_SEC = 0;
5644
+ var DEFAULT_POLL_MS = 200;
5645
+ var KILL_ESCALATION_MS = 2e3;
5646
+ function positiveSeconds(raw, fallback) {
5647
+ if (raw === void 0) return fallback;
5648
+ const n = Number(raw.trim());
5649
+ return Number.isFinite(n) && n > 0 ? n : fallback;
5650
+ }
5651
+ function resolveLivenessConfigFromEnv(env = process.env, explicitOverride = {}) {
5652
+ const earlyExitMs = explicitOverride.earlyExitMs ?? positiveSeconds(env["VO_MCP_SUCCESSOR_EXIT_CHECK_SEC"], DEFAULT_EARLY_EXIT_SEC) * 1e3;
5653
+ const noOutputMs = explicitOverride.noOutputMs ?? positiveSeconds(env["VO_MCP_SUCCESSOR_OUTPUT_CHECK_SEC"], DEFAULT_NO_OUTPUT_SEC) * 1e3;
5654
+ return { earlyExitMs, noOutputMs };
5655
+ }
5656
+ function defaultStatLogBytes(path4) {
5657
+ try {
5658
+ return statSync3(path4).size;
5659
+ } catch {
5660
+ return 0;
5661
+ }
5662
+ }
5663
+ function defaultKillChild(child) {
5664
+ try {
5665
+ child.kill("SIGTERM");
5666
+ } catch {
5667
+ }
5668
+ const escalation = setTimeout(() => {
5669
+ try {
5670
+ child.kill("SIGKILL");
5671
+ } catch {
5672
+ }
5673
+ }, KILL_ESCALATION_MS);
5674
+ escalation.unref();
5675
+ }
5676
+ function checkSuccessorLiveness(child, logPath, config, deps = {}) {
5677
+ const statLogBytes = deps.statLogBytes ?? defaultStatLogBytes;
5678
+ const now = deps.now ?? Date.now;
5679
+ const pollIntervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_MS;
5680
+ const killChild = deps.killChild ?? defaultKillChild;
5681
+ const outputGateEnabled = Number.isFinite(config.noOutputMs) && config.noOutputMs > 0;
5682
+ const startedAt = now();
5683
+ return new Promise((resolve3) => {
5684
+ let settled = false;
5685
+ let pollTimer = null;
5686
+ const onExit = (code, signal) => {
5687
+ const elapsedMs = now() - startedAt;
5688
+ finish({
5689
+ ok: false,
5690
+ reason: `child_exited_early: exit code ${code ?? "null"} signal ${signal ?? "none"} after ${elapsedMs}ms`,
5691
+ detail: { exitCode: code, signal, elapsedMs }
5692
+ });
5693
+ };
5694
+ const cleanup = () => {
5695
+ if (pollTimer !== null) clearInterval(pollTimer);
5696
+ child.off?.("exit", onExit);
5697
+ };
5698
+ const finish = (result) => {
5699
+ if (settled) return;
5700
+ settled = true;
5701
+ cleanup();
5702
+ if (!result.ok) {
5703
+ try {
5704
+ killChild(child);
5705
+ } catch {
5706
+ }
5707
+ }
5708
+ resolve3(result);
5709
+ };
5710
+ if (typeof child.exitCode === "number" || typeof child.signalCode === "string" && child.signalCode.length > 0) {
5711
+ finish({
5712
+ ok: false,
5713
+ reason: `child_exited_early: exit code ${child.exitCode ?? "null"} signal ${child.signalCode ?? "none"} before the liveness watch attached`,
5714
+ detail: { exitCode: child.exitCode ?? null, signal: child.signalCode ?? null, elapsedMs: 0 }
5715
+ });
5716
+ return;
5717
+ }
5718
+ child.on("exit", onExit);
5719
+ const tick = () => {
5720
+ if (settled) return;
5721
+ const elapsedMs = now() - startedAt;
5722
+ const outputSeen = outputGateEnabled ? statLogBytes(logPath) > 0 : true;
5723
+ if (outputSeen && elapsedMs >= config.earlyExitMs) {
5724
+ finish({ ok: true });
5725
+ return;
5726
+ }
5727
+ if (outputGateEnabled && !outputSeen && elapsedMs >= config.noOutputMs) {
5728
+ finish({
5729
+ ok: false,
5730
+ reason: `no_output: log stayed empty for ${elapsedMs}ms (limit ${config.noOutputMs}ms)`,
5731
+ detail: { elapsedMs, logPath }
5732
+ });
5733
+ }
5734
+ };
5735
+ pollTimer = setInterval(tick, pollIntervalMs);
5736
+ tick();
5737
+ });
5738
+ }
5739
+
5420
5740
  // src/tools/session/spawn-successor.ts
5421
5741
  var TOOL_NAME20 = "vo_spawn_successor";
5422
5742
  var MAX_HANDOFF_BYTES = 64e3;
@@ -5448,7 +5768,7 @@ var inputSchema20 = {
5448
5768
  additionalProperties: false
5449
5769
  };
5450
5770
  var RETIRED_COUNTER_INPUT = "spawns_so_far";
5451
- var description20 = "Mode B auto-handoff (roadmap \xA73.4): spawn a DETACHED headless `claude -p` successor with a handoff doc pre-injected into its prompt. Defaults to the newest handoff in ~/.vo/handoffs/. Returns {spawned, pid, log_path, handoff_path}. The successor works under the same gates as any session (ADR-001: verify-before-act, human merge approval) \u2014 this tool never fires autonomously.";
5771
+ var description20 = "Mode B auto-handoff (roadmap \xA73.4): spawn a DETACHED headless `claude -p` successor with a handoff doc pre-injected into its prompt. Defaults to the newest handoff in ~/.vo/handoffs/. Verifies the child is actually alive (survives an early-exit window; an optional log-output window is off by default) before reporting success. Returns {spawned, pid, log_path, handoff_path}. The successor works under the same gates as any session (ADR-001: verify-before-act, human merge approval) \u2014 this tool never fires autonomously.";
5452
5772
  function isToolInput20(v) {
5453
5773
  if (typeof v !== "object" || v === null) return false;
5454
5774
  const o = v;
@@ -5466,7 +5786,7 @@ function retiredCounterRefusal(v) {
5466
5786
  }
5467
5787
  function newestHandoff(dir = join7(homedir5(), ".vo", "handoffs")) {
5468
5788
  try {
5469
- const entries = readdirSync3(dir).filter((f) => f.endsWith(".md")).map((f) => ({ f, m: statSync3(join7(dir, f)).mtimeMs })).sort((a, b) => b.m - a.m);
5789
+ const entries = readdirSync3(dir).filter((f) => f.endsWith(".md")).map((f) => ({ f, m: statSync4(join7(dir, f)).mtimeMs })).sort((a, b) => b.m - a.m);
5470
5790
  return entries.length > 0 && entries[0] ? join7(dir, entries[0].f) : null;
5471
5791
  } catch {
5472
5792
  return null;
@@ -5504,97 +5824,14 @@ function buildSuccessorPrompt(handoffMarkdown, goal) {
5504
5824
  if (goal && goal.trim().length > 0) lines.push("", `OPERATOR GOAL OVERRIDE: ${goal.trim()}`);
5505
5825
  return lines.join("\n");
5506
5826
  }
5507
- function buildSuccessorArgs(maxTurns) {
5508
- const args = ["-p", "--permission-mode", "acceptEdits"];
5509
- if (Number.isInteger(maxTurns) && maxTurns > 0) {
5510
- args.push("--max-turns", String(maxTurns));
5511
- }
5512
- return args;
5513
- }
5514
- function resolveSpawnPlan(env, input, nowIso, platform = process.platform, claim = claimSpawnSlot) {
5515
- const rawBinding = env[SWARM_TIER_BINDING_ENV];
5516
- const hasBinding = typeof rawBinding === "string" && rawBinding.trim().length > 0;
5517
- if (!hasBinding) {
5518
- const explicit = input.agent?.trim();
5519
- if (explicit) {
5520
- const resolved2 = resolveSuccessorLaunch({ agent: explicit, maxTurns: input.max_turns, platform });
5521
- if (!resolved2.ok) return { ok: false, reason: resolved2.reason, tier: "unbound" };
5522
- return {
5523
- ok: true,
5524
- bin: resolved2.bin,
5525
- args: resolved2.args,
5526
- agent: resolved2.agent,
5527
- tier: "unbound",
5528
- bound: false,
5529
- env: {},
5530
- slot: null,
5531
- capUsd: null,
5532
- capRemainingUsd: null
5533
- };
5534
- }
5535
- return {
5536
- ok: true,
5537
- bin: "claude",
5538
- args: buildSuccessorArgs(input.max_turns),
5539
- agent: "claude",
5540
- tier: "unbound",
5541
- bound: false,
5542
- env: {},
5543
- slot: null,
5544
- capUsd: null,
5545
- capRemainingUsd: null
5546
- };
5547
- }
5548
- const binding = inheritSwarmTierBinding(env, nowIso);
5549
- const admission = admitSubagentSpawn(binding);
5550
- if (!admission.allowed) {
5551
- return { ok: false, reason: admission.reason, tier: binding.tier };
5552
- }
5553
- const agentRefusal = agentBindingRefusal(binding, input.agent);
5554
- if (agentRefusal !== null) return { ok: false, reason: agentRefusal, tier: binding.tier };
5555
- const resolved = resolveSuccessorLaunch({
5556
- agent: binding.agent,
5557
- maxTurns: input.max_turns,
5558
- platform
5559
- });
5560
- if (!resolved.ok) return { ok: false, reason: resolved.reason, tier: binding.tier };
5561
- const slot = claim({
5562
- swarmId: binding.swarm_id,
5563
- proposedCeiling: binding.subagent_budget,
5564
- // The spend-cap POOL, recorded once per swarm exactly like the ceiling. The
5565
- // child's cap is DEBITED from it below, not recomputed from this binding.
5566
- proposedCapUsd: binding.spend_cap_usd,
5567
- dir: resolveLedgerDir(env),
5568
- nowIso
5569
- });
5570
- if (!slot.ok) return { ok: false, reason: slot.reason, tier: binding.tier };
5571
- return {
5572
- ok: true,
5573
- bin: resolved.bin,
5574
- args: resolved.args,
5575
- agent: resolved.agent,
5576
- tier: binding.tier,
5577
- bound: true,
5578
- // Re-export the same TIER with a DECREMENTED budget and the spend cap the
5579
- // ledger just DEBITED. Exporting the binding verbatim (what this did before
5580
- // #9312) meant the child re-read the full budget and every generation
5581
- // restarted at zero. Recomputing the cap from THIS binding (what #9312 did)
5582
- // bounded a chain but not a tree: three siblings each re-halved the parent's
5583
- // untouched $50 and walked away with $75 between them.
5584
- env: childBindingEnvFragment(binding, slot.capUsd),
5585
- slot: slot.slot,
5586
- capUsd: slot.capUsd,
5587
- capRemainingUsd: slot.capRemainingUsd
5588
- };
5589
- }
5590
- async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn) {
5827
+ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn, overrides = {}) {
5591
5828
  const retired = retiredCounterRefusal(rawInput);
5592
5829
  if (retired !== null) throw invalidParams(TOOL_NAME20, retired);
5593
5830
  if (!isToolInput20(rawInput)) {
5594
5831
  throw invalidParams(TOOL_NAME20, "invalid input. Optional: { handoff_path, goal, cwd, max_turns, agent }.");
5595
5832
  }
5596
5833
  const handoffPath = rawInput.handoff_path?.trim() || newestHandoff();
5597
- if (!handoffPath || !existsSync4(handoffPath)) {
5834
+ if (!handoffPath || !existsSync5(handoffPath)) {
5598
5835
  return jsonContent({
5599
5836
  tool: TOOL_NAME20,
5600
5837
  schema_version: 1,
@@ -5619,27 +5856,52 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
5619
5856
  }
5620
5857
  });
5621
5858
  }
5859
+ const platform = overrides.platform ?? process.platform;
5860
+ let resolvedBin = plan.bin;
5861
+ if (platform === "win32") {
5862
+ const resolution = resolveNativeWindowsExecutable(plan.bin, process.env, overrides.resolveWindowsExecutable);
5863
+ if (!resolution.ok) {
5864
+ return jsonContent({
5865
+ tool: TOOL_NAME20,
5866
+ schema_version: 1,
5867
+ payload: {
5868
+ spawned: false,
5869
+ reason: resolution.reason,
5870
+ agent: plan.agent,
5871
+ tier: plan.tier,
5872
+ handoff_path: handoffPath
5873
+ }
5874
+ });
5875
+ }
5876
+ resolvedBin = resolution.bin;
5877
+ }
5622
5878
  const logDir = process.env["VO_MCP_SUCCESSOR_LOG_DIR"]?.trim() || join7(homedir5(), ".vo", "successors");
5623
5879
  mkdirSync4(logDir, { recursive: true });
5624
5880
  const logPath = join7(logDir, `successor-${Date.now()}.log`);
5625
5881
  const logFd = openSync2(logPath, "a");
5626
- const child = spawnImpl(plan.bin, [...plan.args], {
5882
+ const child = spawnImpl(resolvedBin, [...plan.args], {
5627
5883
  cwd: rawInput.cwd?.trim() || process.cwd(),
5628
5884
  detached: true,
5629
5885
  stdio: ["pipe", logFd, logFd],
5630
- // Windows: the agent CLIs are .cmd shims they need a shell to resolve.
5631
- // The prompt goes via STDIN below, never argv, so the shell never sees it.
5632
- shell: process.platform === "win32",
5886
+ // Never a shell: `resolvedBin` is either the bare platform-neutral name
5887
+ // (POSIX, resolved by the OS via PATH + shebang) or the native win32 exe
5888
+ // resolved above — routing either through cmd.exe/sh is the extra layer
5889
+ // a detached, unref'd child can lose silently (2026-08-16 incident).
5890
+ shell: false,
5633
5891
  windowsHide: true,
5892
+ windowsVerbatimArguments: false,
5634
5893
  // Carry the SAME binding to the child. Without this the successor inherits
5635
5894
  // no tier and re-resolves its own — which is the split-payer defect one
5636
5895
  // generation down.
5637
5896
  ...plan.bound ? { env: { ...process.env, ...plan.env } } : {}
5638
5897
  });
5898
+ closeSync2(logFd);
5639
5899
  let spawnError = null;
5640
5900
  child.on("error", (e) => {
5641
5901
  spawnError = e.message;
5642
5902
  });
5903
+ child.stdin.on?.("error", () => {
5904
+ });
5643
5905
  try {
5644
5906
  child.stdin.write(prompt);
5645
5907
  child.stdin.end();
@@ -5647,10 +5909,40 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
5647
5909
  }
5648
5910
  child.unref();
5649
5911
  await new Promise((r) => setTimeout(r, 150));
5912
+ if (spawnError) {
5913
+ return jsonContent({
5914
+ tool: TOOL_NAME20,
5915
+ schema_version: 1,
5916
+ payload: {
5917
+ spawned: false,
5918
+ reason: `spawn failed: ${spawnError}`,
5919
+ agent: plan.agent,
5920
+ tier: plan.tier,
5921
+ handoff_path: handoffPath
5922
+ }
5923
+ });
5924
+ }
5925
+ const checkLiveness = overrides.checkLiveness ?? ((c, p) => checkSuccessorLiveness(c, p, resolveLivenessConfigFromEnv(process.env, overrides.livenessConfig), overrides.livenessDeps));
5926
+ const liveness = await checkLiveness(child, logPath);
5927
+ if (!liveness.ok) {
5928
+ return jsonContent({
5929
+ tool: TOOL_NAME20,
5930
+ schema_version: 1,
5931
+ payload: {
5932
+ spawned: false,
5933
+ reason: liveness.reason,
5934
+ pid: child.pid ?? null,
5935
+ log_path: logPath,
5936
+ agent: plan.agent,
5937
+ tier: plan.tier,
5938
+ handoff_path: handoffPath
5939
+ }
5940
+ });
5941
+ }
5650
5942
  return jsonContent({
5651
5943
  tool: TOOL_NAME20,
5652
5944
  schema_version: 1,
5653
- payload: spawnError ? { spawned: false, reason: `spawn failed: ${spawnError}`, agent: plan.agent, tier: plan.tier, handoff_path: handoffPath } : {
5945
+ payload: {
5654
5946
  spawned: true,
5655
5947
  pid: child.pid ?? null,
5656
5948
  log_path: logPath,
@@ -5662,7 +5954,10 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
5662
5954
  // The debit, surfaced so an operator can reconcile a fan-out's spend
5663
5955
  // against the pool without reading the ledger directory by hand.
5664
5956
  ledger_cap_usd: plan.capUsd,
5665
- ledger_cap_remaining_usd: plan.capRemainingUsd
5957
+ ledger_cap_remaining_usd: plan.capRemainingUsd,
5958
+ // Additive (2026-08-17): true only once the child survived the
5959
+ // early-exit window (and the output window, when that gate is enabled).
5960
+ verified_alive: true
5666
5961
  }
5667
5962
  });
5668
5963
  }
@@ -5749,22 +6044,22 @@ async function handleConciergeDispatch(deps, rawInput, _signal) {
5749
6044
  }
5750
6045
 
5751
6046
  // src/tools/memory/sync-config.ts
5752
- import { existsSync as existsSync8 } from "node:fs";
6047
+ import { existsSync as existsSync9 } from "node:fs";
5753
6048
  import { homedir as homedir7 } from "node:os";
5754
6049
  import { join as join11 } from "node:path";
5755
6050
 
5756
6051
  // src/tools/memory/memory-sync-http.ts
5757
6052
  init_safe_memory_file();
5758
- import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync6, readdirSync as readdirSync4 } from "node:fs";
6053
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync6, readdirSync as readdirSync4 } from "node:fs";
5759
6054
 
5760
6055
  // src/tools/memory/sync-lock.ts
5761
- import { closeSync as closeSync2, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync9, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
6056
+ import { closeSync as closeSync3, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync9, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
5762
6057
  import { hostname } from "node:os";
5763
6058
  import { join as join8 } from "node:path";
5764
6059
  import { randomUUID as randomUUID2 } from "node:crypto";
5765
6060
 
5766
6061
  // src/tools/memory/sync-lock-liveness.ts
5767
- import { statSync as statSync4, readFileSync as readFileSync8 } from "node:fs";
6062
+ import { statSync as statSync5, readFileSync as readFileSync8 } from "node:fs";
5768
6063
  function defaultIsProcessAlive(pid) {
5769
6064
  try {
5770
6065
  process.kill(pid, 0);
@@ -5790,10 +6085,10 @@ function toPayload(parsed) {
5790
6085
  acquiredAtMs: typeof acquiredAtMs === "number" && Number.isFinite(acquiredAtMs) ? acquiredAtMs : Number.NaN
5791
6086
  };
5792
6087
  }
5793
- function readLockRecord(path3) {
6088
+ function readLockRecord(path4) {
5794
6089
  let raw;
5795
6090
  try {
5796
- raw = readFileSync8(path3, "utf8");
6091
+ raw = readFileSync8(path4, "utf8");
5797
6092
  } catch {
5798
6093
  return null;
5799
6094
  }
@@ -5803,7 +6098,7 @@ function readLockRecord(path3) {
5803
6098
  return { raw, payload: null };
5804
6099
  }
5805
6100
  }
5806
- function lockAgeMs(record, path3, nowMs) {
6101
+ function lockAgeMs(record, path4, nowMs) {
5807
6102
  let startedMs = Number.NaN;
5808
6103
  if (record.payload) {
5809
6104
  if (Number.isFinite(record.payload.acquiredAtMs)) {
@@ -5814,7 +6109,7 @@ function lockAgeMs(record, path3, nowMs) {
5814
6109
  }
5815
6110
  if (!Number.isFinite(startedMs)) {
5816
6111
  try {
5817
- startedMs = statSync4(path3).mtimeMs;
6112
+ startedMs = statSync5(path4).mtimeMs;
5818
6113
  } catch {
5819
6114
  return null;
5820
6115
  }
@@ -5847,10 +6142,10 @@ var BACKOFF_FACTOR = 1.6;
5847
6142
  function positiveOr(value, fallback) {
5848
6143
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
5849
6144
  }
5850
- function createExclusive2(path3, contents) {
6145
+ function createExclusive2(path4, contents) {
5851
6146
  let fd;
5852
6147
  try {
5853
- fd = openSync3(path3, "wx");
6148
+ fd = openSync3(path4, "wx");
5854
6149
  } catch (err) {
5855
6150
  const code = err.code;
5856
6151
  return { ok: false, exists: code === "EEXIST", message: err instanceof Error ? err.message : String(err) };
@@ -5858,37 +6153,37 @@ function createExclusive2(path3, contents) {
5858
6153
  try {
5859
6154
  writeFileSync4(fd, contents, "utf8");
5860
6155
  } catch (err) {
5861
- closeSync2(fd);
6156
+ closeSync3(fd);
5862
6157
  try {
5863
- unlinkSync2(path3);
6158
+ unlinkSync2(path4);
5864
6159
  } catch {
5865
6160
  }
5866
6161
  return { ok: false, exists: false, message: err instanceof Error ? err.message : String(err) };
5867
6162
  }
5868
- closeSync2(fd);
6163
+ closeSync3(fd);
5869
6164
  return { ok: true };
5870
6165
  }
5871
- function removeAbandoned(path3, expectedRaw) {
6166
+ function removeAbandoned(path4, expectedRaw) {
5872
6167
  let current;
5873
6168
  try {
5874
- current = readFileSync9(path3, "utf8");
6169
+ current = readFileSync9(path4, "utf8");
5875
6170
  } catch {
5876
6171
  return;
5877
6172
  }
5878
6173
  if (current !== expectedRaw) return;
5879
6174
  try {
5880
- unlinkSync2(path3);
6175
+ unlinkSync2(path4);
5881
6176
  } catch {
5882
6177
  }
5883
6178
  }
5884
- function makeRelease(path3, token) {
6179
+ function makeRelease(path4, token) {
5885
6180
  let released = false;
5886
6181
  return () => {
5887
6182
  if (released) return;
5888
6183
  released = true;
5889
6184
  let raw;
5890
6185
  try {
5891
- raw = readFileSync9(path3, "utf8");
6186
+ raw = readFileSync9(path4, "utf8");
5892
6187
  } catch {
5893
6188
  return;
5894
6189
  }
@@ -5900,7 +6195,7 @@ function makeRelease(path3, token) {
5900
6195
  }
5901
6196
  if (!stillOurs) return;
5902
6197
  try {
5903
- unlinkSync2(path3);
6198
+ unlinkSync2(path4);
5904
6199
  } catch {
5905
6200
  }
5906
6201
  };
@@ -5919,7 +6214,7 @@ async function acquireMemorySyncLock(options) {
5919
6214
  }));
5920
6215
  const isProcessAlive = options.isProcessAlive ?? defaultIsProcessAlive;
5921
6216
  const thisHost = hostname();
5922
- const path3 = join8(options.memoryDir, MEMORY_SYNC_LOCK_FILE);
6217
+ const path4 = join8(options.memoryDir, MEMORY_SYNC_LOCK_FILE);
5923
6218
  if (options.createDir === true) mkdirSync5(options.memoryDir, { recursive: true });
5924
6219
  const deadline = now() + waitMs;
5925
6220
  let backoffMs = INITIAL_BACKOFF_MS;
@@ -5935,30 +6230,30 @@ async function acquireMemorySyncLock(options) {
5935
6230
  acquiredAt: new Date(acquiredAtMs).toISOString(),
5936
6231
  acquiredAtMs
5937
6232
  };
5938
- const created = createExclusive2(path3, `${JSON.stringify(payload, null, 2)}
6233
+ const created = createExclusive2(path4, `${JSON.stringify(payload, null, 2)}
5939
6234
  `);
5940
6235
  if (created.ok) {
5941
- return { path: path3, payload, tookOverFrom, release: makeRelease(path3, payload.token) };
6236
+ return { path: path4, payload, tookOverFrom, release: makeRelease(path4, payload.token) };
5942
6237
  }
5943
6238
  if (!created.exists) {
5944
6239
  throw new Error(
5945
- `memory sync lock ${path3} could not be created (${created.message}) \u2014 refusing to sync without exclusion`
6240
+ `memory sync lock ${path4} could not be created (${created.message}) \u2014 refusing to sync without exclusion`
5946
6241
  );
5947
6242
  }
5948
- const record = readLockRecord(path3);
6243
+ const record = readLockRecord(path4);
5949
6244
  let reclaimed = false;
5950
6245
  if (record) {
5951
6246
  holderDescription = describeHolder(record);
5952
- const age = lockAgeMs(record, path3, now());
6247
+ const age = lockAgeMs(record, path4, now());
5953
6248
  if (isLockAbandoned(record, age, ttlMs, isProcessAlive, thisHost)) {
5954
6249
  tookOverFrom = record.payload;
5955
- removeAbandoned(path3, record.raw);
6250
+ removeAbandoned(path4, record.raw);
5956
6251
  reclaimed = true;
5957
6252
  }
5958
6253
  }
5959
6254
  if (now() >= deadline) {
5960
6255
  throw new Error(
5961
- `memory sync lock ${path3} is held by ${holderDescription}; waited ${waitMs}ms \u2014 refusing to sync unlocked (concurrent memory writes corrupt the shared index). If that holder is provably gone, delete the lock file.`
6256
+ `memory sync lock ${path4} is held by ${holderDescription}; waited ${waitMs}ms \u2014 refusing to sync unlocked (concurrent memory writes corrupt the shared index). If that holder is provably gone, delete the lock file.`
5962
6257
  );
5963
6258
  }
5964
6259
  if (reclaimed) backoffMs = INITIAL_BACKOFF_MS;
@@ -6110,7 +6405,7 @@ async function uploadOne(item, controlPlaneUrl, token, sessionId, fetchFn, deadl
6110
6405
  }
6111
6406
  async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn, options = {}) {
6112
6407
  const empty = { pushed: 0, created: 0, updated: 0, skipped: 0, indexRowsPreserved: 0 };
6113
- if (!existsSync5(memoryDir)) {
6408
+ if (!existsSync6(memoryDir)) {
6114
6409
  return empty;
6115
6410
  }
6116
6411
  const localFiles = listPushableFiles(memoryDir).map((f) => ({
@@ -6198,7 +6493,7 @@ init_bounded_sync();
6198
6493
  init_memory_push_cache();
6199
6494
 
6200
6495
  // src/tools/memory/sync-kill-switch.ts
6201
- import { existsSync as existsSync6, readFileSync as readFileSync12 } from "node:fs";
6496
+ import { existsSync as existsSync7, readFileSync as readFileSync12 } from "node:fs";
6202
6497
  import { homedir as homedir6 } from "node:os";
6203
6498
  import { join as join10 } from "node:path";
6204
6499
  var MEMORY_SYNC_DISABLE_ENV = "VO_MCP_DISABLE_MEMORY_SYNC";
@@ -6221,7 +6516,7 @@ function clip(raw) {
6221
6516
  function evaluateMemorySyncKillSwitch(deps = {}) {
6222
6517
  const env = deps.env ?? process.env;
6223
6518
  const home = deps.home ?? homedir6();
6224
- const fileExists = deps.fileExists ?? existsSync6;
6519
+ const fileExists = deps.fileExists ?? existsSync7;
6225
6520
  const readFile3 = deps.readFile ?? ((p) => readFileSync12(p, "utf8"));
6226
6521
  const fired = [];
6227
6522
  const rawEnv = env[MEMORY_SYNC_DISABLE_ENV];
@@ -6305,7 +6600,7 @@ async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch,
6305
6600
  }
6306
6601
  const memoryDir = getMemoryDir(cwd);
6307
6602
  const baseUrl = controlPlaneUrl.replace(/\/+$/, "");
6308
- if (action === "push" && !existsSync8(memoryDir)) {
6603
+ if (action === "push" && !existsSync9(memoryDir)) {
6309
6604
  return {
6310
6605
  synced: true,
6311
6606
  action: "push",
@@ -6486,10 +6781,10 @@ async function getCloudAuth(fetchFn) {
6486
6781
  if (!token) return { ok: false, reason: "Failed to obtain auth token. Run `vo-mcp login` again." };
6487
6782
  return { ok: true, controlPlaneUrl, token };
6488
6783
  }
6489
- async function callPrivateKnowledge(path3, body, fetchFn) {
6784
+ async function callPrivateKnowledge(path4, body, fetchFn) {
6490
6785
  const auth = await getCloudAuth(fetchFn);
6491
6786
  if (!auth.ok) return { ok: false, reason: auth.reason };
6492
- const response = await fetchFn(`${auth.controlPlaneUrl}${path3}`, {
6787
+ const response = await fetchFn(`${auth.controlPlaneUrl}${path4}`, {
6493
6788
  method: "POST",
6494
6789
  headers: {
6495
6790
  authorization: `Bearer ${auth.token}`,
@@ -6706,11 +7001,11 @@ async function handleHqWhiteboardRead(_deps, rawInput, signal) {
6706
7001
  }
6707
7002
 
6708
7003
  // src/tools/skills/skill-corpus.ts
6709
- import { existsSync as existsSync9, statSync as statSync6 } from "node:fs";
7004
+ import { existsSync as existsSync10, statSync as statSync7 } from "node:fs";
6710
7005
  import { dirname as dirname5, isAbsolute, join as join13, resolve as resolve2 } from "node:path";
6711
7006
 
6712
7007
  // ../skill-registry/src/loader.ts
6713
- import { readdirSync as readdirSync6, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
7008
+ import { readdirSync as readdirSync6, readFileSync as readFileSync14, statSync as statSync6 } from "node:fs";
6714
7009
  import { join as join12 } from "node:path";
6715
7010
  var InvalidSkillFrontmatterError = class extends Error {
6716
7011
  constructor(skillFile, reason) {
@@ -6767,7 +7062,7 @@ function loadSkillsFromDir(skillsDir) {
6767
7062
  const entryPath = join12(skillsDir, entry);
6768
7063
  let stat;
6769
7064
  try {
6770
- stat = statSync5(entryPath);
7065
+ stat = statSync6(entryPath);
6771
7066
  } catch {
6772
7067
  continue;
6773
7068
  }
@@ -6816,12 +7111,12 @@ function resolveSkillsDir(env = process.env, startDir = process.cwd()) {
6816
7111
  const override = env.VO_SKILLS_DIR;
6817
7112
  if (typeof override === "string" && override.length > 0) {
6818
7113
  const abs = isAbsolute(override) ? override : resolve2(startDir, override);
6819
- return existsSync9(abs) && statSync6(abs).isDirectory() ? abs : null;
7114
+ return existsSync10(abs) && statSync7(abs).isDirectory() ? abs : null;
6820
7115
  }
6821
7116
  let dir = resolve2(startDir);
6822
7117
  for (let i = 0; i < MAX_WALK_UP_LEVELS; i += 1) {
6823
7118
  const candidate = join13(dir, ".claude", "skills");
6824
- if (existsSync9(candidate) && statSync6(candidate).isDirectory()) return candidate;
7119
+ if (existsSync10(candidate) && statSync7(candidate).isDirectory()) return candidate;
6825
7120
  const parent = dirname5(dir);
6826
7121
  if (parent === dir) break;
6827
7122
  dir = parent;