@algosuite/vo-mcp 0.2.0-beta.4 → 0.2.0-beta.8

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/bin/vo-mcp CHANGED
@@ -5,9 +5,10 @@
5
5
  * Usage:
6
6
  * vo-mcp # MCP stdio server (default)
7
7
  * vo-mcp install # one-command installer
8
+ * vo-mcp update # update MCP + runner package
8
9
  * vo-mcp login # credential login (browser loopback)
9
10
  * vo-mcp pair # device-code pairing (enter a code in the web)
10
- * vo-mcp runner # agent runner daemon
11
+ * vo-mcp runner # supervised agent runner
11
12
  * vo-mcp runner --install-autostart # register runner to start at login
12
13
  * vo-mcp runner --uninstall-autostart # remove auto-start registration
13
14
  */
@@ -17,6 +18,8 @@ const subcommand = process.argv[3];
17
18
 
18
19
  if (command === 'install') {
19
20
  import('../dist/install-cli.js');
21
+ } else if (command === 'update') {
22
+ import('../dist/update-cli.js');
20
23
  } else if (command === 'login') {
21
24
  import('../dist/login-cli.js');
22
25
  } else if (command === 'pair') {
@@ -28,9 +31,11 @@ if (command === 'install') {
28
31
  import('../dist/autostart-cli.js').then((m) => m.installAutostartCli());
29
32
  } else if (subcommand === '--uninstall-autostart') {
30
33
  import('../dist/autostart-cli.js').then((m) => m.uninstallAutostartCli());
31
- } else {
32
- // Bring-your-own runner daemon (bundled by scripts/bundle.mjs into dist/).
34
+ } else if (process.argv.includes('--once') || process.argv.includes('--status') || process.argv.includes('--version') || process.argv.includes('-v')) {
35
+ // Direct diagnostic/one-shot mode; remote maintenance uses the supervisor.
33
36
  import('../dist/runner-cli.js');
37
+ } else {
38
+ import('../dist/runner-supervisor.js');
34
39
  }
35
40
  } else {
36
41
  // Default: MCP stdio server
package/dist/cli.js CHANGED
@@ -1368,6 +1368,31 @@ var init_credential_store = __esm({
1368
1368
  }
1369
1369
  });
1370
1370
 
1371
+ // src/tools/memory/safe-memory-file.ts
1372
+ import { resolve, sep } from "node:path";
1373
+ function isSafeMemoryFileName(fileName) {
1374
+ return fileName.length <= 200 && fileName.trim() === fileName && !fileName.includes("/") && !fileName.includes("\\") && !fileName.includes(":") && SAFE_MEMORY_FILE_RE.test(fileName);
1375
+ }
1376
+ function resolveMemoryFilePath(memoryDir, fileName) {
1377
+ if (!isSafeMemoryFileName(fileName)) {
1378
+ throw new Error(`unsafe memory file_name: ${fileName.slice(0, 80)}`);
1379
+ }
1380
+ const root = resolve(memoryDir);
1381
+ const filePath = resolve(root, fileName);
1382
+ const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
1383
+ if (filePath !== root && !filePath.startsWith(rootPrefix)) {
1384
+ throw new Error(`memory file path escapes memory directory: ${fileName.slice(0, 80)}`);
1385
+ }
1386
+ return filePath;
1387
+ }
1388
+ var SAFE_MEMORY_FILE_RE;
1389
+ var init_safe_memory_file = __esm({
1390
+ "src/tools/memory/safe-memory-file.ts"() {
1391
+ "use strict";
1392
+ SAFE_MEMORY_FILE_RE = /^[A-Za-z0-9][A-Za-z0-9._ -]*\.md$/i;
1393
+ }
1394
+ });
1395
+
1371
1396
  // src/tools/memory/sync-config.ts
1372
1397
  var sync_config_exports = {};
1373
1398
  __export(sync_config_exports, {
@@ -1413,10 +1438,13 @@ async function pullMemory(controlPlaneUrl, token, memoryDir, fetchFn) {
1413
1438
  if (!data.ok || !Array.isArray(data.entries)) {
1414
1439
  throw new Error("GET /api/v1/agent-config/memory/me response missing ok=true or entries array");
1415
1440
  }
1441
+ const writes = data.entries.map((entry) => ({
1442
+ entry,
1443
+ filePath: resolveMemoryFilePath(memoryDir, entry.file_name)
1444
+ }));
1416
1445
  mkdirSync4(memoryDir, { recursive: true });
1417
1446
  const files = [];
1418
- for (const entry of data.entries) {
1419
- const filePath = join7(memoryDir, entry.file_name);
1447
+ for (const { entry, filePath } of writes) {
1420
1448
  writeFileSync3(filePath, entry.content, "utf8");
1421
1449
  files.push(entry.file_name);
1422
1450
  }
@@ -1428,7 +1456,7 @@ async function pushMemory(controlPlaneUrl, token, memoryDir, sessionId, fetchFn)
1428
1456
  }
1429
1457
  const localFiles = readdirSync4(memoryDir).filter((f) => f.endsWith(".md")).map((f) => ({
1430
1458
  file_name: f,
1431
- content: readFileSync7(join7(memoryDir, f), "utf8"),
1459
+ content: readFileSync7(resolveMemoryFilePath(memoryDir, f), "utf8"),
1432
1460
  entry_type: f === "MEMORY.md" ? "index" : "topic"
1433
1461
  }));
1434
1462
  if (localFiles.length === 0) {
@@ -1566,6 +1594,7 @@ var init_sync_config = __esm({
1566
1594
  "src/tools/memory/sync-config.ts"() {
1567
1595
  "use strict";
1568
1596
  init_common();
1597
+ init_safe_memory_file();
1569
1598
  TOOL_NAME22 = "vo_sync_config";
1570
1599
  inputSchema22 = {
1571
1600
  type: "object",
@@ -5023,6 +5052,8 @@ function suggestedHandoffPath(session_id, isoTimestamp) {
5023
5052
  }
5024
5053
 
5025
5054
  // src/tools/session/report-session-state.ts
5055
+ init_auth_token_source();
5056
+ init_credential_store();
5026
5057
  var TOOL_NAME19 = "vo_report_session_state";
5027
5058
  var VALID_AGENT_TYPES = ["claude-code", "codex", "cursor", "continue"];
5028
5059
  var MAX_GOAL_CHARS = 500;
@@ -5073,7 +5104,7 @@ var inputSchema19 = {
5073
5104
  required: ["operator_id", "session_id", "agent_type", "context_used_pct"],
5074
5105
  additionalProperties: false
5075
5106
  };
5076
- 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. Cloud-control-plane mode when VO_CONTROL_PLANE_URL + VO_CONTROL_PLANE_ADMIN_TOKEN + VO_TENANT_ID are set; auto-allocates the session on first report so interactive agents (Claude Code, Cursor, Codex, Continue) appear on the live fleet whiteboard. Stub-local fallback when cloud config is absent or fails. The response shape stays stable across modes (`backend_mode` field in the payload tells the caller which mode produced the verdict).";
5107
+ 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. Cloud-control-plane mode when VO_CONTROL_PLANE_URL plus a user/scoped VO credential (or legacy admin token) is available; auto-allocates the session on first report so interactive agents (Claude Code, Cursor, Codex, Continue) appear on the live fleet whiteboard. Stub-local fallback when cloud config is absent or fails. The response shape stays stable across modes (`backend_mode` field in the payload tells the caller which mode produced the verdict).";
5077
5108
  function isStringArray2(v, maxItems) {
5078
5109
  if (!Array.isArray(v)) return false;
5079
5110
  if (v.length > maxItems) return false;
@@ -5098,14 +5129,39 @@ function isToolInput19(v) {
5098
5129
  }
5099
5130
  return true;
5100
5131
  }
5101
- function getCloudConfig() {
5102
- const url = process.env["VO_CONTROL_PLANE_URL"];
5103
- const token = process.env["VO_CONTROL_PLANE_ADMIN_TOKEN"];
5104
- const tenant_id = process.env["VO_TENANT_ID"];
5105
- if (!url || !token || !tenant_id) return null;
5106
- return { url, token, tenant_id };
5132
+ async function fetchCloudIdentity(url, token, fetchFn) {
5133
+ try {
5134
+ const response = await fetchFn(`${url}/api/v1/auth/me`, {
5135
+ method: "GET",
5136
+ headers: {
5137
+ "Authorization": `Bearer ${token}`
5138
+ }
5139
+ });
5140
+ if (!response.ok) return null;
5141
+ const data = await response.json();
5142
+ if (!data.ok || !data.provisioned || !data.operator_id || !data.tenant_id) return null;
5143
+ return { operator_id: data.operator_id, tenant_id: data.tenant_id };
5144
+ } catch {
5145
+ return null;
5146
+ }
5147
+ }
5148
+ async function getCloudConfig(fetchFn = fetch) {
5149
+ const url = process.env["VO_CONTROL_PLANE_URL"]?.trim();
5150
+ if (!url) return null;
5151
+ const tokenSource = createAuthTokenSourceFromEnv(
5152
+ process.env,
5153
+ fetchFn,
5154
+ () => readStoredCredential(process.env)
5155
+ );
5156
+ const token = await tokenSource?.getToken();
5157
+ if (!token) return null;
5158
+ const tenant_id = process.env["VO_TENANT_ID"]?.trim();
5159
+ if (tenant_id) return { url, token, tenant_id };
5160
+ const identity = await fetchCloudIdentity(url, token, fetchFn);
5161
+ if (!identity) return null;
5162
+ return { url, token, tenant_id: identity.tenant_id, operator_id: identity.operator_id };
5107
5163
  }
5108
- async function tryCloudReportState(cloud, input) {
5164
+ async function tryCloudReportState(cloud, input, fetchFn = fetch) {
5109
5165
  try {
5110
5166
  const reportBody = {
5111
5167
  context_used_pct: input.context_used_pct
@@ -5118,7 +5174,7 @@ async function tryCloudReportState(cloud, input) {
5118
5174
  reportBody["recent_tool_uses"] = input.recent_tool_uses;
5119
5175
  }
5120
5176
  const reportUrl = `${cloud.url}/api/v1/session/${input.session_id}/report-state`;
5121
- let response = await fetch(reportUrl, {
5177
+ let response = await fetchFn(reportUrl, {
5122
5178
  method: "POST",
5123
5179
  headers: {
5124
5180
  "Content-Type": "application/json",
@@ -5128,7 +5184,7 @@ async function tryCloudReportState(cloud, input) {
5128
5184
  });
5129
5185
  if (response.status === 404) {
5130
5186
  const allocateBody = {
5131
- operator_id: input.operator_id,
5187
+ operator_id: cloud.operator_id ?? input.operator_id,
5132
5188
  tenant_id: cloud.tenant_id,
5133
5189
  agent_type: input.agent_type,
5134
5190
  current_goal: input.current_goal ?? "Interactive session"
@@ -5137,7 +5193,7 @@ async function tryCloudReportState(cloud, input) {
5137
5193
  allocateBody["initial_context_used_pct"] = input.context_used_pct;
5138
5194
  }
5139
5195
  const allocateUrl = `${cloud.url}/api/v1/session`;
5140
- const allocateResponse = await fetch(allocateUrl, {
5196
+ const allocateResponse = await fetchFn(allocateUrl, {
5141
5197
  method: "POST",
5142
5198
  headers: {
5143
5199
  "Content-Type": "application/json",
@@ -5148,7 +5204,10 @@ async function tryCloudReportState(cloud, input) {
5148
5204
  if (!allocateResponse.ok) {
5149
5205
  return null;
5150
5206
  }
5151
- response = await fetch(reportUrl, {
5207
+ const allocateData = await allocateResponse.json();
5208
+ const retrySessionId = typeof allocateData.session?.session_id === "string" && allocateData.session.session_id.length > 0 ? allocateData.session.session_id : input.session_id;
5209
+ const retryReportUrl = `${cloud.url}/api/v1/session/${retrySessionId}/report-state`;
5210
+ response = await fetchFn(retryReportUrl, {
5152
5211
  method: "POST",
5153
5212
  headers: {
5154
5213
  "Content-Type": "application/json",
@@ -5187,7 +5246,7 @@ async function handleReportSessionState(deps, rawInput, _signal) {
5187
5246
  `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}).`
5188
5247
  );
5189
5248
  }
5190
- const cloud = getCloudConfig();
5249
+ const cloud = await getCloudConfig();
5191
5250
  if (cloud !== null) {
5192
5251
  const cloudPayload = await tryCloudReportState(cloud, rawInput);
5193
5252
  if (cloudPayload !== null) {
@@ -5433,6 +5492,107 @@ async function handleConciergeDispatch(deps, rawInput, _signal) {
5433
5492
 
5434
5493
  // src/server.ts
5435
5494
  init_sync_config();
5495
+
5496
+ // src/tools/memory/private-knowledge.ts
5497
+ init_common();
5498
+ var UPSERT_TOOL_NAME = "vo_private_knowledge_upsert";
5499
+ var CONTEXT_TOOL_NAME = "vo_private_knowledge_context";
5500
+ var KNOWLEDGE_CLASSES = ["memory", "skill", "doctrine", "hook", "command"];
5501
+ var PRECISION_CHAR_BUDGET = 12e3;
5502
+ var upsertInputSchema = {
5503
+ type: "object",
5504
+ properties: {
5505
+ knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES },
5506
+ source_path: { type: "string", description: "Stable private source identifier; not exposed to other users." },
5507
+ title: { type: "string", description: 'Descriptive, retrieval-friendly title (e.g. "AlgoTax OCR redaction architecture", not "notes") \u2014 retrieval matches on it.' },
5508
+ content: { type: "string", description: "Private knowledge text to store server-side. Keep each entry tight and focused (~1-3 pages, under ~12k chars); split larger corpora into separate entries." }
5509
+ },
5510
+ required: ["knowledge_class", "source_path", "title", "content"],
5511
+ additionalProperties: false
5512
+ };
5513
+ var contextInputSchema = {
5514
+ type: "object",
5515
+ properties: {
5516
+ query: { type: "string" },
5517
+ limit: { type: "number", minimum: 1, maximum: 50 },
5518
+ knowledge_class: { type: "string", enum: KNOWLEDGE_CLASSES }
5519
+ },
5520
+ required: ["query"],
5521
+ additionalProperties: false
5522
+ };
5523
+ var upsertDescription = "Uploads or refreshes the authenticated operator\u2019s private cloud knowledge. Works for Claude, Codex, Cursor, and cowork clients via the same vo-mcp login credential. Returns metadata only, not raw stored content. PRECISION DISCIPLINE: keep each entry tight and focused (~1-3 pages) with a descriptive retrieval-friendly title \u2014 retrieval surfaces whole entries, so small dense entries beat bulk dumps. Split large corpora into focused entries, then run a retrieval self-test via vo_private_knowledge_context before relying on the knowledge.";
5524
+ var contextDescription = "Retrieves prompt-ready private knowledge context for the authenticated operator. Returns snippets/context only; no raw corpus download. Also the retrieval self-test surface: after upserting critical knowledge, query for it here and confirm the entry surfaces before trusting it in downstream work.";
5525
+ function isKnowledgeClass(value) {
5526
+ return typeof value === "string" && KNOWLEDGE_CLASSES.includes(value);
5527
+ }
5528
+ function isUpsertInput(value) {
5529
+ if (typeof value !== "object" || value === null) return false;
5530
+ const input = value;
5531
+ return isKnowledgeClass(input["knowledge_class"]) && typeof input["source_path"] === "string" && typeof input["title"] === "string" && typeof input["content"] === "string";
5532
+ }
5533
+ function isContextInput(value) {
5534
+ if (typeof value !== "object" || value === null) return false;
5535
+ const input = value;
5536
+ if (typeof input["query"] !== "string") return false;
5537
+ if (input["limit"] !== void 0 && typeof input["limit"] !== "number") return false;
5538
+ if (input["knowledge_class"] !== void 0 && !isKnowledgeClass(input["knowledge_class"])) return false;
5539
+ return true;
5540
+ }
5541
+ async function getCloudAuth(fetchFn) {
5542
+ const controlPlaneUrl = process.env["VO_CONTROL_PLANE_URL"]?.replace(/\/+$/, "");
5543
+ if (!controlPlaneUrl) {
5544
+ return { ok: false, reason: "VO_CONTROL_PLANE_URL not set \u2014 cloud mode disabled" };
5545
+ }
5546
+ const { createAuthTokenSourceFromEnv: createAuthTokenSourceFromEnv2 } = await Promise.resolve().then(() => (init_auth_token_source(), auth_token_source_exports));
5547
+ const { readStoredCredential: readStoredCredential2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
5548
+ const tokenSource = createAuthTokenSourceFromEnv2(process.env, fetchFn, () => readStoredCredential2(process.env));
5549
+ if (!tokenSource) return { ok: false, reason: "No auth configured. Run `vo-mcp login`." };
5550
+ const token = await tokenSource.getToken();
5551
+ if (!token) return { ok: false, reason: "Failed to obtain auth token. Run `vo-mcp login` again." };
5552
+ return { ok: true, controlPlaneUrl, token };
5553
+ }
5554
+ async function callPrivateKnowledge(path3, body, fetchFn) {
5555
+ const auth = await getCloudAuth(fetchFn);
5556
+ if (!auth.ok) return { ok: false, reason: auth.reason };
5557
+ const response = await fetchFn(`${auth.controlPlaneUrl}${path3}`, {
5558
+ method: "POST",
5559
+ headers: {
5560
+ authorization: `Bearer ${auth.token}`,
5561
+ "content-type": "application/json"
5562
+ },
5563
+ body: JSON.stringify(body)
5564
+ });
5565
+ const text = await response.text();
5566
+ const parsed = text ? JSON.parse(text) : null;
5567
+ if (response.status < 200 || response.status >= 300) {
5568
+ return { ok: false, status: response.status, response: parsed ?? text };
5569
+ }
5570
+ return parsed;
5571
+ }
5572
+ async function handlePrivateKnowledgeUpsert(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
5573
+ if (!isUpsertInput(rawInput)) {
5574
+ throw invalidParams(UPSERT_TOOL_NAME, "expected { knowledge_class, source_path, title, content }.");
5575
+ }
5576
+ const payload = await callPrivateKnowledge("/api/v1/knowledge/private", rawInput, fetchFn);
5577
+ const envelope = {
5578
+ tool: UPSERT_TOOL_NAME,
5579
+ schema_version: 1,
5580
+ payload
5581
+ };
5582
+ if (rawInput.content.length > PRECISION_CHAR_BUDGET) {
5583
+ envelope.precision_note = `content is ${rawInput.content.length} chars (> ${PRECISION_CHAR_BUDGET}). Tight 1-3 page entries retrieve better \u2014 consider splitting into focused entries, then re-test retrieval via ${CONTEXT_TOOL_NAME}.`;
5584
+ }
5585
+ return jsonContent(envelope);
5586
+ }
5587
+ async function handlePrivateKnowledgeContext(_deps, rawInput, _signal, fetchFn = globalThis.fetch) {
5588
+ if (!isContextInput(rawInput)) {
5589
+ throw invalidParams(CONTEXT_TOOL_NAME, "expected { query, optional limit, optional knowledge_class }.");
5590
+ }
5591
+ const payload = await callPrivateKnowledge("/api/v1/knowledge/private/context", rawInput, fetchFn);
5592
+ return jsonContent({ tool: CONTEXT_TOOL_NAME, schema_version: 1, payload });
5593
+ }
5594
+
5595
+ // src/server.ts
5436
5596
  function buildToolRegistry() {
5437
5597
  return {
5438
5598
  [TOOL_NAME]: {
@@ -5610,6 +5770,22 @@ function buildToolRegistry() {
5610
5770
  inputSchema: inputSchema22
5611
5771
  },
5612
5772
  handler: handleSyncConfig
5773
+ },
5774
+ [UPSERT_TOOL_NAME]: {
5775
+ definition: {
5776
+ name: UPSERT_TOOL_NAME,
5777
+ description: upsertDescription,
5778
+ inputSchema: upsertInputSchema
5779
+ },
5780
+ handler: handlePrivateKnowledgeUpsert
5781
+ },
5782
+ [CONTEXT_TOOL_NAME]: {
5783
+ definition: {
5784
+ name: CONTEXT_TOOL_NAME,
5785
+ description: contextDescription,
5786
+ inputSchema: contextInputSchema
5787
+ },
5788
+ handler: handlePrivateKnowledgeContext
5613
5789
  }
5614
5790
  };
5615
5791
  }
@@ -5901,6 +6077,64 @@ function createNullConsensusEngineClient(reason = NULL_CLIENT_DEFAULT_REASON) {
5901
6077
  };
5902
6078
  }
5903
6079
 
6080
+ // src/consensus/meta-model-caller.ts
6081
+ var META_MODEL_API_BASE_URL = "https://api.meta.ai/v1";
6082
+ var META_CONSENSUS_MODEL = "muse-spark-1.1";
6083
+ var META_MODEL_API_KEY_ENV = "MODEL_API_KEY";
6084
+ var META_MODEL_API_KEY_ALIAS = "META_API";
6085
+ function resolveMetaKey(env) {
6086
+ return String(env[META_MODEL_API_KEY_ENV] || env[META_MODEL_API_KEY_ALIAS] || "").trim();
6087
+ }
6088
+ function positiveMaxTokens(value) {
6089
+ const parsed = Math.floor(Number(value));
6090
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 2048;
6091
+ }
6092
+ function createMetaModelCaller(options = {}) {
6093
+ const fetchImpl = options.fetchImpl ?? fetch;
6094
+ const envSource = options.envSource ?? process.env;
6095
+ const reasoningEffort = options.reasoningEffort ?? "high";
6096
+ return async function callMetaWithMetrics2(prompt, systemPrompt, model, maxTokens, _privacyOptions, signal) {
6097
+ const key = resolveMetaKey(envSource);
6098
+ if (!key) throw new Error(`Missing ${META_MODEL_API_KEY_ENV} for Meta Model API`);
6099
+ const messages = [
6100
+ ...systemPrompt ? [{ role: "system", content: systemPrompt }] : [],
6101
+ { role: "user", content: prompt }
6102
+ ];
6103
+ const response = await fetchImpl(`${META_MODEL_API_BASE_URL}/chat/completions`, {
6104
+ method: "POST",
6105
+ headers: {
6106
+ Authorization: `Bearer ${key}`,
6107
+ "Content-Type": "application/json"
6108
+ },
6109
+ body: JSON.stringify({
6110
+ model: model || META_CONSENSUS_MODEL,
6111
+ messages,
6112
+ max_tokens: positiveMaxTokens(maxTokens),
6113
+ reasoning_effort: reasoningEffort
6114
+ }),
6115
+ signal
6116
+ });
6117
+ const payload = await response.json();
6118
+ if (!response.ok) {
6119
+ const message = String(payload.error?.message || response.statusText || "request failed").slice(0, 500);
6120
+ throw Object.assign(new Error(`Meta Model API ${response.status}: ${message}`), { status: response.status });
6121
+ }
6122
+ const content = payload.choices?.[0]?.message?.content;
6123
+ if (typeof content !== "string" || !content.trim()) {
6124
+ throw new Error(`Meta Model API returned no text (finish=${payload.choices?.[0]?.finish_reason || "unknown"})`);
6125
+ }
6126
+ const inputTokens = Number(payload.usage?.prompt_tokens || 0);
6127
+ const outputTokens = Number(payload.usage?.completion_tokens || 0);
6128
+ return {
6129
+ content,
6130
+ inputTokens,
6131
+ outputTokens,
6132
+ totalTokens: Number(payload.usage?.total_tokens || inputTokens + outputTokens)
6133
+ };
6134
+ };
6135
+ }
6136
+ var callMetaWithMetrics = createMetaModelCaller();
6137
+
5904
6138
  // src/consensus/engine-options.ts
5905
6139
  var AGREEMENT_GATE_ENV_VAR = "VO_CONSENSUS_AGREEMENT_GATE";
5906
6140
  function isTruthyFlag(raw) {
@@ -6046,6 +6280,9 @@ function createClaimClassifier(model, options = {}) {
6046
6280
  };
6047
6281
  }
6048
6282
 
6283
+ // src/constants/consensus-models.ts
6284
+ var XAI_CONSENSUS_MODEL = "grok-4.5";
6285
+
6049
6286
  // src/consensus/engine-client.ts
6050
6287
  function raceAbort(signal) {
6051
6288
  return new Promise((_, reject) => {
@@ -6195,7 +6432,9 @@ var DEFAULT_MODELS = {
6195
6432
  // callGeminiWithMetrics; 2.5-pro REJECTS budget 0 ("only works in thinking mode").
6196
6433
  // Flash is also ~10x cheaper. 2026-06-02.
6197
6434
  google: "gemini-2.5-flash",
6198
- deepseek: "deepseek-chat"
6435
+ deepseek: "deepseek-chat",
6436
+ xai: XAI_CONSENSUS_MODEL,
6437
+ meta: META_CONSENSUS_MODEL
6199
6438
  };
6200
6439
  function probeProviders(env = process.env) {
6201
6440
  const out = [];
@@ -6203,6 +6442,8 @@ function probeProviders(env = process.env) {
6203
6442
  if ((env["OPENAI_API_KEY"] ?? "").trim().length > 0) out.push("openai");
6204
6443
  if ((env["GOOGLE_API_KEY"] ?? "").trim().length > 0) out.push("google");
6205
6444
  if ((env["DEEPSEEK_API_KEY"] ?? "").trim().length > 0) out.push("deepseek");
6445
+ if ((env["XAI_API_KEY"] ?? "").trim().length > 0) out.push("xai");
6446
+ if ((env[META_MODEL_API_KEY_ENV] ?? "").trim().length > 0 || (env[META_MODEL_API_KEY_ALIAS] ?? "").trim().length > 0) out.push("meta");
6206
6447
  return out;
6207
6448
  }
6208
6449
  async function loadFactoryAndCallers(injectedEngine, injectedShared) {
@@ -6225,7 +6466,7 @@ async function loadFactoryAndCallers(injectedEngine, injectedShared) {
6225
6466
  return { ok: false, reason: ENGINE_UNAVAILABLE_REASONS.FUNCTIONS_SHARED_NOT_INSTALLED };
6226
6467
  }
6227
6468
  }
6228
- if (sharedMod === null || typeof sharedMod !== "object" || typeof sharedMod.callAnthropicWithMetrics !== "function" || typeof sharedMod.callOpenAIWithMetrics !== "function" || typeof sharedMod.callGeminiWithMetrics !== "function") {
6469
+ if (sharedMod === null || typeof sharedMod !== "object" || typeof sharedMod.callAnthropicWithMetrics !== "function" || typeof sharedMod.callOpenAIWithMetrics !== "function" || typeof sharedMod.callGeminiWithMetrics !== "function" || typeof sharedMod.callXAIWithMetrics !== "function") {
6229
6470
  return { ok: false, reason: ENGINE_UNAVAILABLE_REASONS.FUNCTIONS_SHARED_NOT_INSTALLED };
6230
6471
  }
6231
6472
  return {
@@ -6248,21 +6489,26 @@ async function tryCreateEngineConsensusClientFromEnvAsync(options = {}) {
6248
6489
  anthropic: loaded.shared.callAnthropicWithMetrics,
6249
6490
  openai: loaded.shared.callOpenAIWithMetrics,
6250
6491
  google: loaded.shared.callGeminiWithMetrics,
6251
- deepseek: loaded.shared.callDeepSeekWithMetrics
6492
+ deepseek: loaded.shared.callDeepSeekWithMetrics,
6493
+ xai: loaded.shared.callXAIWithMetrics,
6494
+ meta: options.metaCaller ?? callMetaWithMetrics
6252
6495
  };
6253
6496
  const modelByProvider = {
6254
6497
  anthropic: options.models?.anthropic ?? DEFAULT_MODELS.anthropic,
6255
6498
  openai: options.models?.openai ?? DEFAULT_MODELS.openai,
6256
6499
  google: options.models?.google ?? DEFAULT_MODELS.google,
6257
- deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek
6500
+ deepseek: options.models?.deepseek ?? DEFAULT_MODELS.deepseek,
6501
+ xai: options.models?.xai ?? DEFAULT_MODELS.xai,
6502
+ meta: options.models?.meta ?? DEFAULT_MODELS.meta
6258
6503
  };
6504
+ const adapterEnv = !(env[META_MODEL_API_KEY_ENV] ?? "").trim() && (env[META_MODEL_API_KEY_ALIAS] ?? "").trim() ? { ...env, [META_MODEL_API_KEY_ENV]: env[META_MODEL_API_KEY_ALIAS] } : env;
6259
6505
  const panel = [];
6260
6506
  for (const p of providers) {
6261
6507
  try {
6262
6508
  const adapter = loaded.engine.createAdapter(p, {
6263
6509
  model: modelByProvider[p],
6264
6510
  caller: callerByProvider[p],
6265
- envSource: env
6511
+ envSource: adapterEnv
6266
6512
  });
6267
6513
  panel.push(adapter);
6268
6514
  } catch {
@@ -6419,6 +6665,69 @@ function tryCreateMoatConsensusClientFromEnv(env = process.env, fetchFn) {
6419
6665
  });
6420
6666
  }
6421
6667
 
6668
+ // src/consensus/fallback-client.ts
6669
+ var MIN_VALID_LOCAL_VERDICTS = 2;
6670
+ var INSUFFICIENT_LOCAL_VERDICTS_REASON = "local-panel-insufficient-valid-verdicts";
6671
+ function createConsensusFallbackClient(primary, fallback, options = {}) {
6672
+ return {
6673
+ async run(request) {
6674
+ const primaryResult = await primary.run(request);
6675
+ if (request.signal?.aborted || !primaryResult.ok && primaryResult.reason === CANCELLED_REASON) {
6676
+ return primaryResult;
6677
+ }
6678
+ if (primaryResult.ok) {
6679
+ const validVerdicts = primaryResult.per_model_verdicts.filter(
6680
+ (verdict) => verdict.verdict !== "error"
6681
+ );
6682
+ if (validVerdicts.length >= MIN_VALID_LOCAL_VERDICTS) return primaryResult;
6683
+ options.onFallback?.(INSUFFICIENT_LOCAL_VERDICTS_REASON);
6684
+ return fallback.run(request);
6685
+ }
6686
+ options.onFallback?.(primaryResult.reason);
6687
+ return fallback.run(request);
6688
+ }
6689
+ };
6690
+ }
6691
+
6692
+ // src/consensus/local-credential-env.ts
6693
+ import { createRequire as createRequire2 } from "node:module";
6694
+ var require2 = createRequire2(import.meta.url);
6695
+ var KEY_SERVICE = "algosuite-vo";
6696
+ var KEYCHAIN_TARGETS = [
6697
+ { account: "anthropic-api-key", envVar: "ANTHROPIC_API_KEY" },
6698
+ { account: "openai-api-key", envVar: "OPENAI_API_KEY" },
6699
+ { account: "xai-api-key", envVar: "XAI_API_KEY" },
6700
+ { account: "meta-api-key", envVar: "MODEL_API_KEY" }
6701
+ ];
6702
+ function loadEntryCtor() {
6703
+ try {
6704
+ return require2("@napi-rs/keyring").Entry ?? null;
6705
+ } catch {
6706
+ return null;
6707
+ }
6708
+ }
6709
+ function readKey(EntryCtor, account) {
6710
+ try {
6711
+ return new EntryCtor(KEY_SERVICE, account).getPassword()?.trim() || null;
6712
+ } catch {
6713
+ return null;
6714
+ }
6715
+ }
6716
+ function withLocalConsensusCredentials(baseEnv = process.env, options = {}) {
6717
+ const env = { ...baseEnv };
6718
+ if (!env.OPENAI_API_KEY?.trim() && env.CODEX_API_KEY?.trim()) {
6719
+ env.OPENAI_API_KEY = env.CODEX_API_KEY;
6720
+ }
6721
+ const EntryCtor = options.EntryCtor === void 0 ? loadEntryCtor() : options.EntryCtor;
6722
+ if (!EntryCtor) return env;
6723
+ for (const target of KEYCHAIN_TARGETS) {
6724
+ if (env[target.envVar]?.trim()) continue;
6725
+ const key = readKey(EntryCtor, target.account);
6726
+ if (key) env[target.envVar] = key;
6727
+ }
6728
+ return env;
6729
+ }
6730
+
6422
6731
  // src/cloud/login.ts
6423
6732
  init_credential_store();
6424
6733
  import { createServer as createServer2 } from "node:http";
@@ -6484,7 +6793,7 @@ async function runLogin(opts = {}) {
6484
6793
  const nowIso = opts.nowIso ?? (() => (/* @__PURE__ */ new Date()).toISOString());
6485
6794
  const openBrowser = opts.openBrowser ?? defaultOpenBrowser;
6486
6795
  const state = randomBytes(32).toString("base64url");
6487
- return new Promise((resolve, reject) => {
6796
+ return new Promise((resolve2, reject) => {
6488
6797
  let settled = false;
6489
6798
  const finish = (err, result) => {
6490
6799
  if (settled) return;
@@ -6492,7 +6801,7 @@ async function runLogin(opts = {}) {
6492
6801
  clearTimeout(timer);
6493
6802
  server.close();
6494
6803
  if (err) reject(err);
6495
- else resolve(result);
6804
+ else resolve2(result);
6496
6805
  };
6497
6806
  const server = createServer2((req, res) => {
6498
6807
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -6682,11 +6991,23 @@ async function main() {
6682
6991
  const ratchets = createStubRatchetClient();
6683
6992
  const testModule = process.env["VO_MCP_TEST_ENGINE_MODULE"];
6684
6993
  const testClient = testModule !== void 0 && testModule.length > 0 ? await loadTestEngineClient(testModule) : null;
6994
+ const localEnv = withLocalConsensusCredentials();
6995
+ const localProviders = probeProviders(localEnv);
6996
+ for (const key of ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_API_KEY", "DEEPSEEK_API_KEY", "XAI_API_KEY", "MODEL_API_KEY"]) {
6997
+ if (!process.env[key] && localEnv[key]) process.env[key] = localEnv[key];
6998
+ }
6999
+ const localConsensus = tryCreateEngineConsensusClientFromEnv({ envSource: localEnv });
6685
7000
  const cloudConsensus = testClient ? null : tryCreateMoatConsensusClientFromEnv();
6686
- if (cloudConsensus) {
6687
- console.error("[vo-mcp] cloud consensus active \u2014 verdicts via vo-moat-plane (no local model keys)");
7001
+ let consensus = testClient ?? localConsensus;
7002
+ if (!testClient && cloudConsensus && localProviders.length >= 2) {
7003
+ console.error(`[vo-mcp] local-first consensus active (${localProviders.join(", ")}); cloud moat is fallback-only`);
7004
+ consensus = createConsensusFallbackClient(localConsensus, cloudConsensus, {
7005
+ onFallback: (reason) => console.error(`[vo-mcp] local consensus unavailable (${reason}); using cloud moat fallback`)
7006
+ });
7007
+ } else if (!testClient && cloudConsensus) {
7008
+ console.error("[vo-mcp] fewer than 2 linked local providers; cloud moat consensus active");
7009
+ consensus = cloudConsensus;
6688
7010
  }
6689
- const consensus = testClient ?? cloudConsensus ?? tryCreateEngineConsensusClientFromEnv();
6690
7011
  let adminCallables = null;
6691
7012
  try {
6692
7013
  adminCallables = buildAdminCallableClientFromEnv();