@algosuite/vo-mcp 0.2.0-beta.17 → 0.2.0-beta.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2064,7 +2064,11 @@ async function handleConsensusJudgment(deps, rawInput, signal) {
2064
2064
  ...engineResult.low_confidence_sources !== void 0 ? { low_confidence_sources: engineResult.low_confidence_sources } : {},
2065
2065
  // Escalation (from citation grade or human-tiebreak synthesizer).
2066
2066
  ...engineResult.escalation_required !== void 0 ? { escalation_required: engineResult.escalation_required } : {},
2067
- ...engineResult.escalation_reason !== void 0 ? { escalation_reason: engineResult.escalation_reason } : {}
2067
+ ...engineResult.escalation_reason !== void 0 ? { escalation_reason: engineResult.escalation_reason } : {},
2068
+ // Critique-uptake (2026-07-20 red-team fix) — the engine computes this
2069
+ // on every call; this spread closes the gap where the visibility report
2070
+ // was itself silently dropped at the payload boundary.
2071
+ ...engineResult.critique_uptake !== void 0 ? { critique_uptake: engineResult.critique_uptake } : {}
2068
2072
  };
2069
2073
  const envelope = {
2070
2074
  tool: TOOL_NAME4,
@@ -5397,6 +5401,195 @@ async function handleHqWhiteboardRead(_deps, rawInput, signal) {
5397
5401
  return jsonContent(await callWhiteboard("GET", input, signal));
5398
5402
  }
5399
5403
 
5404
+ // src/tools/skills/skill-corpus.ts
5405
+ import { existsSync as existsSync6, statSync as statSync5 } from "node:fs";
5406
+ import { dirname as dirname5, isAbsolute, join as join9, resolve as resolve2 } from "node:path";
5407
+
5408
+ // ../skill-registry/src/loader.ts
5409
+ import { readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
5410
+ import { join as join8 } from "node:path";
5411
+ var InvalidSkillFrontmatterError = class extends Error {
5412
+ constructor(skillFile, reason) {
5413
+ super(`Invalid frontmatter in ${skillFile}: ${reason}`);
5414
+ this.skillFile = skillFile;
5415
+ this.reason = reason;
5416
+ }
5417
+ skillFile;
5418
+ reason;
5419
+ name = "InvalidSkillFrontmatterError";
5420
+ };
5421
+ var FRONTMATTER_DELIMITER = "---";
5422
+ function parseFrontmatter(rawInput, sourcePath) {
5423
+ const raw = rawInput.replace(/\r\n/g, "\n");
5424
+ if (!raw.startsWith(`${FRONTMATTER_DELIMITER}
5425
+ `)) {
5426
+ throw new InvalidSkillFrontmatterError(sourcePath, 'file does not start with frontmatter delimiter "---"');
5427
+ }
5428
+ const afterFirst = raw.slice(FRONTMATTER_DELIMITER.length + 1);
5429
+ const closingIdx = afterFirst.indexOf(`
5430
+ ${FRONTMATTER_DELIMITER}
5431
+ `);
5432
+ if (closingIdx === -1) {
5433
+ throw new InvalidSkillFrontmatterError(sourcePath, 'missing closing frontmatter delimiter "---"');
5434
+ }
5435
+ const frontmatterText = afterFirst.slice(0, closingIdx);
5436
+ const body = afterFirst.slice(closingIdx + `
5437
+ ${FRONTMATTER_DELIMITER}
5438
+ `.length);
5439
+ let name = "";
5440
+ let description23 = "";
5441
+ for (const line of frontmatterText.split("\n")) {
5442
+ const trimmed = line.trim();
5443
+ if (trimmed.length === 0) continue;
5444
+ const colonIdx = trimmed.indexOf(":");
5445
+ if (colonIdx === -1) continue;
5446
+ const key = trimmed.slice(0, colonIdx).trim();
5447
+ const value = trimmed.slice(colonIdx + 1).trim();
5448
+ if (key === "name") name = value;
5449
+ else if (key === "description") description23 = value;
5450
+ }
5451
+ if (name.length === 0) {
5452
+ throw new InvalidSkillFrontmatterError(sourcePath, 'missing required field "name"');
5453
+ }
5454
+ if (description23.length === 0) {
5455
+ throw new InvalidSkillFrontmatterError(sourcePath, 'missing required field "description"');
5456
+ }
5457
+ return { name, description: description23, body };
5458
+ }
5459
+ function loadSkillsFromDir(skillsDir) {
5460
+ const entries = readdirSync5(skillsDir);
5461
+ const skills = [];
5462
+ for (const entry of entries) {
5463
+ const entryPath = join8(skillsDir, entry);
5464
+ let stat;
5465
+ try {
5466
+ stat = statSync4(entryPath);
5467
+ } catch {
5468
+ continue;
5469
+ }
5470
+ if (!stat.isDirectory()) continue;
5471
+ const skillFile = join8(entryPath, "SKILL.md");
5472
+ let raw;
5473
+ try {
5474
+ raw = readFileSync8(skillFile, "utf8");
5475
+ } catch {
5476
+ continue;
5477
+ }
5478
+ const { name, description: description23, body } = parseFrontmatter(raw, skillFile);
5479
+ skills.push({ name, description: description23, body, sourcePath: skillFile });
5480
+ }
5481
+ return [...skills].sort((a, b) => a.name.localeCompare(b.name));
5482
+ }
5483
+
5484
+ // src/tools/skills/skill-corpus.ts
5485
+ var LIST_TOOL_NAME = "vo_skill_list";
5486
+ var GET_TOOL_NAME = "vo_skill_get";
5487
+ var listDescription = "List the Algosuite skill corpus (name + trigger description for every skill). Call once near session start to learn which skills exist; then fetch the full instructions for a relevant skill with vo_skill_get. This is the same corpus Claude Code loads natively from .claude/skills \u2014 served over MCP so every vendor works from identical playbooks. Pass refresh:true to re-scan from disk.";
5488
+ var getDescription = "Fetch the full markdown instructions of one Algosuite skill by name. Follow the returned instructions for the current task the same way a native skill invocation would. Use vo_skill_list to discover skill names.";
5489
+ var listInputSchema = {
5490
+ type: "object",
5491
+ properties: {
5492
+ refresh: {
5493
+ type: "boolean",
5494
+ description: "Re-scan the skills directory instead of using the cached corpus."
5495
+ }
5496
+ },
5497
+ required: []
5498
+ };
5499
+ var getInputSchema = {
5500
+ type: "object",
5501
+ properties: {
5502
+ name: {
5503
+ type: "string",
5504
+ description: "Skill name exactly as returned by vo_skill_list."
5505
+ }
5506
+ },
5507
+ required: ["name"]
5508
+ };
5509
+ var MAX_WALK_UP_LEVELS = 8;
5510
+ var cachedCorpus = null;
5511
+ function resolveSkillsDir(env = process.env, startDir = process.cwd()) {
5512
+ const override = env.VO_SKILLS_DIR;
5513
+ if (typeof override === "string" && override.length > 0) {
5514
+ const abs = isAbsolute(override) ? override : resolve2(startDir, override);
5515
+ return existsSync6(abs) && statSync5(abs).isDirectory() ? abs : null;
5516
+ }
5517
+ let dir = resolve2(startDir);
5518
+ for (let i = 0; i < MAX_WALK_UP_LEVELS; i += 1) {
5519
+ const candidate = join9(dir, ".claude", "skills");
5520
+ if (existsSync6(candidate) && statSync5(candidate).isDirectory()) return candidate;
5521
+ const parent = dirname5(dir);
5522
+ if (parent === dir) break;
5523
+ dir = parent;
5524
+ }
5525
+ return null;
5526
+ }
5527
+ function loadCorpus() {
5528
+ const skillsDir = resolveSkillsDir();
5529
+ if (skillsDir === null) {
5530
+ return {
5531
+ skills: [],
5532
+ skillsDir: null,
5533
+ unavailableReason: "No skills directory found. Set VO_SKILLS_DIR or run inside a repo with .claude/skills."
5534
+ };
5535
+ }
5536
+ try {
5537
+ return { skills: loadSkillsFromDir(skillsDir), skillsDir, unavailableReason: null };
5538
+ } catch (err) {
5539
+ const message = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
5540
+ return { skills: [], skillsDir, unavailableReason: message };
5541
+ }
5542
+ }
5543
+ function getCorpus(refresh) {
5544
+ if (refresh || cachedCorpus === null) {
5545
+ cachedCorpus = loadCorpus();
5546
+ }
5547
+ return cachedCorpus;
5548
+ }
5549
+ async function handleSkillList(_deps, rawInput) {
5550
+ const input = rawInput ?? {};
5551
+ const refresh = input.refresh === true;
5552
+ const corpus = getCorpus(refresh);
5553
+ return jsonContent({
5554
+ corpus_available: corpus.unavailableReason === null,
5555
+ skills_dir: corpus.skillsDir,
5556
+ unavailable_reason: corpus.unavailableReason,
5557
+ skill_count: corpus.skills.length,
5558
+ skills: corpus.skills.map((s) => ({ name: s.name, description: s.description }))
5559
+ });
5560
+ }
5561
+ async function handleSkillGet(_deps, rawInput) {
5562
+ const input = rawInput ?? {};
5563
+ if (typeof input.name !== "string" || input.name.trim().length === 0) {
5564
+ throw invalidParams(GET_TOOL_NAME, 'input field "name" (non-empty string) is required');
5565
+ }
5566
+ const requested = input.name.trim();
5567
+ const corpus = getCorpus(false);
5568
+ if (corpus.unavailableReason !== null) {
5569
+ return jsonContent({
5570
+ corpus_available: false,
5571
+ unavailable_reason: corpus.unavailableReason,
5572
+ skill: null
5573
+ });
5574
+ }
5575
+ const skill = corpus.skills.find((s) => s.name === requested);
5576
+ if (skill === void 0) {
5577
+ throw invalidParams(
5578
+ GET_TOOL_NAME,
5579
+ `unknown skill "${requested}". Known skills: ${corpus.skills.map((s) => s.name).join(", ")}`
5580
+ );
5581
+ }
5582
+ return jsonContent({
5583
+ corpus_available: true,
5584
+ skill: {
5585
+ name: skill.name,
5586
+ description: skill.description,
5587
+ instructions: skill.body,
5588
+ source_path: skill.sourcePath
5589
+ }
5590
+ });
5591
+ }
5592
+
5400
5593
  // src/server.ts
5401
5594
  function buildToolRegistry() {
5402
5595
  return {
@@ -5607,6 +5800,22 @@ function buildToolRegistry() {
5607
5800
  inputSchema: readInputSchema
5608
5801
  },
5609
5802
  handler: handleHqWhiteboardRead
5803
+ },
5804
+ [LIST_TOOL_NAME]: {
5805
+ definition: {
5806
+ name: LIST_TOOL_NAME,
5807
+ description: listDescription,
5808
+ inputSchema: listInputSchema
5809
+ },
5810
+ handler: handleSkillList
5811
+ },
5812
+ [GET_TOOL_NAME]: {
5813
+ definition: {
5814
+ name: GET_TOOL_NAME,
5815
+ description: getDescription,
5816
+ inputSchema: getInputSchema
5817
+ },
5818
+ handler: handleSkillGet
5610
5819
  }
5611
5820
  };
5612
5821
  }
@@ -5665,7 +5874,7 @@ function listToolNames() {
5665
5874
  // src/cache/sqlite-cache.ts
5666
5875
  import { createHash as createHash3 } from "node:crypto";
5667
5876
  import { chmodSync as chmodSync3, mkdirSync as mkdirSync5 } from "node:fs";
5668
- import { dirname as dirname5 } from "node:path";
5877
+ import { dirname as dirname6 } from "node:path";
5669
5878
  import { DatabaseSync } from "node:sqlite";
5670
5879
 
5671
5880
  // src/cache/canonicalize.ts
@@ -5710,7 +5919,7 @@ function normalizeString(s) {
5710
5919
  function createSqliteCache(options) {
5711
5920
  const fileBacked = options.dbPath !== ":memory:";
5712
5921
  if (fileBacked) {
5713
- mkdirSync5(dirname5(options.dbPath), { recursive: true, mode: 448 });
5922
+ mkdirSync5(dirname6(options.dbPath), { recursive: true, mode: 448 });
5714
5923
  }
5715
5924
  const versionNamespace = options.cacheVersionNamespace ?? "";
5716
5925
  const db = new DatabaseSync(options.dbPath);
@@ -5910,6 +6119,30 @@ function createMetaModelCaller(options = {}) {
5910
6119
  }
5911
6120
  var callMetaWithMetrics = createMetaModelCaller();
5912
6121
 
6122
+ // src/consensus/consensus-panel.ts
6123
+ var VO_MCP_CONSENSUS_PANEL = {
6124
+ anthropic: "claude-opus-4-7",
6125
+ openai: "gpt-5",
6126
+ // gemini-2.5-FLASH (not -pro): flash accepts the default thinkingBudget=0 from
6127
+ // callGeminiWithMetrics; 2.5-pro REJECTS budget 0 ("only works in thinking mode").
6128
+ // Flash is also ~10x cheaper. 2026-06-02.
6129
+ google: "gemini-2.5-flash",
6130
+ deepseek: "deepseek-chat",
6131
+ // Muse Spark identity is owned by meta-model-caller.ts (single source of
6132
+ // truth for the meta slot); re-exported here so the panel stays complete.
6133
+ meta: META_CONSENSUS_MODEL
6134
+ };
6135
+ function getVoMcpConsensusPanel(panel = VO_MCP_CONSENSUS_PANEL) {
6136
+ for (const [provider, modelId] of Object.entries(panel)) {
6137
+ if (typeof modelId !== "string" || modelId.trim().length === 0) {
6138
+ throw new Error(
6139
+ `getVoMcpConsensusPanel: panel slot "${provider}" has a missing or blank model ID`
6140
+ );
6141
+ }
6142
+ }
6143
+ return panel;
6144
+ }
6145
+
5913
6146
  // src/consensus/engine-options.ts
5914
6147
  var AGREEMENT_GATE_ENV_VAR = "VO_CONSENSUS_AGREEMENT_GATE";
5915
6148
  function isTruthyFlag(raw) {
@@ -6178,6 +6411,10 @@ function createEngineConsensusClient(options) {
6178
6411
  ...mapFanOutDiagnostics(response.fan_out_diagnostics) !== void 0 ? { fan_out_diagnostics: mapFanOutDiagnostics(response.fan_out_diagnostics) } : {},
6179
6412
  // Stage A7-shadow — adaptive-vs-incumbent comparison (PII-free; present iff shadow on).
6180
6413
  ...mapShadowSynthesis(response.shadow_synthesis) !== void 0 ? { shadow_synthesis: mapShadowSynthesis(response.shadow_synthesis) } : {},
6414
+ // Critique-uptake (2026-07-20 red-team fix) — verifier-critique
6415
+ // visibility report; previously computed by the engine on every
6416
+ // call but dropped at this boundary.
6417
+ ...response.critique_uptake !== void 0 ? { critique_uptake: response.critique_uptake } : {},
6181
6418
  // Source-grounded additive outputs (Tier-4 features).
6182
6419
  ...useSourceGrounded ? { source_grounded: true } : {},
6183
6420
  ...sourceExtras?.citation_grade !== void 0 ? { citation_grade: sourceExtras.citation_grade } : {},
@@ -6193,20 +6430,7 @@ function createEngineConsensusClient(options) {
6193
6430
  }
6194
6431
  };
6195
6432
  }
6196
- var DEFAULT_MODELS = {
6197
- // These ids match the strategic-roadmap §4 `newsStandard` / `newsDeep` panel
6198
- // intent — current production model ids. Per handoff §C-3 these MUST come
6199
- // from `CONSENSUS_PANELS` in `functions-shared/shared-model-resolvers.ts`
6200
- // for V1; placeholder defaults here keep Phase 2 Lane A non-blocking.
6201
- anthropic: "claude-opus-4-7",
6202
- openai: "gpt-5",
6203
- // gemini-2.5-FLASH (not -pro): flash accepts the default thinkingBudget=0 from
6204
- // callGeminiWithMetrics; 2.5-pro REJECTS budget 0 ("only works in thinking mode").
6205
- // Flash is also ~10x cheaper. 2026-06-02.
6206
- google: "gemini-2.5-flash",
6207
- deepseek: "deepseek-chat",
6208
- meta: META_CONSENSUS_MODEL
6209
- };
6433
+ var DEFAULT_MODELS = getVoMcpConsensusPanel();
6210
6434
  function probeProviders(env = process.env) {
6211
6435
  const out = [];
6212
6436
  if ((env["ANTHROPIC_API_KEY"] ?? "").trim().length > 0) out.push("anthropic");