@algosuite/vo-mcp 0.1.0 → 0.2.0-beta.1

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/cli.js CHANGED
@@ -1,9 +1,296 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __esm = (fn, res, err) => function __init() {
6
+ if (err) throw err[0];
7
+ try {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ } catch (e) {
10
+ throw err = [e], e;
11
+ }
12
+ };
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+
18
+ // src/cloud/auth-token-source.ts
19
+ var auth_token_source_exports = {};
20
+ __export(auth_token_source_exports, {
21
+ FIREBASE_SECURETOKEN_URL: () => FIREBASE_SECURETOKEN_URL,
22
+ FIREBASE_TOKEN_REFERER: () => FIREBASE_TOKEN_REFERER,
23
+ createAuthTokenSourceFromEnv: () => createAuthTokenSourceFromEnv,
24
+ createFirebaseRefreshTokenSource: () => createFirebaseRefreshTokenSource,
25
+ createStaticTokenSource: () => createStaticTokenSource
26
+ });
27
+ function createStaticTokenSource(token, kind = "admin-token") {
28
+ const value = token.trim();
29
+ return { kind, getToken: async () => value.length > 0 ? value : null };
30
+ }
31
+ function createFirebaseRefreshTokenSource(opts) {
32
+ const refreshToken = opts.refreshToken.trim();
33
+ const apiKey = opts.apiKey.trim();
34
+ const now = opts.now ?? (() => Date.now());
35
+ const fetchFn = opts.fetchFn ?? globalThis.fetch;
36
+ let cachedToken = null;
37
+ let expiresAtMs = 0;
38
+ let inFlight = null;
39
+ async function refresh() {
40
+ try {
41
+ const res = await fetchFn(`${FIREBASE_SECURETOKEN_URL}?key=${encodeURIComponent(apiKey)}`, {
42
+ method: "POST",
43
+ headers: {
44
+ "content-type": "application/x-www-form-urlencoded",
45
+ referer: FIREBASE_TOKEN_REFERER
46
+ },
47
+ body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`
48
+ });
49
+ const text = await res.text();
50
+ if (res.status < 200 || res.status >= 300) {
51
+ cachedToken = null;
52
+ return null;
53
+ }
54
+ const parsed = JSON.parse(text);
55
+ const idToken = typeof parsed.id_token === "string" ? parsed.id_token : "";
56
+ if (!idToken) {
57
+ cachedToken = null;
58
+ return null;
59
+ }
60
+ const expiresInSec = Number(parsed.expires_in);
61
+ const ttlMs = Number.isFinite(expiresInSec) && expiresInSec > 0 ? expiresInSec * 1e3 : 36e5;
62
+ cachedToken = idToken;
63
+ expiresAtMs = now() + ttlMs;
64
+ return idToken;
65
+ } catch {
66
+ cachedToken = null;
67
+ return null;
68
+ }
69
+ }
70
+ return {
71
+ kind: "firebase-refresh",
72
+ async getToken() {
73
+ if (cachedToken && now() < expiresAtMs - REFRESH_SKEW_MS) return cachedToken;
74
+ if (!inFlight) {
75
+ inFlight = refresh().finally(() => {
76
+ inFlight = null;
77
+ });
78
+ }
79
+ return inFlight;
80
+ }
81
+ };
82
+ }
83
+ function createAuthTokenSourceFromEnv(env = process.env, fetchFn, readStoredCred = () => null) {
84
+ const refreshToken = env["VO_USER_REFRESH_TOKEN"]?.trim();
85
+ const apiKey = env["VO_FIREBASE_API_KEY"]?.trim();
86
+ const idToken = env["VO_USER_ID_TOKEN"]?.trim();
87
+ const adminToken = env["VO_CONTROL_PLANE_ADMIN_TOKEN"]?.trim();
88
+ if (refreshToken || apiKey) {
89
+ if (!refreshToken || !apiKey) {
90
+ throw new Error(
91
+ "Per-user refresh auth requires BOTH VO_USER_REFRESH_TOKEN and VO_FIREBASE_API_KEY"
92
+ );
93
+ }
94
+ return createFirebaseRefreshTokenSource({
95
+ refreshToken,
96
+ apiKey,
97
+ ...fetchFn ? { fetchFn } : {}
98
+ });
99
+ }
100
+ if (idToken) return createStaticTokenSource(idToken, "firebase-id-token");
101
+ const stored = readStoredCred();
102
+ if (stored?.vo_credential && stored.vo_credential.trim()) {
103
+ return createStaticTokenSource(stored.vo_credential.trim(), "vo-credential");
104
+ }
105
+ if (stored && stored.refresh_token?.trim() && stored.api_key?.trim()) {
106
+ return createFirebaseRefreshTokenSource({
107
+ refreshToken: stored.refresh_token.trim(),
108
+ apiKey: stored.api_key.trim(),
109
+ ...fetchFn ? { fetchFn } : {}
110
+ });
111
+ }
112
+ if (adminToken) return createStaticTokenSource(adminToken, "admin-token");
113
+ return null;
114
+ }
115
+ var FIREBASE_SECURETOKEN_URL, FIREBASE_TOKEN_REFERER, REFRESH_SKEW_MS;
116
+ var init_auth_token_source = __esm({
117
+ "src/cloud/auth-token-source.ts"() {
118
+ "use strict";
119
+ FIREBASE_SECURETOKEN_URL = "https://securetoken.googleapis.com/v1/token";
120
+ FIREBASE_TOKEN_REFERER = "https://algosuite.ai/";
121
+ REFRESH_SKEW_MS = 6e4;
122
+ }
123
+ });
124
+
125
+ // src/cloud/keychain.ts
126
+ import { createRequire } from "node:module";
127
+ function loadKeyring() {
128
+ if (cached !== void 0) return cached;
129
+ try {
130
+ const req = createRequire(import.meta.url);
131
+ const mod = req("@napi-rs/keyring");
132
+ cached = mod && typeof mod.Entry === "function" ? mod : null;
133
+ } catch {
134
+ cached = null;
135
+ }
136
+ return cached;
137
+ }
138
+ function keychainAvailable() {
139
+ return loadKeyring() !== null;
140
+ }
141
+ function keychainGet() {
142
+ const k = loadKeyring();
143
+ if (!k) return null;
144
+ try {
145
+ return new k.Entry(SERVICE, ACCOUNT).getPassword();
146
+ } catch {
147
+ return null;
148
+ }
149
+ }
150
+ function keychainSet(secret) {
151
+ const k = loadKeyring();
152
+ if (!k) return false;
153
+ try {
154
+ new k.Entry(SERVICE, ACCOUNT).setPassword(secret);
155
+ return true;
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+ function keychainDelete() {
161
+ const k = loadKeyring();
162
+ if (!k) return false;
163
+ try {
164
+ return new k.Entry(SERVICE, ACCOUNT).deletePassword();
165
+ } catch {
166
+ return false;
167
+ }
168
+ }
169
+ var SERVICE, ACCOUNT, cached;
170
+ var init_keychain = __esm({
171
+ "src/cloud/keychain.ts"() {
172
+ "use strict";
173
+ SERVICE = "vo-mcp";
174
+ ACCOUNT = "refresh-credential";
175
+ }
176
+ });
177
+
178
+ // src/cloud/credential-store.ts
179
+ var credential_store_exports = {};
180
+ __export(credential_store_exports, {
181
+ KEYCHAIN_LOCATION: () => KEYCHAIN_LOCATION,
182
+ credentialPath: () => credentialPath,
183
+ readStoredCredential: () => readStoredCredential,
184
+ writeStoredCredential: () => writeStoredCredential
185
+ });
186
+ import { homedir as homedir3 } from "node:os";
187
+ import { join as join5, dirname as dirname4 } from "node:path";
188
+ import {
189
+ existsSync as existsSync3,
190
+ mkdirSync as mkdirSync2,
191
+ readFileSync as readFileSync5,
192
+ writeFileSync as writeFileSync2,
193
+ chmodSync as chmodSync2,
194
+ rmSync
195
+ } from "node:fs";
196
+ function credentialPath(env = process.env) {
197
+ const override = env["VO_MCP_CREDENTIALS_PATH"]?.trim();
198
+ if (override) return override;
199
+ return join5(homedir3(), ".config", "vo-mcp", "credentials.json");
200
+ }
201
+ function keychainEnabled(env, keychain) {
202
+ const disabled = (env["VO_MCP_DISABLE_KEYCHAIN"] ?? "").trim().toLowerCase();
203
+ if (disabled === "1" || disabled === "true" || disabled === "yes") return false;
204
+ return keychain.available();
205
+ }
206
+ function deserialize(raw) {
207
+ try {
208
+ const parsed = JSON.parse(raw);
209
+ const refresh = typeof parsed.refresh_token === "string" ? parsed.refresh_token.trim() : "";
210
+ const apiKey = typeof parsed.api_key === "string" ? parsed.api_key.trim() : "";
211
+ const voCred = typeof parsed.vo_credential === "string" ? parsed.vo_credential.trim() : "";
212
+ if (!voCred && (!refresh || !apiKey)) return null;
213
+ return {
214
+ ...refresh ? { refresh_token: refresh } : {},
215
+ ...apiKey ? { api_key: apiKey } : {},
216
+ ...voCred ? { vo_credential: voCred } : {},
217
+ ...typeof parsed.vo_credential_expires_at === "string" ? { vo_credential_expires_at: parsed.vo_credential_expires_at } : {},
218
+ ...typeof parsed.email === "string" ? { email: parsed.email } : {},
219
+ ...typeof parsed.stored_at === "string" ? { stored_at: parsed.stored_at } : {}
220
+ };
221
+ } catch {
222
+ return null;
223
+ }
224
+ }
225
+ function readFromFile(env) {
226
+ try {
227
+ const p = credentialPath(env);
228
+ if (!existsSync3(p)) return null;
229
+ return deserialize(readFileSync5(p, "utf8"));
230
+ } catch {
231
+ return null;
232
+ }
233
+ }
234
+ function readStoredCredential(env = process.env, keychain = realKeychain) {
235
+ if (keychainEnabled(env, keychain)) {
236
+ const raw = keychain.get();
237
+ const fromKeychain = raw ? deserialize(raw) : null;
238
+ if (fromKeychain) return fromKeychain;
239
+ }
240
+ return readFromFile(env);
241
+ }
242
+ function deleteFile(env) {
243
+ try {
244
+ rmSync(credentialPath(env), { force: true });
245
+ } catch {
246
+ }
247
+ }
248
+ function writeToFile(payload, env) {
249
+ const p = credentialPath(env);
250
+ mkdirSync2(dirname4(p), { recursive: true });
251
+ writeFileSync2(p, `${JSON.stringify(payload, null, 2)}
252
+ `, { mode: 384 });
253
+ try {
254
+ chmodSync2(p, 384);
255
+ } catch {
256
+ }
257
+ return p;
258
+ }
259
+ function writeStoredCredential(cred, storedAt, env = process.env, keychain = realKeychain) {
260
+ const payload = {
261
+ ...cred.refresh_token ? { refresh_token: cred.refresh_token } : {},
262
+ ...cred.api_key ? { api_key: cred.api_key } : {},
263
+ ...cred.vo_credential ? { vo_credential: cred.vo_credential } : {},
264
+ ...cred.vo_credential_expires_at ? { vo_credential_expires_at: cred.vo_credential_expires_at } : {},
265
+ ...cred.email ? { email: cred.email } : {},
266
+ stored_at: cred.stored_at ?? storedAt
267
+ };
268
+ if (keychainEnabled(env, keychain) && keychain.set(JSON.stringify(payload))) {
269
+ deleteFile(env);
270
+ return KEYCHAIN_LOCATION;
271
+ }
272
+ const p = writeToFile(payload, env);
273
+ if (keychainEnabled(env, keychain)) keychain.delete();
274
+ return p;
275
+ }
276
+ var realKeychain, KEYCHAIN_LOCATION;
277
+ var init_credential_store = __esm({
278
+ "src/cloud/credential-store.ts"() {
279
+ "use strict";
280
+ init_keychain();
281
+ realKeychain = {
282
+ available: keychainAvailable,
283
+ get: keychainGet,
284
+ set: keychainSet,
285
+ delete: keychainDelete
286
+ };
287
+ KEYCHAIN_LOCATION = 'OS keychain (service "vo-mcp")';
288
+ }
289
+ });
3
290
 
4
291
  // src/cli.ts
5
- import { homedir as homedir4, hostname } from "node:os";
6
- import { join as join6 } from "node:path";
292
+ import { homedir as homedir6, hostname } from "node:os";
293
+ import { join as join8 } from "node:path";
7
294
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
295
 
9
296
  // src/server.ts
@@ -1511,6 +1798,45 @@ var ALL_RECOGNIZED_GATE_TYPES = [
1511
1798
  ...KNOWN_GATE_TYPES
1512
1799
  ];
1513
1800
 
1801
+ // src/tools/consensus-judgment-source-grounded.ts
1802
+ function isStringArray(v) {
1803
+ return Array.isArray(v) && v.every((e) => typeof e === "string");
1804
+ }
1805
+ function isToolSourceGrounded(v) {
1806
+ if (typeof v !== "object" || v === null) return false;
1807
+ const o = v;
1808
+ if (o["enable_citation_grading"] !== void 0 && typeof o["enable_citation_grading"] !== "boolean") {
1809
+ return false;
1810
+ }
1811
+ if (o["escalate_below"] !== void 0 && typeof o["escalate_below"] !== "number") return false;
1812
+ return true;
1813
+ }
1814
+ function normalizeSourceGrounded(input) {
1815
+ const sourceUrls = input.source_urls !== void 0 ? input.source_urls.filter((u) => u.trim().length > 0) : [];
1816
+ const active = sourceUrls.length > 0;
1817
+ const gradingEnabled = active && input.source_grounded?.enable_citation_grading === true;
1818
+ const escalateBelow = input.source_grounded?.escalate_below;
1819
+ const config = gradingEnabled ? {
1820
+ citation_grader: {
1821
+ enabled: true,
1822
+ ...escalateBelow !== void 0 ? { escalate_below: escalateBelow } : {}
1823
+ }
1824
+ } : void 0;
1825
+ const cacheFragments = {};
1826
+ if (active) cacheFragments["source_urls"] = sourceUrls;
1827
+ if (gradingEnabled) {
1828
+ cacheFragments["citation_grading"] = true;
1829
+ if (escalateBelow !== void 0) cacheFragments["escalate_below"] = escalateBelow;
1830
+ }
1831
+ return {
1832
+ active,
1833
+ sourceUrls,
1834
+ gradingEnabled,
1835
+ ...config !== void 0 ? { config } : {},
1836
+ cacheFragments
1837
+ };
1838
+ }
1839
+
1514
1840
  // src/tools/consensus-judgment.ts
1515
1841
  var TOOL_NAME4 = "vo_consensus_judgment";
1516
1842
  var MAX_PROMPT_BYTES = 64 * 1024;
@@ -1533,6 +1859,26 @@ var inputSchema4 = {
1533
1859
  type: "object",
1534
1860
  description: "Tool-specific context (e.g. source file, diff, observed value).",
1535
1861
  additionalProperties: true
1862
+ },
1863
+ source_urls: {
1864
+ type: "array",
1865
+ items: { type: "string" },
1866
+ description: "Authoritative source URLs to ground the judgment against (Tier-4). When NON-EMPTY, the engine routes to its source-grounded path: it fetches the sources, flags low-confidence retrievals (surfaced as low_confidence_sources), and \u2014 when source_grounded.enable_citation_grading is true \u2014 grades each claim against the cited source text. When absent/empty the call uses the unchanged sourceless consensus path."
1867
+ },
1868
+ source_grounded: {
1869
+ type: "object",
1870
+ description: "Optional source-grounded behaviour toggles. Only meaningful when source_urls is non-empty.",
1871
+ properties: {
1872
+ enable_citation_grading: {
1873
+ type: "boolean",
1874
+ description: "Opt IN to deterministic citation grading (default OFF). A failing grade raises an escalation signal, so this is behaviour-changing. When true, a real claim classifier (a single cheap model call) grades each claim against the cited sources."
1875
+ },
1876
+ escalate_below: {
1877
+ type: "number",
1878
+ description: "Uncertain-claim escalation floor [0..1]; an uncertain claim with confidence below this triggers escalation. Engine default 0.85 when omitted."
1879
+ }
1880
+ },
1881
+ additionalProperties: false
1536
1882
  }
1537
1883
  },
1538
1884
  required: ["prompt"],
@@ -1544,6 +1890,8 @@ function isToolInput4(v) {
1544
1890
  const o = v;
1545
1891
  if (typeof o["prompt"] !== "string") return false;
1546
1892
  if (o["gate_type"] !== void 0 && typeof o["gate_type"] !== "string") return false;
1893
+ if (o["source_urls"] !== void 0 && !isStringArray(o["source_urls"])) return false;
1894
+ if (o["source_grounded"] !== void 0 && !isToolSourceGrounded(o["source_grounded"])) return false;
1547
1895
  return true;
1548
1896
  }
1549
1897
  function isAcceptedGateType(v) {
@@ -1569,10 +1917,15 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
1569
1917
  const rawGate = rawInput.gate_type ?? DEFAULT_GATE_TYPE;
1570
1918
  const gateType = isAcceptedGateType(rawGate) ? rawGate : DEFAULT_GATE_TYPE;
1571
1919
  const inputSizeBytes = bytesOf(rawInput.prompt) + bytesOf(contextStrForSize);
1920
+ const sg = normalizeSourceGrounded({
1921
+ ...rawInput.source_urls !== void 0 ? { source_urls: rawInput.source_urls } : {},
1922
+ ...rawInput.source_grounded !== void 0 ? { source_grounded: rawInput.source_grounded } : {}
1923
+ });
1572
1924
  const cacheInput = {
1573
1925
  prompt: rawInput.prompt,
1574
1926
  gate_type: gateType,
1575
- ...rawInput.context !== void 0 ? { context: rawInput.context } : {}
1927
+ ...rawInput.context !== void 0 ? { context: rawInput.context } : {},
1928
+ ...sg.cacheFragments
1576
1929
  };
1577
1930
  const key = deps.cache.keyFor(TOOL_NAME4, cacheInput);
1578
1931
  const excerpt = rawInput.prompt.slice(0, 300);
@@ -1618,7 +1971,9 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
1618
1971
  gate_type: gateType,
1619
1972
  prompt: rawInput.prompt,
1620
1973
  ...rawInput.context !== void 0 ? { caller_context: rawInput.context } : {},
1621
- ...signal !== void 0 ? { signal } : {}
1974
+ ...signal !== void 0 ? { signal } : {},
1975
+ ...sg.active ? { source_urls: sg.sourceUrls } : {},
1976
+ ...sg.config !== void 0 ? { source_grounded: sg.config } : {}
1622
1977
  });
1623
1978
  } catch (err) {
1624
1979
  engineThrew = true;
@@ -1671,7 +2026,23 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
1671
2026
  synthesized_verdict: synthForEvent,
1672
2027
  engine_version: engineResult.engine_version,
1673
2028
  degraded: engineResult.degraded,
1674
- gate_type: gateType
2029
+ gate_type: gateType,
2030
+ // ─── Consensus-engine feature outputs (additive; 2026-06-13) ─────────────
2031
+ // Feature 2 (calibrated-confidence) — ON by default; the engine attaches
2032
+ // calibrated_confidence + confidence_badge to the synthesized verdict on the
2033
+ // plain runConsensus path. Surface them at the top level so callers don't
2034
+ // have to know the engine's internal SynthesizedVerdict shape.
2035
+ ...engineResult.synthesized_verdict.calibrated_confidence !== void 0 ? { calibrated_confidence: engineResult.synthesized_verdict.calibrated_confidence } : {},
2036
+ ...engineResult.synthesized_verdict.confidence_badge !== void 0 ? { confidence_badge: engineResult.synthesized_verdict.confidence_badge } : {},
2037
+ // Feature 1 (agreement-gate) — fan-out diagnostics (present iff the gate ran).
2038
+ ...engineResult.fan_out_diagnostics !== void 0 ? { fan_out_diagnostics: engineResult.fan_out_diagnostics } : {},
2039
+ // Source-grounded Tier-4 outputs (present iff the call was source-grounded).
2040
+ ...engineResult.source_grounded === true ? { source_grounded: true } : {},
2041
+ ...engineResult.citation_grade !== void 0 ? { citation_grade: engineResult.citation_grade } : {},
2042
+ ...engineResult.low_confidence_sources !== void 0 ? { low_confidence_sources: engineResult.low_confidence_sources } : {},
2043
+ // Escalation (from citation grade or human-tiebreak synthesizer).
2044
+ ...engineResult.escalation_required !== void 0 ? { escalation_required: engineResult.escalation_required } : {},
2045
+ ...engineResult.escalation_reason !== void 0 ? { escalation_reason: engineResult.escalation_reason } : {}
1675
2046
  };
1676
2047
  const envelope = {
1677
2048
  tool: TOOL_NAME4,
@@ -1894,8 +2265,11 @@ var ratchetIdSchema = z3.enum([
1894
2265
  ]);
1895
2266
  var thresholdSchema = z3.enum(["strict", "medium", "permissive"]);
1896
2267
  var userConfigSchema = z3.object({
1897
- enabled: z3.record(ratchetIdSchema, z3.boolean()).optional(),
1898
- thresholds: z3.record(ratchetIdSchema, thresholdSchema).optional(),
2268
+ // zod v4: z.record with ENUM keys validates exhaustively (every key
2269
+ // required); these are user OVERRIDE maps merged over DEFAULT_CONFIG,
2270
+ // so partial-by-design — z.partialRecord restores the v3 semantics.
2271
+ enabled: z3.partialRecord(ratchetIdSchema, z3.boolean()).optional(),
2272
+ thresholds: z3.partialRecord(ratchetIdSchema, thresholdSchema).optional(),
1899
2273
  allowlist: z3.object({
1900
2274
  paths: z3.array(z3.string()).optional(),
1901
2275
  rules: z3.array(z3.string()).optional()
@@ -3301,279 +3675,43 @@ Produce the JSON dispatch plan now.`;
3301
3675
  plan_text: v.raw_response_excerpt,
3302
3676
  confidence: v.confidence,
3303
3677
  parse_ok: parsed !== null
3304
- };
3305
- });
3306
- const synthText = engineResult.synthesized_verdict.reasoning_excerpt;
3307
- const synthPlan = tryParseDispatchPlan(synthText);
3308
- const payload = {
3309
- verdict: synthPlan !== null ? "pass" : "uncertain",
3310
- reason: synthPlan !== null ? "Synthesized dispatch plan parsed successfully" : "Synthesized verdict did not parse as a valid dispatch plan JSON \u2014 see per_model_plans for raw outputs",
3311
- dispatch_plan: synthPlan,
3312
- ...synthPlan === null ? { parse_error: "synthesized verdict text was not valid dispatch-plan JSON" } : {},
3313
- per_model_plans: perModelPlans,
3314
- synthesized_reasoning: synthText,
3315
- consensus_confidence: engineResult.synthesized_verdict.confidence,
3316
- engine_version: engineResult.engine_version,
3317
- ...engineResult.per_model_verdicts.length < 3 ? { degraded: true } : {}
3318
- };
3319
- const envelope = {
3320
- tool: TOOL_NAME7,
3321
- schema_version: 1,
3322
- cache: { hit: false, key },
3323
- payload
3324
- };
3325
- deps.cache.set(key, envelope);
3326
- const enrichedEvent = {
3327
- ...baseEvent,
3328
- consensus_confidence: engineResult.synthesized_verdict.confidence,
3329
- duration_ms: engineResult.duration_ms,
3330
- consensus_engine_version: engineResult.engine_version,
3331
- per_model_verdicts: toEventPerModelVerdicts(engineResult.per_model_verdicts),
3332
- synthesized_verdict: toEventSynthesizedVerdict(engineResult.synthesized_verdict)
3333
- };
3334
- deps.events.append(enrichedEvent);
3335
- return jsonContent(envelope);
3336
- }
3337
-
3338
- // src/cloud/auth-token-source.ts
3339
- var FIREBASE_SECURETOKEN_URL = "https://securetoken.googleapis.com/v1/token";
3340
- var REFRESH_SKEW_MS = 6e4;
3341
- function createStaticTokenSource(token, kind = "admin-token") {
3342
- const value = token.trim();
3343
- return { kind, getToken: async () => value.length > 0 ? value : null };
3344
- }
3345
- function createFirebaseRefreshTokenSource(opts) {
3346
- const refreshToken = opts.refreshToken.trim();
3347
- const apiKey = opts.apiKey.trim();
3348
- const now = opts.now ?? (() => Date.now());
3349
- const fetchFn = opts.fetchFn ?? globalThis.fetch;
3350
- let cachedToken = null;
3351
- let expiresAtMs = 0;
3352
- let inFlight = null;
3353
- async function refresh() {
3354
- try {
3355
- const res = await fetchFn(`${FIREBASE_SECURETOKEN_URL}?key=${encodeURIComponent(apiKey)}`, {
3356
- method: "POST",
3357
- headers: { "content-type": "application/x-www-form-urlencoded" },
3358
- body: `grant_type=refresh_token&refresh_token=${encodeURIComponent(refreshToken)}`
3359
- });
3360
- const text = await res.text();
3361
- if (res.status < 200 || res.status >= 300) {
3362
- cachedToken = null;
3363
- return null;
3364
- }
3365
- const parsed = JSON.parse(text);
3366
- const idToken = typeof parsed.id_token === "string" ? parsed.id_token : "";
3367
- if (!idToken) {
3368
- cachedToken = null;
3369
- return null;
3370
- }
3371
- const expiresInSec = Number(parsed.expires_in);
3372
- const ttlMs = Number.isFinite(expiresInSec) && expiresInSec > 0 ? expiresInSec * 1e3 : 36e5;
3373
- cachedToken = idToken;
3374
- expiresAtMs = now() + ttlMs;
3375
- return idToken;
3376
- } catch {
3377
- cachedToken = null;
3378
- return null;
3379
- }
3380
- }
3381
- return {
3382
- kind: "firebase-refresh",
3383
- async getToken() {
3384
- if (cachedToken && now() < expiresAtMs - REFRESH_SKEW_MS) return cachedToken;
3385
- if (!inFlight) {
3386
- inFlight = refresh().finally(() => {
3387
- inFlight = null;
3388
- });
3389
- }
3390
- return inFlight;
3391
- }
3392
- };
3393
- }
3394
- function createAuthTokenSourceFromEnv(env = process.env, fetchFn, readStoredCred = () => null) {
3395
- const refreshToken = env["VO_USER_REFRESH_TOKEN"]?.trim();
3396
- const apiKey = env["VO_FIREBASE_API_KEY"]?.trim();
3397
- const idToken = env["VO_USER_ID_TOKEN"]?.trim();
3398
- const adminToken = env["VO_CONTROL_PLANE_ADMIN_TOKEN"]?.trim();
3399
- if (refreshToken || apiKey) {
3400
- if (!refreshToken || !apiKey) {
3401
- throw new Error(
3402
- "Per-user refresh auth requires BOTH VO_USER_REFRESH_TOKEN and VO_FIREBASE_API_KEY"
3403
- );
3404
- }
3405
- return createFirebaseRefreshTokenSource({
3406
- refreshToken,
3407
- apiKey,
3408
- ...fetchFn ? { fetchFn } : {}
3409
- });
3410
- }
3411
- if (idToken) return createStaticTokenSource(idToken, "firebase-id-token");
3412
- const stored = readStoredCred();
3413
- if (stored?.vo_credential && stored.vo_credential.trim()) {
3414
- return createStaticTokenSource(stored.vo_credential.trim(), "vo-credential");
3415
- }
3416
- if (stored && stored.refresh_token?.trim() && stored.api_key?.trim()) {
3417
- return createFirebaseRefreshTokenSource({
3418
- refreshToken: stored.refresh_token.trim(),
3419
- apiKey: stored.api_key.trim(),
3420
- ...fetchFn ? { fetchFn } : {}
3421
- });
3422
- }
3423
- if (adminToken) return createStaticTokenSource(adminToken, "admin-token");
3424
- return null;
3425
- }
3426
-
3427
- // src/cloud/credential-store.ts
3428
- import { homedir as homedir3 } from "node:os";
3429
- import { join as join5, dirname as dirname4 } from "node:path";
3430
- import {
3431
- existsSync as existsSync3,
3432
- mkdirSync as mkdirSync2,
3433
- readFileSync as readFileSync5,
3434
- writeFileSync as writeFileSync2,
3435
- chmodSync as chmodSync2,
3436
- rmSync
3437
- } from "node:fs";
3438
-
3439
- // src/cloud/keychain.ts
3440
- import { createRequire } from "node:module";
3441
- var SERVICE = "vo-mcp";
3442
- var ACCOUNT = "refresh-credential";
3443
- var cached;
3444
- function loadKeyring() {
3445
- if (cached !== void 0) return cached;
3446
- try {
3447
- const req = createRequire(import.meta.url);
3448
- const mod = req("@napi-rs/keyring");
3449
- cached = mod && typeof mod.Entry === "function" ? mod : null;
3450
- } catch {
3451
- cached = null;
3452
- }
3453
- return cached;
3454
- }
3455
- function keychainAvailable() {
3456
- return loadKeyring() !== null;
3457
- }
3458
- function keychainGet() {
3459
- const k = loadKeyring();
3460
- if (!k) return null;
3461
- try {
3462
- return new k.Entry(SERVICE, ACCOUNT).getPassword();
3463
- } catch {
3464
- return null;
3465
- }
3466
- }
3467
- function keychainSet(secret) {
3468
- const k = loadKeyring();
3469
- if (!k) return false;
3470
- try {
3471
- new k.Entry(SERVICE, ACCOUNT).setPassword(secret);
3472
- return true;
3473
- } catch {
3474
- return false;
3475
- }
3476
- }
3477
- function keychainDelete() {
3478
- const k = loadKeyring();
3479
- if (!k) return false;
3480
- try {
3481
- return new k.Entry(SERVICE, ACCOUNT).deletePassword();
3482
- } catch {
3483
- return false;
3484
- }
3485
- }
3486
-
3487
- // src/cloud/credential-store.ts
3488
- var realKeychain = {
3489
- available: keychainAvailable,
3490
- get: keychainGet,
3491
- set: keychainSet,
3492
- delete: keychainDelete
3493
- };
3494
- var KEYCHAIN_LOCATION = 'OS keychain (service "vo-mcp")';
3495
- function credentialPath(env = process.env) {
3496
- const override = env["VO_MCP_CREDENTIALS_PATH"]?.trim();
3497
- if (override) return override;
3498
- return join5(homedir3(), ".config", "vo-mcp", "credentials.json");
3499
- }
3500
- function keychainEnabled(env, keychain) {
3501
- const disabled = (env["VO_MCP_DISABLE_KEYCHAIN"] ?? "").trim().toLowerCase();
3502
- if (disabled === "1" || disabled === "true" || disabled === "yes") return false;
3503
- return keychain.available();
3504
- }
3505
- function deserialize(raw) {
3506
- try {
3507
- const parsed = JSON.parse(raw);
3508
- const refresh = typeof parsed.refresh_token === "string" ? parsed.refresh_token.trim() : "";
3509
- const apiKey = typeof parsed.api_key === "string" ? parsed.api_key.trim() : "";
3510
- const voCred = typeof parsed.vo_credential === "string" ? parsed.vo_credential.trim() : "";
3511
- if (!voCred && (!refresh || !apiKey)) return null;
3512
- return {
3513
- ...refresh ? { refresh_token: refresh } : {},
3514
- ...apiKey ? { api_key: apiKey } : {},
3515
- ...voCred ? { vo_credential: voCred } : {},
3516
- ...typeof parsed.vo_credential_expires_at === "string" ? { vo_credential_expires_at: parsed.vo_credential_expires_at } : {},
3517
- ...typeof parsed.email === "string" ? { email: parsed.email } : {},
3518
- ...typeof parsed.stored_at === "string" ? { stored_at: parsed.stored_at } : {}
3519
- };
3520
- } catch {
3521
- return null;
3522
- }
3523
- }
3524
- function readFromFile(env) {
3525
- try {
3526
- const p = credentialPath(env);
3527
- if (!existsSync3(p)) return null;
3528
- return deserialize(readFileSync5(p, "utf8"));
3529
- } catch {
3530
- return null;
3531
- }
3532
- }
3533
- function readStoredCredential(env = process.env, keychain = realKeychain) {
3534
- if (keychainEnabled(env, keychain)) {
3535
- const raw = keychain.get();
3536
- const fromKeychain = raw ? deserialize(raw) : null;
3537
- if (fromKeychain) return fromKeychain;
3538
- }
3539
- return readFromFile(env);
3540
- }
3541
- function deleteFile(env) {
3542
- try {
3543
- rmSync(credentialPath(env), { force: true });
3544
- } catch {
3545
- }
3546
- }
3547
- function writeToFile(payload, env) {
3548
- const p = credentialPath(env);
3549
- mkdirSync2(dirname4(p), { recursive: true });
3550
- writeFileSync2(p, `${JSON.stringify(payload, null, 2)}
3551
- `, { mode: 384 });
3552
- try {
3553
- chmodSync2(p, 384);
3554
- } catch {
3555
- }
3556
- return p;
3557
- }
3558
- function writeStoredCredential(cred, storedAt, env = process.env, keychain = realKeychain) {
3678
+ };
3679
+ });
3680
+ const synthText = engineResult.synthesized_verdict.reasoning_excerpt;
3681
+ const synthPlan = tryParseDispatchPlan(synthText);
3559
3682
  const payload = {
3560
- ...cred.refresh_token ? { refresh_token: cred.refresh_token } : {},
3561
- ...cred.api_key ? { api_key: cred.api_key } : {},
3562
- ...cred.vo_credential ? { vo_credential: cred.vo_credential } : {},
3563
- ...cred.vo_credential_expires_at ? { vo_credential_expires_at: cred.vo_credential_expires_at } : {},
3564
- ...cred.email ? { email: cred.email } : {},
3565
- stored_at: cred.stored_at ?? storedAt
3683
+ verdict: synthPlan !== null ? "pass" : "uncertain",
3684
+ reason: synthPlan !== null ? "Synthesized dispatch plan parsed successfully" : "Synthesized verdict did not parse as a valid dispatch plan JSON \u2014 see per_model_plans for raw outputs",
3685
+ dispatch_plan: synthPlan,
3686
+ ...synthPlan === null ? { parse_error: "synthesized verdict text was not valid dispatch-plan JSON" } : {},
3687
+ per_model_plans: perModelPlans,
3688
+ synthesized_reasoning: synthText,
3689
+ consensus_confidence: engineResult.synthesized_verdict.confidence,
3690
+ engine_version: engineResult.engine_version,
3691
+ ...engineResult.per_model_verdicts.length < 3 ? { degraded: true } : {}
3566
3692
  };
3567
- if (keychainEnabled(env, keychain) && keychain.set(JSON.stringify(payload))) {
3568
- deleteFile(env);
3569
- return KEYCHAIN_LOCATION;
3570
- }
3571
- const p = writeToFile(payload, env);
3572
- if (keychainEnabled(env, keychain)) keychain.delete();
3573
- return p;
3693
+ const envelope = {
3694
+ tool: TOOL_NAME7,
3695
+ schema_version: 1,
3696
+ cache: { hit: false, key },
3697
+ payload
3698
+ };
3699
+ deps.cache.set(key, envelope);
3700
+ const enrichedEvent = {
3701
+ ...baseEvent,
3702
+ consensus_confidence: engineResult.synthesized_verdict.confidence,
3703
+ duration_ms: engineResult.duration_ms,
3704
+ consensus_engine_version: engineResult.engine_version,
3705
+ per_model_verdicts: toEventPerModelVerdicts(engineResult.per_model_verdicts),
3706
+ synthesized_verdict: toEventSynthesizedVerdict(engineResult.synthesized_verdict)
3707
+ };
3708
+ deps.events.append(enrichedEvent);
3709
+ return jsonContent(envelope);
3574
3710
  }
3575
3711
 
3576
3712
  // src/cloud/admin-callable-client.ts
3713
+ init_auth_token_source();
3714
+ init_credential_store();
3577
3715
  var AdminCallableError = class extends Error {
3578
3716
  constructor(status, path3, message) {
3579
3717
  super(message);
@@ -4507,7 +4645,7 @@ var inputSchema19 = {
4507
4645
  additionalProperties: false
4508
4646
  };
4509
4647
  var description19 = "Reports per-session context-window utilization to VO and returns a directive: 'continue' (under 70%), 'prepare_handoff' (70-84%), or 'execute_handoff_now' (\u226585%). Implements V1 launch gate #9 (fleet context lifecycle management) per the official VO roadmap. V1 backend is stub-local \u2014 computes the directive purely from `context_used_pct` against the documented thresholds without a network call. Phase 3 wires this to the deployed vo-control-plane HTTP API; the response shape stays stable across the cutover (`backend_mode` field in the payload tells the caller which mode produced the verdict).";
4510
- function isStringArray(v, maxItems) {
4648
+ function isStringArray2(v, maxItems) {
4511
4649
  if (!Array.isArray(v)) return false;
4512
4650
  if (v.length > maxItems) return false;
4513
4651
  return v.every((item) => typeof item === "string");
@@ -4523,14 +4661,64 @@ function isToolInput19(v) {
4523
4661
  if (!Number.isFinite(o["context_used_pct"])) return false;
4524
4662
  if (o["context_used_pct"] < 0 || o["context_used_pct"] > 100) return false;
4525
4663
  if (o["current_goal"] !== void 0 && typeof o["current_goal"] !== "string") return false;
4526
- if (o["recent_files_touched"] !== void 0 && !isStringArray(o["recent_files_touched"], MAX_RECENT_FILES)) {
4664
+ if (o["recent_files_touched"] !== void 0 && !isStringArray2(o["recent_files_touched"], MAX_RECENT_FILES)) {
4527
4665
  return false;
4528
4666
  }
4529
- if (o["recent_tool_uses"] !== void 0 && !isStringArray(o["recent_tool_uses"], MAX_RECENT_TOOLS)) {
4667
+ if (o["recent_tool_uses"] !== void 0 && !isStringArray2(o["recent_tool_uses"], MAX_RECENT_TOOLS)) {
4530
4668
  return false;
4531
4669
  }
4532
4670
  return true;
4533
4671
  }
4672
+ function getCloudConfig() {
4673
+ const url = process.env["VO_CONTROL_PLANE_URL"];
4674
+ const token = process.env["VO_CONTROL_PLANE_ADMIN_TOKEN"];
4675
+ if (!url || !token) return null;
4676
+ return { url, token };
4677
+ }
4678
+ async function tryCloudReportState(cloud, input) {
4679
+ try {
4680
+ const body = {
4681
+ context_used_pct: input.context_used_pct
4682
+ };
4683
+ if (input.current_goal !== void 0) body["current_goal"] = input.current_goal;
4684
+ if (input.recent_files_touched !== void 0) {
4685
+ body["recent_files_touched"] = input.recent_files_touched;
4686
+ }
4687
+ if (input.recent_tool_uses !== void 0) {
4688
+ body["recent_tool_uses"] = input.recent_tool_uses;
4689
+ }
4690
+ const url = `${cloud.url}/api/v1/session/${input.session_id}/report-state`;
4691
+ const response = await fetch(url, {
4692
+ method: "POST",
4693
+ headers: {
4694
+ "Content-Type": "application/json",
4695
+ "Authorization": `Bearer ${cloud.token}`
4696
+ },
4697
+ body: JSON.stringify(body)
4698
+ });
4699
+ if (!response.ok) {
4700
+ return null;
4701
+ }
4702
+ const data = await response.json();
4703
+ if (!data.ok || !data.session || !data.directive) {
4704
+ return null;
4705
+ }
4706
+ return {
4707
+ directive: data.directive.action,
4708
+ message: data.directive.message,
4709
+ session_id: data.session.session_id,
4710
+ operator_id: data.session.operator_id,
4711
+ agent_type: input.agent_type,
4712
+ context_used_pct: data.session.context_used_pct,
4713
+ backend_mode: "cloud-control-plane",
4714
+ ts: data.session.last_seen_at,
4715
+ schema_version: 1,
4716
+ ...data.directive.action === "execute_handoff_now" ? { handoff_path: suggestedHandoffPath(data.session.session_id, data.session.last_seen_at) } : {}
4717
+ };
4718
+ } catch {
4719
+ return null;
4720
+ }
4721
+ }
4534
4722
  async function handleReportSessionState(deps, rawInput, _signal) {
4535
4723
  if (!isToolInput19(rawInput)) {
4536
4724
  throw invalidParams(
@@ -4538,6 +4726,13 @@ async function handleReportSessionState(deps, rawInput, _signal) {
4538
4726
  `invalid input. Required fields: operator_id (non-empty string), session_id (non-empty string), agent_type (one of: ${VALID_AGENT_TYPES.join(" | ")}), context_used_pct (number 0-100). Optional: current_goal (string \u2264${MAX_GOAL_CHARS} chars), recent_files_touched (string[] \u2264${MAX_RECENT_FILES}), recent_tool_uses (string[] \u2264${MAX_RECENT_TOOLS}).`
4539
4727
  );
4540
4728
  }
4729
+ const cloud = getCloudConfig();
4730
+ if (cloud !== null) {
4731
+ const cloudPayload = await tryCloudReportState(cloud, rawInput);
4732
+ if (cloudPayload !== null) {
4733
+ return jsonContent(cloudPayload);
4734
+ }
4735
+ }
4541
4736
  const directive = computeDirective(rawInput.context_used_pct);
4542
4737
  const ts = deps.now().toISOString();
4543
4738
  const payload = {
@@ -4555,6 +4750,141 @@ async function handleReportSessionState(deps, rawInput, _signal) {
4555
4750
  return jsonContent(payload);
4556
4751
  }
4557
4752
 
4753
+ // src/tools/session/spawn-successor.ts
4754
+ import { spawn } from "node:child_process";
4755
+ import { homedir as homedir4 } from "node:os";
4756
+ import { join as join6 } from "node:path";
4757
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
4758
+ var TOOL_NAME20 = "vo_spawn_successor";
4759
+ var MAX_HANDOFF_BYTES = 64e3;
4760
+ var inputSchema20 = {
4761
+ type: "object",
4762
+ properties: {
4763
+ handoff_path: {
4764
+ type: "string",
4765
+ description: "Path to the handoff doc to pre-inject. Default: the newest .md in ~/.vo/handoffs/."
4766
+ },
4767
+ goal: {
4768
+ type: "string",
4769
+ description: "Optional one-line goal override appended after the handoff."
4770
+ },
4771
+ cwd: {
4772
+ type: "string",
4773
+ description: "Working directory for the successor (default: the repo the handoff names, else process cwd)."
4774
+ },
4775
+ max_turns: {
4776
+ type: "number",
4777
+ description: "Optional --max-turns bound for the successor."
4778
+ }
4779
+ },
4780
+ required: [],
4781
+ additionalProperties: false
4782
+ };
4783
+ 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.";
4784
+ function isToolInput20(v) {
4785
+ if (typeof v !== "object" || v === null) return false;
4786
+ const o = v;
4787
+ if (o["handoff_path"] !== void 0 && typeof o["handoff_path"] !== "string") return false;
4788
+ if (o["goal"] !== void 0 && typeof o["goal"] !== "string") return false;
4789
+ if (o["cwd"] !== void 0 && typeof o["cwd"] !== "string") return false;
4790
+ if (o["max_turns"] !== void 0 && typeof o["max_turns"] !== "number") return false;
4791
+ return true;
4792
+ }
4793
+ function newestHandoff(dir = join6(homedir4(), ".vo", "handoffs")) {
4794
+ try {
4795
+ const entries = readdirSync3(dir).filter((f) => f.endsWith(".md")).map((f) => ({ f, m: statSync3(join6(dir, f)).mtimeMs })).sort((a, b) => b.m - a.m);
4796
+ return entries.length > 0 && entries[0] ? join6(dir, entries[0].f) : null;
4797
+ } catch {
4798
+ return null;
4799
+ }
4800
+ }
4801
+ var MANDATORY_READS = [
4802
+ "CLAUDE.md + AGENTS.md + README.md (repo root)",
4803
+ "docs/current/virtual-office-agent-charter.md, -operating-model.md, -test-architect.md",
4804
+ "docs/current/evidence-grounded-consensus-testing.md",
4805
+ "docs/vo/ADR-001-* (verify + sign, human approves merge; no autonomous bot-merge / headless triggers) + docs/vo/vo-adr-002-two-plane-moat.md",
4806
+ "docs/vo/vo-roadmap-2026-05-26.md (read the Change log tail for current state)",
4807
+ "the operator memory index ~/.claude/projects/C--Users-greyl/memory/MEMORY.md"
4808
+ ];
4809
+ function buildSuccessorPrompt(handoffMarkdown, goal) {
4810
+ const reads = MANDATORY_READS.map((r, i) => ` ${i + 1}. ${r}`).join("\n");
4811
+ const lines = [
4812
+ "You are the SUCCESSOR agent for a Virtual Office lane. The previous session",
4813
+ "exhausted its context and wrote the handoff below. Read it fully, verify its",
4814
+ '"verification needed" items against live state (a handoff is a claim, not',
4815
+ "evidence \u2014 verify via `git show origin/main:<path>`), then continue the lane.",
4816
+ "",
4817
+ "MANDATORY READS before writing any code (NOT all auto-loaded \u2014 open them):",
4818
+ reads,
4819
+ "",
4820
+ "NON-NEGOTIABLES: multi-model consensus verification is the core; test honesty",
4821
+ "(verified-answer-only, no fake green); verify-before-act + human merge approval;",
4822
+ "never a full functions-shared deploy; Gen2 only; work in a worktree on your own",
4823
+ "branch; finish line is MERGED + DEPLOYED + LIVE-VERIFIED, and VO changes update",
4824
+ "the roadmap in the same PR.",
4825
+ "",
4826
+ "--- HANDOFF ---",
4827
+ handoffMarkdown,
4828
+ "--- END HANDOFF ---"
4829
+ ];
4830
+ if (goal && goal.trim().length > 0) lines.push("", `OPERATOR GOAL OVERRIDE: ${goal.trim()}`);
4831
+ return lines.join("\n");
4832
+ }
4833
+ function buildSuccessorArgs(maxTurns) {
4834
+ const args = ["-p", "--permission-mode", "acceptEdits"];
4835
+ if (Number.isInteger(maxTurns) && maxTurns > 0) {
4836
+ args.push("--max-turns", String(maxTurns));
4837
+ }
4838
+ return args;
4839
+ }
4840
+ async function handleSpawnSuccessor(_deps, rawInput, _signal, spawnImpl = spawn) {
4841
+ if (!isToolInput20(rawInput)) {
4842
+ throw invalidParams(TOOL_NAME20, "invalid input. Optional: { handoff_path, goal, cwd, max_turns }.");
4843
+ }
4844
+ const handoffPath = rawInput.handoff_path?.trim() || newestHandoff();
4845
+ if (!handoffPath || !existsSync4(handoffPath)) {
4846
+ return jsonContent({
4847
+ tool: TOOL_NAME20,
4848
+ schema_version: 1,
4849
+ payload: {
4850
+ spawned: false,
4851
+ reason: rawInput.handoff_path ? `handoff not found: ${rawInput.handoff_path}` : "no handoff docs in ~/.vo/handoffs \u2014 write one first (the 85% directive does this)"
4852
+ }
4853
+ });
4854
+ }
4855
+ const handoff = readFileSync6(handoffPath, "utf8").slice(0, MAX_HANDOFF_BYTES);
4856
+ const prompt = buildSuccessorPrompt(handoff, rawInput.goal);
4857
+ const logDir = process.env["VO_MCP_SUCCESSOR_LOG_DIR"]?.trim() || join6(homedir4(), ".vo", "successors");
4858
+ mkdirSync3(logDir, { recursive: true });
4859
+ const logPath = join6(logDir, `successor-${Date.now()}.log`);
4860
+ const logFd = openSync(logPath, "a");
4861
+ const child = spawnImpl("claude", buildSuccessorArgs(rawInput.max_turns), {
4862
+ cwd: rawInput.cwd?.trim() || process.cwd(),
4863
+ detached: true,
4864
+ stdio: ["pipe", logFd, logFd],
4865
+ // Windows: `claude` is a .cmd shim — needs a shell to resolve. The prompt
4866
+ // goes via STDIN below, never argv, so the shell never sees it.
4867
+ shell: process.platform === "win32",
4868
+ windowsHide: true
4869
+ });
4870
+ let spawnError = null;
4871
+ child.on("error", (e) => {
4872
+ spawnError = e.message;
4873
+ });
4874
+ try {
4875
+ child.stdin.write(prompt);
4876
+ child.stdin.end();
4877
+ } catch {
4878
+ }
4879
+ child.unref();
4880
+ await new Promise((r) => setTimeout(r, 150));
4881
+ return jsonContent({
4882
+ tool: TOOL_NAME20,
4883
+ schema_version: 1,
4884
+ payload: spawnError ? { spawned: false, reason: `spawn failed: ${spawnError}`, handoff_path: handoffPath } : { spawned: true, pid: child.pid ?? null, log_path: logPath, handoff_path: handoffPath }
4885
+ });
4886
+ }
4887
+
4558
4888
  // src/tools/concierge/common-concierge.ts
4559
4889
  var CONCIERGE_STUB_REASON = "cloud mode not active in this MCP runtime \u2014 set VO_CONTROL_PLANE_URL + VO_CONTROL_PLANE_ADMIN_TOKEN to enable. The server-side /api/v1/admin/concierge/dispatch endpoint IS built + deployed (vo-control-plane #5724/#5734); in cloud mode this tool returns the routed pack README + file index.";
4560
4890
  var CONCIERGE_GATE_TYPE = "concierge-dispatch";
@@ -4573,10 +4903,10 @@ function isKnownConciergePack(value) {
4573
4903
  }
4574
4904
 
4575
4905
  // src/tools/concierge/dispatch.ts
4576
- var TOOL_NAME20 = "vo_concierge_dispatch";
4906
+ var TOOL_NAME21 = "vo_concierge_dispatch";
4577
4907
  var CALLABLE_NAME10 = "voConciergeDispatch";
4578
4908
  var ADMIN_PATH10 = "/api/v1/admin/concierge/dispatch";
4579
- var inputSchema20 = {
4909
+ var inputSchema21 = {
4580
4910
  type: "object",
4581
4911
  properties: {
4582
4912
  pack: {
@@ -4591,8 +4921,8 @@ var inputSchema20 = {
4591
4921
  },
4592
4922
  additionalProperties: false
4593
4923
  };
4594
- var description20 = "Dispatches a provider-scoped knowledge pack (gcp | firebase | aws | cloudflare | vercel | netlify | tax | hybrid). Cross-vendor MCP equivalent of the /vo-concierge Claude-Code slash command. Route explicitly via `pack`, or via tenant.cloud_provider by passing `tenant_id`. Returns the pack's README (`readme_markdown`) + file index. In cloud mode, dispatches via vo-control-plane and returns `verdict: 'pass'` with the pack/directory data; without cloud config, returns `verdict: 'unimplemented'`.";
4595
- function isToolInput20(v) {
4924
+ var description21 = "Dispatches a provider-scoped knowledge pack (gcp | firebase | aws | cloudflare | vercel | netlify | tax | hybrid). Cross-vendor MCP equivalent of the /vo-concierge Claude-Code slash command. Route explicitly via `pack`, or via tenant.cloud_provider by passing `tenant_id`. Returns the pack's README (`readme_markdown`) + file index. In cloud mode, dispatches via vo-control-plane and returns `verdict: 'pass'` with the pack/directory data; without cloud config, returns `verdict: 'unimplemented'`.";
4925
+ function isToolInput21(v) {
4596
4926
  if (typeof v !== "object" || v === null) return false;
4597
4927
  const obj = v;
4598
4928
  if (obj.pack !== void 0 && typeof obj.pack !== "string") return false;
@@ -4600,15 +4930,15 @@ function isToolInput20(v) {
4600
4930
  return true;
4601
4931
  }
4602
4932
  async function handleConciergeDispatch(deps, rawInput, _signal) {
4603
- if (!isToolInput20(rawInput)) {
4933
+ if (!isToolInput21(rawInput)) {
4604
4934
  throw invalidParams(
4605
- TOOL_NAME20,
4935
+ TOOL_NAME21,
4606
4936
  "invalid input. Expected { pack?: string, tenant_id?: string }."
4607
4937
  );
4608
4938
  }
4609
4939
  if (rawInput.pack !== void 0 && rawInput.pack !== "" && !isKnownConciergePack(rawInput.pack)) {
4610
4940
  throw invalidParams(
4611
- TOOL_NAME20,
4941
+ TOOL_NAME21,
4612
4942
  `unknown pack: ${JSON.stringify(rawInput.pack)}. Known packs: ${KNOWN_CONCIERGE_PACKS.join(", ")}.`
4613
4943
  );
4614
4944
  }
@@ -4619,7 +4949,7 @@ async function handleConciergeDispatch(deps, rawInput, _signal) {
4619
4949
  if (rawInput.pack) cloudBody.pack = rawInput.pack;
4620
4950
  if (rawInput.tenant_id) cloudBody.tenantId = rawInput.tenant_id;
4621
4951
  return buildCloudOrStubResponse({
4622
- toolName: TOOL_NAME20,
4952
+ toolName: TOOL_NAME21,
4623
4953
  callableName: CALLABLE_NAME10,
4624
4954
  adminPath: ADMIN_PATH10,
4625
4955
  normalizedInput,
@@ -4636,6 +4966,254 @@ async function handleConciergeDispatch(deps, rawInput, _signal) {
4636
4966
  });
4637
4967
  }
4638
4968
 
4969
+ // src/tools/memory/sync-config.ts
4970
+ import { homedir as homedir5 } from "node:os";
4971
+ import { join as join7 } from "node:path";
4972
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "node:fs";
4973
+ var TOOL_NAME22 = "vo_sync_config";
4974
+ var inputSchema22 = {
4975
+ type: "object",
4976
+ properties: {
4977
+ action: {
4978
+ type: "string",
4979
+ enum: ["pull", "push"],
4980
+ description: "pull: download cloud memory to local files. push: upload local files to cloud."
4981
+ },
4982
+ cwd: {
4983
+ type: "string",
4984
+ description: "Working directory to derive project slug from (default: process.cwd())."
4985
+ }
4986
+ },
4987
+ required: ["action"],
4988
+ additionalProperties: false
4989
+ };
4990
+ var description22 = "Syncs memory entries between local ~/.claude/projects/<slug>/memory/ and cloud control-plane /api/v1/agent-config/memory/me. Requires operator auth (vo-mcp login). Actions: pull (cloud\u2192local), push (local\u2192cloud). Idempotent; push creates/updates as needed.";
4991
+ function isToolInput22(v) {
4992
+ if (typeof v !== "object" || v === null) return false;
4993
+ const o = v;
4994
+ if (o["action"] !== "pull" && o["action"] !== "push") return false;
4995
+ if (o["cwd"] !== void 0 && typeof o["cwd"] !== "string") return false;
4996
+ return true;
4997
+ }
4998
+ function deriveProjectSlug(cwd) {
4999
+ const normalized = cwd.replace(/\\/g, "/");
5000
+ return normalized.replace(/^([A-Z]):/i, (_, drive) => `${drive.toUpperCase()}-`).replace(/\/$/g, "").split("/").join("--").replace(/\s+/g, "-");
5001
+ }
5002
+ function getMemoryDir(cwd) {
5003
+ const slug = deriveProjectSlug(cwd);
5004
+ return join7(homedir5(), ".claude", "projects", slug, "memory");
5005
+ }
5006
+ async function pullMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn) {
5007
+ const url = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
5008
+ const response = await fetchFn(url, {
5009
+ method: "GET",
5010
+ headers: {
5011
+ authorization: `Bearer ${token}`
5012
+ }
5013
+ });
5014
+ if (response.status !== 200) {
5015
+ const text = await response.text();
5016
+ throw new Error(`GET /api/v1/agent-config/memory/me returned HTTP ${response.status}: ${text.slice(0, 200)}`);
5017
+ }
5018
+ const data = JSON.parse(await response.text());
5019
+ if (!data.ok || !Array.isArray(data.entries)) {
5020
+ throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
5021
+ }
5022
+ mkdirSync4(memoryDir, { recursive: true });
5023
+ const files = [];
5024
+ for (const entry of data.entries) {
5025
+ const filePath = join7(memoryDir, entry.file_name);
5026
+ writeFileSync3(filePath, entry.content, "utf8");
5027
+ files.push(entry.file_name);
5028
+ }
5029
+ return { pulled: data.entries.length, files };
5030
+ }
5031
+ async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn) {
5032
+ if (!existsSync5(memoryDir)) {
5033
+ return { pushed: 0, created: 0, updated: 0 };
5034
+ }
5035
+ const localFiles = readdirSync4(memoryDir).filter((f) => f.endsWith(".md")).map((f) => ({
5036
+ file_name: f,
5037
+ content: readFileSync7(join7(memoryDir, f), "utf8"),
5038
+ entry_type: f === "MEMORY.md" ? "index" : "topic"
5039
+ }));
5040
+ if (localFiles.length === 0) {
5041
+ return { pushed: 0, created: 0, updated: 0 };
5042
+ }
5043
+ const getUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
5044
+ const getResponse = await fetchFn(getUrl, {
5045
+ method: "GET",
5046
+ headers: {
5047
+ authorization: `Bearer ${token}`
5048
+ }
5049
+ });
5050
+ const existingMap = /* @__PURE__ */ new Map();
5051
+ if (getResponse.status === 200) {
5052
+ const getData = JSON.parse(await getResponse.text());
5053
+ if (getData.ok && Array.isArray(getData.entries)) {
5054
+ for (const entry of getData.entries) {
5055
+ existingMap.set(entry.file_name, entry.memory_id);
5056
+ }
5057
+ }
5058
+ }
5059
+ let created = 0;
5060
+ let updated = 0;
5061
+ for (const localFile of localFiles) {
5062
+ const memoryId = existingMap.get(localFile.file_name);
5063
+ if (memoryId) {
5064
+ const updateUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/${memoryId}`;
5065
+ const updateBody = {
5066
+ content: localFile.content,
5067
+ session_id: sessionId
5068
+ };
5069
+ const updateResponse = await fetchFn(updateUrl, {
5070
+ method: "PUT",
5071
+ headers: {
5072
+ authorization: `Bearer ${token}`,
5073
+ "content-type": "application/json"
5074
+ },
5075
+ body: JSON.stringify(updateBody)
5076
+ });
5077
+ if (updateResponse.status !== 200) {
5078
+ const text = await updateResponse.text();
5079
+ throw new Error(
5080
+ `PUT /api/v1/agent-config/memory/${memoryId} returned HTTP ${updateResponse.status}: ${text.slice(0, 200)}`
5081
+ );
5082
+ }
5083
+ const updateData = JSON.parse(await updateResponse.text());
5084
+ if (!updateData.ok) {
5085
+ throw new Error(`PUT /api/v1/agent-config/memory/${memoryId} returned ok=false`);
5086
+ }
5087
+ updated++;
5088
+ } else {
5089
+ const createUrl = `${controlPlaneUrl}/api/v1/agent-config/memory/me`;
5090
+ const createBody = {
5091
+ entry_type: localFile.entry_type,
5092
+ file_name: localFile.file_name,
5093
+ content: localFile.content,
5094
+ session_id: sessionId
5095
+ };
5096
+ const createResponse = await fetchFn(createUrl, {
5097
+ method: "POST",
5098
+ headers: {
5099
+ authorization: `Bearer ${token}`,
5100
+ "content-type": "application/json"
5101
+ },
5102
+ body: JSON.stringify(createBody)
5103
+ });
5104
+ if (createResponse.status !== 200 && createResponse.status !== 201) {
5105
+ const text = await createResponse.text();
5106
+ throw new Error(
5107
+ `POST /api/v1/agent-config/memory/me returned HTTP ${createResponse.status}: ${text.slice(0, 200)}`
5108
+ );
5109
+ }
5110
+ const createData = JSON.parse(await createResponse.text());
5111
+ if (!createData.ok) {
5112
+ throw new Error("POST /api/v1/agent-config/memory/me returned ok=false");
5113
+ }
5114
+ created++;
5115
+ }
5116
+ }
5117
+ return { pushed: localFiles.length, created, updated };
5118
+ }
5119
+ async function handleSyncConfig(deps, rawInput, _signal, fetchFn = globalThis.fetch) {
5120
+ if (!isToolInput22(rawInput)) {
5121
+ throw invalidParams(
5122
+ TOOL_NAME22,
5123
+ 'invalid input. Required: { action: "pull" | "push" }. Optional: { cwd: "<path>" }.'
5124
+ );
5125
+ }
5126
+ const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"];
5127
+ if (!controlPlaneUrl) {
5128
+ return jsonContent({
5129
+ tool: TOOL_NAME22,
5130
+ schema_version: 1,
5131
+ payload: {
5132
+ synced: false,
5133
+ reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled"
5134
+ }
5135
+ });
5136
+ }
5137
+ const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
5138
+ const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
5139
+ const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
5140
+ if (!tokenSource) {
5141
+ return jsonContent({
5142
+ tool: TOOL_NAME22,
5143
+ schema_version: 1,
5144
+ payload: {
5145
+ synced: false,
5146
+ reason: "No auth configured. Run `vo-mcp login` to authenticate as an operator."
5147
+ }
5148
+ });
5149
+ }
5150
+ const token = await tokenSource.getToken();
5151
+ if (!token) {
5152
+ return jsonContent({
5153
+ tool: TOOL_NAME22,
5154
+ schema_version: 1,
5155
+ payload: {
5156
+ synced: false,
5157
+ reason: "Failed to obtain auth token. Run `vo-mcp login` to re-authenticate."
5158
+ }
5159
+ });
5160
+ }
5161
+ const cwd = rawInput.cwd?.trim() || process.cwd();
5162
+ const memoryDir = getMemoryDir(cwd);
5163
+ try {
5164
+ if (rawInput.action === "pull") {
5165
+ const result = await pullMemory(
5166
+ controlPlaneUrl.replace(/\/+$/, ""),
5167
+ token,
5168
+ memoryDir,
5169
+ deps.sessionId,
5170
+ fetchFn
5171
+ );
5172
+ return jsonContent({
5173
+ tool: TOOL_NAME22,
5174
+ schema_version: 1,
5175
+ payload: {
5176
+ synced: true,
5177
+ action: "pull",
5178
+ pulled: result.pulled,
5179
+ files: result.files,
5180
+ memory_dir: memoryDir
5181
+ }
5182
+ });
5183
+ } else {
5184
+ const result = await pushMemory(
5185
+ controlPlaneUrl.replace(/\/+$/, ""),
5186
+ token,
5187
+ memoryDir,
5188
+ deps.sessionId,
5189
+ fetchFn
5190
+ );
5191
+ return jsonContent({
5192
+ tool: TOOL_NAME22,
5193
+ schema_version: 1,
5194
+ payload: {
5195
+ synced: true,
5196
+ action: "push",
5197
+ pushed: result.pushed,
5198
+ created: result.created,
5199
+ updated: result.updated,
5200
+ memory_dir: memoryDir
5201
+ }
5202
+ });
5203
+ }
5204
+ } catch (err) {
5205
+ const message = err instanceof Error ? err.message : String(err);
5206
+ return jsonContent({
5207
+ tool: TOOL_NAME22,
5208
+ schema_version: 1,
5209
+ payload: {
5210
+ synced: false,
5211
+ reason: `Sync failed: ${message}`
5212
+ }
5213
+ });
5214
+ }
5215
+ }
5216
+
4639
5217
  // src/server.ts
4640
5218
  function buildToolRegistry() {
4641
5219
  return {
@@ -4797,7 +5375,23 @@ function buildToolRegistry() {
4797
5375
  description: description20,
4798
5376
  inputSchema: inputSchema20
4799
5377
  },
5378
+ handler: handleSpawnSuccessor
5379
+ },
5380
+ [TOOL_NAME21]: {
5381
+ definition: {
5382
+ name: TOOL_NAME21,
5383
+ description: description21,
5384
+ inputSchema: inputSchema21
5385
+ },
4800
5386
  handler: handleConciergeDispatch
5387
+ },
5388
+ [TOOL_NAME22]: {
5389
+ definition: {
5390
+ name: TOOL_NAME22,
5391
+ description: description22,
5392
+ inputSchema: inputSchema22
5393
+ },
5394
+ handler: handleSyncConfig
4801
5395
  }
4802
5396
  };
4803
5397
  }
@@ -4852,7 +5446,7 @@ function createServer(options) {
4852
5446
 
4853
5447
  // src/cache/sqlite-cache.ts
4854
5448
  import { createHash as createHash3 } from "node:crypto";
4855
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync3 } from "node:fs";
5449
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync5 } from "node:fs";
4856
5450
  import { dirname as dirname5 } from "node:path";
4857
5451
  import { DatabaseSync } from "node:sqlite";
4858
5452
 
@@ -4898,7 +5492,7 @@ function normalizeString(s) {
4898
5492
  function createSqliteCache(options) {
4899
5493
  const fileBacked = options.dbPath !== ":memory:";
4900
5494
  if (fileBacked) {
4901
- mkdirSync3(dirname5(options.dbPath), { recursive: true, mode: 448 });
5495
+ mkdirSync5(dirname5(options.dbPath), { recursive: true, mode: 448 });
4902
5496
  }
4903
5497
  const versionNamespace = options.cacheVersionNamespace ?? "";
4904
5498
  const db = new DatabaseSync(options.dbPath);
@@ -5084,7 +5678,142 @@ function createNullConsensusEngineClient(reason = NULL_CLIENT_DEFAULT_REASON) {
5084
5678
  };
5085
5679
  }
5086
5680
 
5681
+ // src/consensus/engine-options.ts
5682
+ var AGREEMENT_GATE_ENV_VAR = "VO_CONSENSUS_AGREEMENT_GATE";
5683
+ function isTruthyFlag(raw) {
5684
+ if (raw === void 0) return false;
5685
+ const v = raw.trim().toLowerCase();
5686
+ return v === "1" || v === "true" || v === "yes" || v === "on";
5687
+ }
5688
+ function resolveAgreementGate(args) {
5689
+ const env = args.env ?? process.env;
5690
+ const enabled = args.configEnabled === true || args.configEnabled === void 0 && isTruthyFlag(env[AGREEMENT_GATE_ENV_VAR]);
5691
+ if (!enabled) return void 0;
5692
+ return {
5693
+ enabled: true,
5694
+ ...args.probeSize !== void 0 ? { probe_size: args.probeSize } : {},
5695
+ ...args.modalAgreementThreshold !== void 0 ? { modal_agreement_threshold: args.modalAgreementThreshold } : {},
5696
+ ...args.minConfidence !== void 0 ? { min_confidence: args.minConfidence } : {}
5697
+ };
5698
+ }
5699
+ function buildSourceGroundedFields(config, defaultClassifier) {
5700
+ if (config === void 0) return {};
5701
+ const out = {};
5702
+ const grader = config.citation_grader;
5703
+ if (grader !== void 0 && grader.enabled === true) {
5704
+ const classifier = grader.classifier ?? defaultClassifier;
5705
+ if (classifier !== void 0) {
5706
+ out.citation_grader = {
5707
+ enabled: true,
5708
+ classifier,
5709
+ ...grader.escalate_below !== void 0 ? { escalate_below: grader.escalate_below } : {}
5710
+ };
5711
+ }
5712
+ }
5713
+ if (config.allow_url !== void 0) {
5714
+ out.allow_url = config.allow_url;
5715
+ }
5716
+ return out;
5717
+ }
5718
+ function mapFanOutDiagnostics(fd) {
5719
+ if (fd === void 0) return void 0;
5720
+ return {
5721
+ models_called: fd.models_called,
5722
+ panel_size: fd.panel_size,
5723
+ early_exit: fd.early_exit,
5724
+ refused: fd.refused
5725
+ };
5726
+ }
5727
+ function mapCitationGrade(cg) {
5728
+ if (cg === void 0) return void 0;
5729
+ return {
5730
+ ok: cg.ok,
5731
+ unsupported_count: cg.unsupported_count,
5732
+ uncertain_count: cg.uncertain_count,
5733
+ graded_claims: cg.graded_claims.map((g) => ({
5734
+ claim: g.claim,
5735
+ label: g.label,
5736
+ confidence: g.confidence,
5737
+ cited_ids: [...g.cited_ids]
5738
+ }))
5739
+ };
5740
+ }
5741
+
5742
+ // src/consensus/claim-classifier.ts
5743
+ var CLASSIFIER_SYSTEM_PROMPT = [
5744
+ "You are a citation-grading judge. You are given a CLAIM and the full text of the SOURCES it cites.",
5745
+ "Decide whether the SOURCES directly support the CLAIM. Judge ONLY against the supplied sources \u2014 do not use outside knowledge.",
5746
+ "pass = the sources clearly support the claim; fail = the sources contradict or do not support it; uncertain = the sources are insufficient or ambiguous.",
5747
+ "After a one-sentence justification, conclude with EXACTLY one of these lines:",
5748
+ " VERDICT: pass",
5749
+ " VERDICT: fail",
5750
+ " VERDICT: uncertain",
5751
+ "Then on a new line, exactly:",
5752
+ " CONFIDENCE: <0..1>",
5753
+ "Do not include any other VERDICT or CONFIDENCE lines."
5754
+ ].join("\n");
5755
+ function verdictToLabel(verdict) {
5756
+ switch (verdict) {
5757
+ case "pass":
5758
+ return "supported";
5759
+ case "fail":
5760
+ return "unsupported";
5761
+ case "uncertain":
5762
+ return "uncertain";
5763
+ default:
5764
+ return "uncertain";
5765
+ }
5766
+ }
5767
+ function clampConfidence(raw) {
5768
+ if (!Number.isFinite(raw)) return 0;
5769
+ if (raw < 0) return 0;
5770
+ if (raw > 1) return 1;
5771
+ return raw;
5772
+ }
5773
+ function buildClassifyPrompt(claim, sources) {
5774
+ return [
5775
+ "CLAIM:",
5776
+ claim,
5777
+ "",
5778
+ "SOURCES:",
5779
+ sources,
5780
+ "",
5781
+ "Does the cited SOURCES text support the CLAIM? Reply with your verdict (pass = supported, fail = unsupported, uncertain = insufficient)."
5782
+ ].join("\n");
5783
+ }
5784
+ function createClaimClassifier(model, options = {}) {
5785
+ return {
5786
+ async classify(args) {
5787
+ try {
5788
+ const result = await model.call({
5789
+ system_prompt: CLASSIFIER_SYSTEM_PROMPT,
5790
+ user_prompt: buildClassifyPrompt(args.claim, args.sources),
5791
+ ...options.signal !== void 0 ? { abort_signal: options.signal } : {}
5792
+ });
5793
+ if (result.verdict === "error") {
5794
+ return { label: "uncertain", confidence: 0 };
5795
+ }
5796
+ return {
5797
+ label: verdictToLabel(result.verdict),
5798
+ confidence: clampConfidence(result.confidence)
5799
+ };
5800
+ } catch {
5801
+ return { label: "uncertain", confidence: 0 };
5802
+ }
5803
+ }
5804
+ };
5805
+ }
5806
+
5087
5807
  // src/consensus/engine-client.ts
5808
+ function raceAbort(signal) {
5809
+ return new Promise((_, reject) => {
5810
+ const onAbort = () => {
5811
+ reject(new Error("__VO_MCP_CANCELLED__"));
5812
+ };
5813
+ if (signal.aborted) onAbort();
5814
+ else signal.addEventListener("abort", onAbort, { once: true });
5815
+ });
5816
+ }
5088
5817
  async function loadEngineModule() {
5089
5818
  try {
5090
5819
  const moduleName = ["@algosuite", "consensus-engine"].join("/");
@@ -5128,36 +5857,73 @@ function createEngineConsensusClient(options) {
5128
5857
  ...adapter,
5129
5858
  call: (args) => adapter.call({ ...args, abort_signal: signal })
5130
5859
  }));
5860
+ const agreementGate = resolveAgreementGate({
5861
+ ...options.agreement_gate_enabled !== void 0 ? { configEnabled: options.agreement_gate_enabled } : {},
5862
+ ...options.env !== void 0 ? { env: options.env } : {}
5863
+ });
5131
5864
  const engineOptions = {
5132
5865
  panel,
5133
- ...options.per_model_timeout_ms !== void 0 ? { per_model_timeout_ms: options.per_model_timeout_ms } : {}
5866
+ ...options.per_model_timeout_ms !== void 0 ? { per_model_timeout_ms: options.per_model_timeout_ms } : {},
5867
+ ...agreementGate !== void 0 ? { agreement_gate: agreementGate } : {}
5134
5868
  };
5135
- const result = signal === void 0 ? await engine.runConsensus(engineRequest, engineOptions) : await Promise.race([
5136
- engine.runConsensus(engineRequest, engineOptions),
5137
- new Promise((_, reject) => {
5138
- const onAbort = () => {
5139
- reject(new Error("__VO_MCP_CANCELLED__"));
5140
- };
5141
- if (signal.aborted) onAbort();
5142
- else signal.addEventListener("abort", onAbort, { once: true });
5143
- })
5144
- ]);
5145
- if (!result.ok || result.response === void 0) {
5146
- return {
5147
- ok: false,
5148
- reason: result.error?.reason ?? "engine-returned-not-ok"
5869
+ const sources = request.source_urls;
5870
+ const useSourceGrounded = sources !== void 0 && sources.length > 0 && typeof engine.runSourceGroundedConsensus === "function";
5871
+ let response;
5872
+ let sourceExtras;
5873
+ if (useSourceGrounded) {
5874
+ const [firstAdapter] = panel;
5875
+ const classifierOptions = signal === void 0 ? {} : { signal };
5876
+ const defaultClassifier = firstAdapter === void 0 ? void 0 : createClaimClassifier(firstAdapter, classifierOptions);
5877
+ const sgFields = buildSourceGroundedFields(request.source_grounded, defaultClassifier);
5878
+ const sgOptions = {
5879
+ ...engineOptions,
5880
+ source_urls: sources,
5881
+ ...sgFields.allow_url !== void 0 ? { allow_url: sgFields.allow_url } : {},
5882
+ ...sgFields.citation_grader !== void 0 ? { citation_grader: sgFields.citation_grader } : {}
5883
+ };
5884
+ if (typeof engine.runSourceGroundedConsensus !== "function") {
5885
+ return { ok: false, reason: "source-grounded engine missing runSourceGroundedConsensus" };
5886
+ }
5887
+ const sgCall = engine.runSourceGroundedConsensus(engineRequest, sgOptions);
5888
+ const sgResult = signal === void 0 ? await sgCall : await Promise.race([sgCall, raceAbort(signal)]);
5889
+ if (!sgResult.ok) {
5890
+ return { ok: false, reason: sgResult.reason };
5891
+ }
5892
+ response = sgResult.engine.response;
5893
+ sourceExtras = {
5894
+ ...sgResult.citation_grade !== void 0 ? { citation_grade: mapCitationGrade(sgResult.citation_grade) } : {},
5895
+ ...sgResult.low_confidence_sources !== void 0 ? { low_confidence_sources: sgResult.low_confidence_sources } : {},
5896
+ ...sgResult.escalation_required !== void 0 ? { escalation_required: sgResult.escalation_required } : {},
5897
+ ...sgResult.escalation_reason !== void 0 ? { escalation_reason: sgResult.escalation_reason } : {}
5149
5898
  };
5899
+ } else {
5900
+ const result = signal === void 0 ? await engine.runConsensus(engineRequest, engineOptions) : await Promise.race([engine.runConsensus(engineRequest, engineOptions), raceAbort(signal)]);
5901
+ if (!result.ok || result.response === void 0) {
5902
+ return {
5903
+ ok: false,
5904
+ reason: result.error?.reason ?? "engine-returned-not-ok"
5905
+ };
5906
+ }
5907
+ response = result.response;
5150
5908
  }
5151
5909
  return {
5152
5910
  ok: true,
5153
- synthesized_verdict: result.response.synthesized_verdict,
5154
- per_model_verdicts: result.response.per_model_verdicts,
5155
- degraded: result.response.degraded,
5156
- duration_ms: result.response.duration_ms,
5157
- engine_version: result.response.engine_version,
5158
- // Phase 2 Lane D-1 — forward escalation signal when present.
5159
- ...result.response.escalation_required !== void 0 ? { escalation_required: result.response.escalation_required } : {},
5160
- ...result.response.escalation_reason !== void 0 ? { escalation_reason: result.response.escalation_reason } : {}
5911
+ synthesized_verdict: response.synthesized_verdict,
5912
+ per_model_verdicts: response.per_model_verdicts,
5913
+ degraded: response.degraded,
5914
+ duration_ms: response.duration_ms,
5915
+ engine_version: response.engine_version,
5916
+ // Phase 2 Lane D-1 — forward escalation signal when present. The
5917
+ // source-grounded layer's own escalation (from the citation grade)
5918
+ // takes precedence when set, else the synthesizer's.
5919
+ ...sourceExtras?.escalation_required !== void 0 ? { escalation_required: sourceExtras.escalation_required } : response.escalation_required !== void 0 ? { escalation_required: response.escalation_required } : {},
5920
+ ...sourceExtras?.escalation_reason !== void 0 ? { escalation_reason: sourceExtras.escalation_reason } : response.escalation_reason !== void 0 ? { escalation_reason: response.escalation_reason } : {},
5921
+ // Feature 1 (agreement-gate) — fan-out diagnostics (additive telemetry).
5922
+ ...mapFanOutDiagnostics(response.fan_out_diagnostics) !== void 0 ? { fan_out_diagnostics: mapFanOutDiagnostics(response.fan_out_diagnostics) } : {},
5923
+ // Source-grounded additive outputs (Tier-4 features).
5924
+ ...useSourceGrounded ? { source_grounded: true } : {},
5925
+ ...sourceExtras?.citation_grade !== void 0 ? { citation_grade: sourceExtras.citation_grade } : {},
5926
+ ...sourceExtras?.low_confidence_sources !== void 0 ? { low_confidence_sources: sourceExtras.low_confidence_sources } : {}
5161
5927
  };
5162
5928
  } catch (err) {
5163
5929
  const message = err instanceof Error ? err.message : String(err);
@@ -5405,10 +6171,11 @@ function tryCreateMoatConsensusClientFromEnv(env = process.env, fetchFn) {
5405
6171
  }
5406
6172
 
5407
6173
  // src/cloud/login.ts
6174
+ init_credential_store();
5408
6175
  import { createServer as createServer2 } from "node:http";
5409
6176
  import { randomBytes } from "node:crypto";
5410
- import { spawn } from "node:child_process";
5411
- var DEFAULT_DASHBOARD_URL = "https://vo-dashboard.web.app";
6177
+ import { spawn as spawn2 } from "node:child_process";
6178
+ var DEFAULT_DASHBOARD_URL = "https://algosuite.ai";
5412
6179
  var DEFAULT_TIMEOUT_MS = 12e4;
5413
6180
  var MAX_BODY_BYTES = 16384;
5414
6181
  function processCapture(rawBody, expectedState, store) {
@@ -5453,17 +6220,17 @@ function captureHtml() {
5453
6220
  function defaultOpenBrowser(url) {
5454
6221
  const platform = process.platform;
5455
6222
  if (platform === "win32") {
5456
- spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
6223
+ spawn2("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
5457
6224
  } else if (platform === "darwin") {
5458
- spawn("open", [url], { detached: true, stdio: "ignore" }).unref();
6225
+ spawn2("open", [url], { detached: true, stdio: "ignore" }).unref();
5459
6226
  } else {
5460
- spawn("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
6227
+ spawn2("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
5461
6228
  }
5462
6229
  }
5463
6230
  async function runLogin(opts = {}) {
5464
- const dashboardUrl = (opts.dashboardUrl ?? DEFAULT_DASHBOARD_URL).replace(/\/+$/, "");
5465
- const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
5466
6231
  const env = opts.env ?? process.env;
6232
+ const dashboardUrl = (opts.dashboardUrl ?? env["VO_DASHBOARD_URL"]?.trim() ?? DEFAULT_DASHBOARD_URL).replace(/\/+$/, "") || DEFAULT_DASHBOARD_URL;
6233
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
5467
6234
  const log = opts.log ?? ((m) => console.error(m));
5468
6235
  const nowIso = opts.nowIso ?? (() => (/* @__PURE__ */ new Date()).toISOString());
5469
6236
  const openBrowser = opts.openBrowser ?? defaultOpenBrowser;
@@ -5558,6 +6325,7 @@ async function runLogin(opts = {}) {
5558
6325
  }
5559
6326
 
5560
6327
  // src/cloud/vo-credential-exchange.ts
6328
+ init_auth_token_source();
5561
6329
  async function exchangeForVoCredential(opts) {
5562
6330
  const base = opts.controlPlaneUrl.replace(/\/+$/, "");
5563
6331
  if (!base) return null;
@@ -5589,7 +6357,7 @@ async function exchangeForVoCredential(opts) {
5589
6357
  function defaultCacheDbPath() {
5590
6358
  const env = process.env["VO_MCP_DB_PATH"];
5591
6359
  if (env && env.length > 0) return env;
5592
- return join6(homedir4(), ".claude", "vo-mcp-cache.db");
6360
+ return join8(homedir6(), ".claude", "vo-mcp-cache.db");
5593
6361
  }
5594
6362
  async function probeEngineVersion() {
5595
6363
  try {