@algosuite/vo-mcp 0.2.0-beta.70 → 0.2.0-beta.72

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.
@@ -300,15 +300,21 @@ function normalizeClaudePermissionMode(value) {
300
300
  }
301
301
  return normalized;
302
302
  }
303
- function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, model, effort, maxBudgetUsd, researchHarness = false, env = process.env } = {}) {
303
+ function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, model, effort, maxBudgetUsd, researchHarness = false, toolPolicy = "default", structuredOutputSchema, env = process.env } = {}) {
304
304
  const effectivePermissionMode = normalizeClaudePermissionMode(permissionMode);
305
- const noWeb = String(env?.VO_CODE_RUNNER_NO_WEB ?? "").trim() === "1";
305
+ if (!["default", "skill_readonly", "frozen_inputs_only"].includes(toolPolicy)) {
306
+ throw new Error(`unsupported Claude tool policy "${toolPolicy}"`);
307
+ }
308
+ const frozenInputsOnly = toolPolicy === "frozen_inputs_only";
309
+ const skillReadonly = toolPolicy === "skill_readonly";
310
+ const restrictedSkill = frozenInputsOnly || skillReadonly;
311
+ const noWeb = frozenInputsOnly || String(env?.VO_CODE_RUNNER_NO_WEB ?? "").trim() === "1";
306
312
  const research = noWeb ? [] : VO_RESEARCH_TOOLS;
307
313
  const noWorkflow = noWeb || String(env?.VO_CODE_RUNNER_NO_WORKFLOW ?? "").trim() === "1";
308
- const workflow = researchHarness === true && !noWorkflow ? VO_WORKFLOW_TOOLS : [];
309
- const noConsensus = String(env?.VO_CODE_RUNNER_NO_CONSENSUS ?? "").trim() === "1";
314
+ const workflow = !restrictedSkill && researchHarness === true && !noWorkflow ? VO_WORKFLOW_TOOLS : [];
315
+ const noConsensus = frozenInputsOnly || String(env?.VO_CODE_RUNNER_NO_CONSENSUS ?? "").trim() === "1";
310
316
  const consensus = noConsensus ? [] : VO_CONSENSUS_TOOLS;
311
- const baseTools = effectivePermissionMode === DEFAULT_PERMISSION_MODE ? [VO_SESSION_STATE_TOOL, VO_HEADLESS_PNPM_TOOL, VO_HEADLESS_PNPM_FROM_DIR_TOOL] : [VO_SESSION_STATE_TOOL];
317
+ const baseTools = restrictedSkill ? [] : effectivePermissionMode === DEFAULT_PERMISSION_MODE ? [VO_SESSION_STATE_TOOL, VO_HEADLESS_PNPM_TOOL, VO_HEADLESS_PNPM_FROM_DIR_TOOL] : [VO_SESSION_STATE_TOOL];
312
318
  const allowedTools = [...baseTools, ...consensus, ...research, ...workflow].join(",");
313
319
  const args = [
314
320
  "-p",
@@ -320,6 +326,27 @@ function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, m
320
326
  "--allowedTools",
321
327
  allowedTools
322
328
  ];
329
+ if (restrictedSkill) {
330
+ const builtInTools = skillReadonly ? research.join(",") : "";
331
+ args.push(
332
+ "--tools",
333
+ builtInTools,
334
+ "--disable-slash-commands",
335
+ "--no-chrome",
336
+ "--no-session-persistence",
337
+ "--permission-prompts",
338
+ "none"
339
+ );
340
+ }
341
+ if (structuredOutputSchema !== void 0) {
342
+ if (!restrictedSkill || !structuredOutputSchema || typeof structuredOutputSchema !== "object" || Array.isArray(structuredOutputSchema)) {
343
+ throw new Error("structured output schema is allowed only for a restricted skill");
344
+ }
345
+ args.push("--json-schema", JSON.stringify(structuredOutputSchema));
346
+ }
347
+ if (frozenInputsOnly) {
348
+ args.push("--strict-mcp-config", "--safe-mode");
349
+ }
323
350
  if (Number.isInteger(maxTurns) && maxTurns > 0) {
324
351
  args.push("--max-turns", String(maxTurns));
325
352
  }
@@ -332,7 +359,9 @@ function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, m
332
359
  if (typeof maxBudgetUsd === "number" && maxBudgetUsd > 0) {
333
360
  args.push("--max-budget-usd", String(maxBudgetUsd));
334
361
  }
335
- args.push(...context7McpArgs(env));
362
+ if (!restrictedSkill) {
363
+ args.push(...context7McpArgs(env));
364
+ }
336
365
  return args;
337
366
  }
338
367
 
@@ -402,13 +431,15 @@ function extractModelUsage(evt) {
402
431
  // ../../scripts/virtual-office/code-runner/claude-result-event.mjs
403
432
  var CAPPED_RESULT_SUBTYPES = Object.freeze(["error_max_budget_usd", "error_max_turns"]);
404
433
  function buildResultEvent(evt) {
405
- const isError = Boolean(evt.is_error) || evt.subtype === "error_max_turns" || evt.subtype === "error_during_execution";
434
+ const isError = Boolean(evt.is_error) || evt.subtype === "error_max_budget_usd" || evt.subtype === "error_max_turns" || evt.subtype === "error_max_structured_output_retries" || evt.subtype === "error_during_execution";
406
435
  return {
407
436
  kind: "result",
408
437
  isError,
409
438
  costUsd: typeof evt.total_cost_usd === "number" ? evt.total_cost_usd : null,
410
439
  summary: typeof evt.result === "string" && evt.result.length > 0 ? evt.result : evt.subtype || (isError ? "error" : "completed"),
440
+ terminalSubtype: typeof evt.subtype === "string" ? evt.subtype : null,
411
441
  numTurns: typeof evt.num_turns === "number" ? evt.num_turns : null,
442
+ structuredOutput: Object.hasOwn(evt, "structured_output") ? evt.structured_output : null,
412
443
  tokenUsage: extractTokenUsage(evt),
413
444
  modelUsage: extractModelUsage(evt)
414
445
  };
@@ -521,9 +552,145 @@ function applyCliVersionFloor({ versionOutput, env = process.env, log = console.
521
552
  return { refused: !allowUnsafe, check, message };
522
553
  }
523
554
 
555
+ // ../../scripts/virtual-office/code-runner/claude-skill-capability.mjs
556
+ import { spawnSync as spawnSync6 } from "node:child_process";
557
+ import { accessSync, constants, realpathSync as realpathSync2, statSync } from "node:fs";
558
+ import path3 from "node:path";
559
+ var VALIDATED_CLAUDE_SKILL_VERSIONS = Object.freeze(["2.1.263"]);
560
+ var REQUIRED_CLAUDE_SKILL_HELP = Object.freeze([
561
+ "--allowedTools",
562
+ "--disable-slash-commands",
563
+ "--json-schema",
564
+ "--max-budget-usd",
565
+ "--no-chrome",
566
+ "--no-session-persistence",
567
+ "--output-format",
568
+ "--permission-mode",
569
+ "--permission-prompts",
570
+ "--safe-mode",
571
+ "--strict-mcp-config",
572
+ "--tools"
573
+ ]);
574
+ var PROBE_TIMEOUT_MS = 2e3;
575
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
576
+ var cache = /* @__PURE__ */ new Map();
577
+ function hasOption(help, option) {
578
+ const literal = option.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
579
+ return new RegExp(`(^|\\s)${literal}(?=\\s|,|=|<|$)`, "mu").test(help);
580
+ }
581
+ function assessClaudeSkillCapability({ versionOutput, helpOutput }) {
582
+ const version = parseCliVersion(versionOutput);
583
+ if (!version || !VALIDATED_CLAUDE_SKILL_VERSIONS.includes(version)) {
584
+ return { compatible: false, version, reason: "claude version is not in the validated restricted-skill manifest" };
585
+ }
586
+ const help = String(helpOutput ?? "");
587
+ const missing = REQUIRED_CLAUDE_SKILL_HELP.filter((option) => !hasOption(help, option));
588
+ if (missing.length > 0) {
589
+ return { compatible: false, version, reason: `claude help is missing required options: ${missing.join(", ")}` };
590
+ }
591
+ if (!/--permission-prompts[\s\S]{0,300}(?:"none"|\bnone\b)/mu.test(help) || !/--output-format[\s\S]{0,300}\bstream-json\b/mu.test(help)) {
592
+ return { compatible: false, version, reason: "claude help does not prove required none/stream-json values" };
593
+ }
594
+ return { compatible: true, version, reason: "validated restricted-skill CLI contract" };
595
+ }
596
+ function runProbe(bin, args, env, timeoutMs = PROBE_TIMEOUT_MS) {
597
+ if (process.platform === "win32") {
598
+ try {
599
+ const launch = buildWindowsClaudeLaunch({ bin, args, env });
600
+ return spawnSync6(launch.bin, launch.args, {
601
+ ...launch.spawnOptions,
602
+ env,
603
+ encoding: "utf8",
604
+ timeout: timeoutMs
605
+ });
606
+ } catch (error) {
607
+ return { status: null, stdout: "", stderr: "", error };
608
+ }
609
+ }
610
+ return spawnSync6(bin, args, { env, encoding: "utf8", timeout: timeoutMs, windowsHide: true });
611
+ }
612
+ function probeText(probe) {
613
+ return `${String(probe?.stdout ?? "")}
614
+ ${String(probe?.stderr ?? "")}`.trim();
615
+ }
616
+ function resolveClaudeBinaryIdentity(bin = "claude", env = process.env) {
617
+ let resolvedBin = String(bin);
618
+ try {
619
+ if (process.platform === "win32") {
620
+ resolvedBin = buildWindowsClaudeLaunch({ bin: resolvedBin, args: [], env }).bin;
621
+ } else if (!path3.isAbsolute(resolvedBin)) {
622
+ const found = String(env?.PATH ?? "").split(path3.delimiter).find((dir) => {
623
+ try {
624
+ accessSync(path3.join(dir, resolvedBin), constants.X_OK);
625
+ return true;
626
+ } catch {
627
+ return false;
628
+ }
629
+ });
630
+ if (found) resolvedBin = path3.join(found, resolvedBin);
631
+ }
632
+ const canonical = realpathSync2(resolvedBin);
633
+ const stat = statSync(canonical);
634
+ return { resolvedBin: canonical, fingerprint: `${canonical}\0${stat.size}\0${stat.mtimeMs}` };
635
+ } catch {
636
+ const pathValue2 = String(env?.PATH ?? env?.Path ?? "");
637
+ return { resolvedBin, fingerprint: `${resolvedBin}\0${pathValue2}` };
638
+ }
639
+ }
640
+ function probeClaudeSkillCapability({
641
+ bin = "claude",
642
+ env = process.env,
643
+ versionOutput,
644
+ spawnProbe = runProbe,
645
+ now = () => Date.now(),
646
+ cacheTtlMs = CACHE_TTL_MS,
647
+ timeoutMs = PROBE_TIMEOUT_MS,
648
+ freshIdentity = false,
649
+ resolveIdentity = resolveClaudeBinaryIdentity
650
+ } = {}) {
651
+ const identity = resolveIdentity(bin, env);
652
+ const key = identity.fingerprint;
653
+ const existing = cache.get(key);
654
+ if (!freshIdentity && versionOutput === void 0 && existing && now() - existing.at < cacheTtlMs) {
655
+ return existing.value;
656
+ }
657
+ const versionProbe = freshIdentity || versionOutput === void 0 ? spawnProbe(identity.resolvedBin, ["--version"], env, timeoutMs) : null;
658
+ if (versionProbe?.error || versionProbe && versionProbe.status !== 0) {
659
+ return {
660
+ compatible: false,
661
+ version: null,
662
+ resolvedBin: identity.resolvedBin,
663
+ reason: "claude version capability probe failed"
664
+ };
665
+ }
666
+ const effectiveVersionOutput = versionProbe ? probeText(versionProbe) : versionOutput;
667
+ const suppliedVersion = parseCliVersion(effectiveVersionOutput);
668
+ if (existing && now() - existing.at < cacheTtlMs && suppliedVersion === existing.value.version) {
669
+ return existing.value;
670
+ }
671
+ const helpProbe = spawnProbe(identity.resolvedBin, ["--help"], env, timeoutMs);
672
+ if (helpProbe?.error || helpProbe?.status !== 0) {
673
+ return {
674
+ compatible: false,
675
+ version: suppliedVersion,
676
+ resolvedBin: identity.resolvedBin,
677
+ reason: "claude help capability probe failed"
678
+ };
679
+ }
680
+ const assessed = assessClaudeSkillCapability({
681
+ versionOutput: effectiveVersionOutput,
682
+ helpOutput: probeText(helpProbe)
683
+ });
684
+ const value = { ...assessed, resolvedBin: identity.resolvedBin };
685
+ cache.set(key, { at: now(), value });
686
+ return value;
687
+ }
688
+
524
689
  // ../../scripts/virtual-office/code-runner/claude-auth-check.mjs
525
690
  var FIRST_VERSION_TIMEOUT_MS = 4500;
526
691
  var RETRY_VERSION_TIMEOUT_MS = 2e3;
692
+ var AUTH_PROBE_BUDGET_MS = 9500;
693
+ var MIN_SKILL_PROBE_MS = 250;
527
694
  function errorCode(error) {
528
695
  return String(error?.code || "").toUpperCase();
529
696
  }
@@ -549,9 +716,12 @@ async function checkClaudeAuth({
549
716
  spawnVersion = spawnClaudeSync,
550
717
  probeLogin = probeClaudeLoginState,
551
718
  getStoredKey = getAnthropicKey,
552
- env = process.env
719
+ probeSkillCapability = probeClaudeSkillCapability,
720
+ env = process.env,
721
+ now = () => Date.now()
553
722
  } = {}) {
554
723
  try {
724
+ const startedAt = now();
555
725
  let probe = spawnVersion(["--version"], {
556
726
  timeout: FIRST_VERSION_TIMEOUT_MS,
557
727
  encoding: "utf8",
@@ -585,23 +755,40 @@ async function checkClaudeAuth({
585
755
  }
586
756
  const floorGate = applyCliVersionFloor({ versionOutput: probe.stdout, env });
587
757
  if (floorGate.refused) {
588
- return { installed: true, authenticated: false, message: floorGate.message };
758
+ return {
759
+ installed: true,
760
+ authenticated: false,
761
+ version: floorGate.check.version ?? void 0,
762
+ skillCapable: false,
763
+ message: floorGate.message
764
+ };
589
765
  }
590
766
  const loggedIn = retriedAfterTimeout ? null : probeLogin();
591
767
  if (loggedIn === false) {
592
768
  return {
593
769
  installed: true,
594
770
  authenticated: false,
771
+ version: floorGate.check.version ?? void 0,
772
+ skillCapable: false,
595
773
  message: "claude CLI is installed but NOT logged in \u2014 its login is SEPARATE from the Claude Desktop app and the Claude Code IDE extension. Run: claude auth login (Claude subscription), then restart the runner."
596
774
  };
597
775
  }
776
+ const authTier = resolveClaudeAuthTier({ env, loggedIn, getStoredKey });
777
+ const remainingMs = AUTH_PROBE_BUDGET_MS - (now() - startedAt);
778
+ const skillCapability = loggedIn === true && remainingMs >= MIN_SKILL_PROBE_MS ? probeSkillCapability({
779
+ versionOutput: probe.stdout,
780
+ env,
781
+ timeoutMs: Math.min(2e3, remainingMs)
782
+ }) : { compatible: false };
598
783
  return {
599
784
  installed: true,
600
785
  authenticated: true,
786
+ version: floorGate.check.version ?? void 0,
787
+ skillCapable: skillCapability.compatible === true,
601
788
  // Dispatch-time billing signal, carried on the same probe that already
602
789
  // paid for the login read. Never sent for a non-authenticated result:
603
790
  // there is no tier without a working credential.
604
- authTier: resolveClaudeAuthTier({ env, loggedIn, getStoredKey }),
791
+ authTier,
605
792
  message: loggedIn === true ? "claude CLI installed and logged in (claude auth status)" : "claude binary found (login state unknown \u2014 auth check is best-effort)"
606
793
  };
607
794
  } catch (error) {
@@ -624,8 +811,8 @@ var ClaudeRunner = class {
624
811
  get binary() {
625
812
  return "claude";
626
813
  }
627
- buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness } = {}) {
628
- return buildClaudeArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness });
814
+ buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness, toolPolicy, structuredOutputSchema } = {}) {
815
+ return buildClaudeArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness, toolPolicy, structuredOutputSchema });
629
816
  }
630
817
  parseEvent(line) {
631
818
  return parseStreamEvent(line);
@@ -649,11 +836,14 @@ var ClaudeRunner = class {
649
836
  async checkAuth() {
650
837
  return checkClaudeAuth();
651
838
  }
839
+ checkSkillCapability({ bin = this.binary, env = process.env } = {}) {
840
+ return probeClaudeSkillCapability({ bin, env, freshIdentity: true });
841
+ }
652
842
  };
653
843
  var claudeRunner = new ClaudeRunner();
654
844
 
655
845
  // ../../scripts/virtual-office/code-runner/codex-runner.mjs
656
- import { spawnSync as spawnSync6 } from "node:child_process";
846
+ import { spawnSync as spawnSync7 } from "node:child_process";
657
847
  import { existsSync as existsSync3 } from "node:fs";
658
848
  import { win32 } from "node:path";
659
849
 
@@ -905,7 +1095,7 @@ function parseCodexEvent(line) {
905
1095
  return null;
906
1096
  }
907
1097
  var CodexRunner = class {
908
- constructor({ spawn: spawn2 = spawnSync6, resolveBinary = resolveCodexBinary, env = process.env } = {}) {
1098
+ constructor({ spawn: spawn2 = spawnSync7, resolveBinary = resolveCodexBinary, env = process.env } = {}) {
909
1099
  this.spawn = spawn2;
910
1100
  this.resolveBinary = resolveBinary;
911
1101
  this.env = env;
@@ -1016,7 +1206,7 @@ ${login.stderr || ""}`.trim();
1016
1206
  var codexRunner = new CodexRunner();
1017
1207
 
1018
1208
  // ../../scripts/virtual-office/code-runner/cursor-runner.mjs
1019
- import { spawnSync as spawnSync7 } from "node:child_process";
1209
+ import { spawnSync as spawnSync8 } from "node:child_process";
1020
1210
  function buildCursorArgs({ model, prompt } = {}) {
1021
1211
  const args = ["-p", "--output-format", "stream-json", "--force"];
1022
1212
  if (model) {
@@ -1121,7 +1311,7 @@ var CursorRunner = class {
1121
1311
  /** Best-effort: is `cursor-agent` on PATH? Never throws. */
1122
1312
  async checkAuth() {
1123
1313
  try {
1124
- const { status, error } = spawnSync7("cursor-agent", ["--version"], {
1314
+ const { status, error } = spawnSync8("cursor-agent", ["--version"], {
1125
1315
  shell: false,
1126
1316
  windowsHide: true,
1127
1317
  timeout: 3e3,