@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/README.md +2 -0
- package/dist/cli.js +595 -187
- package/dist/cli.js.map +4 -4
- package/dist/index.js +452 -157
- package/dist/index.js.map +4 -4
- package/dist/runner-cli.js +539 -98
- package/dist/runner-cli.js.map +4 -4
- package/dist/runner-supervisor.js +100 -32
- package/dist/runner-supervisor.js.map +4 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -203,24 +203,24 @@ function defaultOverridePath() {
|
|
|
203
203
|
return join2(homedir(), ".claude", "vo-arch-defaults.local.json");
|
|
204
204
|
}
|
|
205
205
|
function loadTenantOverride(opts = {}) {
|
|
206
|
-
const
|
|
207
|
-
if (!existsSync2(
|
|
206
|
+
const path4 = opts.path ?? defaultOverridePath();
|
|
207
|
+
if (!existsSync2(path4)) {
|
|
208
208
|
return { override: null, source_path: null };
|
|
209
209
|
}
|
|
210
|
-
const raw = readFileSync2(
|
|
210
|
+
const raw = readFileSync2(path4, "utf8");
|
|
211
211
|
let parsed;
|
|
212
212
|
try {
|
|
213
213
|
parsed = JSON.parse(raw);
|
|
214
214
|
} catch (err) {
|
|
215
215
|
const m = err instanceof Error ? err.message : String(err);
|
|
216
|
-
throw new Error(`vo-arch-defaults: invalid JSON in override ${
|
|
216
|
+
throw new Error(`vo-arch-defaults: invalid JSON in override ${path4}: ${m}`, { cause: err });
|
|
217
217
|
}
|
|
218
218
|
try {
|
|
219
219
|
const override = parseOverride(parsed);
|
|
220
|
-
return { override, source_path:
|
|
220
|
+
return { override, source_path: path4 };
|
|
221
221
|
} catch (err) {
|
|
222
222
|
const m = err instanceof Error ? err.message : String(err);
|
|
223
|
-
throw new Error(`vo-arch-defaults: override schema validation failed for ${
|
|
223
|
+
throw new Error(`vo-arch-defaults: override schema validation failed for ${path4}: ${m}`, { cause: err });
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
var init_load_override = __esm({
|
|
@@ -371,9 +371,9 @@ function globToRegExp(glob) {
|
|
|
371
371
|
}
|
|
372
372
|
return new RegExp("^" + out + "$");
|
|
373
373
|
}
|
|
374
|
-
function matchesAnyGlob(
|
|
374
|
+
function matchesAnyGlob(path4, globs) {
|
|
375
375
|
for (const g of globs) {
|
|
376
|
-
if (globToRegExp(g).test(
|
|
376
|
+
if (globToRegExp(g).test(path4)) return true;
|
|
377
377
|
}
|
|
378
378
|
return false;
|
|
379
379
|
}
|
|
@@ -1023,7 +1023,16 @@ function assertWithinByteCap(toolName, fieldName, value, maxBytes) {
|
|
|
1023
1023
|
);
|
|
1024
1024
|
}
|
|
1025
1025
|
}
|
|
1026
|
+
function subjectFromEnv(env = process.env) {
|
|
1027
|
+
const code_task_id = (env["VO_CODE_TASK_ID"] ?? "").trim().slice(0, 120);
|
|
1028
|
+
const repo = (env["VO_CODE_TASK_REPO"] ?? "").trim();
|
|
1029
|
+
const subject = {};
|
|
1030
|
+
if (code_task_id) subject.code_task_id = code_task_id;
|
|
1031
|
+
if (repo && REPO_RE.test(repo) && repo.length <= 200) subject.repo = repo;
|
|
1032
|
+
return subject.code_task_id || subject.repo ? subject : null;
|
|
1033
|
+
}
|
|
1026
1034
|
function buildBaseEvent(args) {
|
|
1035
|
+
const subject = args.subject === void 0 ? subjectFromEnv() : args.subject;
|
|
1027
1036
|
return {
|
|
1028
1037
|
schema_version: 1,
|
|
1029
1038
|
event_id: args.eventId ?? randomUUID(),
|
|
@@ -1049,7 +1058,12 @@ function buildBaseEvent(args) {
|
|
|
1049
1058
|
downstream_outcome: null,
|
|
1050
1059
|
vo_mcp_version: VO_MCP_VERSION,
|
|
1051
1060
|
consensus_engine_version: null,
|
|
1052
|
-
cache_hit: false
|
|
1061
|
+
cache_hit: false,
|
|
1062
|
+
// OMIT the key when there is no subject (rather than `subject: null`): the
|
|
1063
|
+
// ingest schema is `.strict()`, so an event with no subject stays valid on a
|
|
1064
|
+
// sink that has not learned the field yet — only subject-carrying events
|
|
1065
|
+
// depend on the sink being current (deploy ordering: sink before producer).
|
|
1066
|
+
...subject ? { subject } : {}
|
|
1053
1067
|
};
|
|
1054
1068
|
}
|
|
1055
1069
|
function jsonContent(value) {
|
|
@@ -1115,6 +1129,16 @@ function aggregateEventTokenUsage(src, engineUsage) {
|
|
|
1115
1129
|
total_cost_usd: anyCost ? costMicroUsd / 1e6 : null
|
|
1116
1130
|
};
|
|
1117
1131
|
}
|
|
1132
|
+
function trajectoryFromEngine(result, known = {}) {
|
|
1133
|
+
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;
|
|
1134
|
+
const called = result.fan_out_diagnostics?.models_called;
|
|
1135
|
+
const turns = typeof called === "number" && Number.isInteger(called) && called >= 0 ? called : rounds !== null && rounds > 0 ? result.per_model_verdicts.length * rounds : null;
|
|
1136
|
+
const sourceGrounded = result.citation_grade !== void 0 || result.low_confidence_sources !== void 0;
|
|
1137
|
+
const tool_calls = typeof known.tool_calls === "number" && Number.isInteger(known.tool_calls) && known.tool_calls >= 0 ? known.tool_calls : sourceGrounded ? null : 0;
|
|
1138
|
+
if (rounds === null && turns === null && tool_calls === null) return {};
|
|
1139
|
+
const trajectory = { rounds, turns, tool_calls };
|
|
1140
|
+
return { trajectory };
|
|
1141
|
+
}
|
|
1118
1142
|
function toEventSynthesizedVerdict(src) {
|
|
1119
1143
|
return {
|
|
1120
1144
|
verdict: src.verdict,
|
|
@@ -1122,12 +1146,13 @@ function toEventSynthesizedVerdict(src) {
|
|
|
1122
1146
|
reasoning_excerpt: sanitizeExcerpt(src.reasoning_excerpt)
|
|
1123
1147
|
};
|
|
1124
1148
|
}
|
|
1125
|
-
var VO_MCP_VERSION;
|
|
1149
|
+
var VO_MCP_VERSION, REPO_RE;
|
|
1126
1150
|
var init_common = __esm({
|
|
1127
1151
|
"src/tools/common.ts"() {
|
|
1128
1152
|
"use strict";
|
|
1129
1153
|
init_events_writer();
|
|
1130
1154
|
VO_MCP_VERSION = readVoMcpVersion();
|
|
1155
|
+
REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
|
|
1131
1156
|
}
|
|
1132
1157
|
});
|
|
1133
1158
|
|
|
@@ -1436,7 +1461,7 @@ var init_safe_memory_file = __esm({
|
|
|
1436
1461
|
});
|
|
1437
1462
|
|
|
1438
1463
|
// src/tools/memory/sync-lock-liveness.ts
|
|
1439
|
-
import { statSync as
|
|
1464
|
+
import { statSync as statSync5, readFileSync as readFileSync8 } from "node:fs";
|
|
1440
1465
|
function defaultIsProcessAlive(pid) {
|
|
1441
1466
|
try {
|
|
1442
1467
|
process.kill(pid, 0);
|
|
@@ -1462,10 +1487,10 @@ function toPayload(parsed) {
|
|
|
1462
1487
|
acquiredAtMs: typeof acquiredAtMs === "number" && Number.isFinite(acquiredAtMs) ? acquiredAtMs : Number.NaN
|
|
1463
1488
|
};
|
|
1464
1489
|
}
|
|
1465
|
-
function readLockRecord(
|
|
1490
|
+
function readLockRecord(path4) {
|
|
1466
1491
|
let raw;
|
|
1467
1492
|
try {
|
|
1468
|
-
raw = readFileSync8(
|
|
1493
|
+
raw = readFileSync8(path4, "utf8");
|
|
1469
1494
|
} catch {
|
|
1470
1495
|
return null;
|
|
1471
1496
|
}
|
|
@@ -1475,7 +1500,7 @@ function readLockRecord(path3) {
|
|
|
1475
1500
|
return { raw, payload: null };
|
|
1476
1501
|
}
|
|
1477
1502
|
}
|
|
1478
|
-
function lockAgeMs(record,
|
|
1503
|
+
function lockAgeMs(record, path4, nowMs) {
|
|
1479
1504
|
let startedMs = Number.NaN;
|
|
1480
1505
|
if (record.payload) {
|
|
1481
1506
|
if (Number.isFinite(record.payload.acquiredAtMs)) {
|
|
@@ -1486,7 +1511,7 @@ function lockAgeMs(record, path3, nowMs) {
|
|
|
1486
1511
|
}
|
|
1487
1512
|
if (!Number.isFinite(startedMs)) {
|
|
1488
1513
|
try {
|
|
1489
|
-
startedMs =
|
|
1514
|
+
startedMs = statSync5(path4).mtimeMs;
|
|
1490
1515
|
} catch {
|
|
1491
1516
|
return null;
|
|
1492
1517
|
}
|
|
@@ -1515,17 +1540,17 @@ var init_sync_lock_liveness = __esm({
|
|
|
1515
1540
|
});
|
|
1516
1541
|
|
|
1517
1542
|
// src/tools/memory/sync-lock.ts
|
|
1518
|
-
import { closeSync as
|
|
1543
|
+
import { closeSync as closeSync3, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync9, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1519
1544
|
import { hostname } from "node:os";
|
|
1520
1545
|
import { join as join8 } from "node:path";
|
|
1521
1546
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1522
1547
|
function positiveOr(value, fallback) {
|
|
1523
1548
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
1524
1549
|
}
|
|
1525
|
-
function createExclusive2(
|
|
1550
|
+
function createExclusive2(path4, contents) {
|
|
1526
1551
|
let fd;
|
|
1527
1552
|
try {
|
|
1528
|
-
fd = openSync3(
|
|
1553
|
+
fd = openSync3(path4, "wx");
|
|
1529
1554
|
} catch (err) {
|
|
1530
1555
|
const code = err.code;
|
|
1531
1556
|
return { ok: false, exists: code === "EEXIST", message: err instanceof Error ? err.message : String(err) };
|
|
@@ -1533,37 +1558,37 @@ function createExclusive2(path3, contents) {
|
|
|
1533
1558
|
try {
|
|
1534
1559
|
writeFileSync4(fd, contents, "utf8");
|
|
1535
1560
|
} catch (err) {
|
|
1536
|
-
|
|
1561
|
+
closeSync3(fd);
|
|
1537
1562
|
try {
|
|
1538
|
-
unlinkSync2(
|
|
1563
|
+
unlinkSync2(path4);
|
|
1539
1564
|
} catch {
|
|
1540
1565
|
}
|
|
1541
1566
|
return { ok: false, exists: false, message: err instanceof Error ? err.message : String(err) };
|
|
1542
1567
|
}
|
|
1543
|
-
|
|
1568
|
+
closeSync3(fd);
|
|
1544
1569
|
return { ok: true };
|
|
1545
1570
|
}
|
|
1546
|
-
function removeAbandoned(
|
|
1571
|
+
function removeAbandoned(path4, expectedRaw) {
|
|
1547
1572
|
let current;
|
|
1548
1573
|
try {
|
|
1549
|
-
current = readFileSync9(
|
|
1574
|
+
current = readFileSync9(path4, "utf8");
|
|
1550
1575
|
} catch {
|
|
1551
1576
|
return;
|
|
1552
1577
|
}
|
|
1553
1578
|
if (current !== expectedRaw) return;
|
|
1554
1579
|
try {
|
|
1555
|
-
unlinkSync2(
|
|
1580
|
+
unlinkSync2(path4);
|
|
1556
1581
|
} catch {
|
|
1557
1582
|
}
|
|
1558
1583
|
}
|
|
1559
|
-
function makeRelease(
|
|
1584
|
+
function makeRelease(path4, token) {
|
|
1560
1585
|
let released = false;
|
|
1561
1586
|
return () => {
|
|
1562
1587
|
if (released) return;
|
|
1563
1588
|
released = true;
|
|
1564
1589
|
let raw;
|
|
1565
1590
|
try {
|
|
1566
|
-
raw = readFileSync9(
|
|
1591
|
+
raw = readFileSync9(path4, "utf8");
|
|
1567
1592
|
} catch {
|
|
1568
1593
|
return;
|
|
1569
1594
|
}
|
|
@@ -1575,7 +1600,7 @@ function makeRelease(path3, token) {
|
|
|
1575
1600
|
}
|
|
1576
1601
|
if (!stillOurs) return;
|
|
1577
1602
|
try {
|
|
1578
|
-
unlinkSync2(
|
|
1603
|
+
unlinkSync2(path4);
|
|
1579
1604
|
} catch {
|
|
1580
1605
|
}
|
|
1581
1606
|
};
|
|
@@ -1594,7 +1619,7 @@ async function acquireMemorySyncLock(options) {
|
|
|
1594
1619
|
}));
|
|
1595
1620
|
const isProcessAlive = options.isProcessAlive ?? defaultIsProcessAlive;
|
|
1596
1621
|
const thisHost = hostname();
|
|
1597
|
-
const
|
|
1622
|
+
const path4 = join8(options.memoryDir, MEMORY_SYNC_LOCK_FILE);
|
|
1598
1623
|
if (options.createDir === true) mkdirSync5(options.memoryDir, { recursive: true });
|
|
1599
1624
|
const deadline = now() + waitMs;
|
|
1600
1625
|
let backoffMs = INITIAL_BACKOFF_MS;
|
|
@@ -1610,30 +1635,30 @@ async function acquireMemorySyncLock(options) {
|
|
|
1610
1635
|
acquiredAt: new Date(acquiredAtMs).toISOString(),
|
|
1611
1636
|
acquiredAtMs
|
|
1612
1637
|
};
|
|
1613
|
-
const created = createExclusive2(
|
|
1638
|
+
const created = createExclusive2(path4, `${JSON.stringify(payload, null, 2)}
|
|
1614
1639
|
`);
|
|
1615
1640
|
if (created.ok) {
|
|
1616
|
-
return { path:
|
|
1641
|
+
return { path: path4, payload, tookOverFrom, release: makeRelease(path4, payload.token) };
|
|
1617
1642
|
}
|
|
1618
1643
|
if (!created.exists) {
|
|
1619
1644
|
throw new Error(
|
|
1620
|
-
`memory sync lock ${
|
|
1645
|
+
`memory sync lock ${path4} could not be created (${created.message}) \u2014 refusing to sync without exclusion`
|
|
1621
1646
|
);
|
|
1622
1647
|
}
|
|
1623
|
-
const record = readLockRecord(
|
|
1648
|
+
const record = readLockRecord(path4);
|
|
1624
1649
|
let reclaimed = false;
|
|
1625
1650
|
if (record) {
|
|
1626
1651
|
holderDescription = describeHolder(record);
|
|
1627
|
-
const age = lockAgeMs(record,
|
|
1652
|
+
const age = lockAgeMs(record, path4, now());
|
|
1628
1653
|
if (isLockAbandoned(record, age, ttlMs, isProcessAlive, thisHost)) {
|
|
1629
1654
|
tookOverFrom = record.payload;
|
|
1630
|
-
removeAbandoned(
|
|
1655
|
+
removeAbandoned(path4, record.raw);
|
|
1631
1656
|
reclaimed = true;
|
|
1632
1657
|
}
|
|
1633
1658
|
}
|
|
1634
1659
|
if (now() >= deadline) {
|
|
1635
1660
|
throw new Error(
|
|
1636
|
-
`memory sync lock ${
|
|
1661
|
+
`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.`
|
|
1637
1662
|
);
|
|
1638
1663
|
}
|
|
1639
1664
|
if (reclaimed) backoffMs = INITIAL_BACKOFF_MS;
|
|
@@ -1897,7 +1922,7 @@ var init_memory_push_cache = __esm({
|
|
|
1897
1922
|
});
|
|
1898
1923
|
|
|
1899
1924
|
// src/tools/memory/memory-sync-http.ts
|
|
1900
|
-
import { existsSync as
|
|
1925
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
1901
1926
|
async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
|
|
1902
1927
|
const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
|
|
1903
1928
|
const response = await withRequestTimeout(
|
|
@@ -1982,7 +2007,7 @@ async function uploadOne(item, controlPlaneUrl, token, sessionId, fetchFn, deadl
|
|
|
1982
2007
|
}
|
|
1983
2008
|
async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn, options = {}) {
|
|
1984
2009
|
const empty = { pushed: 0, created: 0, updated: 0, skipped: 0, indexRowsPreserved: 0 };
|
|
1985
|
-
if (!
|
|
2010
|
+
if (!existsSync6(memoryDir)) {
|
|
1986
2011
|
return empty;
|
|
1987
2012
|
}
|
|
1988
2013
|
const localFiles = listPushableFiles(memoryDir).map((f) => ({
|
|
@@ -2076,7 +2101,7 @@ var init_memory_sync_http = __esm({
|
|
|
2076
2101
|
});
|
|
2077
2102
|
|
|
2078
2103
|
// src/tools/memory/sync-kill-switch.ts
|
|
2079
|
-
import { existsSync as
|
|
2104
|
+
import { existsSync as existsSync7, readFileSync as readFileSync12 } from "node:fs";
|
|
2080
2105
|
import { homedir as homedir6 } from "node:os";
|
|
2081
2106
|
import { join as join10 } from "node:path";
|
|
2082
2107
|
function memorySyncSentinelPath(home) {
|
|
@@ -2095,7 +2120,7 @@ function clip(raw) {
|
|
|
2095
2120
|
function evaluateMemorySyncKillSwitch(deps = {}) {
|
|
2096
2121
|
const env = deps.env ?? process.env;
|
|
2097
2122
|
const home = deps.home ?? homedir6();
|
|
2098
|
-
const fileExists = deps.fileExists ??
|
|
2123
|
+
const fileExists = deps.fileExists ?? existsSync7;
|
|
2099
2124
|
const readFile3 = deps.readFile ?? ((p) => readFileSync12(p, "utf8"));
|
|
2100
2125
|
const fired = [];
|
|
2101
2126
|
const rawEnv = env[MEMORY_SYNC_DISABLE_ENV];
|
|
@@ -2141,7 +2166,7 @@ __export(memory_knowledge_bridge_exports, {
|
|
|
2141
2166
|
extractMemoryTitle: () => extractMemoryTitle,
|
|
2142
2167
|
upsertMemoryFilesAsKnowledge: () => upsertMemoryFilesAsKnowledge
|
|
2143
2168
|
});
|
|
2144
|
-
import { existsSync as
|
|
2169
|
+
import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "node:fs";
|
|
2145
2170
|
function extractMemoryTitle(fileName, content) {
|
|
2146
2171
|
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
2147
2172
|
if (frontmatter) {
|
|
@@ -2156,7 +2181,7 @@ async function upsertMemoryFilesAsKnowledge(options) {
|
|
|
2156
2181
|
const { controlPlaneUrl, token, memoryDir, fetchFn, cache, deadline } = options;
|
|
2157
2182
|
let files;
|
|
2158
2183
|
try {
|
|
2159
|
-
if (!
|
|
2184
|
+
if (!existsSync8(memoryDir)) {
|
|
2160
2185
|
return { attempted: 0, upserted: 0, failed: 0, skipped: 0, failures: [] };
|
|
2161
2186
|
}
|
|
2162
2187
|
files = readdirSync5(memoryDir).filter(
|
|
@@ -2268,7 +2293,7 @@ __export(sync_config_exports, {
|
|
|
2268
2293
|
isNoopSyncReason: () => isNoopSyncReason,
|
|
2269
2294
|
runMemorySync: () => runMemorySync
|
|
2270
2295
|
});
|
|
2271
|
-
import { existsSync as
|
|
2296
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
2272
2297
|
import { homedir as homedir7 } from "node:os";
|
|
2273
2298
|
import { join as join11 } from "node:path";
|
|
2274
2299
|
function isToolInput22(v) {
|
|
@@ -2310,7 +2335,7 @@ async function runMemorySync(action, cwd, sessionId, fetchFn = globalThis.fetch,
|
|
|
2310
2335
|
}
|
|
2311
2336
|
const memoryDir = getMemoryDir(cwd);
|
|
2312
2337
|
const baseUrl = controlPlaneUrl.replace(/\/+$/, "");
|
|
2313
|
-
if (action === "push" && !
|
|
2338
|
+
if (action === "push" && !existsSync9(memoryDir)) {
|
|
2314
2339
|
return {
|
|
2315
2340
|
synced: true,
|
|
2316
2341
|
action: "push",
|
|
@@ -2431,9 +2456,9 @@ var init_sync_config = __esm({
|
|
|
2431
2456
|
});
|
|
2432
2457
|
|
|
2433
2458
|
// src/cli.ts
|
|
2434
|
-
import { homedir as
|
|
2459
|
+
import { homedir as homedir9, hostname as hostname2 } from "node:os";
|
|
2435
2460
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
2436
|
-
import { join as
|
|
2461
|
+
import { join as join15 } from "node:path";
|
|
2437
2462
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2438
2463
|
|
|
2439
2464
|
// src/server.ts
|
|
@@ -2815,7 +2840,8 @@ async function handleCheckHollowTest(deps, rawInput, signal) {
|
|
|
2815
2840
|
consensus_confidence: engineResult.synthesized_verdict.confidence,
|
|
2816
2841
|
duration_ms: engineResult.duration_ms,
|
|
2817
2842
|
consensus_engine_version: engineResult.engine_version,
|
|
2818
|
-
...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
|
|
2843
|
+
...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
|
|
2844
|
+
...trajectoryFromEngine(engineResult)
|
|
2819
2845
|
};
|
|
2820
2846
|
const payload = {
|
|
2821
2847
|
verdict: engineResult.synthesized_verdict.verdict,
|
|
@@ -2999,7 +3025,8 @@ async function handleVerifyAnswer(deps, rawInput, signal) {
|
|
|
2999
3025
|
consensus_confidence: engineResult.synthesized_verdict.confidence,
|
|
3000
3026
|
duration_ms: engineResult.duration_ms,
|
|
3001
3027
|
consensus_engine_version: engineResult.engine_version,
|
|
3002
|
-
...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
|
|
3028
|
+
...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
|
|
3029
|
+
...trajectoryFromEngine(engineResult)
|
|
3003
3030
|
};
|
|
3004
3031
|
const payload = {
|
|
3005
3032
|
verdict: engineResult.synthesized_verdict.verdict,
|
|
@@ -3268,7 +3295,8 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
|
|
|
3268
3295
|
consensus_engine_version: engineResult.engine_version,
|
|
3269
3296
|
per_model_verdicts: perModelForEvent,
|
|
3270
3297
|
synthesized_verdict: synthForEvent,
|
|
3271
|
-
...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
|
|
3298
|
+
...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
|
|
3299
|
+
...trajectoryFromEngine(engineResult)
|
|
3272
3300
|
};
|
|
3273
3301
|
const payload = {
|
|
3274
3302
|
verdict: engineResult.synthesized_verdict.verdict,
|
|
@@ -3483,7 +3511,8 @@ async function handleArchitectureReview(deps, rawInput, signal) {
|
|
|
3483
3511
|
consensus_confidence: engineResult.synthesized_verdict.confidence,
|
|
3484
3512
|
duration_ms: engineResult.duration_ms,
|
|
3485
3513
|
consensus_engine_version: engineResult.engine_version,
|
|
3486
|
-
...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
|
|
3514
|
+
...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
|
|
3515
|
+
...trajectoryFromEngine(engineResult)
|
|
3487
3516
|
};
|
|
3488
3517
|
const escalationRequired = engineResult.escalation_required === true || engineResult.escalation_required === void 0 && engineResult.synthesized_verdict.dissent_summary !== null;
|
|
3489
3518
|
const escalationReason = engineResult.escalation_reason ?? engineResult.synthesized_verdict.dissent_summary ?? "";
|
|
@@ -4971,7 +5000,8 @@ Produce the JSON dispatch plan now.`;
|
|
|
4971
5000
|
consensus_engine_version: engineResult.engine_version,
|
|
4972
5001
|
per_model_verdicts: toEventPerModelVerdicts(engineResult.per_model_verdicts),
|
|
4973
5002
|
synthesized_verdict: toEventSynthesizedVerdict(engineResult.synthesized_verdict),
|
|
4974
|
-
...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage)
|
|
5003
|
+
...aggregateEventTokenUsage(engineResult.per_model_verdicts, engineResult.token_usage),
|
|
5004
|
+
...trajectoryFromEngine(engineResult)
|
|
4975
5005
|
};
|
|
4976
5006
|
deps.events.append(enrichedEvent);
|
|
4977
5007
|
return jsonContent(envelope);
|
|
@@ -4984,10 +5014,10 @@ init_common();
|
|
|
4984
5014
|
init_auth_token_source();
|
|
4985
5015
|
init_credential_store();
|
|
4986
5016
|
var AdminCallableError = class extends Error {
|
|
4987
|
-
constructor(status,
|
|
5017
|
+
constructor(status, path4, message) {
|
|
4988
5018
|
super(message);
|
|
4989
5019
|
this.status = status;
|
|
4990
|
-
this.path =
|
|
5020
|
+
this.path = path4;
|
|
4991
5021
|
this.name = "AdminCallableError";
|
|
4992
5022
|
}
|
|
4993
5023
|
status;
|
|
@@ -5014,21 +5044,21 @@ var HttpAdminCallableClient = class {
|
|
|
5014
5044
|
this.fetchFn = config.fetchFn ?? globalThis.fetch;
|
|
5015
5045
|
this.readOnly = config.readOnly ?? false;
|
|
5016
5046
|
}
|
|
5017
|
-
async invoke(
|
|
5018
|
-
if (!
|
|
5047
|
+
async invoke(path4, body, opts) {
|
|
5048
|
+
if (!path4.startsWith("/")) {
|
|
5019
5049
|
throw new Error(
|
|
5020
|
-
`HttpAdminCallableClient.invoke: path must start with '/', got '${
|
|
5050
|
+
`HttpAdminCallableClient.invoke: path must start with '/', got '${path4}'`
|
|
5021
5051
|
);
|
|
5022
5052
|
}
|
|
5023
5053
|
const token = await this.tokenSource.getToken();
|
|
5024
5054
|
if (!token) {
|
|
5025
5055
|
throw new AdminCallableError(
|
|
5026
5056
|
401,
|
|
5027
|
-
|
|
5028
|
-
`admin proxy ${
|
|
5057
|
+
path4,
|
|
5058
|
+
`admin proxy ${path4}: no auth token available (check VO_USER_REFRESH_TOKEN / VO_USER_ID_TOKEN / VO_CONTROL_PLANE_ADMIN_TOKEN)`
|
|
5029
5059
|
);
|
|
5030
5060
|
}
|
|
5031
|
-
const url = `${this.baseUrl}${
|
|
5061
|
+
const url = `${this.baseUrl}${path4}`;
|
|
5032
5062
|
const response = await this.fetchFn(url, {
|
|
5033
5063
|
method: "POST",
|
|
5034
5064
|
headers: {
|
|
@@ -5041,8 +5071,8 @@ var HttpAdminCallableClient = class {
|
|
|
5041
5071
|
if (response.status < 200 || response.status >= 300) {
|
|
5042
5072
|
throw new AdminCallableError(
|
|
5043
5073
|
response.status,
|
|
5044
|
-
|
|
5045
|
-
`admin proxy ${
|
|
5074
|
+
path4,
|
|
5075
|
+
`admin proxy ${path4} returned HTTP ${response.status}: ${text.slice(0, 200)}`
|
|
5046
5076
|
);
|
|
5047
5077
|
}
|
|
5048
5078
|
let parsed;
|
|
@@ -5051,23 +5081,23 @@ var HttpAdminCallableClient = class {
|
|
|
5051
5081
|
} catch {
|
|
5052
5082
|
throw new AdminCallableError(
|
|
5053
5083
|
response.status,
|
|
5054
|
-
|
|
5055
|
-
`admin proxy ${
|
|
5084
|
+
path4,
|
|
5085
|
+
`admin proxy ${path4} returned non-JSON body`
|
|
5056
5086
|
);
|
|
5057
5087
|
}
|
|
5058
5088
|
if (typeof parsed !== "object" || parsed === null) {
|
|
5059
5089
|
throw new AdminCallableError(
|
|
5060
5090
|
response.status,
|
|
5061
|
-
|
|
5062
|
-
`admin proxy ${
|
|
5091
|
+
path4,
|
|
5092
|
+
`admin proxy ${path4} response not an object`
|
|
5063
5093
|
);
|
|
5064
5094
|
}
|
|
5065
5095
|
const obj = parsed;
|
|
5066
5096
|
if (obj["ok"] !== true) {
|
|
5067
5097
|
throw new AdminCallableError(
|
|
5068
5098
|
response.status,
|
|
5069
|
-
|
|
5070
|
-
`admin proxy ${
|
|
5099
|
+
path4,
|
|
5100
|
+
`admin proxy ${path4} returned ok=false: ${JSON.stringify(obj).slice(0, 200)}`
|
|
5071
5101
|
);
|
|
5072
5102
|
}
|
|
5073
5103
|
if (opts?.rawEnvelope) {
|
|
@@ -5080,8 +5110,8 @@ var HttpAdminCallableClient = class {
|
|
|
5080
5110
|
if (!("result" in obj)) {
|
|
5081
5111
|
throw new AdminCallableError(
|
|
5082
5112
|
response.status,
|
|
5083
|
-
|
|
5084
|
-
`admin proxy ${
|
|
5113
|
+
path4,
|
|
5114
|
+
`admin proxy ${path4} response missing .result field`
|
|
5085
5115
|
);
|
|
5086
5116
|
}
|
|
5087
5117
|
return obj["result"];
|
|
@@ -5673,6 +5703,7 @@ async function handleRejectAndRetry(deps, rawInput, _signal) {
|
|
|
5673
5703
|
init_common();
|
|
5674
5704
|
var TOOL_NAME18 = "vo_review_merge";
|
|
5675
5705
|
var LIST_PATH = "/api/v1/admin/pr/list";
|
|
5706
|
+
var DEFAULT_REVIEW_REPO = "Algosuite-ai/Nexus";
|
|
5676
5707
|
var ENGINE_GATE = "final-deep-verify";
|
|
5677
5708
|
var EVENT_GATE = "merge-review";
|
|
5678
5709
|
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.)";
|
|
@@ -5737,6 +5768,11 @@ async function handleReviewMerge(deps, rawInput, signal) {
|
|
|
5737
5768
|
if (rawInput.notes !== void 0) normalizedInput.notes = rawInput.notes;
|
|
5738
5769
|
const inputJson = JSON.stringify(normalizedInput);
|
|
5739
5770
|
const key = deps.cache.keyFor(TOOL_NAME18, normalizedInput);
|
|
5771
|
+
const subject = {
|
|
5772
|
+
...subjectFromEnv() ?? {},
|
|
5773
|
+
repo: subjectFromEnv()?.repo ?? DEFAULT_REVIEW_REPO,
|
|
5774
|
+
pr_number: prNumber
|
|
5775
|
+
};
|
|
5740
5776
|
const baseEvent = buildBaseEvent({
|
|
5741
5777
|
tool: TOOL_NAME18,
|
|
5742
5778
|
gateType: EVENT_GATE,
|
|
@@ -5744,7 +5780,8 @@ async function handleReviewMerge(deps, rawInput, signal) {
|
|
|
5744
5780
|
inputExcerpt: inputJson.slice(0, 300),
|
|
5745
5781
|
inputSizeBytes: bytesOf(inputJson),
|
|
5746
5782
|
session: deps.session,
|
|
5747
|
-
now: deps.now()
|
|
5783
|
+
now: deps.now(),
|
|
5784
|
+
subject
|
|
5748
5785
|
});
|
|
5749
5786
|
const emit = (payload2, eventExtra) => {
|
|
5750
5787
|
deps.events.append(eventExtra ? { ...baseEvent, ...eventExtra } : baseEvent);
|
|
@@ -5846,7 +5883,8 @@ async function handleReviewMerge(deps, rawInput, signal) {
|
|
|
5846
5883
|
consensus_engine_version: result.engine_version,
|
|
5847
5884
|
per_model_verdicts: perModel,
|
|
5848
5885
|
synthesized_verdict: synth,
|
|
5849
|
-
...aggregateEventTokenUsage(result.per_model_verdicts, result.token_usage)
|
|
5886
|
+
...aggregateEventTokenUsage(result.per_model_verdicts, result.token_usage),
|
|
5887
|
+
...trajectoryFromEngine(result)
|
|
5850
5888
|
});
|
|
5851
5889
|
}
|
|
5852
5890
|
|
|
@@ -6107,7 +6145,7 @@ init_common();
|
|
|
6107
6145
|
import { spawn } from "node:child_process";
|
|
6108
6146
|
import { homedir as homedir5 } from "node:os";
|
|
6109
6147
|
import { join as join7 } from "node:path";
|
|
6110
|
-
import { existsSync as
|
|
6148
|
+
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";
|
|
6111
6149
|
|
|
6112
6150
|
// src/swarm/tier-binding.ts
|
|
6113
6151
|
var SWARM_TIERS = Object.freeze([
|
|
@@ -6313,10 +6351,10 @@ function sanitizeSwarmId(raw) {
|
|
|
6313
6351
|
return id;
|
|
6314
6352
|
}
|
|
6315
6353
|
var CEILING_FILE = "ceiling.json";
|
|
6316
|
-
function createExclusive(
|
|
6354
|
+
function createExclusive(path4, contents) {
|
|
6317
6355
|
let fd;
|
|
6318
6356
|
try {
|
|
6319
|
-
fd = openSync(
|
|
6357
|
+
fd = openSync(path4, "wx");
|
|
6320
6358
|
} catch {
|
|
6321
6359
|
return false;
|
|
6322
6360
|
}
|
|
@@ -6331,18 +6369,18 @@ function capToCents(cap) {
|
|
|
6331
6369
|
return isPositiveCap(cap) ? Math.round(cap * 100) : 0;
|
|
6332
6370
|
}
|
|
6333
6371
|
function readOrRecordLedgerHead(swarmDir, proposedCeiling, proposedCapCents, nowIso) {
|
|
6334
|
-
const
|
|
6372
|
+
const path4 = join6(swarmDir, CEILING_FILE);
|
|
6335
6373
|
const head = JSON.stringify({
|
|
6336
6374
|
ceiling: proposedCeiling,
|
|
6337
6375
|
cap_cents: proposedCapCents,
|
|
6338
6376
|
recorded_at: nowIso
|
|
6339
6377
|
});
|
|
6340
|
-
if (createExclusive(
|
|
6378
|
+
if (createExclusive(path4, head)) {
|
|
6341
6379
|
return { ceiling: proposedCeiling, capCents: proposedCapCents };
|
|
6342
6380
|
}
|
|
6343
6381
|
let parsed;
|
|
6344
6382
|
try {
|
|
6345
|
-
parsed = JSON.parse(readFileSync6(
|
|
6383
|
+
parsed = JSON.parse(readFileSync6(path4, "utf8"));
|
|
6346
6384
|
} catch {
|
|
6347
6385
|
return null;
|
|
6348
6386
|
}
|
|
@@ -6422,6 +6460,288 @@ var claimSpawnSlot = ({ swarmId, proposedCeiling, proposedCapUsd, dir, nowIso })
|
|
|
6422
6460
|
};
|
|
6423
6461
|
};
|
|
6424
6462
|
|
|
6463
|
+
// src/swarm/spawn-plan.ts
|
|
6464
|
+
function resolveSpawnPlan(env, input, nowIso, platform = process.platform, claim = claimSpawnSlot) {
|
|
6465
|
+
const rawBinding = env[SWARM_TIER_BINDING_ENV];
|
|
6466
|
+
const hasBinding = typeof rawBinding === "string" && rawBinding.trim().length > 0;
|
|
6467
|
+
if (!hasBinding) {
|
|
6468
|
+
const explicit = input.agent?.trim();
|
|
6469
|
+
const resolved2 = resolveSuccessorLaunch({ agent: explicit || "claude", maxTurns: input.max_turns, platform });
|
|
6470
|
+
if (!resolved2.ok) return { ok: false, reason: resolved2.reason, tier: "unbound" };
|
|
6471
|
+
return {
|
|
6472
|
+
ok: true,
|
|
6473
|
+
bin: resolved2.bin,
|
|
6474
|
+
args: resolved2.args,
|
|
6475
|
+
agent: resolved2.agent,
|
|
6476
|
+
tier: "unbound",
|
|
6477
|
+
bound: false,
|
|
6478
|
+
env: {},
|
|
6479
|
+
slot: null,
|
|
6480
|
+
capUsd: null,
|
|
6481
|
+
capRemainingUsd: null
|
|
6482
|
+
};
|
|
6483
|
+
}
|
|
6484
|
+
const binding = inheritSwarmTierBinding(env, nowIso);
|
|
6485
|
+
const admission = admitSubagentSpawn(binding);
|
|
6486
|
+
if (!admission.allowed) {
|
|
6487
|
+
return { ok: false, reason: admission.reason, tier: binding.tier };
|
|
6488
|
+
}
|
|
6489
|
+
const agentRefusal = agentBindingRefusal(binding, input.agent);
|
|
6490
|
+
if (agentRefusal !== null) return { ok: false, reason: agentRefusal, tier: binding.tier };
|
|
6491
|
+
const resolved = resolveSuccessorLaunch({
|
|
6492
|
+
agent: binding.agent,
|
|
6493
|
+
maxTurns: input.max_turns,
|
|
6494
|
+
platform
|
|
6495
|
+
});
|
|
6496
|
+
if (!resolved.ok) return { ok: false, reason: resolved.reason, tier: binding.tier };
|
|
6497
|
+
const slot = claim({
|
|
6498
|
+
swarmId: binding.swarm_id,
|
|
6499
|
+
proposedCeiling: binding.subagent_budget,
|
|
6500
|
+
// The spend-cap POOL, recorded once per swarm exactly like the ceiling. The
|
|
6501
|
+
// child's cap is DEBITED from it below, not recomputed from this binding.
|
|
6502
|
+
proposedCapUsd: binding.spend_cap_usd,
|
|
6503
|
+
dir: resolveLedgerDir(env),
|
|
6504
|
+
nowIso
|
|
6505
|
+
});
|
|
6506
|
+
if (!slot.ok) return { ok: false, reason: slot.reason, tier: binding.tier };
|
|
6507
|
+
return {
|
|
6508
|
+
ok: true,
|
|
6509
|
+
bin: resolved.bin,
|
|
6510
|
+
args: resolved.args,
|
|
6511
|
+
agent: resolved.agent,
|
|
6512
|
+
tier: binding.tier,
|
|
6513
|
+
bound: true,
|
|
6514
|
+
// Re-export the same TIER with a DECREMENTED budget and the spend cap the
|
|
6515
|
+
// ledger just DEBITED. Exporting the binding verbatim (what this did before
|
|
6516
|
+
// #9312) meant the child re-read the full budget and every generation
|
|
6517
|
+
// restarted at zero. Recomputing the cap from THIS binding (what #9312 did)
|
|
6518
|
+
// bounded a chain but not a tree: three siblings each re-halved the parent's
|
|
6519
|
+
// untouched $50 and walked away with $75 between them.
|
|
6520
|
+
env: childBindingEnvFragment(binding, slot.capUsd),
|
|
6521
|
+
slot: slot.slot,
|
|
6522
|
+
capUsd: slot.capUsd,
|
|
6523
|
+
capRemainingUsd: slot.capRemainingUsd
|
|
6524
|
+
};
|
|
6525
|
+
}
|
|
6526
|
+
|
|
6527
|
+
// ../../scripts/virtual-office/code-runner/windows-claude-launch.mjs
|
|
6528
|
+
import { existsSync as existsSync4, realpathSync } from "node:fs";
|
|
6529
|
+
import { win32 as path3 } from "node:path";
|
|
6530
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
6531
|
+
var NATIVE_CLAUDE_PARTS = [
|
|
6532
|
+
"node_modules",
|
|
6533
|
+
"@anthropic-ai",
|
|
6534
|
+
"claude-code",
|
|
6535
|
+
"bin",
|
|
6536
|
+
"claude.exe"
|
|
6537
|
+
];
|
|
6538
|
+
function pathValue(env) {
|
|
6539
|
+
for (const key of ["Path", "PATH", "path"]) {
|
|
6540
|
+
if (typeof env?.[key] === "string") return env[key];
|
|
6541
|
+
}
|
|
6542
|
+
return "";
|
|
6543
|
+
}
|
|
6544
|
+
function cleanPathSegment(value) {
|
|
6545
|
+
const trimmed = String(value || "").trim();
|
|
6546
|
+
return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
|
|
6547
|
+
}
|
|
6548
|
+
function envValue(env, name) {
|
|
6549
|
+
const exact = env?.[name];
|
|
6550
|
+
if (typeof exact === "string") return exact.trim();
|
|
6551
|
+
const key = Object.keys(env || {}).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
6552
|
+
return typeof env?.[key] === "string" ? env[key].trim() : "";
|
|
6553
|
+
}
|
|
6554
|
+
function userClaudeCandidates(bin, env) {
|
|
6555
|
+
if (!/^claude(?:\.(?:exe|cmd|ps1))?$/iu.test(bin)) return [];
|
|
6556
|
+
const userProfile = envValue(env, "USERPROFILE");
|
|
6557
|
+
const appData = envValue(env, "APPDATA") || (userProfile ? path3.join(userProfile, "AppData", "Roaming") : "");
|
|
6558
|
+
const localAppData = envValue(env, "LOCALAPPDATA") || (userProfile ? path3.join(userProfile, "AppData", "Local") : "");
|
|
6559
|
+
const candidates = [];
|
|
6560
|
+
if (appData) {
|
|
6561
|
+
const npmBin = path3.join(appData, "npm");
|
|
6562
|
+
candidates.push(
|
|
6563
|
+
path3.join(npmBin, "claude.exe"),
|
|
6564
|
+
path3.join(npmBin, "claude.cmd"),
|
|
6565
|
+
path3.join(npmBin, "claude.ps1"),
|
|
6566
|
+
path3.join(npmBin, "claude"),
|
|
6567
|
+
path3.join(npmBin, ...NATIVE_CLAUDE_PARTS)
|
|
6568
|
+
);
|
|
6569
|
+
}
|
|
6570
|
+
if (userProfile) candidates.push(path3.join(userProfile, ".local", "bin", "claude.exe"));
|
|
6571
|
+
if (localAppData) {
|
|
6572
|
+
candidates.push(
|
|
6573
|
+
path3.join(localAppData, "Microsoft", "WinGet", "Links", "claude.exe"),
|
|
6574
|
+
path3.join(localAppData, "Microsoft", "WindowsApps", "claude.exe")
|
|
6575
|
+
);
|
|
6576
|
+
}
|
|
6577
|
+
return candidates;
|
|
6578
|
+
}
|
|
6579
|
+
function pathCandidates(bin, env) {
|
|
6580
|
+
if (path3.isAbsolute(bin) || /[\\/]/u.test(bin)) {
|
|
6581
|
+
return [path3.resolve(bin)];
|
|
6582
|
+
}
|
|
6583
|
+
const extension = path3.extname(bin);
|
|
6584
|
+
const fromPath = pathValue(env).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path3.join(directory, bin)] : [
|
|
6585
|
+
path3.join(directory, `${bin}.exe`),
|
|
6586
|
+
path3.join(directory, `${bin}.cmd`),
|
|
6587
|
+
path3.join(directory, `${bin}.ps1`),
|
|
6588
|
+
path3.join(directory, bin)
|
|
6589
|
+
]);
|
|
6590
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6591
|
+
return [...fromPath, ...userClaudeCandidates(bin, env)].filter((candidate) => {
|
|
6592
|
+
const key = candidate.toLowerCase();
|
|
6593
|
+
if (seen.has(key)) return false;
|
|
6594
|
+
seen.add(key);
|
|
6595
|
+
return true;
|
|
6596
|
+
});
|
|
6597
|
+
}
|
|
6598
|
+
function canonicalExistingPath(candidate, exists, canonicalize2) {
|
|
6599
|
+
if (!exists(candidate)) return null;
|
|
6600
|
+
try {
|
|
6601
|
+
return canonicalize2(candidate);
|
|
6602
|
+
} catch {
|
|
6603
|
+
return null;
|
|
6604
|
+
}
|
|
6605
|
+
}
|
|
6606
|
+
function resolveWindowsClaudeExecutable({
|
|
6607
|
+
bin = "claude",
|
|
6608
|
+
env = process.env,
|
|
6609
|
+
exists = existsSync4,
|
|
6610
|
+
canonicalize: canonicalize2 = realpathSync
|
|
6611
|
+
} = {}) {
|
|
6612
|
+
const requested = String(bin || "").trim();
|
|
6613
|
+
if (!requested || requested.includes("\0")) {
|
|
6614
|
+
throw new TypeError("Claude executable must be a non-empty path without NUL bytes");
|
|
6615
|
+
}
|
|
6616
|
+
for (const candidate of pathCandidates(requested, env)) {
|
|
6617
|
+
const found = canonicalExistingPath(candidate, exists, canonicalize2);
|
|
6618
|
+
if (!found) continue;
|
|
6619
|
+
if (path3.extname(found).toLowerCase() === ".exe") return found;
|
|
6620
|
+
const native = path3.join(path3.dirname(found), ...NATIVE_CLAUDE_PARTS);
|
|
6621
|
+
const resolvedNative = canonicalExistingPath(native, exists, canonicalize2);
|
|
6622
|
+
if (resolvedNative) return resolvedNative;
|
|
6623
|
+
}
|
|
6624
|
+
const error = new Error(
|
|
6625
|
+
`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.`
|
|
6626
|
+
);
|
|
6627
|
+
error.code = "ENOENT";
|
|
6628
|
+
throw error;
|
|
6629
|
+
}
|
|
6630
|
+
|
|
6631
|
+
// src/swarm/successor-windows-exe.ts
|
|
6632
|
+
var resolveWindowsClaudeExe = (bin, env) => resolveWindowsClaudeExecutable({ bin, env });
|
|
6633
|
+
function resolveNativeWindowsExecutable(bin, env = process.env, resolve3 = resolveWindowsClaudeExe) {
|
|
6634
|
+
try {
|
|
6635
|
+
return { ok: true, bin: resolve3(bin, env) };
|
|
6636
|
+
} catch (error) {
|
|
6637
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6638
|
+
return {
|
|
6639
|
+
ok: false,
|
|
6640
|
+
reason: `could not resolve a native Windows executable for '${bin}': ${message}`
|
|
6641
|
+
};
|
|
6642
|
+
}
|
|
6643
|
+
}
|
|
6644
|
+
|
|
6645
|
+
// src/swarm/successor-liveness.ts
|
|
6646
|
+
import { statSync as statSync3 } from "node:fs";
|
|
6647
|
+
var DEFAULT_EARLY_EXIT_SEC = 10;
|
|
6648
|
+
var DEFAULT_NO_OUTPUT_SEC = 0;
|
|
6649
|
+
var DEFAULT_POLL_MS = 200;
|
|
6650
|
+
var KILL_ESCALATION_MS = 2e3;
|
|
6651
|
+
function positiveSeconds(raw, fallback) {
|
|
6652
|
+
if (raw === void 0) return fallback;
|
|
6653
|
+
const n = Number(raw.trim());
|
|
6654
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
6655
|
+
}
|
|
6656
|
+
function resolveLivenessConfigFromEnv(env = process.env, explicitOverride = {}) {
|
|
6657
|
+
const earlyExitMs = explicitOverride.earlyExitMs ?? positiveSeconds(env["VO_MCP_SUCCESSOR_EXIT_CHECK_SEC"], DEFAULT_EARLY_EXIT_SEC) * 1e3;
|
|
6658
|
+
const noOutputMs = explicitOverride.noOutputMs ?? positiveSeconds(env["VO_MCP_SUCCESSOR_OUTPUT_CHECK_SEC"], DEFAULT_NO_OUTPUT_SEC) * 1e3;
|
|
6659
|
+
return { earlyExitMs, noOutputMs };
|
|
6660
|
+
}
|
|
6661
|
+
function defaultStatLogBytes(path4) {
|
|
6662
|
+
try {
|
|
6663
|
+
return statSync3(path4).size;
|
|
6664
|
+
} catch {
|
|
6665
|
+
return 0;
|
|
6666
|
+
}
|
|
6667
|
+
}
|
|
6668
|
+
function defaultKillChild(child) {
|
|
6669
|
+
try {
|
|
6670
|
+
child.kill("SIGTERM");
|
|
6671
|
+
} catch {
|
|
6672
|
+
}
|
|
6673
|
+
const escalation = setTimeout(() => {
|
|
6674
|
+
try {
|
|
6675
|
+
child.kill("SIGKILL");
|
|
6676
|
+
} catch {
|
|
6677
|
+
}
|
|
6678
|
+
}, KILL_ESCALATION_MS);
|
|
6679
|
+
escalation.unref();
|
|
6680
|
+
}
|
|
6681
|
+
function checkSuccessorLiveness(child, logPath, config, deps = {}) {
|
|
6682
|
+
const statLogBytes = deps.statLogBytes ?? defaultStatLogBytes;
|
|
6683
|
+
const now = deps.now ?? Date.now;
|
|
6684
|
+
const pollIntervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_MS;
|
|
6685
|
+
const killChild = deps.killChild ?? defaultKillChild;
|
|
6686
|
+
const outputGateEnabled = Number.isFinite(config.noOutputMs) && config.noOutputMs > 0;
|
|
6687
|
+
const startedAt = now();
|
|
6688
|
+
return new Promise((resolve3) => {
|
|
6689
|
+
let settled = false;
|
|
6690
|
+
let pollTimer = null;
|
|
6691
|
+
const onExit = (code, signal) => {
|
|
6692
|
+
const elapsedMs = now() - startedAt;
|
|
6693
|
+
finish({
|
|
6694
|
+
ok: false,
|
|
6695
|
+
reason: `child_exited_early: exit code ${code ?? "null"} signal ${signal ?? "none"} after ${elapsedMs}ms`,
|
|
6696
|
+
detail: { exitCode: code, signal, elapsedMs }
|
|
6697
|
+
});
|
|
6698
|
+
};
|
|
6699
|
+
const cleanup = () => {
|
|
6700
|
+
if (pollTimer !== null) clearInterval(pollTimer);
|
|
6701
|
+
child.off?.("exit", onExit);
|
|
6702
|
+
};
|
|
6703
|
+
const finish = (result) => {
|
|
6704
|
+
if (settled) return;
|
|
6705
|
+
settled = true;
|
|
6706
|
+
cleanup();
|
|
6707
|
+
if (!result.ok) {
|
|
6708
|
+
try {
|
|
6709
|
+
killChild(child);
|
|
6710
|
+
} catch {
|
|
6711
|
+
}
|
|
6712
|
+
}
|
|
6713
|
+
resolve3(result);
|
|
6714
|
+
};
|
|
6715
|
+
if (typeof child.exitCode === "number" || typeof child.signalCode === "string" && child.signalCode.length > 0) {
|
|
6716
|
+
finish({
|
|
6717
|
+
ok: false,
|
|
6718
|
+
reason: `child_exited_early: exit code ${child.exitCode ?? "null"} signal ${child.signalCode ?? "none"} before the liveness watch attached`,
|
|
6719
|
+
detail: { exitCode: child.exitCode ?? null, signal: child.signalCode ?? null, elapsedMs: 0 }
|
|
6720
|
+
});
|
|
6721
|
+
return;
|
|
6722
|
+
}
|
|
6723
|
+
child.on("exit", onExit);
|
|
6724
|
+
const tick = () => {
|
|
6725
|
+
if (settled) return;
|
|
6726
|
+
const elapsedMs = now() - startedAt;
|
|
6727
|
+
const outputSeen = outputGateEnabled ? statLogBytes(logPath) > 0 : true;
|
|
6728
|
+
if (outputSeen && elapsedMs >= config.earlyExitMs) {
|
|
6729
|
+
finish({ ok: true });
|
|
6730
|
+
return;
|
|
6731
|
+
}
|
|
6732
|
+
if (outputGateEnabled && !outputSeen && elapsedMs >= config.noOutputMs) {
|
|
6733
|
+
finish({
|
|
6734
|
+
ok: false,
|
|
6735
|
+
reason: `no_output: log stayed empty for ${elapsedMs}ms (limit ${config.noOutputMs}ms)`,
|
|
6736
|
+
detail: { elapsedMs, logPath }
|
|
6737
|
+
});
|
|
6738
|
+
}
|
|
6739
|
+
};
|
|
6740
|
+
pollTimer = setInterval(tick, pollIntervalMs);
|
|
6741
|
+
tick();
|
|
6742
|
+
});
|
|
6743
|
+
}
|
|
6744
|
+
|
|
6425
6745
|
// src/tools/session/spawn-successor.ts
|
|
6426
6746
|
var TOOL_NAME20 = "vo_spawn_successor";
|
|
6427
6747
|
var MAX_HANDOFF_BYTES = 64e3;
|
|
@@ -6453,7 +6773,7 @@ var inputSchema20 = {
|
|
|
6453
6773
|
additionalProperties: false
|
|
6454
6774
|
};
|
|
6455
6775
|
var RETIRED_COUNTER_INPUT = "spawns_so_far";
|
|
6456
|
-
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.";
|
|
6776
|
+
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.";
|
|
6457
6777
|
function isToolInput20(v) {
|
|
6458
6778
|
if (typeof v !== "object" || v === null) return false;
|
|
6459
6779
|
const o = v;
|
|
@@ -6471,7 +6791,7 @@ function retiredCounterRefusal(v) {
|
|
|
6471
6791
|
}
|
|
6472
6792
|
function newestHandoff(dir = join7(homedir5(), ".vo", "handoffs")) {
|
|
6473
6793
|
try {
|
|
6474
|
-
const entries = readdirSync3(dir).filter((f) => f.endsWith(".md")).map((f) => ({ f, m:
|
|
6794
|
+
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);
|
|
6475
6795
|
return entries.length > 0 && entries[0] ? join7(dir, entries[0].f) : null;
|
|
6476
6796
|
} catch {
|
|
6477
6797
|
return null;
|
|
@@ -6509,97 +6829,14 @@ function buildSuccessorPrompt(handoffMarkdown, goal) {
|
|
|
6509
6829
|
if (goal && goal.trim().length > 0) lines.push("", `OPERATOR GOAL OVERRIDE: ${goal.trim()}`);
|
|
6510
6830
|
return lines.join("\n");
|
|
6511
6831
|
}
|
|
6512
|
-
function
|
|
6513
|
-
const args = ["-p", "--permission-mode", "acceptEdits"];
|
|
6514
|
-
if (Number.isInteger(maxTurns) && maxTurns > 0) {
|
|
6515
|
-
args.push("--max-turns", String(maxTurns));
|
|
6516
|
-
}
|
|
6517
|
-
return args;
|
|
6518
|
-
}
|
|
6519
|
-
function resolveSpawnPlan(env, input, nowIso, platform = process.platform, claim = claimSpawnSlot) {
|
|
6520
|
-
const rawBinding = env[SWARM_TIER_BINDING_ENV];
|
|
6521
|
-
const hasBinding = typeof rawBinding === "string" && rawBinding.trim().length > 0;
|
|
6522
|
-
if (!hasBinding) {
|
|
6523
|
-
const explicit = input.agent?.trim();
|
|
6524
|
-
if (explicit) {
|
|
6525
|
-
const resolved2 = resolveSuccessorLaunch({ agent: explicit, maxTurns: input.max_turns, platform });
|
|
6526
|
-
if (!resolved2.ok) return { ok: false, reason: resolved2.reason, tier: "unbound" };
|
|
6527
|
-
return {
|
|
6528
|
-
ok: true,
|
|
6529
|
-
bin: resolved2.bin,
|
|
6530
|
-
args: resolved2.args,
|
|
6531
|
-
agent: resolved2.agent,
|
|
6532
|
-
tier: "unbound",
|
|
6533
|
-
bound: false,
|
|
6534
|
-
env: {},
|
|
6535
|
-
slot: null,
|
|
6536
|
-
capUsd: null,
|
|
6537
|
-
capRemainingUsd: null
|
|
6538
|
-
};
|
|
6539
|
-
}
|
|
6540
|
-
return {
|
|
6541
|
-
ok: true,
|
|
6542
|
-
bin: "claude",
|
|
6543
|
-
args: buildSuccessorArgs(input.max_turns),
|
|
6544
|
-
agent: "claude",
|
|
6545
|
-
tier: "unbound",
|
|
6546
|
-
bound: false,
|
|
6547
|
-
env: {},
|
|
6548
|
-
slot: null,
|
|
6549
|
-
capUsd: null,
|
|
6550
|
-
capRemainingUsd: null
|
|
6551
|
-
};
|
|
6552
|
-
}
|
|
6553
|
-
const binding = inheritSwarmTierBinding(env, nowIso);
|
|
6554
|
-
const admission = admitSubagentSpawn(binding);
|
|
6555
|
-
if (!admission.allowed) {
|
|
6556
|
-
return { ok: false, reason: admission.reason, tier: binding.tier };
|
|
6557
|
-
}
|
|
6558
|
-
const agentRefusal = agentBindingRefusal(binding, input.agent);
|
|
6559
|
-
if (agentRefusal !== null) return { ok: false, reason: agentRefusal, tier: binding.tier };
|
|
6560
|
-
const resolved = resolveSuccessorLaunch({
|
|
6561
|
-
agent: binding.agent,
|
|
6562
|
-
maxTurns: input.max_turns,
|
|
6563
|
-
platform
|
|
6564
|
-
});
|
|
6565
|
-
if (!resolved.ok) return { ok: false, reason: resolved.reason, tier: binding.tier };
|
|
6566
|
-
const slot = claim({
|
|
6567
|
-
swarmId: binding.swarm_id,
|
|
6568
|
-
proposedCeiling: binding.subagent_budget,
|
|
6569
|
-
// The spend-cap POOL, recorded once per swarm exactly like the ceiling. The
|
|
6570
|
-
// child's cap is DEBITED from it below, not recomputed from this binding.
|
|
6571
|
-
proposedCapUsd: binding.spend_cap_usd,
|
|
6572
|
-
dir: resolveLedgerDir(env),
|
|
6573
|
-
nowIso
|
|
6574
|
-
});
|
|
6575
|
-
if (!slot.ok) return { ok: false, reason: slot.reason, tier: binding.tier };
|
|
6576
|
-
return {
|
|
6577
|
-
ok: true,
|
|
6578
|
-
bin: resolved.bin,
|
|
6579
|
-
args: resolved.args,
|
|
6580
|
-
agent: resolved.agent,
|
|
6581
|
-
tier: binding.tier,
|
|
6582
|
-
bound: true,
|
|
6583
|
-
// Re-export the same TIER with a DECREMENTED budget and the spend cap the
|
|
6584
|
-
// ledger just DEBITED. Exporting the binding verbatim (what this did before
|
|
6585
|
-
// #9312) meant the child re-read the full budget and every generation
|
|
6586
|
-
// restarted at zero. Recomputing the cap from THIS binding (what #9312 did)
|
|
6587
|
-
// bounded a chain but not a tree: three siblings each re-halved the parent's
|
|
6588
|
-
// untouched $50 and walked away with $75 between them.
|
|
6589
|
-
env: childBindingEnvFragment(binding, slot.capUsd),
|
|
6590
|
-
slot: slot.slot,
|
|
6591
|
-
capUsd: slot.capUsd,
|
|
6592
|
-
capRemainingUsd: slot.capRemainingUsd
|
|
6593
|
-
};
|
|
6594
|
-
}
|
|
6595
|
-
async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn) {
|
|
6832
|
+
async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn, overrides = {}) {
|
|
6596
6833
|
const retired = retiredCounterRefusal(rawInput);
|
|
6597
6834
|
if (retired !== null) throw invalidParams(TOOL_NAME20, retired);
|
|
6598
6835
|
if (!isToolInput20(rawInput)) {
|
|
6599
6836
|
throw invalidParams(TOOL_NAME20, "invalid input. Optional: { handoff_path, goal, cwd, max_turns, agent }.");
|
|
6600
6837
|
}
|
|
6601
6838
|
const handoffPath = rawInput.handoff_path?.trim() || newestHandoff();
|
|
6602
|
-
if (!handoffPath || !
|
|
6839
|
+
if (!handoffPath || !existsSync5(handoffPath)) {
|
|
6603
6840
|
return jsonContent({
|
|
6604
6841
|
tool: TOOL_NAME20,
|
|
6605
6842
|
schema_version: 1,
|
|
@@ -6624,27 +6861,52 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
|
|
|
6624
6861
|
}
|
|
6625
6862
|
});
|
|
6626
6863
|
}
|
|
6864
|
+
const platform = overrides.platform ?? process.platform;
|
|
6865
|
+
let resolvedBin = plan.bin;
|
|
6866
|
+
if (platform === "win32") {
|
|
6867
|
+
const resolution = resolveNativeWindowsExecutable(plan.bin, process.env, overrides.resolveWindowsExecutable);
|
|
6868
|
+
if (!resolution.ok) {
|
|
6869
|
+
return jsonContent({
|
|
6870
|
+
tool: TOOL_NAME20,
|
|
6871
|
+
schema_version: 1,
|
|
6872
|
+
payload: {
|
|
6873
|
+
spawned: false,
|
|
6874
|
+
reason: resolution.reason,
|
|
6875
|
+
agent: plan.agent,
|
|
6876
|
+
tier: plan.tier,
|
|
6877
|
+
handoff_path: handoffPath
|
|
6878
|
+
}
|
|
6879
|
+
});
|
|
6880
|
+
}
|
|
6881
|
+
resolvedBin = resolution.bin;
|
|
6882
|
+
}
|
|
6627
6883
|
const logDir = process.env["VO_MCP_SUCCESSOR_LOG_DIR"]?.trim() || join7(homedir5(), ".vo", "successors");
|
|
6628
6884
|
mkdirSync4(logDir, { recursive: true });
|
|
6629
6885
|
const logPath = join7(logDir, `successor-${Date.now()}.log`);
|
|
6630
6886
|
const logFd = openSync2(logPath, "a");
|
|
6631
|
-
const child = spawnImpl(
|
|
6887
|
+
const child = spawnImpl(resolvedBin, [...plan.args], {
|
|
6632
6888
|
cwd: rawInput.cwd?.trim() || process.cwd(),
|
|
6633
6889
|
detached: true,
|
|
6634
6890
|
stdio: ["pipe", logFd, logFd],
|
|
6635
|
-
//
|
|
6636
|
-
//
|
|
6637
|
-
|
|
6891
|
+
// Never a shell: `resolvedBin` is either the bare platform-neutral name
|
|
6892
|
+
// (POSIX, resolved by the OS via PATH + shebang) or the native win32 exe
|
|
6893
|
+
// resolved above — routing either through cmd.exe/sh is the extra layer
|
|
6894
|
+
// a detached, unref'd child can lose silently (2026-08-16 incident).
|
|
6895
|
+
shell: false,
|
|
6638
6896
|
windowsHide: true,
|
|
6897
|
+
windowsVerbatimArguments: false,
|
|
6639
6898
|
// Carry the SAME binding to the child. Without this the successor inherits
|
|
6640
6899
|
// no tier and re-resolves its own — which is the split-payer defect one
|
|
6641
6900
|
// generation down.
|
|
6642
6901
|
...plan.bound ? { env: { ...process.env, ...plan.env } } : {}
|
|
6643
6902
|
});
|
|
6903
|
+
closeSync2(logFd);
|
|
6644
6904
|
let spawnError = null;
|
|
6645
6905
|
child.on("error", (e) => {
|
|
6646
6906
|
spawnError = e.message;
|
|
6647
6907
|
});
|
|
6908
|
+
child.stdin.on?.("error", () => {
|
|
6909
|
+
});
|
|
6648
6910
|
try {
|
|
6649
6911
|
child.stdin.write(prompt);
|
|
6650
6912
|
child.stdin.end();
|
|
@@ -6652,10 +6914,40 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
|
|
|
6652
6914
|
}
|
|
6653
6915
|
child.unref();
|
|
6654
6916
|
await new Promise((r) => setTimeout(r, 150));
|
|
6917
|
+
if (spawnError) {
|
|
6918
|
+
return jsonContent({
|
|
6919
|
+
tool: TOOL_NAME20,
|
|
6920
|
+
schema_version: 1,
|
|
6921
|
+
payload: {
|
|
6922
|
+
spawned: false,
|
|
6923
|
+
reason: `spawn failed: ${spawnError}`,
|
|
6924
|
+
agent: plan.agent,
|
|
6925
|
+
tier: plan.tier,
|
|
6926
|
+
handoff_path: handoffPath
|
|
6927
|
+
}
|
|
6928
|
+
});
|
|
6929
|
+
}
|
|
6930
|
+
const checkLiveness = overrides.checkLiveness ?? ((c, p) => checkSuccessorLiveness(c, p, resolveLivenessConfigFromEnv(process.env, overrides.livenessConfig), overrides.livenessDeps));
|
|
6931
|
+
const liveness = await checkLiveness(child, logPath);
|
|
6932
|
+
if (!liveness.ok) {
|
|
6933
|
+
return jsonContent({
|
|
6934
|
+
tool: TOOL_NAME20,
|
|
6935
|
+
schema_version: 1,
|
|
6936
|
+
payload: {
|
|
6937
|
+
spawned: false,
|
|
6938
|
+
reason: liveness.reason,
|
|
6939
|
+
pid: child.pid ?? null,
|
|
6940
|
+
log_path: logPath,
|
|
6941
|
+
agent: plan.agent,
|
|
6942
|
+
tier: plan.tier,
|
|
6943
|
+
handoff_path: handoffPath
|
|
6944
|
+
}
|
|
6945
|
+
});
|
|
6946
|
+
}
|
|
6655
6947
|
return jsonContent({
|
|
6656
6948
|
tool: TOOL_NAME20,
|
|
6657
6949
|
schema_version: 1,
|
|
6658
|
-
payload:
|
|
6950
|
+
payload: {
|
|
6659
6951
|
spawned: true,
|
|
6660
6952
|
pid: child.pid ?? null,
|
|
6661
6953
|
log_path: logPath,
|
|
@@ -6667,7 +6959,10 @@ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn)
|
|
|
6667
6959
|
// The debit, surfaced so an operator can reconcile a fan-out's spend
|
|
6668
6960
|
// against the pool without reading the ledger directory by hand.
|
|
6669
6961
|
ledger_cap_usd: plan.capUsd,
|
|
6670
|
-
ledger_cap_remaining_usd: plan.capRemainingUsd
|
|
6962
|
+
ledger_cap_remaining_usd: plan.capRemainingUsd,
|
|
6963
|
+
// Additive (2026-08-17): true only once the child survived the
|
|
6964
|
+
// early-exit window (and the output window, when that gate is enabled).
|
|
6965
|
+
verified_alive: true
|
|
6671
6966
|
}
|
|
6672
6967
|
});
|
|
6673
6968
|
}
|
|
@@ -6851,10 +7146,10 @@ async function getCloudAuth(fetchFn) {
|
|
|
6851
7146
|
if (!token) return { ok: false, reason: "Failed to obtain auth token. Run `vo-mcp login` again." };
|
|
6852
7147
|
return { ok: true, controlPlaneUrl, token };
|
|
6853
7148
|
}
|
|
6854
|
-
async function callPrivateKnowledge(
|
|
7149
|
+
async function callPrivateKnowledge(path4, body, fetchFn) {
|
|
6855
7150
|
const auth = await getCloudAuth(fetchFn);
|
|
6856
7151
|
if (!auth.ok) return { ok: false, reason: auth.reason };
|
|
6857
|
-
const response = await fetchFn(`${auth.controlPlaneUrl}${
|
|
7152
|
+
const response = await fetchFn(`${auth.controlPlaneUrl}${path4}`, {
|
|
6858
7153
|
method: "POST",
|
|
6859
7154
|
headers: {
|
|
6860
7155
|
authorization: `Bearer ${auth.token}`,
|
|
@@ -7072,11 +7367,11 @@ async function handleHqWhiteboardRead(_deps, rawInput, signal) {
|
|
|
7072
7367
|
}
|
|
7073
7368
|
|
|
7074
7369
|
// src/tools/skills/skill-corpus.ts
|
|
7075
|
-
import { existsSync as
|
|
7370
|
+
import { existsSync as existsSync10, statSync as statSync7 } from "node:fs";
|
|
7076
7371
|
import { dirname as dirname5, isAbsolute, join as join13, resolve as resolve2 } from "node:path";
|
|
7077
7372
|
|
|
7078
7373
|
// ../skill-registry/src/loader.ts
|
|
7079
|
-
import { readdirSync as readdirSync6, readFileSync as readFileSync14, statSync as
|
|
7374
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync14, statSync as statSync6 } from "node:fs";
|
|
7080
7375
|
import { join as join12 } from "node:path";
|
|
7081
7376
|
var InvalidSkillFrontmatterError = class extends Error {
|
|
7082
7377
|
constructor(skillFile, reason) {
|
|
@@ -7133,7 +7428,7 @@ function loadSkillsFromDir(skillsDir) {
|
|
|
7133
7428
|
const entryPath = join12(skillsDir, entry);
|
|
7134
7429
|
let stat;
|
|
7135
7430
|
try {
|
|
7136
|
-
stat =
|
|
7431
|
+
stat = statSync6(entryPath);
|
|
7137
7432
|
} catch {
|
|
7138
7433
|
continue;
|
|
7139
7434
|
}
|
|
@@ -7183,12 +7478,12 @@ function resolveSkillsDir(env = process.env, startDir = process.cwd()) {
|
|
|
7183
7478
|
const override = env.VO_SKILLS_DIR;
|
|
7184
7479
|
if (typeof override === "string" && override.length > 0) {
|
|
7185
7480
|
const abs = isAbsolute(override) ? override : resolve2(startDir, override);
|
|
7186
|
-
return
|
|
7481
|
+
return existsSync10(abs) && statSync7(abs).isDirectory() ? abs : null;
|
|
7187
7482
|
}
|
|
7188
7483
|
let dir = resolve2(startDir);
|
|
7189
7484
|
for (let i = 0; i < MAX_WALK_UP_LEVELS; i += 1) {
|
|
7190
7485
|
const candidate = join13(dir, ".claude", "skills");
|
|
7191
|
-
if (
|
|
7486
|
+
if (existsSync10(candidate) && statSync7(candidate).isDirectory()) return candidate;
|
|
7192
7487
|
const parent = dirname5(dir);
|
|
7193
7488
|
if (parent === dir) break;
|
|
7194
7489
|
dir = parent;
|
|
@@ -8396,6 +8691,109 @@ function createConsensusFallbackClient(primary, fallback, options = {}) {
|
|
|
8396
8691
|
};
|
|
8397
8692
|
}
|
|
8398
8693
|
|
|
8694
|
+
// src/consensus/shadow-client.ts
|
|
8695
|
+
import { appendFileSync as appendFileSync2, chmodSync as chmodSync4, mkdirSync as mkdirSync8, renameSync, statSync as statSync8 } from "node:fs";
|
|
8696
|
+
import { homedir as homedir8 } from "node:os";
|
|
8697
|
+
import { dirname as dirname7, join as join14 } from "node:path";
|
|
8698
|
+
var DEFAULT_SHADOW_TIMEOUT_MS = 2e4;
|
|
8699
|
+
var SHADOW_RECEIPT_MAX_BYTES = 20 * 1024 * 1024;
|
|
8700
|
+
function defaultShadowReceiptPath(env = process.env) {
|
|
8701
|
+
const p = (env["VO_MCP_MOAT_SHADOW_PATH"] ?? "").trim();
|
|
8702
|
+
return p || join14(homedir8(), ".claude", "vo-mcp-moat-shadow.jsonl");
|
|
8703
|
+
}
|
|
8704
|
+
function appendShadowReceipt(receipt, path4 = defaultShadowReceiptPath()) {
|
|
8705
|
+
try {
|
|
8706
|
+
mkdirSync8(dirname7(path4), { recursive: true, mode: 448 });
|
|
8707
|
+
try {
|
|
8708
|
+
if (statSync8(path4).size > SHADOW_RECEIPT_MAX_BYTES) renameSync(path4, `${path4}.1`);
|
|
8709
|
+
} catch {
|
|
8710
|
+
}
|
|
8711
|
+
appendFileSync2(path4, `${JSON.stringify(receipt)}
|
|
8712
|
+
`, "utf8");
|
|
8713
|
+
try {
|
|
8714
|
+
chmodSync4(path4, 384);
|
|
8715
|
+
} catch {
|
|
8716
|
+
}
|
|
8717
|
+
} catch {
|
|
8718
|
+
}
|
|
8719
|
+
}
|
|
8720
|
+
function summarize(result) {
|
|
8721
|
+
if (result.ok) {
|
|
8722
|
+
return {
|
|
8723
|
+
ok: true,
|
|
8724
|
+
verdict: result.synthesized_verdict.verdict,
|
|
8725
|
+
confidence: result.synthesized_verdict.confidence,
|
|
8726
|
+
...result.receipt_id ? { receipt_id: result.receipt_id } : {}
|
|
8727
|
+
};
|
|
8728
|
+
}
|
|
8729
|
+
return { ok: false, reason: result.reason };
|
|
8730
|
+
}
|
|
8731
|
+
async function runMoat(shadow, request, timeoutMs, linkCallerSignal) {
|
|
8732
|
+
const callerSignal = linkCallerSignal ? request.signal : void 0;
|
|
8733
|
+
if (callerSignal?.aborted) return { ok: false, reason: CANCELLED_REASON };
|
|
8734
|
+
const controller = new AbortController();
|
|
8735
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
8736
|
+
const onCallerAbort = () => controller.abort();
|
|
8737
|
+
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
8738
|
+
try {
|
|
8739
|
+
const { signal: _ignored, ...rest } = request;
|
|
8740
|
+
void _ignored;
|
|
8741
|
+
return await shadow.run({ ...rest, signal: controller.signal });
|
|
8742
|
+
} catch (err) {
|
|
8743
|
+
return { ok: false, reason: `moat-shadow-threw: ${err instanceof Error ? err.message : String(err)}`.slice(0, 200) };
|
|
8744
|
+
} finally {
|
|
8745
|
+
clearTimeout(timer);
|
|
8746
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
8747
|
+
}
|
|
8748
|
+
}
|
|
8749
|
+
function createConsensusShadowClient(primary, shadow, options = {}) {
|
|
8750
|
+
const authoritative = options.authoritative === true;
|
|
8751
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_SHADOW_TIMEOUT_MS;
|
|
8752
|
+
const now = options.now ?? (() => Date.now());
|
|
8753
|
+
const random = options.random ?? Math.random;
|
|
8754
|
+
const samplePct = Math.min(100, Math.max(0, options.samplePct ?? 100));
|
|
8755
|
+
const onReceipt = options.onReceipt ?? ((r) => appendShadowReceipt(r));
|
|
8756
|
+
function receiptFor(request, local, moat, startedAt) {
|
|
8757
|
+
const l = summarize(local);
|
|
8758
|
+
const m = summarize(moat);
|
|
8759
|
+
return {
|
|
8760
|
+
ts: new Date(now()).toISOString(),
|
|
8761
|
+
gate_type: request.gate_type,
|
|
8762
|
+
mode: authoritative ? "authoritative" : "shadow",
|
|
8763
|
+
local: l,
|
|
8764
|
+
moat: m,
|
|
8765
|
+
agree: l.ok && m.ok ? l.verdict === m.verdict : null,
|
|
8766
|
+
duration_ms: Math.max(0, now() - startedAt)
|
|
8767
|
+
};
|
|
8768
|
+
}
|
|
8769
|
+
return {
|
|
8770
|
+
async run(request) {
|
|
8771
|
+
const startedAt = now();
|
|
8772
|
+
const primaryResult = await primary.run(request);
|
|
8773
|
+
if (request.signal?.aborted || !primaryResult.ok && primaryResult.reason === CANCELLED_REASON) {
|
|
8774
|
+
return primaryResult;
|
|
8775
|
+
}
|
|
8776
|
+
if (!primaryResult.ok || primaryResult.receipt_id) return primaryResult;
|
|
8777
|
+
if (authoritative) {
|
|
8778
|
+
const moatResult = await runMoat(shadow, request, timeoutMs, true);
|
|
8779
|
+
try {
|
|
8780
|
+
onReceipt(receiptFor(request, primaryResult, moatResult, startedAt));
|
|
8781
|
+
} catch {
|
|
8782
|
+
}
|
|
8783
|
+
return moatResult.ok ? moatResult : primaryResult;
|
|
8784
|
+
}
|
|
8785
|
+
if (samplePct < 100 && random() * 100 >= samplePct) return primaryResult;
|
|
8786
|
+
void runMoat(shadow, request, timeoutMs, false).then((moatResult) => {
|
|
8787
|
+
try {
|
|
8788
|
+
onReceipt(receiptFor(request, primaryResult, moatResult, startedAt));
|
|
8789
|
+
} catch {
|
|
8790
|
+
}
|
|
8791
|
+
});
|
|
8792
|
+
return primaryResult;
|
|
8793
|
+
}
|
|
8794
|
+
};
|
|
8795
|
+
}
|
|
8796
|
+
|
|
8399
8797
|
// src/consensus/local-credential-env.ts
|
|
8400
8798
|
import { createRequire as createRequire2 } from "node:module";
|
|
8401
8799
|
var require2 = createRequire2(import.meta.url);
|
|
@@ -8466,11 +8864,11 @@ function processCapture(rawBody, expectedState, store) {
|
|
|
8466
8864
|
return { ok: false, httpStatus: 400, error: "login response missing refresh_token / api_key" };
|
|
8467
8865
|
}
|
|
8468
8866
|
const email = typeof data.email === "string" && data.email.trim() ? data.email.trim() : void 0;
|
|
8469
|
-
const
|
|
8867
|
+
const path4 = store({ refresh_token: refresh, api_key: apiKey, ...email ? { email } : {} });
|
|
8470
8868
|
return {
|
|
8471
8869
|
ok: true,
|
|
8472
8870
|
httpStatus: 200,
|
|
8473
|
-
result: { ...email ? { email } : {}, credentialPath:
|
|
8871
|
+
result: { ...email ? { email } : {}, credentialPath: path4 },
|
|
8474
8872
|
captured: { refresh_token: refresh, api_key: apiKey, ...email ? { email } : {} }
|
|
8475
8873
|
};
|
|
8476
8874
|
}
|
|
@@ -8558,8 +8956,8 @@ async function runLogin(opts = {}) {
|
|
|
8558
8956
|
}
|
|
8559
8957
|
} catch {
|
|
8560
8958
|
}
|
|
8561
|
-
const
|
|
8562
|
-
result = { ...capt.email ? { email: capt.email } : {}, credentialPath:
|
|
8959
|
+
const path4 = writeNow(cred);
|
|
8960
|
+
result = { ...capt.email ? { email: capt.email } : {}, credentialPath: path4 };
|
|
8563
8961
|
}
|
|
8564
8962
|
res.writeHead(outcome.httpStatus, { "content-type": "text/html; charset=utf-8" });
|
|
8565
8963
|
res.end(outcome.ok ? "<h2>AlgoHQ login complete \u2014 you can close this tab.</h2>" : `<h2>Login failed: ${outcome.error}</h2>`);
|
|
@@ -8629,7 +9027,7 @@ init_common();
|
|
|
8629
9027
|
function defaultCacheDbPath() {
|
|
8630
9028
|
const env = process.env["VO_MCP_DB_PATH"];
|
|
8631
9029
|
if (env && env.length > 0) return env;
|
|
8632
|
-
return
|
|
9030
|
+
return join15(homedir9(), ".claude", "vo-mcp-cache.db");
|
|
8633
9031
|
}
|
|
8634
9032
|
async function probeEngineVersion() {
|
|
8635
9033
|
try {
|
|
@@ -8717,6 +9115,16 @@ async function main() {
|
|
|
8717
9115
|
consensus = createConsensusFallbackClient(localConsensus, cloudConsensus, {
|
|
8718
9116
|
onFallback: (reason) => console.error(`[vo-mcp] local consensus unavailable (${reason}); using cloud moat fallback`)
|
|
8719
9117
|
});
|
|
9118
|
+
const moatAuthoritative = process.env["VO_CONSENSUS_MOAT_AUTHORITATIVE"] === "1";
|
|
9119
|
+
if (moatAuthoritative || process.env["VO_CONSENSUS_MOAT_SHADOW"] === "1") {
|
|
9120
|
+
const pctRaw = Number(process.env["VO_CONSENSUS_MOAT_SHADOW_PCT"] ?? "100");
|
|
9121
|
+
const samplePct = Number.isFinite(pctRaw) ? Math.min(100, Math.max(0, pctRaw)) : 100;
|
|
9122
|
+
const shadowedLocal = createConsensusShadowClient(localConsensus, cloudConsensus, { authoritative: moatAuthoritative, samplePct });
|
|
9123
|
+
consensus = createConsensusFallbackClient(shadowedLocal, cloudConsensus, {
|
|
9124
|
+
onFallback: (reason) => console.error(`[vo-mcp] local consensus unavailable (${reason}); using cloud moat fallback`)
|
|
9125
|
+
});
|
|
9126
|
+
console.error(`[vo-mcp] moat ${moatAuthoritative ? "AUTHORITATIVE" : `SHADOW (${samplePct}%)`} active \u2014 local verdicts also reach the moat verify path (receipts: ${defaultShadowReceiptPath()})`);
|
|
9127
|
+
}
|
|
8720
9128
|
} else if (!testClient && cloudConsensus) {
|
|
8721
9129
|
console.error("[vo-mcp] fewer than 2 linked local providers; cloud moat consensus active");
|
|
8722
9130
|
consensus = cloudConsensus;
|
|
@@ -8785,12 +9193,12 @@ if (process.argv[2] === "login") {
|
|
|
8785
9193
|
const sessionId = randomUUID6();
|
|
8786
9194
|
const appendSyncLog = async (line) => {
|
|
8787
9195
|
try {
|
|
8788
|
-
const { appendFileSync:
|
|
8789
|
-
const { join:
|
|
8790
|
-
const { homedir:
|
|
8791
|
-
const dir =
|
|
8792
|
-
|
|
8793
|
-
|
|
9196
|
+
const { appendFileSync: appendFileSync3, mkdirSync: mkdirSync9 } = await import("node:fs");
|
|
9197
|
+
const { join: join16 } = await import("node:path");
|
|
9198
|
+
const { homedir: homedir10 } = await import("node:os");
|
|
9199
|
+
const dir = join16(homedir10(), ".claude");
|
|
9200
|
+
mkdirSync9(dir, { recursive: true });
|
|
9201
|
+
appendFileSync3(join16(dir, "vo-mcp-sync.log"), `${line}
|
|
8794
9202
|
`, "utf8");
|
|
8795
9203
|
} catch {
|
|
8796
9204
|
}
|