@probelabs/visor 0.1.132-ee → 0.1.137-ee

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.
Files changed (106) hide show
  1. package/dist/config/config-reloader.d.ts +1 -0
  2. package/dist/config/config-reloader.d.ts.map +1 -1
  3. package/dist/config/config-watcher.d.ts +1 -0
  4. package/dist/config/config-watcher.d.ts.map +1 -1
  5. package/dist/config.d.ts +4 -0
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/docs/ai-custom-tools-usage.md +37 -0
  8. package/dist/docs/ai-custom-tools.md +43 -1
  9. package/dist/docs/custom-tools.md +70 -1
  10. package/dist/docs/script.md +542 -27
  11. package/dist/docs/testing/cookbook.md +47 -0
  12. package/dist/examples/README.md +4 -0
  13. package/dist/examples/api-tools-ai-example.yaml +63 -0
  14. package/dist/examples/api-tools-inline-overlay-example.yaml +126 -0
  15. package/dist/examples/api-tools-library.yaml +18 -0
  16. package/dist/examples/api-tools-mcp-example.yaml +55 -0
  17. package/dist/examples/openapi/profiles-overlay-rename.yaml +3 -0
  18. package/dist/examples/openapi/users-api.json +91 -0
  19. package/dist/examples/openapi/users-overlay-rename.yaml +3 -0
  20. package/dist/generated/config-schema.d.ts +223 -74
  21. package/dist/generated/config-schema.d.ts.map +1 -1
  22. package/dist/generated/config-schema.json +251 -79
  23. package/dist/index.js +45128 -25001
  24. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  25. package/dist/providers/api-tool-executor.d.ts +43 -0
  26. package/dist/providers/api-tool-executor.d.ts.map +1 -0
  27. package/dist/providers/command-check-provider.d.ts.map +1 -1
  28. package/dist/providers/custom-tool-executor.d.ts +21 -0
  29. package/dist/providers/custom-tool-executor.d.ts.map +1 -1
  30. package/dist/providers/mcp-check-provider.d.ts.map +1 -1
  31. package/dist/providers/mcp-custom-sse-server.d.ts.map +1 -1
  32. package/dist/providers/script-check-provider.d.ts +18 -2
  33. package/dist/providers/script-check-provider.d.ts.map +1 -1
  34. package/dist/sdk/{check-provider-registry-7TCA3NSG.mjs → check-provider-registry-SCL4KP55.mjs} +9 -8
  35. package/dist/sdk/{check-provider-registry-KUDVEKAC.mjs → check-provider-registry-ULZRI3TC.mjs} +9 -8
  36. package/dist/sdk/{chunk-27RV5RR2.mjs → chunk-BRD36I43.mjs} +3 -3
  37. package/dist/sdk/{chunk-BGBXLPLL.mjs → chunk-E2N3U5HU.mjs} +5 -5
  38. package/dist/sdk/{chunk-XGI47XIH.mjs → chunk-F4K5WFSM.mjs} +1896 -1762
  39. package/dist/sdk/chunk-F4K5WFSM.mjs.map +1 -0
  40. package/dist/sdk/{chunk-2RNTEWOA.mjs → chunk-HQIVGUSV.mjs} +1896 -1762
  41. package/dist/sdk/chunk-HQIVGUSV.mjs.map +1 -0
  42. package/dist/sdk/{chunk-U3BLLEW3.mjs → chunk-KPRFDKQX.mjs} +329 -80
  43. package/dist/sdk/chunk-KPRFDKQX.mjs.map +1 -0
  44. package/dist/sdk/{chunk-VF6XIUE4.mjs → chunk-LW3INISN.mjs} +32 -1
  45. package/dist/sdk/{chunk-VF6XIUE4.mjs.map → chunk-LW3INISN.mjs.map} +1 -1
  46. package/dist/sdk/{chunk-VG7FWDC2.mjs → chunk-QUEWQWDX.mjs} +11 -4
  47. package/dist/sdk/{chunk-VG7FWDC2.mjs.map → chunk-QUEWQWDX.mjs.map} +1 -1
  48. package/dist/sdk/chunk-XKCER23W.mjs +1490 -0
  49. package/dist/sdk/chunk-XKCER23W.mjs.map +1 -0
  50. package/dist/sdk/{chunk-XJQKTK6V.mjs → chunk-ZUEQNCKB.mjs} +2 -2
  51. package/dist/sdk/{config-FMIIATKX.mjs → config-3UIU4TMP.mjs} +3 -3
  52. package/dist/sdk/{failure-condition-evaluator-PNONVBXD.mjs → failure-condition-evaluator-B5JJFYKU.mjs} +4 -4
  53. package/dist/sdk/{github-frontend-WR4S3NG5.mjs → github-frontend-VAWVSCNX.mjs} +4 -4
  54. package/dist/sdk/{host-TROSAWTE.mjs → host-67XTJ3BN.mjs} +2 -2
  55. package/dist/sdk/{host-U7V54J2H.mjs → host-TEQ7HKKH.mjs} +2 -2
  56. package/dist/sdk/{liquid-extensions-YDIIH33Q.mjs → liquid-extensions-PLBOMRLI.mjs} +3 -3
  57. package/dist/sdk/{routing-F4FOWVKF.mjs → routing-SEQYM4N6.mjs} +6 -6
  58. package/dist/sdk/schedule-tool-2COUUTF7.mjs +18 -0
  59. package/dist/sdk/{schedule-tool-handler-ULNF7TZW.mjs → schedule-tool-handler-5BDMLHS5.mjs} +10 -9
  60. package/dist/sdk/{schedule-tool-handler-VFES42DD.mjs → schedule-tool-handler-D7XX7WM4.mjs} +10 -9
  61. package/dist/sdk/sdk.d.mts +55 -2
  62. package/dist/sdk/sdk.d.ts +55 -2
  63. package/dist/sdk/sdk.js +2424 -539
  64. package/dist/sdk/sdk.js.map +1 -1
  65. package/dist/sdk/sdk.mjs +8 -7
  66. package/dist/sdk/sdk.mjs.map +1 -1
  67. package/dist/sdk/{trace-helpers-RDPXIN4S.mjs → trace-helpers-FAAGLXBI.mjs} +2 -2
  68. package/dist/sdk/{workflow-check-provider-4NFWH6YO.mjs → workflow-check-provider-K4MQQOYQ.mjs} +10 -9
  69. package/dist/sdk/{workflow-check-provider-5XS62BCJ.mjs → workflow-check-provider-WLA7LO56.mjs} +10 -9
  70. package/dist/sdk/workflow-check-provider-WLA7LO56.mjs.map +1 -0
  71. package/dist/state-machine/dispatch/execution-invoker.d.ts.map +1 -1
  72. package/dist/state-machine-execution-engine.d.ts.map +1 -1
  73. package/dist/test-runner/core/test-execution-wrapper.d.ts.map +1 -1
  74. package/dist/test-runner/index.d.ts.map +1 -1
  75. package/dist/test-runner/validator.d.ts.map +1 -1
  76. package/dist/types/config.d.ts +55 -2
  77. package/dist/types/config.d.ts.map +1 -1
  78. package/dist/utils/config-loader.d.ts +5 -0
  79. package/dist/utils/config-loader.d.ts.map +1 -1
  80. package/dist/utils/sandbox.d.ts +8 -0
  81. package/dist/utils/sandbox.d.ts.map +1 -1
  82. package/dist/utils/script-tool-environment.d.ts +90 -0
  83. package/dist/utils/script-tool-environment.d.ts.map +1 -0
  84. package/dist/utils/tool-resolver.d.ts +18 -0
  85. package/dist/utils/tool-resolver.d.ts.map +1 -0
  86. package/package.json +11 -4
  87. package/dist/sdk/chunk-2RNTEWOA.mjs.map +0 -1
  88. package/dist/sdk/chunk-U3BLLEW3.mjs.map +0 -1
  89. package/dist/sdk/chunk-XGI47XIH.mjs.map +0 -1
  90. /package/dist/sdk/{check-provider-registry-7TCA3NSG.mjs.map → check-provider-registry-SCL4KP55.mjs.map} +0 -0
  91. /package/dist/sdk/{check-provider-registry-KUDVEKAC.mjs.map → check-provider-registry-ULZRI3TC.mjs.map} +0 -0
  92. /package/dist/sdk/{chunk-27RV5RR2.mjs.map → chunk-BRD36I43.mjs.map} +0 -0
  93. /package/dist/sdk/{chunk-BGBXLPLL.mjs.map → chunk-E2N3U5HU.mjs.map} +0 -0
  94. /package/dist/sdk/{chunk-XJQKTK6V.mjs.map → chunk-ZUEQNCKB.mjs.map} +0 -0
  95. /package/dist/sdk/{config-FMIIATKX.mjs.map → config-3UIU4TMP.mjs.map} +0 -0
  96. /package/dist/sdk/{failure-condition-evaluator-PNONVBXD.mjs.map → failure-condition-evaluator-B5JJFYKU.mjs.map} +0 -0
  97. /package/dist/sdk/{github-frontend-WR4S3NG5.mjs.map → github-frontend-VAWVSCNX.mjs.map} +0 -0
  98. /package/dist/sdk/{host-TROSAWTE.mjs.map → host-67XTJ3BN.mjs.map} +0 -0
  99. /package/dist/sdk/{host-U7V54J2H.mjs.map → host-TEQ7HKKH.mjs.map} +0 -0
  100. /package/dist/sdk/{liquid-extensions-YDIIH33Q.mjs.map → liquid-extensions-PLBOMRLI.mjs.map} +0 -0
  101. /package/dist/sdk/{routing-F4FOWVKF.mjs.map → routing-SEQYM4N6.mjs.map} +0 -0
  102. /package/dist/sdk/{schedule-tool-handler-ULNF7TZW.mjs.map → schedule-tool-2COUUTF7.mjs.map} +0 -0
  103. /package/dist/sdk/{schedule-tool-handler-VFES42DD.mjs.map → schedule-tool-handler-5BDMLHS5.mjs.map} +0 -0
  104. /package/dist/sdk/{trace-helpers-RDPXIN4S.mjs.map → schedule-tool-handler-D7XX7WM4.mjs.map} +0 -0
  105. /package/dist/sdk/{workflow-check-provider-4NFWH6YO.mjs.map → trace-helpers-FAAGLXBI.mjs.map} +0 -0
  106. /package/dist/sdk/{workflow-check-provider-5XS62BCJ.mjs.map → workflow-check-provider-K4MQQOYQ.mjs.map} +0 -0
package/dist/sdk/sdk.js CHANGED
@@ -693,9 +693,10 @@ var require_package = __commonJS({
693
693
  format: "prettier --write src tests",
694
694
  "format:check": "prettier --check src tests",
695
695
  clean: "",
696
+ "clean:traces": "node scripts/clean-traces.js",
696
697
  prebuild: "npm run clean && node scripts/generate-config-schema.js",
697
- pretest: "node scripts/generate-config-schema.js && npm run build:cli",
698
- "pretest:unit": "node scripts/generate-config-schema.js && npm run build:cli",
698
+ pretest: "npm run clean:traces && node scripts/generate-config-schema.js && npm run build:cli",
699
+ "pretest:unit": "npm run clean:traces && node scripts/generate-config-schema.js && npm run build:cli",
699
700
  "test:with-build": "npm run build:cli && jest",
700
701
  "test:yaml": "node dist/index.js test --progress compact",
701
702
  "test:yaml:parallel": "node dist/index.js test --progress compact --max-parallel 4",
@@ -742,25 +743,31 @@ var require_package = __commonJS({
742
743
  homepage: "https://github.com/probelabs/visor#readme",
743
744
  dependencies: {
744
745
  "@actions/core": "^1.11.1",
746
+ "@apidevtools/swagger-parser": "^12.1.0",
745
747
  "@modelcontextprotocol/sdk": "^1.25.3",
746
748
  "@nyariv/sandboxjs": "github:probelabs/SandboxJS#f1c13b8eee98734a8ea024061eada4aa9a9ff2e9",
747
749
  "@octokit/action": "^8.0.2",
748
750
  "@octokit/auth-app": "^8.1.0",
749
751
  "@octokit/core": "^7.0.3",
750
752
  "@octokit/rest": "^22.0.0",
751
- "@probelabs/probe": "^0.6.0-rc245",
753
+ "@probelabs/probe": "^0.6.0-rc255",
752
754
  "@types/commander": "^2.12.0",
753
755
  "@types/uuid": "^10.0.0",
756
+ acorn: "^8.16.0",
757
+ "acorn-walk": "^8.3.5",
754
758
  ajv: "^8.17.1",
755
759
  "ajv-formats": "^3.0.1",
756
760
  "better-sqlite3": "^11.0.0",
757
761
  blessed: "^0.1.81",
758
762
  "cli-table3": "^0.6.5",
759
763
  commander: "^14.0.0",
764
+ deepmerge: "^4.3.1",
760
765
  dotenv: "^17.2.3",
761
766
  ignore: "^7.0.5",
762
767
  "js-yaml": "^4.1.0",
768
+ "jsonpath-plus": "^10.4.0",
763
769
  liquidjs: "^10.21.1",
770
+ minimatch: "^10.2.2",
764
771
  "node-cron": "^3.0.3",
765
772
  open: "^9.1.0",
766
773
  "simple-git": "^3.28.0",
@@ -938,19 +945,19 @@ function __getOrCreateNdjsonPath() {
938
945
  try {
939
946
  if (process.env.VISOR_TELEMETRY_SINK && process.env.VISOR_TELEMETRY_SINK !== "file")
940
947
  return null;
941
- const path29 = require("path");
942
- const fs25 = require("fs");
948
+ const path30 = require("path");
949
+ const fs26 = require("fs");
943
950
  if (process.env.VISOR_FALLBACK_TRACE_FILE) {
944
951
  __ndjsonPath = process.env.VISOR_FALLBACK_TRACE_FILE;
945
- const dir = path29.dirname(__ndjsonPath);
946
- if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
952
+ const dir = path30.dirname(__ndjsonPath);
953
+ if (!fs26.existsSync(dir)) fs26.mkdirSync(dir, { recursive: true });
947
954
  return __ndjsonPath;
948
955
  }
949
- const outDir = process.env.VISOR_TRACE_DIR || path29.join(process.cwd(), "output", "traces");
950
- if (!fs25.existsSync(outDir)) fs25.mkdirSync(outDir, { recursive: true });
956
+ const outDir = process.env.VISOR_TRACE_DIR || path30.join(process.cwd(), "output", "traces");
957
+ if (!fs26.existsSync(outDir)) fs26.mkdirSync(outDir, { recursive: true });
951
958
  if (!__ndjsonPath) {
952
959
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
953
- __ndjsonPath = path29.join(outDir, `${ts}.ndjson`);
960
+ __ndjsonPath = path30.join(outDir, `${ts}.ndjson`);
954
961
  }
955
962
  return __ndjsonPath;
956
963
  } catch {
@@ -959,11 +966,11 @@ function __getOrCreateNdjsonPath() {
959
966
  }
960
967
  function _appendRunMarker() {
961
968
  try {
962
- const fs25 = require("fs");
969
+ const fs26 = require("fs");
963
970
  const p = __getOrCreateNdjsonPath();
964
971
  if (!p) return;
965
972
  const line = { name: "visor.run", attributes: { started: true } };
966
- fs25.appendFileSync(p, JSON.stringify(line) + "\n", "utf8");
973
+ fs26.appendFileSync(p, JSON.stringify(line) + "\n", "utf8");
967
974
  } catch {
968
975
  }
969
976
  }
@@ -2311,6 +2318,36 @@ ${src}
2311
2318
  }
2312
2319
  return out;
2313
2320
  }
2321
+ async function compileAndRunAsync(sandbox, transformedCode, scope, opts = { injectLog: true, logPrefix: "[async-sandbox]" }) {
2322
+ const inject = opts?.injectLog === true;
2323
+ let safePrefix = String(opts?.logPrefix ?? "[async-sandbox]");
2324
+ safePrefix = safePrefix.replace(/[\r\n\t\0]/g, "").replace(/[`$\\]/g, "").replace(/\$\{/g, "").slice(0, 64);
2325
+ const scopeWithLog = inject ? {
2326
+ ...scope,
2327
+ log: (...args) => {
2328
+ try {
2329
+ console.log(safePrefix, ...args);
2330
+ } catch {
2331
+ }
2332
+ }
2333
+ } : scope;
2334
+ const codePreview = transformedCode.replace(/\s+/g, " ").trim().slice(0, 100);
2335
+ const contextInfo = safePrefix !== "[async-sandbox]" ? ` [${safePrefix}]` : "";
2336
+ let exec2;
2337
+ try {
2338
+ exec2 = sandbox.compileAsync(transformedCode);
2339
+ } catch (e) {
2340
+ const msg = e instanceof Error ? e.message : String(e);
2341
+ throw new Error(`async_sandbox_compile_error${contextInfo}: ${msg} | code: ${codePreview}`);
2342
+ }
2343
+ try {
2344
+ const result = await exec2(scopeWithLog).run();
2345
+ return result;
2346
+ } catch (e) {
2347
+ const msg = e instanceof Error ? e.message : String(e);
2348
+ throw new Error(`async_sandbox_execution_error${contextInfo}: ${msg} | code: ${codePreview}`);
2349
+ }
2350
+ }
2314
2351
  var import_sandboxjs;
2315
2352
  var init_sandbox = __esm({
2316
2353
  "src/utils/sandbox.ts"() {
@@ -3697,9 +3734,9 @@ function configureLiquidWithExtensions(liquid) {
3697
3734
  });
3698
3735
  liquid.registerFilter("get", (obj, pathExpr) => {
3699
3736
  if (obj == null) return void 0;
3700
- const path29 = typeof pathExpr === "string" ? pathExpr : String(pathExpr || "");
3701
- if (!path29) return obj;
3702
- const parts = path29.split(".");
3737
+ const path30 = typeof pathExpr === "string" ? pathExpr : String(pathExpr || "");
3738
+ if (!path30) return obj;
3739
+ const parts = path30.split(".");
3703
3740
  let cur = obj;
3704
3741
  for (const p of parts) {
3705
3742
  if (cur == null) return void 0;
@@ -3818,9 +3855,9 @@ function configureLiquidWithExtensions(liquid) {
3818
3855
  }
3819
3856
  }
3820
3857
  const defaultRole = typeof rolesCfg.default === "string" && rolesCfg.default.trim() ? rolesCfg.default.trim() : void 0;
3821
- const getNested = (obj, path29) => {
3822
- if (!obj || !path29) return void 0;
3823
- const parts = path29.split(".");
3858
+ const getNested = (obj, path30) => {
3859
+ if (!obj || !path30) return void 0;
3860
+ const parts = path30.split(".");
3824
3861
  let cur = obj;
3825
3862
  for (const p of parts) {
3826
3863
  if (cur == null) return void 0;
@@ -6372,8 +6409,8 @@ var init_dependency_gating = __esm({
6372
6409
  async function renderTemplateContent(checkId, checkConfig, reviewSummary) {
6373
6410
  try {
6374
6411
  const { createExtendedLiquid: createExtendedLiquid2 } = await Promise.resolve().then(() => (init_liquid_extensions(), liquid_extensions_exports));
6375
- const fs25 = await import("fs/promises");
6376
- const path29 = await import("path");
6412
+ const fs26 = await import("fs/promises");
6413
+ const path30 = await import("path");
6377
6414
  const schemaRaw = checkConfig.schema || "plain";
6378
6415
  const schema = typeof schemaRaw === "string" ? schemaRaw : "code-review";
6379
6416
  let templateContent;
@@ -6381,24 +6418,24 @@ async function renderTemplateContent(checkId, checkConfig, reviewSummary) {
6381
6418
  templateContent = String(checkConfig.template.content);
6382
6419
  } else if (checkConfig.template && checkConfig.template.file) {
6383
6420
  const file = String(checkConfig.template.file);
6384
- const resolved = path29.resolve(process.cwd(), file);
6385
- templateContent = await fs25.readFile(resolved, "utf-8");
6421
+ const resolved = path30.resolve(process.cwd(), file);
6422
+ templateContent = await fs26.readFile(resolved, "utf-8");
6386
6423
  } else if (schema && schema !== "plain") {
6387
6424
  const sanitized = String(schema).replace(/[^a-zA-Z0-9-]/g, "");
6388
6425
  if (sanitized) {
6389
6426
  const candidatePaths = [
6390
- path29.join(__dirname, "output", sanitized, "template.liquid"),
6427
+ path30.join(__dirname, "output", sanitized, "template.liquid"),
6391
6428
  // bundled: dist/output/
6392
- path29.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
6429
+ path30.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
6393
6430
  // source: output/
6394
- path29.join(process.cwd(), "output", sanitized, "template.liquid"),
6431
+ path30.join(process.cwd(), "output", sanitized, "template.liquid"),
6395
6432
  // fallback: cwd/output/
6396
- path29.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
6433
+ path30.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
6397
6434
  // fallback: cwd/dist/output/
6398
6435
  ];
6399
6436
  for (const p of candidatePaths) {
6400
6437
  try {
6401
- templateContent = await fs25.readFile(p, "utf-8");
6438
+ templateContent = await fs26.readFile(p, "utf-8");
6402
6439
  if (templateContent) break;
6403
6440
  } catch {
6404
6441
  }
@@ -6803,7 +6840,7 @@ async function processDiffWithOutline(diffContent) {
6803
6840
  }
6804
6841
  try {
6805
6842
  const originalProbePath = process.env.PROBE_PATH;
6806
- const fs25 = require("fs");
6843
+ const fs26 = require("fs");
6807
6844
  const possiblePaths = [
6808
6845
  // Relative to current working directory (most common in production)
6809
6846
  path6.join(process.cwd(), "node_modules/@probelabs/probe/bin/probe-binary"),
@@ -6814,7 +6851,7 @@ async function processDiffWithOutline(diffContent) {
6814
6851
  ];
6815
6852
  let probeBinaryPath;
6816
6853
  for (const candidatePath of possiblePaths) {
6817
- if (fs25.existsSync(candidatePath)) {
6854
+ if (fs26.existsSync(candidatePath)) {
6818
6855
  probeBinaryPath = candidatePath;
6819
6856
  break;
6820
6857
  }
@@ -7900,8 +7937,8 @@ ${schemaString}`);
7900
7937
  }
7901
7938
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
7902
7939
  try {
7903
- const fs25 = require("fs");
7904
- const path29 = require("path");
7940
+ const fs26 = require("fs");
7941
+ const path30 = require("path");
7905
7942
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7906
7943
  const provider = this.config.provider || "auto";
7907
7944
  const model = this.config.model || "default";
@@ -8015,20 +8052,20 @@ ${"=".repeat(60)}
8015
8052
  `;
8016
8053
  readableVersion += `${"=".repeat(60)}
8017
8054
  `;
8018
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path29.join(process.cwd(), "debug-artifacts");
8019
- if (!fs25.existsSync(debugArtifactsDir)) {
8020
- fs25.mkdirSync(debugArtifactsDir, { recursive: true });
8055
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path30.join(process.cwd(), "debug-artifacts");
8056
+ if (!fs26.existsSync(debugArtifactsDir)) {
8057
+ fs26.mkdirSync(debugArtifactsDir, { recursive: true });
8021
8058
  }
8022
- const debugFile = path29.join(
8059
+ const debugFile = path30.join(
8023
8060
  debugArtifactsDir,
8024
8061
  `prompt-${_checkName || "unknown"}-${timestamp}.json`
8025
8062
  );
8026
- fs25.writeFileSync(debugFile, debugJson, "utf-8");
8027
- const readableFile = path29.join(
8063
+ fs26.writeFileSync(debugFile, debugJson, "utf-8");
8064
+ const readableFile = path30.join(
8028
8065
  debugArtifactsDir,
8029
8066
  `prompt-${_checkName || "unknown"}-${timestamp}.txt`
8030
8067
  );
8031
- fs25.writeFileSync(readableFile, readableVersion, "utf-8");
8068
+ fs26.writeFileSync(readableFile, readableVersion, "utf-8");
8032
8069
  log(`
8033
8070
  \u{1F4BE} Full debug info saved to:`);
8034
8071
  log(` JSON: ${debugFile}`);
@@ -8061,8 +8098,8 @@ ${"=".repeat(60)}
8061
8098
  log(`\u{1F4E4} Response length: ${response.length} characters`);
8062
8099
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8063
8100
  try {
8064
- const fs25 = require("fs");
8065
- const path29 = require("path");
8101
+ const fs26 = require("fs");
8102
+ const path30 = require("path");
8066
8103
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8067
8104
  const agentAny2 = agent;
8068
8105
  let fullHistory = [];
@@ -8073,8 +8110,8 @@ ${"=".repeat(60)}
8073
8110
  } else if (agentAny2._messages) {
8074
8111
  fullHistory = agentAny2._messages;
8075
8112
  }
8076
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path29.join(process.cwd(), "debug-artifacts");
8077
- const sessionBase = path29.join(
8113
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path30.join(process.cwd(), "debug-artifacts");
8114
+ const sessionBase = path30.join(
8078
8115
  debugArtifactsDir,
8079
8116
  `session-${_checkName || "unknown"}-${timestamp}`
8080
8117
  );
@@ -8086,7 +8123,7 @@ ${"=".repeat(60)}
8086
8123
  schema: effectiveSchema,
8087
8124
  totalMessages: fullHistory.length
8088
8125
  };
8089
- fs25.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
8126
+ fs26.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
8090
8127
  let readable = `=============================================================
8091
8128
  `;
8092
8129
  readable += `COMPLETE AI SESSION HISTORY (AFTER RESPONSE)
@@ -8113,7 +8150,7 @@ ${"=".repeat(60)}
8113
8150
  `;
8114
8151
  readable += content + "\n";
8115
8152
  });
8116
- fs25.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
8153
+ fs26.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
8117
8154
  log(`\u{1F4BE} Complete session history saved:`);
8118
8155
  log(` - Contains ALL ${fullHistory.length} messages (prompts + responses)`);
8119
8156
  } catch (error) {
@@ -8122,11 +8159,11 @@ ${"=".repeat(60)}
8122
8159
  }
8123
8160
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8124
8161
  try {
8125
- const fs25 = require("fs");
8126
- const path29 = require("path");
8162
+ const fs26 = require("fs");
8163
+ const path30 = require("path");
8127
8164
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8128
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path29.join(process.cwd(), "debug-artifacts");
8129
- const responseFile = path29.join(
8165
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path30.join(process.cwd(), "debug-artifacts");
8166
+ const responseFile = path30.join(
8130
8167
  debugArtifactsDir,
8131
8168
  `response-${_checkName || "unknown"}-${timestamp}.txt`
8132
8169
  );
@@ -8159,7 +8196,7 @@ ${"=".repeat(60)}
8159
8196
  `;
8160
8197
  responseContent += `${"=".repeat(60)}
8161
8198
  `;
8162
- fs25.writeFileSync(responseFile, responseContent, "utf-8");
8199
+ fs26.writeFileSync(responseFile, responseContent, "utf-8");
8163
8200
  log(`\u{1F4BE} Response saved to: ${responseFile}`);
8164
8201
  } catch (error) {
8165
8202
  log(`\u26A0\uFE0F Could not save response file: ${error}`);
@@ -8175,9 +8212,9 @@ ${"=".repeat(60)}
8175
8212
  await agentAny._telemetryConfig.shutdown();
8176
8213
  log(`\u{1F4CA} OpenTelemetry trace saved to: ${agentAny._traceFilePath}`);
8177
8214
  if (process.env.GITHUB_ACTIONS) {
8178
- const fs25 = require("fs");
8179
- if (fs25.existsSync(agentAny._traceFilePath)) {
8180
- const stats = fs25.statSync(agentAny._traceFilePath);
8215
+ const fs26 = require("fs");
8216
+ if (fs26.existsSync(agentAny._traceFilePath)) {
8217
+ const stats = fs26.statSync(agentAny._traceFilePath);
8181
8218
  console.log(
8182
8219
  `::notice title=AI Trace Saved::${agentAny._traceFilePath} (${stats.size} bytes)`
8183
8220
  );
@@ -8378,8 +8415,8 @@ ${schemaString}`);
8378
8415
  const model = this.config.model || "default";
8379
8416
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8380
8417
  try {
8381
- const fs25 = require("fs");
8382
- const path29 = require("path");
8418
+ const fs26 = require("fs");
8419
+ const path30 = require("path");
8383
8420
  const os3 = require("os");
8384
8421
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8385
8422
  const debugData = {
@@ -8453,18 +8490,18 @@ ${"=".repeat(60)}
8453
8490
  readableVersion += `${"=".repeat(60)}
8454
8491
  `;
8455
8492
  const tempDir = os3.tmpdir();
8456
- const promptFile = path29.join(tempDir, `visor-prompt-${timestamp}.txt`);
8457
- fs25.writeFileSync(promptFile, prompt, "utf-8");
8493
+ const promptFile = path30.join(tempDir, `visor-prompt-${timestamp}.txt`);
8494
+ fs26.writeFileSync(promptFile, prompt, "utf-8");
8458
8495
  log(`
8459
8496
  \u{1F4BE} Prompt saved to: ${promptFile}`);
8460
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path29.join(process.cwd(), "debug-artifacts");
8497
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path30.join(process.cwd(), "debug-artifacts");
8461
8498
  try {
8462
- const base = path29.join(
8499
+ const base = path30.join(
8463
8500
  debugArtifactsDir,
8464
8501
  `prompt-${_checkName || "unknown"}-${timestamp}`
8465
8502
  );
8466
- fs25.writeFileSync(base + ".json", debugJson, "utf-8");
8467
- fs25.writeFileSync(base + ".summary.txt", readableVersion, "utf-8");
8503
+ fs26.writeFileSync(base + ".json", debugJson, "utf-8");
8504
+ fs26.writeFileSync(base + ".summary.txt", readableVersion, "utf-8");
8468
8505
  log(`
8469
8506
  \u{1F4BE} Full debug info saved to directory: ${debugArtifactsDir}`);
8470
8507
  } catch {
@@ -8509,8 +8546,8 @@ $ ${cliCommand}
8509
8546
  log(`\u{1F4E4} Response length: ${response.length} characters`);
8510
8547
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8511
8548
  try {
8512
- const fs25 = require("fs");
8513
- const path29 = require("path");
8549
+ const fs26 = require("fs");
8550
+ const path30 = require("path");
8514
8551
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8515
8552
  const agentAny = agent;
8516
8553
  let fullHistory = [];
@@ -8521,8 +8558,8 @@ $ ${cliCommand}
8521
8558
  } else if (agentAny._messages) {
8522
8559
  fullHistory = agentAny._messages;
8523
8560
  }
8524
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path29.join(process.cwd(), "debug-artifacts");
8525
- const sessionBase = path29.join(
8561
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path30.join(process.cwd(), "debug-artifacts");
8562
+ const sessionBase = path30.join(
8526
8563
  debugArtifactsDir,
8527
8564
  `session-${_checkName || "unknown"}-${timestamp}`
8528
8565
  );
@@ -8534,7 +8571,7 @@ $ ${cliCommand}
8534
8571
  schema: effectiveSchema,
8535
8572
  totalMessages: fullHistory.length
8536
8573
  };
8537
- fs25.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
8574
+ fs26.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
8538
8575
  let readable = `=============================================================
8539
8576
  `;
8540
8577
  readable += `COMPLETE AI SESSION HISTORY (AFTER RESPONSE)
@@ -8561,7 +8598,7 @@ ${"=".repeat(60)}
8561
8598
  `;
8562
8599
  readable += content + "\n";
8563
8600
  });
8564
- fs25.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
8601
+ fs26.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
8565
8602
  log(`\u{1F4BE} Complete session history saved:`);
8566
8603
  log(` - Contains ALL ${fullHistory.length} messages (prompts + responses)`);
8567
8604
  } catch (error) {
@@ -8570,11 +8607,11 @@ ${"=".repeat(60)}
8570
8607
  }
8571
8608
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8572
8609
  try {
8573
- const fs25 = require("fs");
8574
- const path29 = require("path");
8610
+ const fs26 = require("fs");
8611
+ const path30 = require("path");
8575
8612
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8576
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path29.join(process.cwd(), "debug-artifacts");
8577
- const responseFile = path29.join(
8613
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path30.join(process.cwd(), "debug-artifacts");
8614
+ const responseFile = path30.join(
8578
8615
  debugArtifactsDir,
8579
8616
  `response-${_checkName || "unknown"}-${timestamp}.txt`
8580
8617
  );
@@ -8607,7 +8644,7 @@ ${"=".repeat(60)}
8607
8644
  `;
8608
8645
  responseContent += `${"=".repeat(60)}
8609
8646
  `;
8610
- fs25.writeFileSync(responseFile, responseContent, "utf-8");
8647
+ fs26.writeFileSync(responseFile, responseContent, "utf-8");
8611
8648
  log(`\u{1F4BE} Response saved to: ${responseFile}`);
8612
8649
  } catch (error) {
8613
8650
  log(`\u26A0\uFE0F Could not save response file: ${error}`);
@@ -8625,9 +8662,9 @@ ${"=".repeat(60)}
8625
8662
  await telemetry.shutdown();
8626
8663
  log(`\u{1F4CA} OpenTelemetry trace saved to: ${traceFilePath}`);
8627
8664
  if (process.env.GITHUB_ACTIONS) {
8628
- const fs25 = require("fs");
8629
- if (fs25.existsSync(traceFilePath)) {
8630
- const stats = fs25.statSync(traceFilePath);
8665
+ const fs26 = require("fs");
8666
+ if (fs26.existsSync(traceFilePath)) {
8667
+ const stats = fs26.statSync(traceFilePath);
8631
8668
  console.log(
8632
8669
  `::notice title=AI Trace Saved::OpenTelemetry trace file size: ${stats.size} bytes`
8633
8670
  );
@@ -8665,8 +8702,8 @@ ${"=".repeat(60)}
8665
8702
  * Load schema content from schema files or inline definitions
8666
8703
  */
8667
8704
  async loadSchemaContent(schema) {
8668
- const fs25 = require("fs").promises;
8669
- const path29 = require("path");
8705
+ const fs26 = require("fs").promises;
8706
+ const path30 = require("path");
8670
8707
  if (typeof schema === "object" && schema !== null) {
8671
8708
  log("\u{1F4CB} Using inline schema object from configuration");
8672
8709
  return JSON.stringify(schema);
@@ -8679,14 +8716,14 @@ ${"=".repeat(60)}
8679
8716
  }
8680
8717
  } catch {
8681
8718
  }
8682
- if ((schema.startsWith("./") || schema.includes(".json")) && !path29.isAbsolute(schema)) {
8719
+ if ((schema.startsWith("./") || schema.includes(".json")) && !path30.isAbsolute(schema)) {
8683
8720
  if (schema.includes("..") || schema.includes("\0")) {
8684
8721
  throw new Error("Invalid schema path: path traversal not allowed");
8685
8722
  }
8686
8723
  try {
8687
- const schemaPath = path29.resolve(process.cwd(), schema);
8724
+ const schemaPath = path30.resolve(process.cwd(), schema);
8688
8725
  log(`\u{1F4CB} Loading custom schema from file: ${schemaPath}`);
8689
- const schemaContent = await fs25.readFile(schemaPath, "utf-8");
8726
+ const schemaContent = await fs26.readFile(schemaPath, "utf-8");
8690
8727
  return schemaContent.trim();
8691
8728
  } catch (error) {
8692
8729
  throw new Error(
@@ -8700,22 +8737,22 @@ ${"=".repeat(60)}
8700
8737
  }
8701
8738
  const candidatePaths = [
8702
8739
  // GitHub Action bundle location
8703
- path29.join(__dirname, "output", sanitizedSchemaName, "schema.json"),
8740
+ path30.join(__dirname, "output", sanitizedSchemaName, "schema.json"),
8704
8741
  // Historical fallback when src/output was inadvertently bundled as output1/
8705
- path29.join(__dirname, "output1", sanitizedSchemaName, "schema.json"),
8742
+ path30.join(__dirname, "output1", sanitizedSchemaName, "schema.json"),
8706
8743
  // Local dev (repo root)
8707
- path29.join(process.cwd(), "output", sanitizedSchemaName, "schema.json")
8744
+ path30.join(process.cwd(), "output", sanitizedSchemaName, "schema.json")
8708
8745
  ];
8709
8746
  for (const schemaPath of candidatePaths) {
8710
8747
  try {
8711
- const schemaContent = await fs25.readFile(schemaPath, "utf-8");
8748
+ const schemaContent = await fs26.readFile(schemaPath, "utf-8");
8712
8749
  return schemaContent.trim();
8713
8750
  } catch {
8714
8751
  }
8715
8752
  }
8716
- const distPath = path29.join(__dirname, "output", sanitizedSchemaName, "schema.json");
8717
- const distAltPath = path29.join(__dirname, "output1", sanitizedSchemaName, "schema.json");
8718
- const cwdPath = path29.join(process.cwd(), "output", sanitizedSchemaName, "schema.json");
8753
+ const distPath = path30.join(__dirname, "output", sanitizedSchemaName, "schema.json");
8754
+ const distAltPath = path30.join(__dirname, "output1", sanitizedSchemaName, "schema.json");
8755
+ const cwdPath = path30.join(process.cwd(), "output", sanitizedSchemaName, "schema.json");
8719
8756
  throw new Error(
8720
8757
  `Failed to load schema '${sanitizedSchemaName}'. Tried: ${distPath}, ${distAltPath}, and ${cwdPath}. Ensure build copies 'output/' into dist (build:cli), or provide a custom schema file/path.`
8721
8758
  );
@@ -9391,6 +9428,758 @@ var init_command_executor = __esm({
9391
9428
  }
9392
9429
  });
9393
9430
 
9431
+ // src/providers/api-tool-executor.ts
9432
+ function isHttpUrl(value) {
9433
+ return value.startsWith("http://") || value.startsWith("https://");
9434
+ }
9435
+ function toStringArray(value) {
9436
+ if (!value) return [];
9437
+ if (Array.isArray(value)) {
9438
+ return value.map((item) => String(item).trim()).filter(Boolean);
9439
+ }
9440
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
9441
+ }
9442
+ function isPlainObject(value) {
9443
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
9444
+ }
9445
+ function toOverlaySourceArray(value) {
9446
+ if (!value) return [];
9447
+ if (typeof value === "string" || isPlainObject(value)) {
9448
+ return [value];
9449
+ }
9450
+ if (Array.isArray(value)) {
9451
+ return value.filter((item) => typeof item === "string" || isPlainObject(item));
9452
+ }
9453
+ return [];
9454
+ }
9455
+ function resolvePathOrUrl(candidate, baseDir) {
9456
+ if (isHttpUrl(candidate)) return candidate;
9457
+ if (import_path4.default.isAbsolute(candidate)) return candidate;
9458
+ if (isHttpUrl(baseDir)) {
9459
+ return new URL(candidate, baseDir).toString();
9460
+ }
9461
+ return import_path4.default.resolve(baseDir, candidate);
9462
+ }
9463
+ async function readTextFromPathOrUrl(location) {
9464
+ if (isHttpUrl(location)) {
9465
+ const res = await fetch(location);
9466
+ if (!res.ok) {
9467
+ throw new Error(`Failed to fetch ${location}: ${res.status} ${res.statusText}`);
9468
+ }
9469
+ return await res.text();
9470
+ }
9471
+ return await import_promises2.default.readFile(location, "utf8");
9472
+ }
9473
+ function parseJsonOrYaml(raw, location) {
9474
+ const ext = import_path4.default.extname(location).toLowerCase();
9475
+ if (ext === ".yaml" || ext === ".yml") {
9476
+ return import_js_yaml.default.load(raw);
9477
+ }
9478
+ try {
9479
+ return JSON.parse(raw);
9480
+ } catch {
9481
+ return import_js_yaml.default.load(raw);
9482
+ }
9483
+ }
9484
+ function parseOverlayTargetPath(pathValue) {
9485
+ if (!Array.isArray(pathValue) || pathValue.length === 0) return "$";
9486
+ return pathValue.map((segment, index) => {
9487
+ if (index === 0) return "$";
9488
+ if (typeof segment === "number") return `[${segment}]`;
9489
+ if (/^[A-Za-z_][\w$]*$/.test(segment)) return `.${segment}`;
9490
+ return `['${segment.replace(/'/g, "\\'")}']`;
9491
+ }).join("");
9492
+ }
9493
+ function applyOverlayActions(target, overlay) {
9494
+ const cloned = JSON.parse(JSON.stringify(target));
9495
+ if (!overlay || typeof overlay !== "object") return cloned;
9496
+ const actions = Array.isArray(overlay.actions) ? overlay.actions : [];
9497
+ if (actions.length === 0) {
9498
+ return (0, import_deepmerge.default)(cloned, overlay, {
9499
+ arrayMerge: (dst, src) => dst.concat(src)
9500
+ });
9501
+ }
9502
+ for (const action of actions) {
9503
+ const targetExpr = action?.target;
9504
+ if (!targetExpr || typeof targetExpr !== "string") continue;
9505
+ let matches = [];
9506
+ try {
9507
+ matches = (0, import_jsonpath_plus.JSONPath)({
9508
+ path: targetExpr,
9509
+ json: cloned,
9510
+ resultType: "all"
9511
+ });
9512
+ } catch (error) {
9513
+ logger.warn(`[ApiToolExecutor] Invalid overlay target "${targetExpr}": ${error}`);
9514
+ continue;
9515
+ }
9516
+ if (matches.length === 0) {
9517
+ continue;
9518
+ }
9519
+ for (const match of matches) {
9520
+ if (!match || typeof match !== "object") continue;
9521
+ const parent = match.parent;
9522
+ const key = match.parentProperty;
9523
+ if (parent === void 0 || key === void 0) {
9524
+ const jsonPath = parseOverlayTargetPath(
9525
+ match.path || []
9526
+ );
9527
+ logger.debug(`[ApiToolExecutor] Overlay target has no writable parent: ${jsonPath}`);
9528
+ continue;
9529
+ }
9530
+ if (action.remove === true) {
9531
+ if (Array.isArray(parent)) {
9532
+ parent.splice(Number(key), 1);
9533
+ } else if (parent && typeof parent === "object") {
9534
+ delete parent[key];
9535
+ }
9536
+ continue;
9537
+ }
9538
+ if (action.update === void 0) {
9539
+ continue;
9540
+ }
9541
+ const current = match.value;
9542
+ if (Array.isArray(current)) {
9543
+ current.push(action.update);
9544
+ } else if (current && typeof current === "object") {
9545
+ const merged = (0, import_deepmerge.default)(current, action.update, {
9546
+ arrayMerge: (dst, src) => dst.concat(src)
9547
+ });
9548
+ if (Array.isArray(parent)) {
9549
+ parent[Number(key)] = merged;
9550
+ } else {
9551
+ parent[key] = merged;
9552
+ }
9553
+ } else if (Array.isArray(parent)) {
9554
+ parent[Number(key)] = action.update;
9555
+ } else {
9556
+ parent[key] = action.update;
9557
+ }
9558
+ }
9559
+ }
9560
+ return cloned;
9561
+ }
9562
+ function isRefObject(value) {
9563
+ return Boolean(value && typeof value === "object" && "$ref" in value);
9564
+ }
9565
+ function isSchemaObject(value) {
9566
+ return Boolean(
9567
+ value && typeof value === "object" && !Array.isArray(value) && !isRefObject(value)
9568
+ );
9569
+ }
9570
+ function getSchemaFromContent(content) {
9571
+ if (!content || typeof content !== "object") return void 0;
9572
+ const entries = Object.values(content);
9573
+ const withSchema = entries.find((entry) => entry && typeof entry === "object" && entry.schema);
9574
+ return withSchema?.schema;
9575
+ }
9576
+ function mapOpenApiTypeToJsonType(schema) {
9577
+ if (!schema || !schema.type) return { type: "string" };
9578
+ const openApiType = String(schema.type);
9579
+ const nullable = schema.nullable === true;
9580
+ switch (openApiType) {
9581
+ case "integer":
9582
+ case "number":
9583
+ case "boolean":
9584
+ case "string":
9585
+ case "array":
9586
+ case "object":
9587
+ return { type: openApiType, format: schema.format, nullable };
9588
+ default:
9589
+ return { type: "string", format: schema.format, nullable };
9590
+ }
9591
+ }
9592
+ function openApiSchemaToJsonSchema(schema) {
9593
+ if (!schema) {
9594
+ return { type: "string" };
9595
+ }
9596
+ const mapped = mapOpenApiTypeToJsonType(schema);
9597
+ const result = {
9598
+ type: mapped.nullable ? [mapped.type, "null"] : mapped.type
9599
+ };
9600
+ if (mapped.format) result.format = mapped.format;
9601
+ if (schema.description !== void 0) result.description = schema.description;
9602
+ if (schema.default !== void 0) result.default = schema.default;
9603
+ if (schema.enum !== void 0) result.enum = schema.enum;
9604
+ if (schema.example !== void 0) result.example = schema.example;
9605
+ if (schema.minimum !== void 0) result.minimum = schema.minimum;
9606
+ if (schema.maximum !== void 0) result.maximum = schema.maximum;
9607
+ if (schema.minLength !== void 0) result.minLength = schema.minLength;
9608
+ if (schema.maxLength !== void 0) result.maxLength = schema.maxLength;
9609
+ if (schema.pattern !== void 0) result.pattern = schema.pattern;
9610
+ if (schema.multipleOf !== void 0) result.multipleOf = schema.multipleOf;
9611
+ if (schema.minItems !== void 0) result.minItems = schema.minItems;
9612
+ if (schema.maxItems !== void 0) result.maxItems = schema.maxItems;
9613
+ if (schema.uniqueItems !== void 0) result.uniqueItems = schema.uniqueItems;
9614
+ if (schema.type === "object" && schema.properties && typeof schema.properties === "object") {
9615
+ const props = {};
9616
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
9617
+ if (isSchemaObject(propSchema)) {
9618
+ props[propName] = openApiSchemaToJsonSchema(propSchema);
9619
+ }
9620
+ }
9621
+ result.properties = props;
9622
+ if (Array.isArray(schema.required) && schema.required.length > 0) {
9623
+ result.required = schema.required;
9624
+ }
9625
+ if (schema.additionalProperties === true || schema.additionalProperties === false) {
9626
+ result.additionalProperties = schema.additionalProperties;
9627
+ } else if (isSchemaObject(schema.additionalProperties)) {
9628
+ result.additionalProperties = openApiSchemaToJsonSchema(schema.additionalProperties);
9629
+ }
9630
+ }
9631
+ if (schema.type === "array" && isSchemaObject(schema.items)) {
9632
+ result.items = openApiSchemaToJsonSchema(schema.items);
9633
+ }
9634
+ for (const [key, value] of Object.entries(schema)) {
9635
+ if (key.startsWith("x-")) {
9636
+ result[key] = value;
9637
+ }
9638
+ }
9639
+ return result;
9640
+ }
9641
+ function shouldIncludeOperation(operationId, pathValue, method, whitelist, blacklist) {
9642
+ const methodPath = `${method.toUpperCase()}:${pathValue}`;
9643
+ const opKey = operationId || methodPath;
9644
+ if (whitelist.length > 0) {
9645
+ return whitelist.some((pattern) => (0, import_minimatch.minimatch)(opKey, pattern) || (0, import_minimatch.minimatch)(methodPath, pattern));
9646
+ }
9647
+ if (blacklist.length > 0) {
9648
+ return !blacklist.some((pattern) => (0, import_minimatch.minimatch)(opKey, pattern) || (0, import_minimatch.minimatch)(methodPath, pattern));
9649
+ }
9650
+ return true;
9651
+ }
9652
+ function getApiToolConfig(tool) {
9653
+ return {
9654
+ customHeaders: tool.headers || {},
9655
+ disableXMcp: Boolean(tool.disableXMcp ?? tool.disable_x_mcp ?? false),
9656
+ apiKey: tool.apiKey ?? tool.api_key,
9657
+ securitySchemeName: tool.securitySchemeName ?? tool.security_scheme_name,
9658
+ securityCredentials: tool.securityCredentials || tool.security_credentials || {},
9659
+ requestTimeoutMs: tool.requestTimeoutMs ?? tool.request_timeout_ms ?? tool.timeout ?? 3e4
9660
+ };
9661
+ }
9662
+ function buildOutputSchema(operation) {
9663
+ const responses = operation.responses;
9664
+ if (!responses || typeof responses !== "object") return void 0;
9665
+ const successCode = Object.keys(responses).find((code) => code.startsWith("2"));
9666
+ if (!successCode) return void 0;
9667
+ const response = responses[successCode];
9668
+ if (!response || typeof response !== "object" || isRefObject(response)) return void 0;
9669
+ const jsonSchema = response.content?.["application/json"]?.schema || getSchemaFromContent(response.content);
9670
+ if (!isSchemaObject(jsonSchema)) return void 0;
9671
+ const mapped = openApiSchemaToJsonSchema(jsonSchema);
9672
+ if (response.description && typeof response.description === "string") {
9673
+ mapped.description = response.description;
9674
+ }
9675
+ return mapped;
9676
+ }
9677
+ function getToolName(operationId, operation, pathItem, tool) {
9678
+ let toolName = operationId;
9679
+ const opExtension = operation["x-mcp"];
9680
+ const pathExtension = pathItem["x-mcp"];
9681
+ if (opExtension && typeof opExtension === "object" && typeof opExtension.name === "string") {
9682
+ toolName = opExtension.name;
9683
+ } else if (pathExtension && typeof pathExtension === "object" && typeof pathExtension.name === "string") {
9684
+ toolName = pathExtension.name;
9685
+ }
9686
+ const prefix = tool.namePrefix || tool.name_prefix;
9687
+ if (prefix) {
9688
+ return `${prefix}${toolName}`;
9689
+ }
9690
+ return toolName;
9691
+ }
9692
+ function getToolDescription(operation, pathItem) {
9693
+ const opExtension = operation["x-mcp"];
9694
+ const pathExtension = pathItem["x-mcp"];
9695
+ if (opExtension && typeof opExtension === "object" && typeof opExtension.description === "string") {
9696
+ return opExtension.description;
9697
+ }
9698
+ if (pathExtension && typeof pathExtension === "object" && typeof pathExtension.description === "string") {
9699
+ return pathExtension.description;
9700
+ }
9701
+ return typeof operation.description === "string" && operation.description || typeof operation.summary === "string" && operation.summary || typeof pathItem.summary === "string" && pathItem.summary || "No description available.";
9702
+ }
9703
+ function isApiToolDefinition(tool) {
9704
+ return Boolean(tool && tool.type === "api");
9705
+ }
9706
+ async function loadOpenApiDocument(tool) {
9707
+ if (!tool.spec) {
9708
+ throw new Error(`API tool '${tool.name}' is missing required field: spec`);
9709
+ }
9710
+ const configuredBaseDir = tool.__baseDir;
9711
+ const baseDir = (() => {
9712
+ if (tool.cwd) {
9713
+ if (import_path4.default.isAbsolute(tool.cwd) || isHttpUrl(tool.cwd)) {
9714
+ return tool.cwd;
9715
+ }
9716
+ if (configuredBaseDir) {
9717
+ return resolvePathOrUrl(tool.cwd, configuredBaseDir);
9718
+ }
9719
+ return import_path4.default.resolve(tool.cwd);
9720
+ }
9721
+ return configuredBaseDir || process.cwd();
9722
+ })();
9723
+ const dereferenceWithContext = async (source, spec) => {
9724
+ try {
9725
+ return await import_swagger_parser.default.dereference(spec);
9726
+ } catch (error) {
9727
+ const errorMessage = error instanceof Error ? error.message : String(error);
9728
+ throw new Error(
9729
+ `Failed to dereference OpenAPI spec for API tool '${tool.name}' from ${source}: ${errorMessage}`
9730
+ );
9731
+ }
9732
+ };
9733
+ let openapi;
9734
+ if (typeof tool.spec === "string") {
9735
+ const specLocation = resolvePathOrUrl(tool.spec, baseDir);
9736
+ if (isHttpUrl(specLocation)) {
9737
+ const raw = await readTextFromPathOrUrl(specLocation);
9738
+ const parsed = parseJsonOrYaml(raw, specLocation);
9739
+ openapi = await dereferenceWithContext(specLocation, parsed);
9740
+ } else {
9741
+ openapi = await dereferenceWithContext(specLocation, specLocation);
9742
+ }
9743
+ } else if (isPlainObject(tool.spec)) {
9744
+ openapi = await dereferenceWithContext("inline spec", JSON.parse(JSON.stringify(tool.spec)));
9745
+ } else {
9746
+ throw new Error(
9747
+ `API tool '${tool.name}' has invalid spec field (expected string path/URL or object)`
9748
+ );
9749
+ }
9750
+ const overlays = toOverlaySourceArray(tool.overlays);
9751
+ let working = openapi;
9752
+ for (const overlaySource of overlays) {
9753
+ let overlay = overlaySource;
9754
+ if (typeof overlaySource === "string") {
9755
+ const resolved = resolvePathOrUrl(overlaySource, baseDir);
9756
+ const raw = await readTextFromPathOrUrl(resolved);
9757
+ overlay = parseJsonOrYaml(raw, resolved);
9758
+ }
9759
+ working = applyOverlayActions(working, overlay);
9760
+ }
9761
+ return working;
9762
+ }
9763
+ function mapOpenApiToTools(openapi, tool) {
9764
+ const paths = openapi?.paths;
9765
+ if (!paths || typeof paths !== "object") {
9766
+ return [];
9767
+ }
9768
+ const targetUrl = tool.targetUrl || tool.target_url;
9769
+ const baseServerUrl = String(targetUrl || openapi?.servers?.[0]?.url || "").replace(/\/$/, "");
9770
+ if (!baseServerUrl) {
9771
+ throw new Error(
9772
+ `API tool '${tool.name}' cannot determine target API URL. Set targetUrl/target_url or provide OpenAPI servers[].`
9773
+ );
9774
+ }
9775
+ const whitelist = toStringArray(tool.whitelist);
9776
+ const blacklist = toStringArray(tool.blacklist);
9777
+ const globalSecurity = Array.isArray(openapi?.security) ? openapi.security : null;
9778
+ const securitySchemes = openapi?.components?.securitySchemes;
9779
+ const mapped = [];
9780
+ const apiToolConfig = getApiToolConfig(tool);
9781
+ for (const [pathValue, pathItemRaw] of Object.entries(paths)) {
9782
+ const pathItem = pathItemRaw;
9783
+ if (!pathItem || typeof pathItem !== "object") continue;
9784
+ for (const [method, operationRaw] of Object.entries(pathItem)) {
9785
+ if (!HTTP_METHODS.has(method.toLowerCase())) continue;
9786
+ const operation = operationRaw;
9787
+ if (!operation || typeof operation !== "object") continue;
9788
+ const operationId = typeof operation.operationId === "string" ? String(operation.operationId) : void 0;
9789
+ if (!operationId) {
9790
+ logger.debug(
9791
+ `[ApiToolExecutor] Skipping ${method.toUpperCase()} ${pathValue} (missing operationId)`
9792
+ );
9793
+ continue;
9794
+ }
9795
+ if (!shouldIncludeOperation(operationId, pathValue, method, whitelist, blacklist)) {
9796
+ continue;
9797
+ }
9798
+ const toolName = getToolName(operationId, operation, pathItem, tool);
9799
+ const toolDescription = getToolDescription(operation, pathItem);
9800
+ const inputSchema = {
9801
+ type: "object",
9802
+ properties: {}
9803
+ };
9804
+ const requiredNames = /* @__PURE__ */ new Set();
9805
+ const allParameters = [
9806
+ ...Array.isArray(pathItem.parameters) ? pathItem.parameters : [],
9807
+ ...Array.isArray(operation.parameters) ? operation.parameters : []
9808
+ ].filter((param) => param && typeof param === "object" && !isRefObject(param));
9809
+ for (const param of allParameters) {
9810
+ const paramObj = param;
9811
+ const paramName = typeof paramObj.name === "string" ? paramObj.name : "";
9812
+ if (!paramName) continue;
9813
+ if (!isSchemaObject(paramObj.schema)) continue;
9814
+ const schema = openApiSchemaToJsonSchema(paramObj.schema);
9815
+ if (typeof paramObj.description === "string") {
9816
+ schema.description = paramObj.description;
9817
+ }
9818
+ schema["x-parameter-location"] = paramObj.in || "query";
9819
+ if (paramObj.example !== void 0) schema.example = paramObj.example;
9820
+ if (paramObj.deprecated === true) schema.deprecated = true;
9821
+ inputSchema.properties[paramName] = schema;
9822
+ if (paramObj.required === true) requiredNames.add(paramName);
9823
+ }
9824
+ const requestBody = !isRefObject(operation.requestBody) ? operation.requestBody : void 0;
9825
+ if (requestBody && typeof requestBody === "object") {
9826
+ const reqSchema = requestBody.content?.["application/json"]?.schema || getSchemaFromContent(requestBody.content);
9827
+ if (isSchemaObject(reqSchema)) {
9828
+ const bodySchema = openApiSchemaToJsonSchema(reqSchema);
9829
+ if (typeof requestBody.description === "string") {
9830
+ bodySchema.description = requestBody.description;
9831
+ }
9832
+ const contentTypes = Object.keys(requestBody.content || {});
9833
+ if (contentTypes.length > 0) {
9834
+ bodySchema["x-content-types"] = contentTypes;
9835
+ }
9836
+ inputSchema.properties.requestBody = bodySchema;
9837
+ if (requestBody.required === true) {
9838
+ requiredNames.add("requestBody");
9839
+ }
9840
+ }
9841
+ }
9842
+ if (requiredNames.size > 0) {
9843
+ inputSchema.required = Array.from(requiredNames);
9844
+ }
9845
+ const securityRequirements = Array.isArray(operation.security) ? operation.security : globalSecurity;
9846
+ mapped.push({
9847
+ sourceToolName: tool.name,
9848
+ mcpToolDefinition: {
9849
+ name: toolName,
9850
+ description: toolDescription,
9851
+ inputSchema,
9852
+ outputSchema: buildOutputSchema(operation)
9853
+ },
9854
+ apiCallDetails: {
9855
+ method: method.toUpperCase(),
9856
+ pathTemplate: pathValue,
9857
+ serverUrl: baseServerUrl,
9858
+ parameters: allParameters,
9859
+ requestBody,
9860
+ securityRequirements,
9861
+ securitySchemes,
9862
+ apiToolConfig
9863
+ }
9864
+ });
9865
+ }
9866
+ }
9867
+ return mapped;
9868
+ }
9869
+ function validateParameterValue(value, paramDef) {
9870
+ const schema = paramDef.schema;
9871
+ if (!schema || typeof schema !== "object") return null;
9872
+ const schemaType = schema.type;
9873
+ if (schemaType === "integer") {
9874
+ if (typeof value !== "number" || !Number.isInteger(value)) {
9875
+ return "must be an integer";
9876
+ }
9877
+ } else if (schemaType === "number") {
9878
+ if (typeof value !== "number") {
9879
+ return `expected number, got ${typeof value}`;
9880
+ }
9881
+ } else if (schemaType === "boolean") {
9882
+ if (typeof value !== "boolean") {
9883
+ return `expected boolean, got ${typeof value}`;
9884
+ }
9885
+ } else if (schemaType === "string") {
9886
+ if (typeof value !== "string") {
9887
+ return `expected string, got ${typeof value}`;
9888
+ }
9889
+ } else if (schemaType === "array") {
9890
+ if (!Array.isArray(value)) {
9891
+ return `expected array, got ${typeof value}`;
9892
+ }
9893
+ } else if (schemaType === "object") {
9894
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
9895
+ return `expected object, got ${Array.isArray(value) ? "array" : typeof value}`;
9896
+ }
9897
+ }
9898
+ if (schema.minimum !== void 0 && typeof value === "number" && value < schema.minimum) {
9899
+ return `must be >= ${schema.minimum}`;
9900
+ }
9901
+ if (schema.maximum !== void 0 && typeof value === "number" && value > schema.maximum) {
9902
+ return `must be <= ${schema.maximum}`;
9903
+ }
9904
+ if (schema.minLength !== void 0 && typeof value === "string" && value.length < schema.minLength) {
9905
+ return `length must be >= ${schema.minLength}`;
9906
+ }
9907
+ if (schema.maxLength !== void 0 && typeof value === "string" && value.length > schema.maxLength) {
9908
+ return `length must be <= ${schema.maxLength}`;
9909
+ }
9910
+ if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
9911
+ return `must be one of: ${schema.enum.join(", ")}`;
9912
+ }
9913
+ return null;
9914
+ }
9915
+ function applySecurityToRequest(details, headers, queryParams) {
9916
+ const requirements = details.securityRequirements;
9917
+ if (!requirements || requirements.length === 0) {
9918
+ return;
9919
+ }
9920
+ const schemes = details.securitySchemes || {};
9921
+ const cfg = details.apiToolConfig;
9922
+ const tryResolveCredential = (schemeName) => {
9923
+ if (cfg.securityCredentials[schemeName]) {
9924
+ return cfg.securityCredentials[schemeName];
9925
+ }
9926
+ if (cfg.securitySchemeName && cfg.securitySchemeName !== schemeName) {
9927
+ return void 0;
9928
+ }
9929
+ return cfg.apiKey;
9930
+ };
9931
+ for (const requirement of requirements) {
9932
+ const schemeNames = Object.keys(requirement);
9933
+ if (schemeNames.length === 0) return;
9934
+ const tempHeaders = { ...headers };
9935
+ const tempQuery = new URLSearchParams(queryParams);
9936
+ let satisfied = true;
9937
+ for (const schemeName of schemeNames) {
9938
+ const scheme = schemes[schemeName];
9939
+ if (!scheme || typeof scheme !== "object") {
9940
+ satisfied = false;
9941
+ break;
9942
+ }
9943
+ const credential = tryResolveCredential(schemeName);
9944
+ if (!credential) {
9945
+ satisfied = false;
9946
+ break;
9947
+ }
9948
+ switch (scheme.type) {
9949
+ case "apiKey":
9950
+ if (scheme.in === "header") {
9951
+ tempHeaders[String(scheme.name)] = credential;
9952
+ } else if (scheme.in === "query") {
9953
+ tempQuery.set(String(scheme.name), credential);
9954
+ } else if (scheme.in === "cookie") {
9955
+ const cookieName = String(scheme.name);
9956
+ const existing = tempHeaders["Cookie"];
9957
+ tempHeaders["Cookie"] = existing ? `${existing}; ${cookieName}=${credential}` : `${cookieName}=${credential}`;
9958
+ } else {
9959
+ satisfied = false;
9960
+ }
9961
+ break;
9962
+ case "http":
9963
+ if (typeof scheme.scheme !== "string") {
9964
+ satisfied = false;
9965
+ break;
9966
+ }
9967
+ if (scheme.scheme.toLowerCase() === "bearer") {
9968
+ tempHeaders["Authorization"] = `Bearer ${credential}`;
9969
+ } else if (scheme.scheme.toLowerCase() === "basic") {
9970
+ tempHeaders["Authorization"] = `Basic ${Buffer.from(credential).toString("base64")}`;
9971
+ } else {
9972
+ satisfied = false;
9973
+ }
9974
+ break;
9975
+ case "oauth2":
9976
+ case "openIdConnect":
9977
+ tempHeaders["Authorization"] = `Bearer ${credential}`;
9978
+ break;
9979
+ default:
9980
+ satisfied = false;
9981
+ }
9982
+ if (!satisfied) {
9983
+ break;
9984
+ }
9985
+ }
9986
+ if (satisfied) {
9987
+ Object.assign(headers, tempHeaders);
9988
+ queryParams.forEach((_value, key) => queryParams.delete(key));
9989
+ tempQuery.forEach((value, key) => queryParams.append(key, value));
9990
+ return;
9991
+ }
9992
+ }
9993
+ }
9994
+ function responseBodyToString(body) {
9995
+ if (typeof body === "string") return body;
9996
+ try {
9997
+ return JSON.stringify(body);
9998
+ } catch {
9999
+ return String(body);
10000
+ }
10001
+ }
10002
+ async function executeMappedApiTool(mappedTool, args) {
10003
+ const { apiCallDetails } = mappedTool;
10004
+ const { method, pathTemplate, serverUrl, parameters, requestBody, apiToolConfig } = apiCallDetails;
10005
+ const urlPath = pathTemplate.replace(/{([^}]+)}/g, (_token, rawName) => {
10006
+ const value = args[rawName];
10007
+ if (value === void 0 || value === null) {
10008
+ return `{${rawName}}`;
10009
+ }
10010
+ return encodeURIComponent(String(value));
10011
+ });
10012
+ if (urlPath.includes("{") || urlPath.includes("}")) {
10013
+ throw new Error(`Missing required path parameters for ${method} ${pathTemplate}`);
10014
+ }
10015
+ let endpoint;
10016
+ try {
10017
+ endpoint = new URL(`${serverUrl}${urlPath}`);
10018
+ } catch (error) {
10019
+ const errorMessage = error instanceof Error ? error.message : String(error);
10020
+ throw new Error(
10021
+ `Failed to construct endpoint URL for API tool '${mappedTool.sourceToolName}' operation '${mappedTool.mcpToolDefinition.name}' (${method} ${pathTemplate}) with serverUrl '${serverUrl}': ${errorMessage}`
10022
+ );
10023
+ }
10024
+ const queryParams = new URLSearchParams(endpoint.search);
10025
+ const headers = { ...apiToolConfig.customHeaders };
10026
+ let requestBodyValue;
10027
+ for (const param of parameters) {
10028
+ const name = String(param.name || "");
10029
+ if (!name) continue;
10030
+ const value = args[name];
10031
+ if (value === void 0 || value === null) {
10032
+ if (param.required) {
10033
+ throw new Error(`Missing required parameter: ${name}`);
10034
+ }
10035
+ continue;
10036
+ }
10037
+ const validationError = validateParameterValue(value, param);
10038
+ if (validationError) {
10039
+ throw new Error(`Parameter '${name}' ${validationError}`);
10040
+ }
10041
+ switch (param.in) {
10042
+ case "query":
10043
+ if (Array.isArray(value)) {
10044
+ for (const item of value) {
10045
+ queryParams.append(name, String(item));
10046
+ }
10047
+ } else {
10048
+ queryParams.set(name, String(value));
10049
+ }
10050
+ break;
10051
+ case "header":
10052
+ headers[name] = String(value);
10053
+ break;
10054
+ case "path":
10055
+ break;
10056
+ case "cookie": {
10057
+ const existing = headers["Cookie"];
10058
+ headers["Cookie"] = existing ? `${existing}; ${name}=${String(value)}` : `${name}=${String(value)}`;
10059
+ break;
10060
+ }
10061
+ default:
10062
+ break;
10063
+ }
10064
+ }
10065
+ if (requestBody && requestBody.required && args.requestBody === void 0) {
10066
+ throw new Error("Missing required requestBody parameter");
10067
+ }
10068
+ if (args.requestBody !== void 0) {
10069
+ requestBodyValue = args.requestBody;
10070
+ if (!headers["Content-Type"]) {
10071
+ headers["Content-Type"] = "application/json";
10072
+ }
10073
+ }
10074
+ if (!apiToolConfig.disableXMcp) {
10075
+ headers["X-MCP"] = "1";
10076
+ }
10077
+ applySecurityToRequest(apiCallDetails, headers, queryParams);
10078
+ endpoint.search = queryParams.toString();
10079
+ const controller = new AbortController();
10080
+ const timeout = setTimeout(() => controller.abort(), apiToolConfig.requestTimeoutMs);
10081
+ try {
10082
+ const response = await fetch(endpoint.toString(), {
10083
+ method,
10084
+ headers,
10085
+ body: requestBodyValue === void 0 ? void 0 : headers["Content-Type"]?.includes("application/json") ? JSON.stringify(requestBodyValue) : String(requestBodyValue),
10086
+ signal: controller.signal
10087
+ });
10088
+ const raw = await response.text();
10089
+ let body = raw;
10090
+ const contentType = response.headers.get("content-type") || "";
10091
+ if (contentType.includes("json") && raw.trim().length > 0) {
10092
+ try {
10093
+ body = JSON.parse(raw);
10094
+ } catch {
10095
+ body = raw;
10096
+ }
10097
+ } else if (raw.trim().length > 0) {
10098
+ try {
10099
+ body = JSON.parse(raw);
10100
+ } catch {
10101
+ body = raw;
10102
+ }
10103
+ } else {
10104
+ body = null;
10105
+ }
10106
+ if (response.ok) {
10107
+ return body;
10108
+ }
10109
+ throw new Error(`API Error ${response.status}: ${responseBodyToString(body)}`);
10110
+ } catch (error) {
10111
+ if (error instanceof Error && error.name === "AbortError") {
10112
+ throw new Error(`API request timed out after ${apiToolConfig.requestTimeoutMs}ms`);
10113
+ }
10114
+ throw error;
10115
+ } finally {
10116
+ clearTimeout(timeout);
10117
+ }
10118
+ }
10119
+ var import_promises2, import_path4, import_js_yaml, import_swagger_parser, import_deepmerge, import_jsonpath_plus, import_minimatch, HTTP_METHODS, ApiToolRegistry;
10120
+ var init_api_tool_executor = __esm({
10121
+ "src/providers/api-tool-executor.ts"() {
10122
+ "use strict";
10123
+ import_promises2 = __toESM(require("fs/promises"));
10124
+ import_path4 = __toESM(require("path"));
10125
+ import_js_yaml = __toESM(require("js-yaml"));
10126
+ import_swagger_parser = __toESM(require("@apidevtools/swagger-parser"));
10127
+ import_deepmerge = __toESM(require("deepmerge"));
10128
+ import_jsonpath_plus = require("jsonpath-plus");
10129
+ import_minimatch = require("minimatch");
10130
+ init_logger();
10131
+ HTTP_METHODS = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
10132
+ ApiToolRegistry = class {
10133
+ bundleCache = /* @__PURE__ */ new Map();
10134
+ operationCache = /* @__PURE__ */ new Map();
10135
+ registerMappedTools(sourceToolName, mappedTools) {
10136
+ this.bundleCache.set(sourceToolName, mappedTools);
10137
+ for (const mapped of mappedTools) {
10138
+ const existing = this.operationCache.get(mapped.mcpToolDefinition.name);
10139
+ if (existing && existing.sourceToolName !== sourceToolName) {
10140
+ logger.warn(
10141
+ `[ApiToolExecutor] Tool name collision: '${mapped.mcpToolDefinition.name}' from '${sourceToolName}' overrides '${existing.sourceToolName}'. Use namePrefix to avoid collisions.`
10142
+ );
10143
+ }
10144
+ this.operationCache.set(mapped.mcpToolDefinition.name, mapped);
10145
+ }
10146
+ }
10147
+ async ensureBundle(sourceToolName, tool) {
10148
+ const cached = this.bundleCache.get(sourceToolName);
10149
+ if (cached) return cached;
10150
+ if (!isApiToolDefinition(tool)) {
10151
+ return [];
10152
+ }
10153
+ const openapi = await loadOpenApiDocument(tool);
10154
+ const mapped = mapOpenApiToTools(openapi, tool);
10155
+ this.registerMappedTools(sourceToolName, mapped);
10156
+ return mapped;
10157
+ }
10158
+ async ensureAll(toolMap) {
10159
+ for (const [sourceToolName, tool] of toolMap.entries()) {
10160
+ if (!isApiToolDefinition(tool)) continue;
10161
+ await this.ensureBundle(sourceToolName, tool);
10162
+ }
10163
+ }
10164
+ async listMappedTools(toolMap) {
10165
+ await this.ensureAll(toolMap);
10166
+ return Array.from(this.operationCache.values());
10167
+ }
10168
+ async getMappedTool(toolName, toolMap) {
10169
+ const cached = this.operationCache.get(toolName);
10170
+ if (cached) return cached;
10171
+ for (const [sourceToolName, tool] of toolMap.entries()) {
10172
+ if (!isApiToolDefinition(tool)) continue;
10173
+ await this.ensureBundle(sourceToolName, tool);
10174
+ const resolved = this.operationCache.get(toolName);
10175
+ if (resolved) return resolved;
10176
+ }
10177
+ return void 0;
10178
+ }
10179
+ };
10180
+ }
10181
+ });
10182
+
9394
10183
  // src/providers/custom-tool-executor.ts
9395
10184
  var import_ajv, CustomToolExecutor;
9396
10185
  var init_custom_tool_executor = __esm({
@@ -9401,11 +10190,13 @@ var init_custom_tool_executor = __esm({
9401
10190
  init_logger();
9402
10191
  init_command_executor();
9403
10192
  import_ajv = __toESM(require("ajv"));
10193
+ init_api_tool_executor();
9404
10194
  CustomToolExecutor = class {
9405
10195
  liquid;
9406
10196
  sandbox;
9407
10197
  tools;
9408
10198
  ajv;
10199
+ apiToolRegistry;
9409
10200
  constructor(tools) {
9410
10201
  this.liquid = createExtendedLiquid({
9411
10202
  cache: false,
@@ -9413,7 +10204,8 @@ var init_custom_tool_executor = __esm({
9413
10204
  strictVariables: false
9414
10205
  });
9415
10206
  this.tools = new Map(Object.entries(tools || {}));
9416
- this.ajv = new import_ajv.default({ allErrors: true, verbose: true });
10207
+ this.ajv = new import_ajv.default({ allErrors: true, verbose: true, strict: false });
10208
+ this.apiToolRegistry = new ApiToolRegistry();
9417
10209
  }
9418
10210
  /**
9419
10211
  * Register a custom tool
@@ -9422,6 +10214,13 @@ var init_custom_tool_executor = __esm({
9422
10214
  if (!tool.name) {
9423
10215
  throw new Error("Tool must have a name");
9424
10216
  }
10217
+ if (isApiToolDefinition(tool)) {
10218
+ if (!tool.spec) {
10219
+ throw new Error(`API tool '${tool.name}' must define 'spec'`);
10220
+ }
10221
+ } else if (!tool.exec) {
10222
+ throw new Error(`Tool '${tool.name}' must define 'exec' (or set type: 'api')`);
10223
+ }
9425
10224
  this.tools.set(tool.name, tool);
9426
10225
  }
9427
10226
  /**
@@ -9464,12 +10263,45 @@ var init_custom_tool_executor = __esm({
9464
10263
  throw new Error(`Input validation failed for tool '${tool.name}': ${errors}`);
9465
10264
  }
9466
10265
  }
10266
+ /**
10267
+ * Validate input against a JSON schema object
10268
+ */
10269
+ validateInputSchema(toolName, schema, input) {
10270
+ if (!schema) {
10271
+ return;
10272
+ }
10273
+ const validate = this.ajv.compile(schema);
10274
+ const valid = validate(input);
10275
+ if (!valid) {
10276
+ const errors = validate.errors?.map((err) => {
10277
+ if (err.instancePath) {
10278
+ return `${err.instancePath}: ${err.message}`;
10279
+ }
10280
+ return err.message;
10281
+ }).join(", ");
10282
+ throw new Error(`Input validation failed for tool '${toolName}': ${errors}`);
10283
+ }
10284
+ }
9467
10285
  /**
9468
10286
  * Execute a custom tool
9469
10287
  */
9470
10288
  async execute(toolName, args, context2) {
9471
10289
  const tool = this.tools.get(toolName);
10290
+ if (tool && isApiToolDefinition(tool)) {
10291
+ throw new Error(
10292
+ `Tool '${toolName}' is an API bundle. Call one of its generated operations instead.`
10293
+ );
10294
+ }
9472
10295
  if (!tool) {
10296
+ const apiMappedTool = await this.apiToolRegistry.getMappedTool(toolName, this.tools);
10297
+ if (apiMappedTool) {
10298
+ this.validateInputSchema(
10299
+ toolName,
10300
+ apiMappedTool.mcpToolDefinition.inputSchema,
10301
+ args
10302
+ );
10303
+ return await executeMappedApiTool(apiMappedTool, args);
10304
+ }
9473
10305
  throw new Error(`Tool not found: ${toolName}`);
9474
10306
  }
9475
10307
  this.validateInput(tool, args);
@@ -9478,6 +10310,9 @@ var init_custom_tool_executor = __esm({
9478
10310
  args,
9479
10311
  input: args
9480
10312
  };
10313
+ if (!tool.exec) {
10314
+ throw new Error(`Tool '${toolName}' is missing exec command`);
10315
+ }
9481
10316
  const command = await this.liquid.parseAndRender(tool.exec, templateContext);
9482
10317
  let stdin;
9483
10318
  if (tool.stdin) {
@@ -9537,6 +10372,50 @@ var init_custom_tool_executor = __esm({
9537
10372
  }
9538
10373
  return output;
9539
10374
  }
10375
+ /**
10376
+ * Check if a tool exists (direct or API-generated)
10377
+ */
10378
+ async hasTool(toolName) {
10379
+ if (this.tools.has(toolName)) {
10380
+ const tool = this.tools.get(toolName);
10381
+ return !isApiToolDefinition(tool);
10382
+ }
10383
+ const apiMappedTool = await this.apiToolRegistry.getMappedTool(toolName, this.tools);
10384
+ return Boolean(apiMappedTool);
10385
+ }
10386
+ /**
10387
+ * Get all available tool names, including API-generated operations
10388
+ */
10389
+ async getToolNames() {
10390
+ const names = [];
10391
+ for (const tool of this.tools.values()) {
10392
+ if (!isApiToolDefinition(tool)) {
10393
+ names.push(tool.name);
10394
+ }
10395
+ }
10396
+ const apiTools = await this.apiToolRegistry.listMappedTools(this.tools);
10397
+ for (const mapped of apiTools) {
10398
+ names.push(mapped.mcpToolDefinition.name);
10399
+ }
10400
+ return names;
10401
+ }
10402
+ /**
10403
+ * List MCP-compatible tool definitions including API-generated operations
10404
+ */
10405
+ async listMcpTools() {
10406
+ const directTools = this.getTools().filter((tool) => !isApiToolDefinition(tool)).map((tool) => ({
10407
+ name: tool.name,
10408
+ description: tool.description,
10409
+ inputSchema: tool.inputSchema
10410
+ }));
10411
+ const apiTools = await this.apiToolRegistry.listMappedTools(this.tools);
10412
+ const mappedApiTools = apiTools.map((tool) => ({
10413
+ name: tool.mcpToolDefinition.name,
10414
+ description: tool.mcpToolDefinition.description,
10415
+ inputSchema: tool.mcpToolDefinition.inputSchema
10416
+ }));
10417
+ return [...directTools, ...mappedApiTools];
10418
+ }
9540
10419
  /**
9541
10420
  * Apply JavaScript transform to output
9542
10421
  */
@@ -9564,7 +10443,7 @@ var init_custom_tool_executor = __esm({
9564
10443
  * Convert custom tools to MCP tool format
9565
10444
  */
9566
10445
  toMcpTools() {
9567
- return Array.from(this.tools.values()).map((tool) => ({
10446
+ return Array.from(this.tools.values()).filter((tool) => !isApiToolDefinition(tool)).map((tool) => ({
9568
10447
  name: tool.name,
9569
10448
  description: tool.description,
9570
10449
  inputSchema: tool.inputSchema,
@@ -9582,12 +10461,12 @@ var workflow_registry_exports = {};
9582
10461
  __export(workflow_registry_exports, {
9583
10462
  WorkflowRegistry: () => WorkflowRegistry
9584
10463
  });
9585
- var import_fs3, path8, yaml, import_ajv2, import_ajv_formats, WorkflowRegistry;
10464
+ var import_fs3, path9, yaml, import_ajv2, import_ajv_formats, WorkflowRegistry;
9586
10465
  var init_workflow_registry = __esm({
9587
10466
  "src/workflow-registry.ts"() {
9588
10467
  "use strict";
9589
10468
  import_fs3 = require("fs");
9590
- path8 = __toESM(require("path"));
10469
+ path9 = __toESM(require("path"));
9591
10470
  yaml = __toESM(require("js-yaml"));
9592
10471
  init_logger();
9593
10472
  init_dependency_resolver();
@@ -9923,9 +10802,9 @@ var init_workflow_registry = __esm({
9923
10802
  const importBasePath = new URL(".", resolvedUrl).toString();
9924
10803
  return { content: await response.text(), resolvedSource: resolvedUrl, importBasePath };
9925
10804
  }
9926
- const filePath = path8.isAbsolute(source) ? source : path8.resolve(basePath || process.cwd(), source);
10805
+ const filePath = path9.isAbsolute(source) ? source : path9.resolve(basePath || process.cwd(), source);
9927
10806
  const content = await import_fs3.promises.readFile(filePath, "utf-8");
9928
- return { content, resolvedSource: filePath, importBasePath: path8.dirname(filePath) };
10807
+ return { content, resolvedSource: filePath, importBasePath: path9.dirname(filePath) };
9929
10808
  }
9930
10809
  /**
9931
10810
  * Parse workflow content (YAML or JSON)
@@ -10682,12 +11561,12 @@ var init_config_merger = __esm({
10682
11561
  });
10683
11562
 
10684
11563
  // src/utils/config-loader.ts
10685
- var fs7, path9, yaml2, ConfigLoader;
11564
+ var fs8, path10, yaml2, ConfigLoader;
10686
11565
  var init_config_loader = __esm({
10687
11566
  "src/utils/config-loader.ts"() {
10688
11567
  "use strict";
10689
- fs7 = __toESM(require("fs"));
10690
- path9 = __toESM(require("path"));
11568
+ fs8 = __toESM(require("fs"));
11569
+ path10 = __toESM(require("path"));
10691
11570
  yaml2 = __toESM(require("js-yaml"));
10692
11571
  ConfigLoader = class {
10693
11572
  constructor(options = {}) {
@@ -10707,6 +11586,19 @@ var init_config_loader = __esm({
10707
11586
  }
10708
11587
  cache = /* @__PURE__ */ new Map();
10709
11588
  loadedConfigs = /* @__PURE__ */ new Set();
11589
+ /**
11590
+ * Annotate tool definitions with their source base directory.
11591
+ * This is used at runtime to resolve relative tool assets (e.g., API specs).
11592
+ */
11593
+ annotateToolsBaseDir(config, baseDir) {
11594
+ if (!config.tools || typeof config.tools !== "object") {
11595
+ return;
11596
+ }
11597
+ for (const tool of Object.values(config.tools)) {
11598
+ if (!tool || typeof tool !== "object") continue;
11599
+ tool.__baseDir = tool.__baseDir || baseDir;
11600
+ }
11601
+ }
10710
11602
  /**
10711
11603
  * Determine the source type from a string
10712
11604
  */
@@ -10768,7 +11660,7 @@ var init_config_loader = __esm({
10768
11660
  return source.toLowerCase();
10769
11661
  case "local" /* LOCAL */:
10770
11662
  const basePath = this.options.baseDir || process.cwd();
10771
- return path9.resolve(basePath, source);
11663
+ return path10.resolve(basePath, source);
10772
11664
  default:
10773
11665
  return source;
10774
11666
  }
@@ -10778,10 +11670,10 @@ var init_config_loader = __esm({
10778
11670
  */
10779
11671
  async fetchLocalConfig(filePath) {
10780
11672
  const basePath = this.options.baseDir || process.cwd();
10781
- const resolvedPath = path9.resolve(basePath, filePath);
11673
+ const resolvedPath = path10.resolve(basePath, filePath);
10782
11674
  this.validateLocalPath(resolvedPath);
10783
11675
  try {
10784
- const content = fs7.readFileSync(resolvedPath, "utf8");
11676
+ const content = fs8.readFileSync(resolvedPath, "utf8");
10785
11677
  const config = yaml2.load(content);
10786
11678
  if (!config || typeof config !== "object") {
10787
11679
  throw new Error(`Invalid YAML in configuration file: ${resolvedPath}`);
@@ -10791,8 +11683,9 @@ var init_config_loader = __esm({
10791
11683
  config.extends = Array.isArray(inc) ? inc : [inc];
10792
11684
  delete config.include;
10793
11685
  }
11686
+ this.annotateToolsBaseDir(config, path10.dirname(resolvedPath));
10794
11687
  const previousBaseDir = this.options.baseDir;
10795
- this.options.baseDir = path9.dirname(resolvedPath);
11688
+ this.options.baseDir = path10.dirname(resolvedPath);
10796
11689
  try {
10797
11690
  if (config.extends) {
10798
11691
  const processedConfig = await this.processExtends(config);
@@ -10848,6 +11741,12 @@ var init_config_loader = __esm({
10848
11741
  if (!config || typeof config !== "object") {
10849
11742
  throw new Error(`Invalid YAML in remote configuration: ${url}`);
10850
11743
  }
11744
+ try {
11745
+ const parsed = new URL(url);
11746
+ const baseUrl = new URL(".", parsed).toString();
11747
+ this.annotateToolsBaseDir(config, baseUrl);
11748
+ } catch {
11749
+ }
10851
11750
  this.cache.set(url, {
10852
11751
  config,
10853
11752
  timestamp: Date.now(),
@@ -10875,25 +11774,25 @@ var init_config_loader = __esm({
10875
11774
  async fetchDefaultConfig() {
10876
11775
  const possiblePaths = [
10877
11776
  // Only support new non-dot filename
10878
- path9.join(__dirname, "defaults", "visor.yaml"),
11777
+ path10.join(__dirname, "defaults", "visor.yaml"),
10879
11778
  // When running from source
10880
- path9.join(__dirname, "..", "..", "defaults", "visor.yaml"),
11779
+ path10.join(__dirname, "..", "..", "defaults", "visor.yaml"),
10881
11780
  // Try via package root
10882
- this.findPackageRoot() ? path9.join(this.findPackageRoot(), "defaults", "visor.yaml") : "",
11781
+ this.findPackageRoot() ? path10.join(this.findPackageRoot(), "defaults", "visor.yaml") : "",
10883
11782
  // GitHub Action environment variable
10884
- process.env.GITHUB_ACTION_PATH ? path9.join(process.env.GITHUB_ACTION_PATH, "defaults", "visor.yaml") : "",
10885
- process.env.GITHUB_ACTION_PATH ? path9.join(process.env.GITHUB_ACTION_PATH, "dist", "defaults", "visor.yaml") : ""
11783
+ process.env.GITHUB_ACTION_PATH ? path10.join(process.env.GITHUB_ACTION_PATH, "defaults", "visor.yaml") : "",
11784
+ process.env.GITHUB_ACTION_PATH ? path10.join(process.env.GITHUB_ACTION_PATH, "dist", "defaults", "visor.yaml") : ""
10886
11785
  ].filter((p) => p);
10887
11786
  let defaultConfigPath;
10888
11787
  for (const possiblePath of possiblePaths) {
10889
- if (fs7.existsSync(possiblePath)) {
11788
+ if (fs8.existsSync(possiblePath)) {
10890
11789
  defaultConfigPath = possiblePath;
10891
11790
  break;
10892
11791
  }
10893
11792
  }
10894
11793
  if (defaultConfigPath) {
10895
11794
  console.error(`\u{1F4E6} Loading bundled default configuration from ${defaultConfigPath}`);
10896
- const content = fs7.readFileSync(defaultConfigPath, "utf8");
11795
+ const content = fs8.readFileSync(defaultConfigPath, "utf8");
10897
11796
  let config = yaml2.load(content);
10898
11797
  if (!config || typeof config !== "object") {
10899
11798
  throw new Error("Invalid default configuration");
@@ -10907,7 +11806,7 @@ var init_config_loader = __esm({
10907
11806
  if (config.extends) {
10908
11807
  const previousBaseDir = this.options.baseDir;
10909
11808
  try {
10910
- this.options.baseDir = path9.dirname(defaultConfigPath);
11809
+ this.options.baseDir = path10.dirname(defaultConfigPath);
10911
11810
  return await this.processExtends(config);
10912
11811
  } finally {
10913
11812
  this.options.baseDir = previousBaseDir;
@@ -10984,9 +11883,17 @@ var init_config_loader = __esm({
10984
11883
  */
10985
11884
  validateLocalPath(resolvedPath) {
10986
11885
  const projectRoot = this.options.projectRoot || process.cwd();
10987
- const normalizedPath = path9.normalize(resolvedPath);
10988
- const normalizedRoot = path9.normalize(projectRoot);
10989
- if (!normalizedPath.startsWith(normalizedRoot)) {
11886
+ const canonicalize = (p) => {
11887
+ const resolved = path10.resolve(p);
11888
+ try {
11889
+ return path10.normalize(fs8.realpathSync.native(resolved));
11890
+ } catch {
11891
+ return path10.normalize(resolved);
11892
+ }
11893
+ };
11894
+ const normalizedPath = canonicalize(resolvedPath);
11895
+ const normalizedRoot = canonicalize(projectRoot);
11896
+ if (normalizedPath !== normalizedRoot && !normalizedPath.startsWith(`${normalizedRoot}${path10.sep}`)) {
10990
11897
  throw new Error(
10991
11898
  `Security error: Path traversal detected. Cannot access files outside project root: ${projectRoot}`
10992
11899
  );
@@ -10997,7 +11904,7 @@ var init_config_loader = __esm({
10997
11904
  "/.ssh/",
10998
11905
  "/.aws/",
10999
11906
  "/.env",
11000
- "/private/"
11907
+ "/private/etc/"
11001
11908
  ];
11002
11909
  const lowerPath = normalizedPath.toLowerCase();
11003
11910
  for (const pattern of sensitivePatterns) {
@@ -11011,19 +11918,19 @@ var init_config_loader = __esm({
11011
11918
  */
11012
11919
  findPackageRoot() {
11013
11920
  let currentDir = __dirname;
11014
- const root = path9.parse(currentDir).root;
11921
+ const root = path10.parse(currentDir).root;
11015
11922
  while (currentDir !== root) {
11016
- const packageJsonPath = path9.join(currentDir, "package.json");
11017
- if (fs7.existsSync(packageJsonPath)) {
11923
+ const packageJsonPath = path10.join(currentDir, "package.json");
11924
+ if (fs8.existsSync(packageJsonPath)) {
11018
11925
  try {
11019
- const packageJson = JSON.parse(fs7.readFileSync(packageJsonPath, "utf8"));
11926
+ const packageJson = JSON.parse(fs8.readFileSync(packageJsonPath, "utf8"));
11020
11927
  if (packageJson.name === "@probelabs/visor") {
11021
11928
  return currentDir;
11022
11929
  }
11023
11930
  } catch {
11024
11931
  }
11025
11932
  }
11026
- currentDir = path9.dirname(currentDir);
11933
+ currentDir = path10.dirname(currentDir);
11027
11934
  }
11028
11935
  return null;
11029
11936
  }
@@ -11271,6 +12178,11 @@ var init_config_schema = __esm({
11271
12178
  CustomToolDefinition: {
11272
12179
  type: "object",
11273
12180
  properties: {
12181
+ type: {
12182
+ type: "string",
12183
+ enum: ["command", "api"],
12184
+ description: "Tool implementation type (defaults to 'command')"
12185
+ },
11274
12186
  name: {
11275
12187
  type: "string",
11276
12188
  description: "Tool name - used to reference the tool in MCP blocks"
@@ -11308,7 +12220,7 @@ var init_config_schema = __esm({
11308
12220
  },
11309
12221
  exec: {
11310
12222
  type: "string",
11311
- description: "Command to execute - supports Liquid template"
12223
+ description: "Command to execute - supports Liquid template (required for type: 'command')"
11312
12224
  },
11313
12225
  stdin: {
11314
12226
  type: "string",
@@ -11341,9 +12253,132 @@ var init_config_schema = __esm({
11341
12253
  outputSchema: {
11342
12254
  $ref: "#/definitions/Record%3Cstring%2Cunknown%3E",
11343
12255
  description: "Expected output schema for validation"
12256
+ },
12257
+ spec: {
12258
+ anyOf: [
12259
+ {
12260
+ type: "string"
12261
+ },
12262
+ {
12263
+ $ref: "#/definitions/Record%3Cstring%2Cunknown%3E"
12264
+ }
12265
+ ],
12266
+ description: "OpenAPI specification path/URL or inline object (required for type: 'api')"
12267
+ },
12268
+ overlays: {
12269
+ anyOf: [
12270
+ {
12271
+ type: "string"
12272
+ },
12273
+ {
12274
+ $ref: "#/definitions/Record%3Cstring%2Cunknown%3E"
12275
+ },
12276
+ {
12277
+ type: "array",
12278
+ items: {
12279
+ anyOf: [
12280
+ {
12281
+ type: "string"
12282
+ },
12283
+ {
12284
+ $ref: "#/definitions/Record%3Cstring%2Cunknown%3E"
12285
+ }
12286
+ ]
12287
+ }
12288
+ }
12289
+ ],
12290
+ description: "Overlay path/URL, inline object, or a mixed array applied in order"
12291
+ },
12292
+ targetUrl: {
12293
+ type: "string",
12294
+ description: "Override API base URL instead of OpenAPI servers"
12295
+ },
12296
+ target_url: {
12297
+ type: "string",
12298
+ description: "Alias for targetUrl (snake_case)"
12299
+ },
12300
+ whitelist: {
12301
+ anyOf: [
12302
+ {
12303
+ type: "array",
12304
+ items: {
12305
+ type: "string"
12306
+ }
12307
+ },
12308
+ {
12309
+ type: "string"
12310
+ }
12311
+ ],
12312
+ description: "Include only operations matching these glob patterns (operationId or METHOD:/path)"
12313
+ },
12314
+ blacklist: {
12315
+ anyOf: [
12316
+ {
12317
+ type: "array",
12318
+ items: {
12319
+ type: "string"
12320
+ }
12321
+ },
12322
+ {
12323
+ type: "string"
12324
+ }
12325
+ ],
12326
+ description: "Exclude operations matching these glob patterns (ignored when whitelist is set)"
12327
+ },
12328
+ headers: {
12329
+ $ref: "#/definitions/Record%3Cstring%2Cstring%3E",
12330
+ description: "Extra headers added to all API requests"
12331
+ },
12332
+ disableXMcp: {
12333
+ type: "boolean",
12334
+ description: "Disable X-MCP: 1 request header"
12335
+ },
12336
+ disable_x_mcp: {
12337
+ type: "boolean",
12338
+ description: "Alias for disableXMcp (snake_case)"
12339
+ },
12340
+ apiKey: {
12341
+ type: "string",
12342
+ description: "API key fallback credential used by security schemes"
12343
+ },
12344
+ api_key: {
12345
+ type: "string",
12346
+ description: "Alias for apiKey (snake_case)"
12347
+ },
12348
+ securitySchemeName: {
12349
+ type: "string",
12350
+ description: "Preferred security scheme name (optional hint)"
12351
+ },
12352
+ security_scheme_name: {
12353
+ type: "string",
12354
+ description: "Alias for securitySchemeName (snake_case)"
12355
+ },
12356
+ securityCredentials: {
12357
+ $ref: "#/definitions/Record%3Cstring%2Cstring%3E",
12358
+ description: "Credentials by OpenAPI security scheme name"
12359
+ },
12360
+ security_credentials: {
12361
+ $ref: "#/definitions/Record%3Cstring%2Cstring%3E",
12362
+ description: "Alias for securityCredentials (snake_case)"
12363
+ },
12364
+ namePrefix: {
12365
+ type: "string",
12366
+ description: "Optional prefix prepended to generated operation tool names"
12367
+ },
12368
+ name_prefix: {
12369
+ type: "string",
12370
+ description: "Alias for namePrefix (snake_case)"
12371
+ },
12372
+ requestTimeoutMs: {
12373
+ type: "number",
12374
+ description: "Request timeout in milliseconds for API calls"
12375
+ },
12376
+ request_timeout_ms: {
12377
+ type: "number",
12378
+ description: "Alias for requestTimeoutMs (snake_case)"
11344
12379
  }
11345
12380
  },
11346
- required: ["name", "exec"],
12381
+ required: ["name"],
11347
12382
  additionalProperties: false,
11348
12383
  description: "Custom tool definition for use in MCP blocks",
11349
12384
  patternProperties: {
@@ -11474,6 +12509,46 @@ var init_config_schema = __esm({
11474
12509
  type: "string",
11475
12510
  description: "Script content to execute for script checks"
11476
12511
  },
12512
+ tools: {
12513
+ type: "array",
12514
+ items: {
12515
+ anyOf: [
12516
+ {
12517
+ type: "string"
12518
+ },
12519
+ {
12520
+ type: "object",
12521
+ properties: {
12522
+ workflow: {
12523
+ type: "string"
12524
+ },
12525
+ args: {
12526
+ $ref: "#/definitions/Record%3Cstring%2Cunknown%3E"
12527
+ }
12528
+ },
12529
+ required: ["workflow"],
12530
+ additionalProperties: false
12531
+ }
12532
+ ]
12533
+ },
12534
+ description: "Tool names to expose inside script checks (string names or workflow references)"
12535
+ },
12536
+ tools_js: {
12537
+ type: "string",
12538
+ description: "JavaScript expression to dynamically compute tools for script checks"
12539
+ },
12540
+ mcp_servers: {
12541
+ $ref: "#/definitions/Record%3Cstring%2CMcpServerConfig%3E",
12542
+ description: "MCP servers whose tools are exposed inside script checks"
12543
+ },
12544
+ enable_fetch: {
12545
+ type: "boolean",
12546
+ description: "Enable fetch() function in script checks (default: false)"
12547
+ },
12548
+ enable_bash: {
12549
+ type: "boolean",
12550
+ description: "Enable bash() function in script checks (default: false)"
12551
+ },
11477
12552
  schedule: {
11478
12553
  type: "string",
11479
12554
  description: 'Cron schedule expression (e.g., "0 2 * * *") - optional for any check type'
@@ -11819,7 +12894,7 @@ var init_config_schema = __esm({
11819
12894
  description: "Arguments/inputs for the workflow"
11820
12895
  },
11821
12896
  overrides: {
11822
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-27516-src_types_config.ts-0-51381%3E%3E",
12897
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867%3E%3E",
11823
12898
  description: "Override specific step configurations in the workflow"
11824
12899
  },
11825
12900
  output_mapping: {
@@ -11835,7 +12910,7 @@ var init_config_schema = __esm({
11835
12910
  description: "Config file path - alternative to workflow ID (loads a Visor config file as workflow)"
11836
12911
  },
11837
12912
  workflow_overrides: {
11838
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-27516-src_types_config.ts-0-51381%3E%3E",
12913
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867%3E%3E",
11839
12914
  description: "Alias for overrides - workflow step overrides (backward compatibility)"
11840
12915
  },
11841
12916
  ref: {
@@ -11941,6 +13016,72 @@ var init_config_schema = __esm({
11941
13016
  ],
11942
13017
  description: "Valid check types in configuration"
11943
13018
  },
13019
+ "Record<string,McpServerConfig>": {
13020
+ type: "object",
13021
+ additionalProperties: {
13022
+ $ref: "#/definitions/McpServerConfig"
13023
+ }
13024
+ },
13025
+ McpServerConfig: {
13026
+ type: "object",
13027
+ properties: {
13028
+ command: {
13029
+ type: "string",
13030
+ description: "Command to execute (presence indicates stdio server)"
13031
+ },
13032
+ args: {
13033
+ type: "array",
13034
+ items: {
13035
+ type: "string"
13036
+ },
13037
+ description: "Arguments to pass to the command"
13038
+ },
13039
+ env: {
13040
+ $ref: "#/definitions/Record%3Cstring%2Cstring%3E",
13041
+ description: "Environment variables for the MCP server"
13042
+ },
13043
+ url: {
13044
+ type: "string",
13045
+ description: "URL endpoint (presence indicates external server)"
13046
+ },
13047
+ transport: {
13048
+ type: "string",
13049
+ enum: ["stdio", "sse", "http"],
13050
+ description: "Transport type"
13051
+ },
13052
+ workflow: {
13053
+ type: "string",
13054
+ description: "Workflow ID or path (presence indicates workflow tool)"
13055
+ },
13056
+ inputs: {
13057
+ $ref: "#/definitions/Record%3Cstring%2Cunknown%3E",
13058
+ description: "Inputs to pass to workflow"
13059
+ },
13060
+ description: {
13061
+ type: "string",
13062
+ description: "Tool description for AI"
13063
+ },
13064
+ allowedMethods: {
13065
+ type: "array",
13066
+ items: {
13067
+ type: "string"
13068
+ },
13069
+ description: 'Whitelist specific methods from this MCP server (supports wildcards like "search_*")'
13070
+ },
13071
+ blockedMethods: {
13072
+ type: "array",
13073
+ items: {
13074
+ type: "string"
13075
+ },
13076
+ description: 'Block specific methods from this MCP server (supports wildcards like "*_delete")'
13077
+ }
13078
+ },
13079
+ additionalProperties: false,
13080
+ description: "Unified MCP server/tool entry - type detected by which properties are present\n\nDetection logic (priority order): 1. Has `command` \u2192 stdio MCP server (external process) 2. Has `url` \u2192 SSE/HTTP MCP server (external endpoint) 3. Has `workflow` \u2192 workflow tool reference 4. Empty `{}` or just key \u2192 auto-detect from `tools:` section",
13081
+ patternProperties: {
13082
+ "^x-": {}
13083
+ }
13084
+ },
11944
13085
  EventTrigger: {
11945
13086
  type: "string",
11946
13087
  enum: [
@@ -12069,72 +13210,6 @@ var init_config_schema = __esm({
12069
13210
  "^x-": {}
12070
13211
  }
12071
13212
  },
12072
- "Record<string,McpServerConfig>": {
12073
- type: "object",
12074
- additionalProperties: {
12075
- $ref: "#/definitions/McpServerConfig"
12076
- }
12077
- },
12078
- McpServerConfig: {
12079
- type: "object",
12080
- properties: {
12081
- command: {
12082
- type: "string",
12083
- description: "Command to execute (presence indicates stdio server)"
12084
- },
12085
- args: {
12086
- type: "array",
12087
- items: {
12088
- type: "string"
12089
- },
12090
- description: "Arguments to pass to the command"
12091
- },
12092
- env: {
12093
- $ref: "#/definitions/Record%3Cstring%2Cstring%3E",
12094
- description: "Environment variables for the MCP server"
12095
- },
12096
- url: {
12097
- type: "string",
12098
- description: "URL endpoint (presence indicates external server)"
12099
- },
12100
- transport: {
12101
- type: "string",
12102
- enum: ["stdio", "sse", "http"],
12103
- description: "Transport type"
12104
- },
12105
- workflow: {
12106
- type: "string",
12107
- description: "Workflow ID or path (presence indicates workflow tool)"
12108
- },
12109
- inputs: {
12110
- $ref: "#/definitions/Record%3Cstring%2Cunknown%3E",
12111
- description: "Inputs to pass to workflow"
12112
- },
12113
- description: {
12114
- type: "string",
12115
- description: "Tool description for AI"
12116
- },
12117
- allowedMethods: {
12118
- type: "array",
12119
- items: {
12120
- type: "string"
12121
- },
12122
- description: 'Whitelist specific methods from this MCP server (supports wildcards like "search_*")'
12123
- },
12124
- blockedMethods: {
12125
- type: "array",
12126
- items: {
12127
- type: "string"
12128
- },
12129
- description: 'Block specific methods from this MCP server (supports wildcards like "*_delete")'
12130
- }
12131
- },
12132
- additionalProperties: false,
12133
- description: "Unified MCP server/tool entry - type detected by which properties are present\n\nDetection logic (priority order): 1. Has `command` \u2192 stdio MCP server (external process) 2. Has `url` \u2192 SSE/HTTP MCP server (external endpoint) 3. Has `workflow` \u2192 workflow tool reference 4. Empty `{}` or just key \u2192 auto-detect from `tools:` section",
12134
- patternProperties: {
12135
- "^x-": {}
12136
- }
12137
- },
12138
13213
  AIRetryConfig: {
12139
13214
  type: "object",
12140
13215
  properties: {
@@ -12523,7 +13598,7 @@ var init_config_schema = __esm({
12523
13598
  description: "Custom output name (defaults to workflow name)"
12524
13599
  },
12525
13600
  overrides: {
12526
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-27516-src_types_config.ts-0-51381%3E%3E",
13601
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867%3E%3E",
12527
13602
  description: "Step overrides"
12528
13603
  },
12529
13604
  output_mapping: {
@@ -12538,13 +13613,13 @@ var init_config_schema = __esm({
12538
13613
  "^x-": {}
12539
13614
  }
12540
13615
  },
12541
- "Record<string,Partial<interface-src_types_config.ts-13489-27516-src_types_config.ts-0-51381>>": {
13616
+ "Record<string,Partial<interface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867>>": {
12542
13617
  type: "object",
12543
13618
  additionalProperties: {
12544
- $ref: "#/definitions/Partial%3Cinterface-src_types_config.ts-13489-27516-src_types_config.ts-0-51381%3E"
13619
+ $ref: "#/definitions/Partial%3Cinterface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867%3E"
12545
13620
  }
12546
13621
  },
12547
- "Partial<interface-src_types_config.ts-13489-27516-src_types_config.ts-0-51381>": {
13622
+ "Partial<interface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867>": {
12548
13623
  type: "object",
12549
13624
  additionalProperties: false
12550
13625
  },
@@ -13792,13 +14867,13 @@ __export(config_exports, {
13792
14867
  ConfigManager: () => ConfigManager,
13793
14868
  VALID_EVENT_TRIGGERS: () => VALID_EVENT_TRIGGERS
13794
14869
  });
13795
- var yaml3, fs8, path10, import_simple_git, import_ajv3, import_ajv_formats2, VALID_EVENT_TRIGGERS, ConfigManager, __ajvValidate, __ajvErrors;
14870
+ var yaml3, fs9, path11, import_simple_git, import_ajv3, import_ajv_formats2, VALID_EVENT_TRIGGERS, ConfigManager, __ajvValidate, __ajvErrors;
13796
14871
  var init_config = __esm({
13797
14872
  "src/config.ts"() {
13798
14873
  "use strict";
13799
14874
  yaml3 = __toESM(require("js-yaml"));
13800
- fs8 = __toESM(require("fs"));
13801
- path10 = __toESM(require("path"));
14875
+ fs9 = __toESM(require("fs"));
14876
+ path11 = __toESM(require("path"));
13802
14877
  init_logger();
13803
14878
  import_simple_git = __toESM(require("simple-git"));
13804
14879
  init_config_loader();
@@ -13837,16 +14912,28 @@ var init_config = __esm({
13837
14912
  validEventTriggers = [...VALID_EVENT_TRIGGERS];
13838
14913
  validOutputFormats = ["table", "json", "markdown", "sarif"];
13839
14914
  validGroupByOptions = ["check", "file", "severity", "group"];
14915
+ /**
14916
+ * Annotate tools with the originating config directory for relative asset resolution.
14917
+ */
14918
+ annotateToolBaseDirs(config, baseDir) {
14919
+ if (!config.tools || typeof config.tools !== "object") {
14920
+ return;
14921
+ }
14922
+ for (const tool of Object.values(config.tools)) {
14923
+ if (!tool || typeof tool !== "object") continue;
14924
+ tool.__baseDir = tool.__baseDir || baseDir;
14925
+ }
14926
+ }
13840
14927
  /**
13841
14928
  * Load configuration from a file
13842
14929
  */
13843
14930
  async loadConfig(configPath, options = {}) {
13844
14931
  const { validate = true, mergeDefaults = true, allowedRemotePatterns } = options;
13845
- const resolvedPath = path10.isAbsolute(configPath) ? configPath : path10.resolve(process.cwd(), configPath);
14932
+ const resolvedPath = path11.isAbsolute(configPath) ? configPath : path11.resolve(process.cwd(), configPath);
13846
14933
  try {
13847
14934
  let configContent;
13848
14935
  try {
13849
- configContent = fs8.readFileSync(resolvedPath, "utf8");
14936
+ configContent = fs9.readFileSync(resolvedPath, "utf8");
13850
14937
  } catch (readErr) {
13851
14938
  if (readErr && (readErr.code === "ENOENT" || readErr.code === "ENOTDIR")) {
13852
14939
  throw new Error(`Configuration file not found: ${resolvedPath}`);
@@ -13868,7 +14955,7 @@ var init_config = __esm({
13868
14955
  const extendsValue = parsedConfig.extends || parsedConfig.include;
13869
14956
  if (extendsValue) {
13870
14957
  const loaderOptions = {
13871
- baseDir: path10.dirname(resolvedPath),
14958
+ baseDir: path11.dirname(resolvedPath),
13872
14959
  allowRemote: this.isRemoteExtendsAllowed(),
13873
14960
  maxDepth: 10,
13874
14961
  allowedRemotePatterns
@@ -13887,10 +14974,11 @@ var init_config = __esm({
13887
14974
  parsedConfig = merger.removeDisabledChecks(parsedConfig);
13888
14975
  }
13889
14976
  if (parsedConfig.id && typeof parsedConfig.id === "string") {
13890
- parsedConfig = await this.convertWorkflowToConfig(parsedConfig, path10.dirname(resolvedPath));
14977
+ parsedConfig = await this.convertWorkflowToConfig(parsedConfig, path11.dirname(resolvedPath));
13891
14978
  }
14979
+ this.annotateToolBaseDirs(parsedConfig, path11.dirname(resolvedPath));
13892
14980
  parsedConfig = this.normalizeStepsAndChecks(parsedConfig, !!extendsValue);
13893
- await this.loadWorkflows(parsedConfig, path10.dirname(resolvedPath));
14981
+ await this.loadWorkflows(parsedConfig, path11.dirname(resolvedPath));
13894
14982
  if (validate) {
13895
14983
  this.validateConfig(parsedConfig);
13896
14984
  }
@@ -13949,6 +15037,7 @@ var init_config = __esm({
13949
15037
  if (parsedConfig.id && typeof parsedConfig.id === "string") {
13950
15038
  parsedConfig = await this.convertWorkflowToConfig(parsedConfig, baseDir || process.cwd());
13951
15039
  }
15040
+ this.annotateToolBaseDirs(parsedConfig, baseDir || process.cwd());
13952
15041
  parsedConfig = this.normalizeStepsAndChecks(parsedConfig, !!extendsValue);
13953
15042
  await this.loadWorkflows(parsedConfig, baseDir || process.cwd());
13954
15043
  if (validate) this.validateConfig(parsedConfig);
@@ -13968,16 +15057,16 @@ var init_config = __esm({
13968
15057
  const searchDirs = [gitRoot, process.cwd()].filter(Boolean);
13969
15058
  for (const baseDir of searchDirs) {
13970
15059
  const candidates = ["visor.yaml", "visor.yml", ".visor.yaml", ".visor.yml"].map(
13971
- (p) => path10.join(baseDir, p)
15060
+ (p) => path11.join(baseDir, p)
13972
15061
  );
13973
15062
  for (const p of candidates) {
13974
15063
  try {
13975
- const st = fs8.statSync(p);
15064
+ const st = fs9.statSync(p);
13976
15065
  if (!st.isFile()) continue;
13977
- const isLegacy = path10.basename(p).startsWith(".");
15066
+ const isLegacy = path11.basename(p).startsWith(".");
13978
15067
  if (isLegacy) {
13979
15068
  if (process.env.VISOR_STRICT_CONFIG_NAME === "true") {
13980
- const rel = path10.relative(baseDir, p);
15069
+ const rel = path11.relative(baseDir, p);
13981
15070
  throw new Error(
13982
15071
  `Legacy config detected: ${rel}. Please rename to visor.yaml (or visor.yml).`
13983
15072
  );
@@ -14040,23 +15129,23 @@ var init_config = __esm({
14040
15129
  const possiblePaths = [];
14041
15130
  if (typeof __dirname !== "undefined") {
14042
15131
  possiblePaths.push(
14043
- path10.join(__dirname, "defaults", "visor.yaml"),
14044
- path10.join(__dirname, "..", "defaults", "visor.yaml")
15132
+ path11.join(__dirname, "defaults", "visor.yaml"),
15133
+ path11.join(__dirname, "..", "defaults", "visor.yaml")
14045
15134
  );
14046
15135
  }
14047
15136
  const pkgRoot = this.findPackageRoot();
14048
15137
  if (pkgRoot) {
14049
- possiblePaths.push(path10.join(pkgRoot, "defaults", "visor.yaml"));
15138
+ possiblePaths.push(path11.join(pkgRoot, "defaults", "visor.yaml"));
14050
15139
  }
14051
15140
  if (process.env.GITHUB_ACTION_PATH) {
14052
15141
  possiblePaths.push(
14053
- path10.join(process.env.GITHUB_ACTION_PATH, "defaults", "visor.yaml"),
14054
- path10.join(process.env.GITHUB_ACTION_PATH, "dist", "defaults", "visor.yaml")
15142
+ path11.join(process.env.GITHUB_ACTION_PATH, "defaults", "visor.yaml"),
15143
+ path11.join(process.env.GITHUB_ACTION_PATH, "dist", "defaults", "visor.yaml")
14055
15144
  );
14056
15145
  }
14057
15146
  let bundledConfigPath;
14058
15147
  for (const possiblePath of possiblePaths) {
14059
- if (fs8.existsSync(possiblePath)) {
15148
+ if (fs9.existsSync(possiblePath)) {
14060
15149
  bundledConfigPath = possiblePath;
14061
15150
  break;
14062
15151
  }
@@ -14064,7 +15153,7 @@ var init_config = __esm({
14064
15153
  if (bundledConfigPath) {
14065
15154
  console.error(`\u{1F4E6} Loading bundled default configuration from ${bundledConfigPath}`);
14066
15155
  const readAndParse = (p) => {
14067
- const raw = fs8.readFileSync(p, "utf8");
15156
+ const raw = fs9.readFileSync(p, "utf8");
14068
15157
  const obj = yaml3.load(raw);
14069
15158
  if (!obj || typeof obj !== "object") return {};
14070
15159
  if (obj.include && !obj.extends) {
@@ -14074,7 +15163,7 @@ var init_config = __esm({
14074
15163
  }
14075
15164
  return obj;
14076
15165
  };
14077
- const baseDir = path10.dirname(bundledConfigPath);
15166
+ const baseDir = path11.dirname(bundledConfigPath);
14078
15167
  const merger = new (init_config_merger(), __toCommonJS(config_merger_exports)).ConfigMerger();
14079
15168
  const loadWithExtendsSync = (p) => {
14080
15169
  const current = readAndParse(p);
@@ -14086,7 +15175,7 @@ var init_config = __esm({
14086
15175
  let acc = {};
14087
15176
  for (const src of list) {
14088
15177
  const rel = typeof src === "string" ? src : String(src);
14089
- const abs = path10.isAbsolute(rel) ? rel : path10.resolve(baseDir, rel);
15178
+ const abs = path11.isAbsolute(rel) ? rel : path11.resolve(baseDir, rel);
14090
15179
  const parentCfg = loadWithExtendsSync(abs);
14091
15180
  acc = merger.merge(acc, parentCfg);
14092
15181
  }
@@ -14110,18 +15199,18 @@ var init_config = __esm({
14110
15199
  */
14111
15200
  findPackageRoot() {
14112
15201
  let currentDir = __dirname;
14113
- while (currentDir !== path10.dirname(currentDir)) {
14114
- const packageJsonPath = path10.join(currentDir, "package.json");
14115
- if (fs8.existsSync(packageJsonPath)) {
15202
+ while (currentDir !== path11.dirname(currentDir)) {
15203
+ const packageJsonPath = path11.join(currentDir, "package.json");
15204
+ if (fs9.existsSync(packageJsonPath)) {
14116
15205
  try {
14117
- const packageJson = JSON.parse(fs8.readFileSync(packageJsonPath, "utf8"));
15206
+ const packageJson = JSON.parse(fs9.readFileSync(packageJsonPath, "utf8"));
14118
15207
  if (packageJson.name === "@probelabs/visor") {
14119
15208
  return currentDir;
14120
15209
  }
14121
15210
  } catch {
14122
15211
  }
14123
15212
  }
14124
- currentDir = path10.dirname(currentDir);
15213
+ currentDir = path11.dirname(currentDir);
14125
15214
  }
14126
15215
  return null;
14127
15216
  }
@@ -14461,6 +15550,42 @@ ${errors}`);
14461
15550
  if (config.ai_mcp_servers) {
14462
15551
  this.validateMcpServersObject(config.ai_mcp_servers, "ai_mcp_servers", errors, warnings);
14463
15552
  }
15553
+ if (config.tools) {
15554
+ for (const [toolName, toolDef] of Object.entries(config.tools)) {
15555
+ const type = toolDef.type || "command";
15556
+ if (type === "api") {
15557
+ const spec = toolDef.spec;
15558
+ const hasStringSpec = typeof spec === "string" && spec.trim().length > 0;
15559
+ const hasInlineSpec = !!spec && typeof spec === "object" && !Array.isArray(spec);
15560
+ if (!hasStringSpec && !hasInlineSpec) {
15561
+ errors.push({
15562
+ field: `tools.${toolName}.spec`,
15563
+ message: `Invalid tool configuration for "${toolName}": missing spec field (required for type: api)`
15564
+ });
15565
+ }
15566
+ const overlays = toolDef.overlays;
15567
+ if (overlays !== void 0) {
15568
+ const isInlineOverlay = !!overlays && typeof overlays === "object" && !Array.isArray(overlays);
15569
+ const isStringOverlay = typeof overlays === "string";
15570
+ const isMixedArrayOverlay = Array.isArray(overlays) && overlays.every(
15571
+ (item) => typeof item === "string" || !!item && typeof item === "object" && !Array.isArray(item)
15572
+ );
15573
+ if (!isInlineOverlay && !isStringOverlay && !isMixedArrayOverlay) {
15574
+ errors.push({
15575
+ field: `tools.${toolName}.overlays`,
15576
+ message: `Invalid tool configuration for "${toolName}": overlays must be a string, object, or array of strings/objects`,
15577
+ value: overlays
15578
+ });
15579
+ }
15580
+ }
15581
+ } else if (!toolDef.exec || typeof toolDef.exec !== "string") {
15582
+ errors.push({
15583
+ field: `tools.${toolName}.exec`,
15584
+ message: `Invalid tool configuration for "${toolName}": missing exec field (required for command tools)`
15585
+ });
15586
+ }
15587
+ }
15588
+ }
14464
15589
  if (config.output) {
14465
15590
  this.validateOutputConfig(config.output, errors);
14466
15591
  }
@@ -14878,7 +16003,7 @@ ${errors}`);
14878
16003
  if (policy.engine === "local" && policy.rules) {
14879
16004
  const rulesPath = Array.isArray(policy.rules) ? policy.rules : [policy.rules];
14880
16005
  for (const rp of rulesPath) {
14881
- if (typeof rp === "string" && !fs8.existsSync(path10.resolve(rp))) {
16006
+ if (typeof rp === "string" && !fs9.existsSync(path11.resolve(rp))) {
14882
16007
  warnings.push({
14883
16008
  field: "policy.rules",
14884
16009
  message: `Policy rules path does not exist: ${rp}. It will be resolved at runtime.`,
@@ -14928,7 +16053,7 @@ ${errors}`);
14928
16053
  });
14929
16054
  }
14930
16055
  }
14931
- if (policy.data && typeof policy.data === "string" && !fs8.existsSync(path10.resolve(policy.data))) {
16056
+ if (policy.data && typeof policy.data === "string" && !fs9.existsSync(path11.resolve(policy.data))) {
14932
16057
  warnings.push({
14933
16058
  field: "policy.data",
14934
16059
  message: `Policy data file does not exist: ${policy.data}. It will be resolved at runtime.`,
@@ -15078,7 +16203,7 @@ ${errors}`);
15078
16203
  try {
15079
16204
  if (!__ajvValidate) {
15080
16205
  try {
15081
- const jsonPath = path10.resolve(__dirname, "generated", "config-schema.json");
16206
+ const jsonPath = path11.resolve(__dirname, "generated", "config-schema.json");
15082
16207
  const jsonSchema = require(jsonPath);
15083
16208
  if (jsonSchema) {
15084
16209
  const ajv = new import_ajv3.default({ allErrors: true, allowUnionTypes: true, strict: false });
@@ -15128,6 +16253,9 @@ ${errors}`);
15128
16253
  if (topLevel && allowedTopLevelKeys.has(addl)) {
15129
16254
  continue;
15130
16255
  }
16256
+ if (!topLevel && addl === "__baseDir" && pathStr.startsWith("tools.")) {
16257
+ continue;
16258
+ }
15131
16259
  if (!topLevel && addl === "sandbox" && pathStr.match(/^(checks|steps)\.[^.]+$/)) {
15132
16260
  continue;
15133
16261
  }
@@ -15332,7 +16460,7 @@ var workflow_check_provider_exports = {};
15332
16460
  __export(workflow_check_provider_exports, {
15333
16461
  WorkflowCheckProvider: () => WorkflowCheckProvider
15334
16462
  });
15335
- var path11, yaml4, WorkflowCheckProvider;
16463
+ var path12, yaml4, WorkflowCheckProvider;
15336
16464
  var init_workflow_check_provider = __esm({
15337
16465
  "src/providers/workflow-check-provider.ts"() {
15338
16466
  "use strict";
@@ -15343,7 +16471,7 @@ var init_workflow_check_provider = __esm({
15343
16471
  init_sandbox();
15344
16472
  init_human_id();
15345
16473
  init_liquid_extensions();
15346
- path11 = __toESM(require("path"));
16474
+ path12 = __toESM(require("path"));
15347
16475
  yaml4 = __toESM(require("js-yaml"));
15348
16476
  WorkflowCheckProvider = class extends CheckProvider {
15349
16477
  registry;
@@ -15552,13 +16680,13 @@ var init_workflow_check_provider = __esm({
15552
16680
  const loadConfigLiquid = createExtendedLiquid();
15553
16681
  const loadConfig2 = (filePath) => {
15554
16682
  try {
15555
- const normalizedBasePath = path11.normalize(basePath);
15556
- const resolvedPath = path11.isAbsolute(filePath) ? path11.normalize(filePath) : path11.normalize(path11.resolve(basePath, filePath));
15557
- const basePathWithSep = normalizedBasePath.endsWith(path11.sep) ? normalizedBasePath : normalizedBasePath + path11.sep;
16683
+ const normalizedBasePath = path12.normalize(basePath);
16684
+ const resolvedPath = path12.isAbsolute(filePath) ? path12.normalize(filePath) : path12.normalize(path12.resolve(basePath, filePath));
16685
+ const basePathWithSep = normalizedBasePath.endsWith(path12.sep) ? normalizedBasePath : normalizedBasePath + path12.sep;
15558
16686
  if (!resolvedPath.startsWith(basePathWithSep) && resolvedPath !== normalizedBasePath) {
15559
16687
  throw new Error(`Path '${filePath}' escapes base directory`);
15560
16688
  }
15561
- const configDir = path11.dirname(resolvedPath);
16689
+ const configDir = path12.dirname(resolvedPath);
15562
16690
  const rawContent = require("fs").readFileSync(resolvedPath, "utf-8");
15563
16691
  const renderedContent = loadConfigLiquid.parseAndRenderSync(rawContent, {
15564
16692
  basePath: configDir
@@ -16009,17 +17137,17 @@ var init_workflow_check_provider = __esm({
16009
17137
  * so it can be executed by the state machine as a nested workflow.
16010
17138
  */
16011
17139
  async loadWorkflowFromConfigPath(sourcePath, baseDir) {
16012
- const path29 = require("path");
16013
- const fs25 = require("fs");
17140
+ const path30 = require("path");
17141
+ const fs26 = require("fs");
16014
17142
  const yaml5 = require("js-yaml");
16015
- const resolved = path29.isAbsolute(sourcePath) ? sourcePath : path29.resolve(baseDir, sourcePath);
16016
- if (!fs25.existsSync(resolved)) {
17143
+ const resolved = path30.isAbsolute(sourcePath) ? sourcePath : path30.resolve(baseDir, sourcePath);
17144
+ if (!fs26.existsSync(resolved)) {
16017
17145
  throw new Error(`Workflow config not found at: ${resolved}`);
16018
17146
  }
16019
- const rawContent = fs25.readFileSync(resolved, "utf8");
17147
+ const rawContent = fs26.readFileSync(resolved, "utf8");
16020
17148
  const rawData = yaml5.load(rawContent);
16021
17149
  if (rawData.imports && Array.isArray(rawData.imports)) {
16022
- const configDir = path29.dirname(resolved);
17150
+ const configDir = path30.dirname(resolved);
16023
17151
  for (const source of rawData.imports) {
16024
17152
  const results = await this.registry.import(source, {
16025
17153
  basePath: configDir,
@@ -16049,8 +17177,8 @@ ${errors}`);
16049
17177
  if (!steps || Object.keys(steps).length === 0) {
16050
17178
  throw new Error(`Config '${resolved}' does not contain any steps to execute as a workflow`);
16051
17179
  }
16052
- const id = path29.basename(resolved).replace(/\.(ya?ml)$/i, "");
16053
- const name = loaded.name || `Workflow from ${path29.basename(resolved)}`;
17180
+ const id = path30.basename(resolved).replace(/\.(ya?ml)$/i, "");
17181
+ const name = loaded.name || `Workflow from ${path30.basename(resolved)}`;
16054
17182
  const workflowDef = {
16055
17183
  id,
16056
17184
  name,
@@ -16289,11 +17417,11 @@ function fromDbRow(row) {
16289
17417
  previousResponse: row.previous_response ?? void 0
16290
17418
  };
16291
17419
  }
16292
- var import_path4, import_fs4, import_uuid, SqliteStoreBackend;
17420
+ var import_path5, import_fs4, import_uuid, SqliteStoreBackend;
16293
17421
  var init_sqlite_store = __esm({
16294
17422
  "src/scheduler/store/sqlite-store.ts"() {
16295
17423
  "use strict";
16296
- import_path4 = __toESM(require("path"));
17424
+ import_path5 = __toESM(require("path"));
16297
17425
  import_fs4 = __toESM(require("fs"));
16298
17426
  import_uuid = require("uuid");
16299
17427
  init_logger();
@@ -16306,8 +17434,8 @@ var init_sqlite_store = __esm({
16306
17434
  this.dbPath = filename || ".visor/schedules.db";
16307
17435
  }
16308
17436
  async initialize() {
16309
- const resolvedPath = import_path4.default.resolve(process.cwd(), this.dbPath);
16310
- const dir = import_path4.default.dirname(resolvedPath);
17437
+ const resolvedPath = import_path5.default.resolve(process.cwd(), this.dbPath);
17438
+ const dir = import_path5.default.dirname(resolvedPath);
16311
17439
  import_fs4.default.mkdirSync(dir, { recursive: true });
16312
17440
  const { createRequire } = require("module");
16313
17441
  const runtimeRequire = createRequire(__filename);
@@ -16703,10 +17831,10 @@ var init_store = __esm({
16703
17831
 
16704
17832
  // src/scheduler/store/json-migrator.ts
16705
17833
  async function migrateJsonToBackend(jsonPath, backend) {
16706
- const resolvedPath = import_path5.default.resolve(process.cwd(), jsonPath);
17834
+ const resolvedPath = import_path6.default.resolve(process.cwd(), jsonPath);
16707
17835
  let content;
16708
17836
  try {
16709
- content = await import_promises2.default.readFile(resolvedPath, "utf-8");
17837
+ content = await import_promises3.default.readFile(resolvedPath, "utf-8");
16710
17838
  } catch (err) {
16711
17839
  if (err.code === "ENOENT") {
16712
17840
  return 0;
@@ -16753,7 +17881,7 @@ async function migrateJsonToBackend(jsonPath, backend) {
16753
17881
  async function renameToMigrated(resolvedPath) {
16754
17882
  const migratedPath = `${resolvedPath}.migrated`;
16755
17883
  try {
16756
- await import_promises2.default.rename(resolvedPath, migratedPath);
17884
+ await import_promises3.default.rename(resolvedPath, migratedPath);
16757
17885
  logger.info(`[JsonMigrator] Backed up ${resolvedPath} \u2192 ${migratedPath}`);
16758
17886
  } catch (err) {
16759
17887
  logger.warn(
@@ -16761,12 +17889,12 @@ async function renameToMigrated(resolvedPath) {
16761
17889
  );
16762
17890
  }
16763
17891
  }
16764
- var import_promises2, import_path5;
17892
+ var import_promises3, import_path6;
16765
17893
  var init_json_migrator = __esm({
16766
17894
  "src/scheduler/store/json-migrator.ts"() {
16767
17895
  "use strict";
16768
- import_promises2 = __toESM(require("fs/promises"));
16769
- import_path5 = __toESM(require("path"));
17896
+ import_promises3 = __toESM(require("fs/promises"));
17897
+ import_path6 = __toESM(require("path"));
16770
17898
  init_logger();
16771
17899
  }
16772
17900
  });
@@ -17044,6 +18172,13 @@ var init_schedule_parser = __esm({
17044
18172
  });
17045
18173
 
17046
18174
  // src/scheduler/schedule-tool.ts
18175
+ var schedule_tool_exports = {};
18176
+ __export(schedule_tool_exports, {
18177
+ buildScheduleToolContext: () => buildScheduleToolContext,
18178
+ getScheduleToolDefinition: () => getScheduleToolDefinition,
18179
+ handleScheduleAction: () => handleScheduleAction,
18180
+ isScheduleTool: () => isScheduleTool
18181
+ });
17047
18182
  function matchGlobPattern(pattern, value) {
17048
18183
  const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
17049
18184
  return new RegExp(`^${regexPattern}$`).test(value);
@@ -18374,7 +19509,23 @@ var init_mcp_custom_sse_server = __esm({
18374
19509
  * Handle tools/list MCP request
18375
19510
  */
18376
19511
  async handleToolsList(id) {
18377
- const allTools = Array.from(this.tools.values());
19512
+ const normalizeInputSchema = (schema) => {
19513
+ if (schema && schema.type === "object") {
19514
+ return schema;
19515
+ }
19516
+ return {
19517
+ type: "object",
19518
+ properties: {},
19519
+ required: []
19520
+ };
19521
+ };
19522
+ const regularTools = await this.toolExecutor.listMcpTools();
19523
+ const workflowTools = Array.from(this.tools.values()).filter(isWorkflowTool).map((tool) => ({
19524
+ name: tool.name,
19525
+ description: tool.description || `Execute ${tool.name}`,
19526
+ inputSchema: normalizeInputSchema(tool.inputSchema)
19527
+ }));
19528
+ const allTools = [...regularTools, ...workflowTools];
18378
19529
  if (this.debug) {
18379
19530
  logger.debug(
18380
19531
  `[CustomToolsSSEServer:${this.sessionId}] Listing ${allTools.length} tools: ${allTools.map((t) => t.name).join(", ")}`
@@ -18387,11 +19538,7 @@ var init_mcp_custom_sse_server = __esm({
18387
19538
  tools: allTools.map((tool) => ({
18388
19539
  name: tool.name,
18389
19540
  description: tool.description || `Execute ${tool.name}`,
18390
- inputSchema: tool.inputSchema || {
18391
- type: "object",
18392
- properties: {},
18393
- required: []
18394
- }
19541
+ inputSchema: normalizeInputSchema(tool.inputSchema)
18395
19542
  }))
18396
19543
  }
18397
19544
  };
@@ -18581,8 +19728,45 @@ var init_mcp_custom_sse_server = __esm({
18581
19728
  }
18582
19729
  });
18583
19730
 
19731
+ // src/utils/tool-resolver.ts
19732
+ function resolveTools(toolItems, globalTools, logPrefix = "[ToolResolver]") {
19733
+ const tools = /* @__PURE__ */ new Map();
19734
+ for (const item of toolItems) {
19735
+ const workflowTool = resolveWorkflowToolFromItem(item);
19736
+ if (workflowTool) {
19737
+ logger.debug(`${logPrefix} Loaded workflow '${workflowTool.name}' as tool`);
19738
+ tools.set(workflowTool.name, workflowTool);
19739
+ continue;
19740
+ }
19741
+ if (typeof item === "string") {
19742
+ if (globalTools && globalTools[item]) {
19743
+ const tool = globalTools[item];
19744
+ tool.name = tool.name || item;
19745
+ tools.set(item, tool);
19746
+ continue;
19747
+ }
19748
+ logger.warn(`${logPrefix} Tool '${item}' not found in global tools or workflow registry`);
19749
+ } else if (isWorkflowToolReference(item)) {
19750
+ logger.warn(`${logPrefix} Workflow '${item.workflow}' referenced but not found in registry`);
19751
+ }
19752
+ }
19753
+ if (tools.size === 0 && toolItems.length > 0 && !globalTools) {
19754
+ logger.warn(
19755
+ `${logPrefix} Tools specified but no global tools found in configuration and no workflows matched`
19756
+ );
19757
+ }
19758
+ return tools;
19759
+ }
19760
+ var init_tool_resolver = __esm({
19761
+ "src/utils/tool-resolver.ts"() {
19762
+ "use strict";
19763
+ init_workflow_tool_executor();
19764
+ init_logger();
19765
+ }
19766
+ });
19767
+
18584
19768
  // src/providers/ai-check-provider.ts
18585
- var import_promises3, import_path6, AICheckProvider;
19769
+ var import_promises4, import_path7, AICheckProvider;
18586
19770
  var init_ai_check_provider = __esm({
18587
19771
  "src/providers/ai-check-provider.ts"() {
18588
19772
  "use strict";
@@ -18591,13 +19775,14 @@ var init_ai_check_provider = __esm({
18591
19775
  init_env_resolver();
18592
19776
  init_issue_filter();
18593
19777
  init_liquid_extensions();
18594
- import_promises3 = __toESM(require("fs/promises"));
18595
- import_path6 = __toESM(require("path"));
19778
+ import_promises4 = __toESM(require("fs/promises"));
19779
+ import_path7 = __toESM(require("path"));
18596
19780
  init_lazy_otel();
18597
19781
  init_state_capture();
18598
19782
  init_mcp_custom_sse_server();
18599
19783
  init_logger();
18600
19784
  init_workflow_tool_executor();
19785
+ init_tool_resolver();
18601
19786
  init_sandbox();
18602
19787
  init_schedule_tool();
18603
19788
  init_schedule_tool_handler();
@@ -18756,7 +19941,7 @@ var init_ai_check_provider = __esm({
18756
19941
  const hasFileExtension = /\.[a-zA-Z0-9]{1,10}$/i.test(str);
18757
19942
  const hasPathSeparators = /[\/\\]/.test(str);
18758
19943
  const isRelativePath = /^\.{1,2}\//.test(str);
18759
- const isAbsolutePath = import_path6.default.isAbsolute(str);
19944
+ const isAbsolutePath = import_path7.default.isAbsolute(str);
18760
19945
  const hasTypicalFileChars = /^[a-zA-Z0-9._\-\/\\:~]+$/.test(str);
18761
19946
  if (!(hasFileExtension || isRelativePath || isAbsolutePath || hasPathSeparators)) {
18762
19947
  return false;
@@ -18766,14 +19951,14 @@ var init_ai_check_provider = __esm({
18766
19951
  }
18767
19952
  try {
18768
19953
  let resolvedPath;
18769
- if (import_path6.default.isAbsolute(str)) {
18770
- resolvedPath = import_path6.default.normalize(str);
19954
+ if (import_path7.default.isAbsolute(str)) {
19955
+ resolvedPath = import_path7.default.normalize(str);
18771
19956
  } else {
18772
- resolvedPath = import_path6.default.resolve(process.cwd(), str);
19957
+ resolvedPath = import_path7.default.resolve(process.cwd(), str);
18773
19958
  }
18774
- const fs25 = require("fs").promises;
19959
+ const fs26 = require("fs").promises;
18775
19960
  try {
18776
- const stat = await fs25.stat(resolvedPath);
19961
+ const stat = await fs26.stat(resolvedPath);
18777
19962
  return stat.isFile();
18778
19963
  } catch {
18779
19964
  return hasFileExtension && (isRelativePath || isAbsolutePath || hasPathSeparators);
@@ -18790,14 +19975,14 @@ var init_ai_check_provider = __esm({
18790
19975
  throw new Error("Prompt file must have .liquid extension");
18791
19976
  }
18792
19977
  let resolvedPath;
18793
- if (import_path6.default.isAbsolute(promptPath)) {
19978
+ if (import_path7.default.isAbsolute(promptPath)) {
18794
19979
  resolvedPath = promptPath;
18795
19980
  } else {
18796
- resolvedPath = import_path6.default.resolve(process.cwd(), promptPath);
19981
+ resolvedPath = import_path7.default.resolve(process.cwd(), promptPath);
18797
19982
  }
18798
- if (!import_path6.default.isAbsolute(promptPath)) {
18799
- const normalizedPath = import_path6.default.normalize(resolvedPath);
18800
- const currentDir = import_path6.default.resolve(process.cwd());
19983
+ if (!import_path7.default.isAbsolute(promptPath)) {
19984
+ const normalizedPath = import_path7.default.normalize(resolvedPath);
19985
+ const currentDir = import_path7.default.resolve(process.cwd());
18801
19986
  if (!normalizedPath.startsWith(currentDir)) {
18802
19987
  throw new Error("Invalid prompt file path: path traversal detected");
18803
19988
  }
@@ -18806,7 +19991,7 @@ var init_ai_check_provider = __esm({
18806
19991
  throw new Error("Invalid prompt file path: path traversal detected");
18807
19992
  }
18808
19993
  try {
18809
- const promptContent = await import_promises3.default.readFile(resolvedPath, "utf-8");
19994
+ const promptContent = await import_promises4.default.readFile(resolvedPath, "utf-8");
18810
19995
  return promptContent;
18811
19996
  } catch (error) {
18812
19997
  throw new Error(
@@ -20209,37 +21394,8 @@ ${processedPrompt}` : processedPrompt;
20209
21394
  * Supports both traditional custom tools and workflow-as-tool references
20210
21395
  */
20211
21396
  loadCustomTools(toolItems, config) {
20212
- const tools = /* @__PURE__ */ new Map();
20213
21397
  const globalTools = config.__globalTools;
20214
- for (const item of toolItems) {
20215
- const workflowTool = resolveWorkflowToolFromItem(item);
20216
- if (workflowTool) {
20217
- logger.debug(`[AICheckProvider] Loaded workflow '${workflowTool.name}' as custom tool`);
20218
- tools.set(workflowTool.name, workflowTool);
20219
- continue;
20220
- }
20221
- if (typeof item === "string") {
20222
- if (globalTools && globalTools[item]) {
20223
- const tool = globalTools[item];
20224
- tool.name = tool.name || item;
20225
- tools.set(item, tool);
20226
- continue;
20227
- }
20228
- logger.warn(
20229
- `[AICheckProvider] Custom tool '${item}' not found in global tools or workflow registry`
20230
- );
20231
- } else if (isWorkflowToolReference(item)) {
20232
- logger.warn(
20233
- `[AICheckProvider] Workflow '${item.workflow}' referenced but not found in registry`
20234
- );
20235
- }
20236
- }
20237
- if (tools.size === 0 && toolItems.length > 0 && !globalTools) {
20238
- logger.warn(
20239
- `[AICheckProvider] ai_custom_tools specified but no global tools found in configuration and no workflows matched`
20240
- );
20241
- }
20242
- return tools;
21398
+ return resolveTools(toolItems, globalTools, "[AICheckProvider]");
20243
21399
  }
20244
21400
  /**
20245
21401
  * Intersect config-level allowedTools with policy-level allowedTools.
@@ -20789,7 +21945,7 @@ var init_template_context = __esm({
20789
21945
  });
20790
21946
 
20791
21947
  // src/providers/http-client-provider.ts
20792
- var fs12, path15, HttpClientProvider;
21948
+ var fs13, path16, HttpClientProvider;
20793
21949
  var init_http_client_provider = __esm({
20794
21950
  "src/providers/http-client-provider.ts"() {
20795
21951
  "use strict";
@@ -20799,8 +21955,8 @@ var init_http_client_provider = __esm({
20799
21955
  init_sandbox();
20800
21956
  init_template_context();
20801
21957
  init_logger();
20802
- fs12 = __toESM(require("fs"));
20803
- path15 = __toESM(require("path"));
21958
+ fs13 = __toESM(require("fs"));
21959
+ path16 = __toESM(require("path"));
20804
21960
  HttpClientProvider = class extends CheckProvider {
20805
21961
  liquid;
20806
21962
  sandbox;
@@ -20895,14 +22051,14 @@ var init_http_client_provider = __esm({
20895
22051
  const parentContext = context2?._parentContext;
20896
22052
  const workingDirectory = parentContext?.workingDirectory;
20897
22053
  const workspaceEnabled = parentContext?.workspace?.isEnabled?.();
20898
- if (workspaceEnabled && workingDirectory && !path15.isAbsolute(resolvedOutputFile)) {
20899
- resolvedOutputFile = path15.join(workingDirectory, resolvedOutputFile);
22054
+ if (workspaceEnabled && workingDirectory && !path16.isAbsolute(resolvedOutputFile)) {
22055
+ resolvedOutputFile = path16.join(workingDirectory, resolvedOutputFile);
20900
22056
  logger.debug(
20901
22057
  `[http_client] Resolved relative output_file to workspace: ${resolvedOutputFile}`
20902
22058
  );
20903
22059
  }
20904
- if (skipIfExists && fs12.existsSync(resolvedOutputFile)) {
20905
- const stats = fs12.statSync(resolvedOutputFile);
22060
+ if (skipIfExists && fs13.existsSync(resolvedOutputFile)) {
22061
+ const stats = fs13.statSync(resolvedOutputFile);
20906
22062
  logger.verbose(`[http_client] File cached: ${resolvedOutputFile} (${stats.size} bytes)`);
20907
22063
  return {
20908
22064
  issues: [],
@@ -21113,13 +22269,13 @@ var init_http_client_provider = __esm({
21113
22269
  ]
21114
22270
  };
21115
22271
  }
21116
- const parentDir = path15.dirname(outputFile);
21117
- if (parentDir && !fs12.existsSync(parentDir)) {
21118
- fs12.mkdirSync(parentDir, { recursive: true });
22272
+ const parentDir = path16.dirname(outputFile);
22273
+ if (parentDir && !fs13.existsSync(parentDir)) {
22274
+ fs13.mkdirSync(parentDir, { recursive: true });
21119
22275
  }
21120
22276
  const arrayBuffer = await response.arrayBuffer();
21121
22277
  const buffer = Buffer.from(arrayBuffer);
21122
- fs12.writeFileSync(outputFile, buffer);
22278
+ fs13.writeFileSync(outputFile, buffer);
21123
22279
  const contentType = response.headers.get("content-type") || "application/octet-stream";
21124
22280
  logger.verbose(`[http_client] Downloaded: ${outputFile} (${buffer.length} bytes)`);
21125
22281
  return {
@@ -21878,7 +23034,7 @@ var init_claude_code_types = __esm({
21878
23034
  function isClaudeCodeConstructor(value) {
21879
23035
  return typeof value === "function";
21880
23036
  }
21881
- var import_promises4, import_path7, ClaudeCodeSDKNotInstalledError, ClaudeCodeAPIKeyMissingError, ClaudeCodeCheckProvider;
23037
+ var import_promises5, import_path8, ClaudeCodeSDKNotInstalledError, ClaudeCodeAPIKeyMissingError, ClaudeCodeCheckProvider;
21882
23038
  var init_claude_code_check_provider = __esm({
21883
23039
  "src/providers/claude-code-check-provider.ts"() {
21884
23040
  "use strict";
@@ -21886,8 +23042,8 @@ var init_claude_code_check_provider = __esm({
21886
23042
  init_env_resolver();
21887
23043
  init_issue_filter();
21888
23044
  init_liquid_extensions();
21889
- import_promises4 = __toESM(require("fs/promises"));
21890
- import_path7 = __toESM(require("path"));
23045
+ import_promises5 = __toESM(require("fs/promises"));
23046
+ import_path8 = __toESM(require("path"));
21891
23047
  init_claude_code_types();
21892
23048
  ClaudeCodeSDKNotInstalledError = class extends Error {
21893
23049
  constructor() {
@@ -22035,7 +23191,7 @@ var init_claude_code_check_provider = __esm({
22035
23191
  const hasFileExtension = /\.[a-zA-Z0-9]{1,10}$/i.test(str);
22036
23192
  const hasPathSeparators = /[\/\\]/.test(str);
22037
23193
  const isRelativePath = /^\.{1,2}\//.test(str);
22038
- const isAbsolutePath = import_path7.default.isAbsolute(str);
23194
+ const isAbsolutePath = import_path8.default.isAbsolute(str);
22039
23195
  const hasTypicalFileChars = /^[a-zA-Z0-9._\-\/\\:~]+$/.test(str);
22040
23196
  if (!(hasFileExtension || isRelativePath || isAbsolutePath || hasPathSeparators)) {
22041
23197
  return false;
@@ -22045,13 +23201,13 @@ var init_claude_code_check_provider = __esm({
22045
23201
  }
22046
23202
  try {
22047
23203
  let resolvedPath;
22048
- if (import_path7.default.isAbsolute(str)) {
22049
- resolvedPath = import_path7.default.normalize(str);
23204
+ if (import_path8.default.isAbsolute(str)) {
23205
+ resolvedPath = import_path8.default.normalize(str);
22050
23206
  } else {
22051
- resolvedPath = import_path7.default.resolve(process.cwd(), str);
23207
+ resolvedPath = import_path8.default.resolve(process.cwd(), str);
22052
23208
  }
22053
23209
  try {
22054
- const stat = await import_promises4.default.stat(resolvedPath);
23210
+ const stat = await import_promises5.default.stat(resolvedPath);
22055
23211
  return stat.isFile();
22056
23212
  } catch {
22057
23213
  return hasFileExtension && (isRelativePath || isAbsolutePath || hasPathSeparators);
@@ -22068,14 +23224,14 @@ var init_claude_code_check_provider = __esm({
22068
23224
  throw new Error("Prompt file must have .liquid extension");
22069
23225
  }
22070
23226
  let resolvedPath;
22071
- if (import_path7.default.isAbsolute(promptPath)) {
23227
+ if (import_path8.default.isAbsolute(promptPath)) {
22072
23228
  resolvedPath = promptPath;
22073
23229
  } else {
22074
- resolvedPath = import_path7.default.resolve(process.cwd(), promptPath);
23230
+ resolvedPath = import_path8.default.resolve(process.cwd(), promptPath);
22075
23231
  }
22076
- if (!import_path7.default.isAbsolute(promptPath)) {
22077
- const normalizedPath = import_path7.default.normalize(resolvedPath);
22078
- const currentDir = import_path7.default.resolve(process.cwd());
23232
+ if (!import_path8.default.isAbsolute(promptPath)) {
23233
+ const normalizedPath = import_path8.default.normalize(resolvedPath);
23234
+ const currentDir = import_path8.default.resolve(process.cwd());
22079
23235
  if (!normalizedPath.startsWith(currentDir)) {
22080
23236
  throw new Error("Invalid prompt file path: path traversal detected");
22081
23237
  }
@@ -22084,7 +23240,7 @@ var init_claude_code_check_provider = __esm({
22084
23240
  throw new Error("Invalid prompt file path: path traversal detected");
22085
23241
  }
22086
23242
  try {
22087
- const promptContent = await import_promises4.default.readFile(resolvedPath, "utf-8");
23243
+ const promptContent = await import_promises5.default.readFile(resolvedPath, "utf-8");
22088
23244
  return promptContent;
22089
23245
  } catch (error) {
22090
23246
  throw new Error(
@@ -22436,6 +23592,7 @@ var init_command_check_provider = __esm({
22436
23592
  init_sandbox();
22437
23593
  init_liquid_extensions();
22438
23594
  init_logger();
23595
+ init_env_resolver();
22439
23596
  init_command_executor();
22440
23597
  init_author_permissions();
22441
23598
  init_lazy_otel();
@@ -22637,7 +23794,7 @@ var init_command_check_provider = __esm({
22637
23794
  if (config.env) {
22638
23795
  for (const [key, value] of Object.entries(config.env)) {
22639
23796
  if (value !== void 0 && value !== null) {
22640
- scriptEnv[key] = String(value);
23797
+ scriptEnv[key] = String(EnvironmentResolver.resolveValue(value));
22641
23798
  }
22642
23799
  }
22643
23800
  }
@@ -24653,14 +25810,14 @@ var require_util = __commonJS({
24653
25810
  }
24654
25811
  const port = url.port != null ? url.port : url.protocol === "https:" ? 443 : 80;
24655
25812
  let origin = url.origin != null ? url.origin : `${url.protocol}//${url.hostname}:${port}`;
24656
- let path29 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
25813
+ let path30 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
24657
25814
  if (origin.endsWith("/")) {
24658
25815
  origin = origin.substring(0, origin.length - 1);
24659
25816
  }
24660
- if (path29 && !path29.startsWith("/")) {
24661
- path29 = `/${path29}`;
25817
+ if (path30 && !path30.startsWith("/")) {
25818
+ path30 = `/${path30}`;
24662
25819
  }
24663
- url = new URL(origin + path29);
25820
+ url = new URL(origin + path30);
24664
25821
  }
24665
25822
  return url;
24666
25823
  }
@@ -26274,20 +27431,20 @@ var require_parseParams = __commonJS({
26274
27431
  var require_basename = __commonJS({
26275
27432
  "node_modules/@fastify/busboy/lib/utils/basename.js"(exports2, module2) {
26276
27433
  "use strict";
26277
- module2.exports = function basename4(path29) {
26278
- if (typeof path29 !== "string") {
27434
+ module2.exports = function basename4(path30) {
27435
+ if (typeof path30 !== "string") {
26279
27436
  return "";
26280
27437
  }
26281
- for (var i = path29.length - 1; i >= 0; --i) {
26282
- switch (path29.charCodeAt(i)) {
27438
+ for (var i = path30.length - 1; i >= 0; --i) {
27439
+ switch (path30.charCodeAt(i)) {
26283
27440
  case 47:
26284
27441
  // '/'
26285
27442
  case 92:
26286
- path29 = path29.slice(i + 1);
26287
- return path29 === ".." || path29 === "." ? "" : path29;
27443
+ path30 = path30.slice(i + 1);
27444
+ return path30 === ".." || path30 === "." ? "" : path30;
26288
27445
  }
26289
27446
  }
26290
- return path29 === ".." || path29 === "." ? "" : path29;
27447
+ return path30 === ".." || path30 === "." ? "" : path30;
26291
27448
  };
26292
27449
  }
26293
27450
  });
@@ -29318,7 +30475,7 @@ var require_request = __commonJS({
29318
30475
  }
29319
30476
  var Request = class _Request {
29320
30477
  constructor(origin, {
29321
- path: path29,
30478
+ path: path30,
29322
30479
  method,
29323
30480
  body,
29324
30481
  headers,
@@ -29332,11 +30489,11 @@ var require_request = __commonJS({
29332
30489
  throwOnError,
29333
30490
  expectContinue
29334
30491
  }, handler) {
29335
- if (typeof path29 !== "string") {
30492
+ if (typeof path30 !== "string") {
29336
30493
  throw new InvalidArgumentError("path must be a string");
29337
- } else if (path29[0] !== "/" && !(path29.startsWith("http://") || path29.startsWith("https://")) && method !== "CONNECT") {
30494
+ } else if (path30[0] !== "/" && !(path30.startsWith("http://") || path30.startsWith("https://")) && method !== "CONNECT") {
29338
30495
  throw new InvalidArgumentError("path must be an absolute URL or start with a slash");
29339
- } else if (invalidPathRegex.exec(path29) !== null) {
30496
+ } else if (invalidPathRegex.exec(path30) !== null) {
29340
30497
  throw new InvalidArgumentError("invalid request path");
29341
30498
  }
29342
30499
  if (typeof method !== "string") {
@@ -29399,7 +30556,7 @@ var require_request = __commonJS({
29399
30556
  this.completed = false;
29400
30557
  this.aborted = false;
29401
30558
  this.upgrade = upgrade || null;
29402
- this.path = query ? util.buildURL(path29, query) : path29;
30559
+ this.path = query ? util.buildURL(path30, query) : path30;
29403
30560
  this.origin = origin;
29404
30561
  this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent;
29405
30562
  this.blocking = blocking == null ? false : blocking;
@@ -30407,9 +31564,9 @@ var require_RedirectHandler = __commonJS({
30407
31564
  return this.handler.onHeaders(statusCode, headers, resume, statusText);
30408
31565
  }
30409
31566
  const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)));
30410
- const path29 = search ? `${pathname}${search}` : pathname;
31567
+ const path30 = search ? `${pathname}${search}` : pathname;
30411
31568
  this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin);
30412
- this.opts.path = path29;
31569
+ this.opts.path = path30;
30413
31570
  this.opts.origin = origin;
30414
31571
  this.opts.maxRedirections = 0;
30415
31572
  this.opts.query = null;
@@ -31651,7 +32808,7 @@ var require_client = __commonJS({
31651
32808
  writeH2(client, client[kHTTP2Session], request);
31652
32809
  return;
31653
32810
  }
31654
- const { body, method, path: path29, host, upgrade, headers, blocking, reset } = request;
32811
+ const { body, method, path: path30, host, upgrade, headers, blocking, reset } = request;
31655
32812
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
31656
32813
  if (body && typeof body.read === "function") {
31657
32814
  body.read(0);
@@ -31701,7 +32858,7 @@ var require_client = __commonJS({
31701
32858
  if (blocking) {
31702
32859
  socket[kBlocking] = true;
31703
32860
  }
31704
- let header = `${method} ${path29} HTTP/1.1\r
32861
+ let header = `${method} ${path30} HTTP/1.1\r
31705
32862
  `;
31706
32863
  if (typeof host === "string") {
31707
32864
  header += `host: ${host}\r
@@ -31764,7 +32921,7 @@ upgrade: ${upgrade}\r
31764
32921
  return true;
31765
32922
  }
31766
32923
  function writeH2(client, session, request) {
31767
- const { body, method, path: path29, host, upgrade, expectContinue, signal, headers: reqHeaders } = request;
32924
+ const { body, method, path: path30, host, upgrade, expectContinue, signal, headers: reqHeaders } = request;
31768
32925
  let headers;
31769
32926
  if (typeof reqHeaders === "string") headers = Request[kHTTP2CopyHeaders](reqHeaders.trim());
31770
32927
  else headers = reqHeaders;
@@ -31807,7 +32964,7 @@ upgrade: ${upgrade}\r
31807
32964
  });
31808
32965
  return true;
31809
32966
  }
31810
- headers[HTTP2_HEADER_PATH] = path29;
32967
+ headers[HTTP2_HEADER_PATH] = path30;
31811
32968
  headers[HTTP2_HEADER_SCHEME] = "https";
31812
32969
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
31813
32970
  if (body && typeof body.read === "function") {
@@ -34050,20 +35207,20 @@ var require_mock_utils = __commonJS({
34050
35207
  }
34051
35208
  return true;
34052
35209
  }
34053
- function safeUrl(path29) {
34054
- if (typeof path29 !== "string") {
34055
- return path29;
35210
+ function safeUrl(path30) {
35211
+ if (typeof path30 !== "string") {
35212
+ return path30;
34056
35213
  }
34057
- const pathSegments = path29.split("?");
35214
+ const pathSegments = path30.split("?");
34058
35215
  if (pathSegments.length !== 2) {
34059
- return path29;
35216
+ return path30;
34060
35217
  }
34061
35218
  const qp = new URLSearchParams(pathSegments.pop());
34062
35219
  qp.sort();
34063
35220
  return [...pathSegments, qp.toString()].join("?");
34064
35221
  }
34065
- function matchKey(mockDispatch2, { path: path29, method, body, headers }) {
34066
- const pathMatch = matchValue(mockDispatch2.path, path29);
35222
+ function matchKey(mockDispatch2, { path: path30, method, body, headers }) {
35223
+ const pathMatch = matchValue(mockDispatch2.path, path30);
34067
35224
  const methodMatch = matchValue(mockDispatch2.method, method);
34068
35225
  const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true;
34069
35226
  const headersMatch = matchHeaders(mockDispatch2, headers);
@@ -34081,7 +35238,7 @@ var require_mock_utils = __commonJS({
34081
35238
  function getMockDispatch(mockDispatches, key) {
34082
35239
  const basePath = key.query ? buildURL(key.path, key.query) : key.path;
34083
35240
  const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath;
34084
- let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path29 }) => matchValue(safeUrl(path29), resolvedPath));
35241
+ let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path30 }) => matchValue(safeUrl(path30), resolvedPath));
34085
35242
  if (matchedMockDispatches.length === 0) {
34086
35243
  throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`);
34087
35244
  }
@@ -34118,9 +35275,9 @@ var require_mock_utils = __commonJS({
34118
35275
  }
34119
35276
  }
34120
35277
  function buildKey(opts) {
34121
- const { path: path29, method, body, headers, query } = opts;
35278
+ const { path: path30, method, body, headers, query } = opts;
34122
35279
  return {
34123
- path: path29,
35280
+ path: path30,
34124
35281
  method,
34125
35282
  body,
34126
35283
  headers,
@@ -34569,10 +35726,10 @@ var require_pending_interceptors_formatter = __commonJS({
34569
35726
  }
34570
35727
  format(pendingInterceptors) {
34571
35728
  const withPrettyHeaders = pendingInterceptors.map(
34572
- ({ method, path: path29, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
35729
+ ({ method, path: path30, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
34573
35730
  Method: method,
34574
35731
  Origin: origin,
34575
- Path: path29,
35732
+ Path: path30,
34576
35733
  "Status code": statusCode,
34577
35734
  Persistent: persist ? "\u2705" : "\u274C",
34578
35735
  Invocations: timesInvoked,
@@ -39193,8 +40350,8 @@ var require_util6 = __commonJS({
39193
40350
  }
39194
40351
  }
39195
40352
  }
39196
- function validateCookiePath(path29) {
39197
- for (const char of path29) {
40353
+ function validateCookiePath(path30) {
40354
+ for (const char of path30) {
39198
40355
  const code = char.charCodeAt(0);
39199
40356
  if (code < 33 || char === ";") {
39200
40357
  throw new Error("Invalid cookie path");
@@ -40874,11 +42031,11 @@ var require_undici = __commonJS({
40874
42031
  if (typeof opts.path !== "string") {
40875
42032
  throw new InvalidArgumentError("invalid opts.path");
40876
42033
  }
40877
- let path29 = opts.path;
42034
+ let path30 = opts.path;
40878
42035
  if (!opts.path.startsWith("/")) {
40879
- path29 = `/${path29}`;
42036
+ path30 = `/${path30}`;
40880
42037
  }
40881
- url = new URL(util.parseOrigin(url).origin + path29);
42038
+ url = new URL(util.parseOrigin(url).origin + path30);
40882
42039
  } else {
40883
42040
  if (!opts) {
40884
42041
  opts = typeof url === "object" ? url : {};
@@ -41306,10 +42463,11 @@ var init_mcp_check_provider = __esm({
41306
42463
  'No custom tools available. Define tools in the "tools" section of your configuration.'
41307
42464
  );
41308
42465
  }
41309
- const tool = this.customToolExecutor.getTool(config.method);
41310
- if (!tool) {
42466
+ const hasTool = await this.customToolExecutor.hasTool(config.method);
42467
+ if (!hasTool) {
42468
+ const availableToolNames = await this.customToolExecutor.getToolNames();
41311
42469
  throw new Error(
41312
- `Custom tool not found: ${config.method}. Available tools: ${this.customToolExecutor.getTools().map((t) => t.name).join(", ")}`
42470
+ `Custom tool not found: ${config.method}. Available tools: ${availableToolNames.join(", ")}`
41313
42471
  );
41314
42472
  }
41315
42473
  const context2 = {
@@ -41420,11 +42578,24 @@ var init_mcp_check_provider = __esm({
41420
42578
  * Execute MCP method using stdio transport
41421
42579
  */
41422
42580
  async executeStdioMethod(config, methodArgs, timeout) {
42581
+ const env = {};
42582
+ for (const [key, value] of Object.entries(process.env)) {
42583
+ if (value !== void 0) {
42584
+ env[key] = value;
42585
+ }
42586
+ }
42587
+ if (config.env) {
42588
+ for (const [key, value] of Object.entries(config.env)) {
42589
+ if (value !== void 0 && value !== null) {
42590
+ env[key] = String(EnvironmentResolver.resolveValue(value));
42591
+ }
42592
+ }
42593
+ }
41423
42594
  return this.executeWithTransport(
41424
42595
  () => new import_stdio.StdioClientTransport({
41425
42596
  command: config.command,
41426
42597
  args: config.command_args,
41427
- env: config.env,
42598
+ env,
41428
42599
  cwd: config.workingDirectory,
41429
42600
  stderr: "pipe"
41430
42601
  // Prevent child stderr from corrupting TUI
@@ -42149,7 +43320,7 @@ var init_stdin_reader = __esm({
42149
43320
  });
42150
43321
 
42151
43322
  // src/providers/human-input-check-provider.ts
42152
- var fs14, path17, HumanInputCheckProvider;
43323
+ var fs15, path18, HumanInputCheckProvider;
42153
43324
  var init_human_input_check_provider = __esm({
42154
43325
  "src/providers/human-input-check-provider.ts"() {
42155
43326
  "use strict";
@@ -42158,8 +43329,8 @@ var init_human_input_check_provider = __esm({
42158
43329
  init_prompt_state();
42159
43330
  init_liquid_extensions();
42160
43331
  init_stdin_reader();
42161
- fs14 = __toESM(require("fs"));
42162
- path17 = __toESM(require("path"));
43332
+ fs15 = __toESM(require("fs"));
43333
+ path18 = __toESM(require("path"));
42163
43334
  HumanInputCheckProvider = class _HumanInputCheckProvider extends CheckProvider {
42164
43335
  liquid;
42165
43336
  /**
@@ -42333,19 +43504,19 @@ var init_human_input_check_provider = __esm({
42333
43504
  */
42334
43505
  async tryReadFile(filePath) {
42335
43506
  try {
42336
- const absolutePath = path17.isAbsolute(filePath) ? filePath : path17.resolve(process.cwd(), filePath);
42337
- const normalizedPath = path17.normalize(absolutePath);
43507
+ const absolutePath = path18.isAbsolute(filePath) ? filePath : path18.resolve(process.cwd(), filePath);
43508
+ const normalizedPath = path18.normalize(absolutePath);
42338
43509
  const cwd = process.cwd();
42339
- if (!normalizedPath.startsWith(cwd + path17.sep) && normalizedPath !== cwd) {
43510
+ if (!normalizedPath.startsWith(cwd + path18.sep) && normalizedPath !== cwd) {
42340
43511
  return null;
42341
43512
  }
42342
43513
  try {
42343
- await fs14.promises.access(normalizedPath, fs14.constants.R_OK);
42344
- const stats = await fs14.promises.stat(normalizedPath);
43514
+ await fs15.promises.access(normalizedPath, fs15.constants.R_OK);
43515
+ const stats = await fs15.promises.stat(normalizedPath);
42345
43516
  if (!stats.isFile()) {
42346
43517
  return null;
42347
43518
  }
42348
- const content = await fs14.promises.readFile(normalizedPath, "utf-8");
43519
+ const content = await fs15.promises.readFile(normalizedPath, "utf-8");
42349
43520
  return content.trim();
42350
43521
  } catch {
42351
43522
  return null;
@@ -42630,6 +43801,494 @@ ${snippet}`
42630
43801
  }
42631
43802
  });
42632
43803
 
43804
+ // src/utils/script-tool-environment.ts
43805
+ function formatSyntaxError(code, err) {
43806
+ const line = err.loc?.line ?? 0;
43807
+ const col = err.loc?.column ?? 0;
43808
+ const baseMsg = err.message?.replace(/\s*\(\d+:\d+\)$/, "") || "Syntax error";
43809
+ if (!line) return `Syntax error: ${baseMsg}`;
43810
+ const lines = code.split("\n");
43811
+ const snippetLines = [];
43812
+ const start = Math.max(0, line - 2);
43813
+ const end = Math.min(lines.length, line + 1);
43814
+ for (let i = start; i < end; i++) {
43815
+ const lineNum = String(i + 1).padStart(3, " ");
43816
+ if (i === line - 1) {
43817
+ snippetLines.push(` > ${lineNum} | ${lines[i]}`);
43818
+ snippetLines.push(` ${" ".repeat(lineNum.length)} | ${" ".repeat(col)}^`);
43819
+ } else {
43820
+ snippetLines.push(` ${lineNum} | ${lines[i]}`);
43821
+ }
43822
+ }
43823
+ return `Syntax error at line ${line}, column ${col}: ${baseMsg}
43824
+
43825
+ ${snippetLines.join("\n")}`;
43826
+ }
43827
+ function levenshtein(a, b) {
43828
+ const m = a.length;
43829
+ const n = b.length;
43830
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
43831
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
43832
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
43833
+ for (let i = 1; i <= m; i++) {
43834
+ for (let j = 1; j <= n; j++) {
43835
+ dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
43836
+ }
43837
+ }
43838
+ return dp[m][n];
43839
+ }
43840
+ function transformScriptForAsync(code, asyncFunctionNames, opts) {
43841
+ if (asyncFunctionNames.size === 0) {
43842
+ return `return (() => {
43843
+ ${code}
43844
+ })()`;
43845
+ }
43846
+ let ast;
43847
+ try {
43848
+ ast = acorn.parse(code, {
43849
+ ecmaVersion: 2022,
43850
+ sourceType: "script",
43851
+ allowReturnOutsideFunction: true,
43852
+ locations: true
43853
+ });
43854
+ } catch (e) {
43855
+ throw new Error(formatSyntaxError(code, e));
43856
+ }
43857
+ if (opts?.knownGlobals) {
43858
+ lintUnknownCalls(code, ast, opts.knownGlobals, opts.disabledBuiltins);
43859
+ }
43860
+ const insertions = [];
43861
+ const functionsNeedingAsync = /* @__PURE__ */ new Set();
43862
+ const functionScopes = [];
43863
+ walk.full(ast, (node) => {
43864
+ if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") {
43865
+ functionScopes.push(node);
43866
+ }
43867
+ });
43868
+ walk.full(ast, (node) => {
43869
+ if (node.type !== "CallExpression") return;
43870
+ const calleeName = getCalleeName(node);
43871
+ if (!calleeName || !asyncFunctionNames.has(calleeName)) return;
43872
+ insertions.push({ offset: node.start, text: "await " });
43873
+ for (const fn of functionScopes) {
43874
+ const body2 = fn.body;
43875
+ if (body2 && body2.start <= node.start && body2.end >= node.end) {
43876
+ functionsNeedingAsync.add(fn);
43877
+ }
43878
+ }
43879
+ });
43880
+ walk.full(ast, (node) => {
43881
+ if (node.type !== "CallExpression") return;
43882
+ const callNode = node;
43883
+ const calleeName = getCalleeName(callNode);
43884
+ if (calleeName !== "map" || !callNode.arguments || callNode.arguments.length < 2) return;
43885
+ const callback = callNode.arguments[1];
43886
+ if (callback.type === "ArrowFunctionExpression" || callback.type === "FunctionExpression") {
43887
+ let hasAsyncCall = false;
43888
+ walk.full(callback, (inner) => {
43889
+ if (inner.type === "CallExpression") {
43890
+ const innerName = getCalleeName(inner);
43891
+ if (innerName && asyncFunctionNames.has(innerName)) {
43892
+ hasAsyncCall = true;
43893
+ }
43894
+ }
43895
+ });
43896
+ if (hasAsyncCall) {
43897
+ functionsNeedingAsync.add(callback);
43898
+ }
43899
+ }
43900
+ });
43901
+ walk.full(ast, (node) => {
43902
+ if (node.type === "WhileStatement" || node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement") {
43903
+ const body2 = node.body;
43904
+ if (body2 && body2.type === "BlockStatement" && body2.body && body2.body.length > 0) {
43905
+ insertions.push({ offset: body2.start + 1, text: " __checkLoop();" });
43906
+ }
43907
+ }
43908
+ });
43909
+ for (const fn of functionsNeedingAsync) {
43910
+ insertions.push({ offset: fn.start, text: "async " });
43911
+ }
43912
+ const body = ast.body;
43913
+ if (body && body.length > 0) {
43914
+ const lastStmt = body[body.length - 1];
43915
+ if (lastStmt.type === "ExpressionStatement") {
43916
+ insertions.push({ offset: lastStmt.start, text: "return " });
43917
+ }
43918
+ }
43919
+ insertions.sort((a, b) => b.offset - a.offset);
43920
+ let transformed = code;
43921
+ for (const ins of insertions) {
43922
+ transformed = transformed.slice(0, ins.offset) + ins.text + transformed.slice(ins.offset);
43923
+ }
43924
+ return `return (async () => {
43925
+ ${transformed}
43926
+ })()`;
43927
+ }
43928
+ function getCalleeName(callExpr) {
43929
+ const callee = callExpr.callee;
43930
+ if (callee.type === "Identifier" && callee.name) {
43931
+ return callee.name;
43932
+ }
43933
+ return null;
43934
+ }
43935
+ function lintUnknownCalls(_code, ast, knownGlobals, disabledBuiltins) {
43936
+ const declaredFunctions = /* @__PURE__ */ new Set();
43937
+ walk.full(ast, (node) => {
43938
+ if (node.type === "FunctionDeclaration" && node.id?.name) {
43939
+ declaredFunctions.add(node.id.name);
43940
+ }
43941
+ if (node.type === "VariableDeclarator" && node.id?.name) {
43942
+ const init = node.init;
43943
+ if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) {
43944
+ declaredFunctions.add(node.id.name);
43945
+ }
43946
+ }
43947
+ });
43948
+ const warnings = [];
43949
+ walk.full(ast, (node) => {
43950
+ if (node.type !== "CallExpression") return;
43951
+ const name = getCalleeName(node);
43952
+ if (!name) return;
43953
+ if (knownGlobals.has(name) || JS_BUILTINS.has(name) || declaredFunctions.has(name)) return;
43954
+ const loc = node.loc?.start;
43955
+ const lineInfo = loc ? ` (line ${loc.line}, column ${loc.column})` : "";
43956
+ if (disabledBuiltins?.has(name)) {
43957
+ const hint = disabledBuiltins.get(name);
43958
+ warnings.push(`'${name}()' is not enabled${lineInfo}. ${hint}`);
43959
+ return;
43960
+ }
43961
+ const allNames = [...knownGlobals];
43962
+ let bestMatch = "";
43963
+ let bestDist = Infinity;
43964
+ for (const candidate of allNames) {
43965
+ const dist = levenshtein(name.toLowerCase(), candidate.toLowerCase());
43966
+ if (dist < bestDist) {
43967
+ bestDist = dist;
43968
+ bestMatch = candidate;
43969
+ }
43970
+ }
43971
+ const maxLen = Math.max(name.length, bestMatch.length);
43972
+ const suggestion = bestDist <= Math.ceil(maxLen * 0.4) ? ` Did you mean '${bestMatch}'?` : "";
43973
+ warnings.push(`Unknown function '${name}()'${lineInfo}.${suggestion}`);
43974
+ });
43975
+ if (warnings.length > 0) {
43976
+ throw new Error(`Script lint errors:
43977
+ ${warnings.map((w) => ` - ${w}`).join("\n")}`);
43978
+ }
43979
+ }
43980
+ function tryParseJSON(text) {
43981
+ if (typeof text !== "string") return text;
43982
+ const firstChar = text.trimStart()[0];
43983
+ if (firstChar === "{" || firstChar === "[") {
43984
+ try {
43985
+ return JSON.parse(text);
43986
+ } catch {
43987
+ }
43988
+ }
43989
+ return text;
43990
+ }
43991
+ function buildToolGlobals(opts) {
43992
+ const { resolvedTools, mcpClients, toolContext, workflowContext } = opts;
43993
+ const globals = {};
43994
+ const asyncFunctionNames = /* @__PURE__ */ new Set();
43995
+ const commandTools = {};
43996
+ for (const [name, tool] of resolvedTools) {
43997
+ if (!isWorkflowTool(tool)) {
43998
+ commandTools[name] = tool;
43999
+ }
44000
+ }
44001
+ const toolExecutor = new CustomToolExecutor(commandTools);
44002
+ const allToolInfo = [];
44003
+ for (const [name, tool] of resolvedTools) {
44004
+ const toolFn = async (args = {}) => {
44005
+ try {
44006
+ if (isWorkflowTool(tool)) {
44007
+ if (!workflowContext) {
44008
+ return `ERROR: Workflow context not available for tool '${name}'`;
44009
+ }
44010
+ return await executeWorkflowAsTool(
44011
+ tool.__workflowId,
44012
+ args,
44013
+ workflowContext,
44014
+ tool.__argsOverrides
44015
+ );
44016
+ }
44017
+ return await toolExecutor.execute(name, args, toolContext);
44018
+ } catch (e) {
44019
+ const msg = e instanceof Error ? e.message : String(e);
44020
+ logger.warn(`[script:${name}] Tool error: ${msg}`);
44021
+ return `ERROR: ${msg}`;
44022
+ }
44023
+ };
44024
+ globals[name] = toolFn;
44025
+ asyncFunctionNames.add(name);
44026
+ allToolInfo.push({ name, description: tool.description });
44027
+ }
44028
+ if (mcpClients) {
44029
+ for (const entry of mcpClients) {
44030
+ for (const mcpTool of entry.tools) {
44031
+ const globalName = `${entry.serverName}_${mcpTool.name}`;
44032
+ const mcpToolFn = async (args = {}) => {
44033
+ try {
44034
+ const result = await entry.client.callTool({
44035
+ name: mcpTool.name,
44036
+ arguments: args
44037
+ });
44038
+ const content = result?.content;
44039
+ if (Array.isArray(content) && content.length > 0) {
44040
+ const text = content[0]?.text;
44041
+ if (text !== void 0) {
44042
+ return tryParseJSON(text);
44043
+ }
44044
+ }
44045
+ return result;
44046
+ } catch (e) {
44047
+ const msg = e instanceof Error ? e.message : String(e);
44048
+ logger.warn(`[script:${globalName}] MCP tool error: ${msg}`);
44049
+ return `ERROR: ${msg}`;
44050
+ }
44051
+ };
44052
+ globals[globalName] = mcpToolFn;
44053
+ asyncFunctionNames.add(globalName);
44054
+ allToolInfo.push({ name: globalName, description: mcpTool.description });
44055
+ }
44056
+ }
44057
+ }
44058
+ const callToolFn = async (name, args = {}) => {
44059
+ const fn = globals[name];
44060
+ if (!fn || typeof fn !== "function") {
44061
+ const available = Array.from(asyncFunctionNames).join(", ");
44062
+ return `ERROR: Tool '${name}' not found. Available: ${available}`;
44063
+ }
44064
+ return fn(args);
44065
+ };
44066
+ globals.callTool = callToolFn;
44067
+ asyncFunctionNames.add("callTool");
44068
+ globals.listTools = () => {
44069
+ return [...allToolInfo];
44070
+ };
44071
+ return { globals, asyncFunctionNames };
44072
+ }
44073
+ function buildBuiltinGlobals(opts) {
44074
+ const globals = {};
44075
+ const asyncFunctionNames = /* @__PURE__ */ new Set();
44076
+ const scheduleFn = async (args = {}) => {
44077
+ try {
44078
+ const { handleScheduleAction: handleScheduleAction2, buildScheduleToolContext: buildScheduleToolContext2 } = await Promise.resolve().then(() => (init_schedule_tool(), schedule_tool_exports));
44079
+ const { extractSlackContext: extractSlackContext2 } = await Promise.resolve().then(() => (init_schedule_tool_handler(), schedule_tool_handler_exports));
44080
+ const parentCtx = opts.sessionInfo?._parentContext;
44081
+ const webhookData = parentCtx?.prInfo?.eventContext?.webhookData;
44082
+ const visorCfg = parentCtx?.config;
44083
+ const slackContext = webhookData ? extractSlackContext2(webhookData) : null;
44084
+ const availableWorkflows = visorCfg?.checks ? Object.keys(visorCfg.checks) : void 0;
44085
+ const permissions = visorCfg?.scheduler?.permissions;
44086
+ const context2 = buildScheduleToolContext2(
44087
+ {
44088
+ slackContext: slackContext || void 0,
44089
+ cliContext: slackContext ? void 0 : { userId: "script" }
44090
+ },
44091
+ availableWorkflows,
44092
+ permissions
44093
+ );
44094
+ return await handleScheduleAction2(args, context2);
44095
+ } catch (e) {
44096
+ const msg = e instanceof Error ? e.message : String(e);
44097
+ logger.warn(`[script:schedule] Error: ${msg}`);
44098
+ return `ERROR: ${msg}`;
44099
+ }
44100
+ };
44101
+ globals.schedule = scheduleFn;
44102
+ asyncFunctionNames.add("schedule");
44103
+ if (opts.config.enable_fetch === true) {
44104
+ const fetchFn = async (args = {}) => {
44105
+ try {
44106
+ const url = String(args.url || "");
44107
+ if (!url) return "ERROR: url is required";
44108
+ const method = String(args.method || "GET");
44109
+ const headers = args.headers || {};
44110
+ const body = args.body != null ? String(args.body) : void 0;
44111
+ const timeout = Number(args.timeout) || 3e4;
44112
+ const controller = new AbortController();
44113
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
44114
+ try {
44115
+ const resp = await globalThis.fetch(url, {
44116
+ method,
44117
+ headers,
44118
+ body: method !== "GET" ? body : void 0,
44119
+ signal: controller.signal
44120
+ });
44121
+ clearTimeout(timeoutId);
44122
+ const contentType = resp.headers.get("content-type") || "";
44123
+ if (contentType.includes("json")) return await resp.json();
44124
+ const text = await resp.text();
44125
+ try {
44126
+ return JSON.parse(text);
44127
+ } catch {
44128
+ return text;
44129
+ }
44130
+ } finally {
44131
+ clearTimeout(timeoutId);
44132
+ }
44133
+ } catch (e) {
44134
+ const msg = e instanceof Error ? e.message : String(e);
44135
+ logger.warn(`[script:fetch] Error: ${msg}`);
44136
+ return `ERROR: ${msg}`;
44137
+ }
44138
+ };
44139
+ globals.fetch = fetchFn;
44140
+ asyncFunctionNames.add("fetch");
44141
+ }
44142
+ const octokit = opts.config.eventContext?.octokit;
44143
+ if (octokit) {
44144
+ const githubFn = async (args = {}) => {
44145
+ try {
44146
+ const op = String(args.op || "");
44147
+ const repoEnv = process.env.GITHUB_REPOSITORY || "";
44148
+ let owner = "";
44149
+ let repo = "";
44150
+ if (repoEnv.includes("/")) {
44151
+ [owner, repo] = repoEnv.split("/");
44152
+ }
44153
+ if (!owner || !repo) {
44154
+ const ec = opts.config.eventContext || {};
44155
+ owner = owner || ec.repository?.owner?.login || "";
44156
+ repo = repo || ec.repository?.name || "";
44157
+ }
44158
+ const prNumber = opts.prInfo?.number;
44159
+ if (!owner || !repo || !prNumber) {
44160
+ return "ERROR: Missing GitHub repo/PR context";
44161
+ }
44162
+ const values = Array.isArray(args.values) ? args.values.map(String) : typeof args.value === "string" ? [args.value] : typeof args.values === "string" ? [args.values] : [];
44163
+ switch (op) {
44164
+ case "labels.add":
44165
+ await octokit.rest.issues.addLabels({
44166
+ owner,
44167
+ repo,
44168
+ issue_number: prNumber,
44169
+ labels: values
44170
+ });
44171
+ return { success: true, op };
44172
+ case "labels.remove":
44173
+ for (const name of values) {
44174
+ try {
44175
+ await octokit.rest.issues.removeLabel({
44176
+ owner,
44177
+ repo,
44178
+ issue_number: prNumber,
44179
+ name
44180
+ });
44181
+ } catch {
44182
+ }
44183
+ }
44184
+ return { success: true, op };
44185
+ case "comment.create":
44186
+ await octokit.rest.issues.createComment({
44187
+ owner,
44188
+ repo,
44189
+ issue_number: prNumber,
44190
+ body: values[0] || ""
44191
+ });
44192
+ return { success: true, op };
44193
+ default:
44194
+ return `ERROR: Unknown github op '${op}'. Supported: labels.add, labels.remove, comment.create`;
44195
+ }
44196
+ } catch (e) {
44197
+ const msg = e instanceof Error ? e.message : String(e);
44198
+ logger.warn(`[script:github] Error: ${msg}`);
44199
+ return `ERROR: ${msg}`;
44200
+ }
44201
+ };
44202
+ globals.github = githubFn;
44203
+ asyncFunctionNames.add("github");
44204
+ }
44205
+ if (opts.config.enable_bash === true) {
44206
+ const bashFn = async (args = {}) => {
44207
+ try {
44208
+ const { CommandExecutor: CommandExecutor2 } = await Promise.resolve().then(() => (init_command_executor(), command_executor_exports));
44209
+ const executor = CommandExecutor2.getInstance();
44210
+ const command = String(args.command || "");
44211
+ if (!command) return "ERROR: command is required";
44212
+ return await executor.execute(command, {
44213
+ cwd: args.cwd ? String(args.cwd) : void 0,
44214
+ env: args.env,
44215
+ timeout: Number(args.timeout) || 3e4
44216
+ });
44217
+ } catch (e) {
44218
+ const msg = e instanceof Error ? e.message : String(e);
44219
+ logger.warn(`[script:bash] Error: ${msg}`);
44220
+ return `ERROR: ${msg}`;
44221
+ }
44222
+ };
44223
+ globals.bash = bashFn;
44224
+ asyncFunctionNames.add("bash");
44225
+ }
44226
+ return { globals, asyncFunctionNames };
44227
+ }
44228
+ var acorn, walk, JS_BUILTINS;
44229
+ var init_script_tool_environment = __esm({
44230
+ "src/utils/script-tool-environment.ts"() {
44231
+ "use strict";
44232
+ acorn = __toESM(require("acorn"));
44233
+ walk = __toESM(require("acorn-walk"));
44234
+ init_custom_tool_executor();
44235
+ init_workflow_tool_executor();
44236
+ init_logger();
44237
+ JS_BUILTINS = /* @__PURE__ */ new Set([
44238
+ // Constructors & types
44239
+ "Array",
44240
+ "Object",
44241
+ "String",
44242
+ "Number",
44243
+ "Boolean",
44244
+ "Date",
44245
+ "RegExp",
44246
+ "Error",
44247
+ "TypeError",
44248
+ "RangeError",
44249
+ "Map",
44250
+ "Set",
44251
+ "WeakMap",
44252
+ "WeakSet",
44253
+ "Promise",
44254
+ "Symbol",
44255
+ "BigInt",
44256
+ "Proxy",
44257
+ "Reflect",
44258
+ // Static methods commonly called as functions
44259
+ "parseInt",
44260
+ "parseFloat",
44261
+ "isNaN",
44262
+ "isFinite",
44263
+ "encodeURIComponent",
44264
+ "decodeURIComponent",
44265
+ "encodeURI",
44266
+ "decodeURI",
44267
+ // Timers
44268
+ "setTimeout",
44269
+ "clearTimeout",
44270
+ "setInterval",
44271
+ "clearInterval",
44272
+ // JSON/Math accessed via method calls are on objects, but just in case
44273
+ "JSON",
44274
+ "Math",
44275
+ "console",
44276
+ // Node.js globals — sandbox blocks these at runtime with a better error
44277
+ "require",
44278
+ "process",
44279
+ "Buffer",
44280
+ "global",
44281
+ "globalThis",
44282
+ // Common patterns
44283
+ "eval",
44284
+ "Function",
44285
+ "alert",
44286
+ "confirm",
44287
+ "prompt"
44288
+ ]);
44289
+ }
44290
+ });
44291
+
42633
44292
  // src/providers/script-check-provider.ts
42634
44293
  var ScriptCheckProvider;
42635
44294
  var init_script_check_provider = __esm({
@@ -42642,6 +44301,10 @@ var init_script_check_provider = __esm({
42642
44301
  init_sandbox();
42643
44302
  init_template_context();
42644
44303
  init_script_memory_ops();
44304
+ init_tool_resolver();
44305
+ init_script_tool_environment();
44306
+ init_workflow_tool_executor();
44307
+ init_env_resolver();
42645
44308
  ScriptCheckProvider = class extends CheckProvider {
42646
44309
  liquid;
42647
44310
  constructor() {
@@ -42658,7 +44321,7 @@ var init_script_check_provider = __esm({
42658
44321
  return "script";
42659
44322
  }
42660
44323
  getDescription() {
42661
- return "Execute JavaScript with access to PR context, dependency outputs, and memory.";
44324
+ return "Execute JavaScript with access to PR context, dependency outputs, memory, and tools.";
42662
44325
  }
42663
44326
  async validateConfig(config) {
42664
44327
  if (!config || typeof config !== "object") return false;
@@ -42708,16 +44371,77 @@ var init_script_check_provider = __esm({
42708
44371
  ctx.atob = (str) => {
42709
44372
  return Buffer.from(String(str), "base64").toString("binary");
42710
44373
  };
44374
+ const { globals: builtinGlobals, asyncFunctionNames: builtinAsyncNames } = buildBuiltinGlobals({
44375
+ config,
44376
+ prInfo,
44377
+ sessionInfo: _sessionInfo
44378
+ });
44379
+ Object.assign(ctx, builtinGlobals);
44380
+ const hasTools = Array.isArray(config.tools) || config.tools_js || config.mcp_servers;
42711
44381
  const sandbox = this.createSecureSandbox();
42712
44382
  let result;
42713
- try {
42714
- result = compileAndRun(
44383
+ let mcpClients = [];
44384
+ try {
44385
+ const asyncFunctionNames = new Set(builtinAsyncNames);
44386
+ if (hasTools) {
44387
+ const toolItems = this.resolveToolItems(config, prInfo, dependencyResults, ctx);
44388
+ const globalTools = config.__globalTools;
44389
+ const resolvedTools = resolveTools(toolItems, globalTools, "[script]");
44390
+ mcpClients = await this.connectMcpServers(config.mcp_servers);
44391
+ const toolContext = {
44392
+ pr: ctx.pr,
44393
+ files: ctx.files || prInfo.files,
44394
+ outputs: ctx.outputs,
44395
+ env: process.env
44396
+ };
44397
+ const parentCtx = _sessionInfo?._parentContext;
44398
+ const workflowContext = {
44399
+ prInfo,
44400
+ outputs: dependencyResults,
44401
+ executionContext: _sessionInfo,
44402
+ workspace: parentCtx?.workspace
44403
+ };
44404
+ const { globals: toolGlobals, asyncFunctionNames: toolAsyncNames } = buildToolGlobals({
44405
+ resolvedTools,
44406
+ mcpClients,
44407
+ toolContext,
44408
+ workflowContext
44409
+ });
44410
+ Object.assign(ctx, toolGlobals);
44411
+ for (const name of toolAsyncNames) asyncFunctionNames.add(name);
44412
+ }
44413
+ let loopIterations = 0;
44414
+ const maxLoopIterations = 1e4;
44415
+ ctx.__checkLoop = () => {
44416
+ loopIterations++;
44417
+ if (loopIterations > maxLoopIterations) {
44418
+ throw new Error(`Loop exceeded maximum of ${maxLoopIterations} iterations`);
44419
+ }
44420
+ };
44421
+ const knownGlobals = new Set(Object.keys(ctx));
44422
+ for (const name of asyncFunctionNames) knownGlobals.add(name);
44423
+ knownGlobals.add("__checkLoop");
44424
+ knownGlobals.add("log");
44425
+ const disabledBuiltins = /* @__PURE__ */ new Map();
44426
+ if (!config.enable_bash) {
44427
+ disabledBuiltins.set("bash", "Add 'enable_bash: true' to your check config to enable it.");
44428
+ }
44429
+ if (!config.enable_fetch) {
44430
+ disabledBuiltins.set(
44431
+ "fetch",
44432
+ "Add 'enable_fetch: true' to your check config to enable it."
44433
+ );
44434
+ }
44435
+ const transformed = transformScriptForAsync(script, asyncFunctionNames, {
44436
+ knownGlobals,
44437
+ disabledBuiltins
44438
+ });
44439
+ result = await compileAndRunAsync(
42715
44440
  sandbox,
42716
- script,
44441
+ transformed,
42717
44442
  { ...ctx },
42718
44443
  {
42719
44444
  injectLog: true,
42720
- wrapFunction: true,
42721
44445
  logPrefix: "[script]"
42722
44446
  }
42723
44447
  );
@@ -42737,6 +44461,8 @@ var init_script_check_provider = __esm({
42737
44461
  ],
42738
44462
  output: null
42739
44463
  };
44464
+ } finally {
44465
+ await this.disconnectMcpClients(mcpClients);
42740
44466
  }
42741
44467
  try {
42742
44468
  if (needsSave() && memoryStore.getConfig().storage === "file" && memoryStore.getConfig().auto_save) {
@@ -42762,17 +44488,167 @@ var init_script_check_provider = __esm({
42762
44488
  }
42763
44489
  return out;
42764
44490
  }
44491
+ /**
44492
+ * Resolve tool items from static config and optional JS expression.
44493
+ */
44494
+ resolveToolItems(config, prInfo, dependencyResults, ctx) {
44495
+ let items = [];
44496
+ const staticTools = config.tools;
44497
+ if (Array.isArray(staticTools)) {
44498
+ items = staticTools.filter(
44499
+ (item) => typeof item === "string" || isWorkflowToolReference(item)
44500
+ );
44501
+ }
44502
+ const toolsJsExpr = config.tools_js;
44503
+ if (toolsJsExpr && dependencyResults) {
44504
+ try {
44505
+ const jsSandbox = this.createSecureSandbox();
44506
+ const jsCtx = ctx || buildProviderTemplateContext(prInfo, dependencyResults);
44507
+ jsCtx.env = process.env;
44508
+ jsCtx.inputs = config.workflowInputs || {};
44509
+ const evalResult = compileAndRun(jsSandbox, toolsJsExpr, jsCtx, {
44510
+ injectLog: true,
44511
+ wrapFunction: true,
44512
+ logPrefix: "[tools_js]"
44513
+ });
44514
+ if (Array.isArray(evalResult)) {
44515
+ const dynamic = evalResult.filter(
44516
+ (item) => typeof item === "string" || isWorkflowToolReference(item)
44517
+ );
44518
+ const existingNames = new Set(items.map((i) => typeof i === "string" ? i : i.workflow));
44519
+ for (const tool of dynamic) {
44520
+ const name = typeof tool === "string" ? tool : tool.workflow;
44521
+ if (!existingNames.has(name)) {
44522
+ items.push(tool);
44523
+ }
44524
+ }
44525
+ }
44526
+ } catch (error) {
44527
+ logger.error(
44528
+ `[script] Failed to evaluate tools_js: ${error instanceof Error ? error.message : "Unknown error"}`
44529
+ );
44530
+ }
44531
+ }
44532
+ return items;
44533
+ }
44534
+ /**
44535
+ * Connect to MCP servers and discover their tools.
44536
+ */
44537
+ async connectMcpServers(mcpServersConfig) {
44538
+ if (!mcpServersConfig || Object.keys(mcpServersConfig).length === 0) {
44539
+ return [];
44540
+ }
44541
+ const entries = [];
44542
+ for (const [serverName, serverConfig] of Object.entries(mcpServersConfig)) {
44543
+ try {
44544
+ const { Client: Client2 } = await import("@modelcontextprotocol/sdk/client/index.js");
44545
+ const client = new Client2(
44546
+ { name: "visor-script-client", version: "1.0.0" },
44547
+ { capabilities: {} }
44548
+ );
44549
+ const env = {};
44550
+ for (const [key, value] of Object.entries(process.env)) {
44551
+ if (value !== void 0) env[key] = value;
44552
+ }
44553
+ if (serverConfig.env) {
44554
+ for (const [key, value] of Object.entries(serverConfig.env)) {
44555
+ env[key] = String(EnvironmentResolver.resolveValue(String(value)));
44556
+ }
44557
+ }
44558
+ const timeout = (serverConfig.timeout || 60) * 1e3;
44559
+ if (serverConfig.command) {
44560
+ const { StdioClientTransport: StdioClientTransport2 } = await import("@modelcontextprotocol/sdk/client/stdio.js");
44561
+ const transport = new StdioClientTransport2({
44562
+ command: serverConfig.command,
44563
+ args: serverConfig.args,
44564
+ env,
44565
+ stderr: "pipe"
44566
+ });
44567
+ await Promise.race([
44568
+ client.connect(transport),
44569
+ new Promise(
44570
+ (_, reject) => setTimeout(() => reject(new Error("MCP connection timeout")), timeout)
44571
+ )
44572
+ ]);
44573
+ } else if (serverConfig.url) {
44574
+ const transportType = serverConfig.transport || "sse";
44575
+ if (transportType === "sse") {
44576
+ const { SSEClientTransport: SSEClientTransport2 } = await import("@modelcontextprotocol/sdk/client/sse.js");
44577
+ await Promise.race([
44578
+ client.connect(new SSEClientTransport2(new URL(serverConfig.url))),
44579
+ new Promise(
44580
+ (_, reject) => setTimeout(() => reject(new Error("MCP connection timeout")), timeout)
44581
+ )
44582
+ ]);
44583
+ } else {
44584
+ const { StreamableHTTPClientTransport: StreamableHTTPClientTransport2 } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
44585
+ await Promise.race([
44586
+ client.connect(new StreamableHTTPClientTransport2(new URL(serverConfig.url))),
44587
+ new Promise(
44588
+ (_, reject) => setTimeout(() => reject(new Error("MCP connection timeout")), timeout)
44589
+ )
44590
+ ]);
44591
+ }
44592
+ } else {
44593
+ logger.warn(`[script] MCP server '${serverName}' has no command or url, skipping`);
44594
+ continue;
44595
+ }
44596
+ let tools = [];
44597
+ try {
44598
+ const listResult = await client.listTools();
44599
+ tools = (listResult?.tools || []).map((t) => ({
44600
+ name: t.name,
44601
+ description: t.description,
44602
+ inputSchema: t.inputSchema
44603
+ }));
44604
+ logger.debug(
44605
+ `[script] MCP '${serverName}': ${tools.length} tools [${tools.map((t) => t.name).join(", ")}]`
44606
+ );
44607
+ } catch (err) {
44608
+ logger.warn(
44609
+ `[script] Could not list tools from MCP '${serverName}': ${err instanceof Error ? err.message : String(err)}`
44610
+ );
44611
+ }
44612
+ entries.push({ client, serverName, tools });
44613
+ } catch (err) {
44614
+ logger.error(
44615
+ `[script] Failed to connect MCP '${serverName}': ${err instanceof Error ? err.message : String(err)}`
44616
+ );
44617
+ }
44618
+ }
44619
+ return entries;
44620
+ }
44621
+ /**
44622
+ * Disconnect all MCP clients.
44623
+ */
44624
+ async disconnectMcpClients(clients) {
44625
+ for (const entry of clients) {
44626
+ try {
44627
+ await entry.client.close();
44628
+ } catch (err) {
44629
+ logger.debug(
44630
+ `[script] Error closing MCP '${entry.serverName}': ${err instanceof Error ? err.message : String(err)}`
44631
+ );
44632
+ }
44633
+ }
44634
+ }
42765
44635
  getSupportedConfigKeys() {
42766
44636
  return [
42767
44637
  "type",
42768
44638
  "content",
44639
+ "tools",
44640
+ "tools_js",
44641
+ "mcp_servers",
44642
+ "enable_fetch",
44643
+ "enable_bash",
42769
44644
  "depends_on",
42770
44645
  "group",
42771
44646
  "on",
42772
44647
  "if",
42773
44648
  "fail_if",
42774
44649
  "on_fail",
42775
- "on_success"
44650
+ "on_success",
44651
+ "timeout"
42776
44652
  ];
42777
44653
  }
42778
44654
  async isAvailable() {
@@ -42781,19 +44657,18 @@ var init_script_check_provider = __esm({
42781
44657
  getRequirements() {
42782
44658
  return ["No external dependencies required"];
42783
44659
  }
42784
- // No local buildTemplateContext; uses shared builder above
42785
44660
  };
42786
44661
  }
42787
44662
  });
42788
44663
 
42789
44664
  // src/utils/worktree-manager.ts
42790
- var fs15, fsp, path18, crypto, WorktreeManager, worktreeManager;
44665
+ var fs16, fsp, path19, crypto, WorktreeManager, worktreeManager;
42791
44666
  var init_worktree_manager = __esm({
42792
44667
  "src/utils/worktree-manager.ts"() {
42793
44668
  "use strict";
42794
- fs15 = __toESM(require("fs"));
44669
+ fs16 = __toESM(require("fs"));
42795
44670
  fsp = __toESM(require("fs/promises"));
42796
- path18 = __toESM(require("path"));
44671
+ path19 = __toESM(require("path"));
42797
44672
  crypto = __toESM(require("crypto"));
42798
44673
  init_command_executor();
42799
44674
  init_logger();
@@ -42809,7 +44684,7 @@ var init_worktree_manager = __esm({
42809
44684
  } catch {
42810
44685
  cwd = "/tmp";
42811
44686
  }
42812
- const defaultBasePath = process.env.VISOR_WORKTREE_PATH || path18.join(cwd, ".visor", "worktrees");
44687
+ const defaultBasePath = process.env.VISOR_WORKTREE_PATH || path19.join(cwd, ".visor", "worktrees");
42813
44688
  this.config = {
42814
44689
  enabled: true,
42815
44690
  base_path: defaultBasePath,
@@ -42846,20 +44721,20 @@ var init_worktree_manager = __esm({
42846
44721
  }
42847
44722
  const reposDir = this.getReposDir();
42848
44723
  const worktreesDir = this.getWorktreesDir();
42849
- if (!fs15.existsSync(reposDir)) {
42850
- fs15.mkdirSync(reposDir, { recursive: true });
44724
+ if (!fs16.existsSync(reposDir)) {
44725
+ fs16.mkdirSync(reposDir, { recursive: true });
42851
44726
  logger.debug(`Created repos directory: ${reposDir}`);
42852
44727
  }
42853
- if (!fs15.existsSync(worktreesDir)) {
42854
- fs15.mkdirSync(worktreesDir, { recursive: true });
44728
+ if (!fs16.existsSync(worktreesDir)) {
44729
+ fs16.mkdirSync(worktreesDir, { recursive: true });
42855
44730
  logger.debug(`Created worktrees directory: ${worktreesDir}`);
42856
44731
  }
42857
44732
  }
42858
44733
  getReposDir() {
42859
- return path18.join(this.config.base_path, "repos");
44734
+ return path19.join(this.config.base_path, "repos");
42860
44735
  }
42861
44736
  getWorktreesDir() {
42862
- return path18.join(this.config.base_path, "worktrees");
44737
+ return path19.join(this.config.base_path, "worktrees");
42863
44738
  }
42864
44739
  /**
42865
44740
  * Generate a deterministic worktree ID based on repository and ref.
@@ -42877,8 +44752,8 @@ var init_worktree_manager = __esm({
42877
44752
  async getOrCreateBareRepo(repository, repoUrl, token, fetchDepth, cloneTimeoutMs) {
42878
44753
  const reposDir = this.getReposDir();
42879
44754
  const repoName = repository.replace(/\//g, "-");
42880
- const bareRepoPath = path18.join(reposDir, `${repoName}.git`);
42881
- if (fs15.existsSync(bareRepoPath)) {
44755
+ const bareRepoPath = path19.join(reposDir, `${repoName}.git`);
44756
+ if (fs16.existsSync(bareRepoPath)) {
42882
44757
  logger.debug(`Bare repository already exists: ${bareRepoPath}`);
42883
44758
  const verifyResult = await this.verifyBareRepoRemote(bareRepoPath, repoUrl);
42884
44759
  if (verifyResult === "timeout") {
@@ -42997,11 +44872,11 @@ var init_worktree_manager = __esm({
42997
44872
  options.cloneTimeoutMs
42998
44873
  );
42999
44874
  const worktreeId = this.generateWorktreeId(repository, ref);
43000
- let worktreePath = options.workingDirectory || path18.join(this.getWorktreesDir(), worktreeId);
44875
+ let worktreePath = options.workingDirectory || path19.join(this.getWorktreesDir(), worktreeId);
43001
44876
  if (options.workingDirectory) {
43002
44877
  worktreePath = this.validatePath(options.workingDirectory);
43003
44878
  }
43004
- if (fs15.existsSync(worktreePath)) {
44879
+ if (fs16.existsSync(worktreePath)) {
43005
44880
  logger.debug(`Worktree already exists: ${worktreePath}`);
43006
44881
  const metadata2 = await this.loadMetadata(worktreePath);
43007
44882
  if (metadata2) {
@@ -43242,9 +45117,9 @@ var init_worktree_manager = __esm({
43242
45117
  const result = await this.executeGitCommand(removeCmd, { timeout: 3e4 });
43243
45118
  if (result.exitCode !== 0) {
43244
45119
  logger.warn(`Failed to remove worktree via git: ${result.stderr}`);
43245
- if (fs15.existsSync(worktree_path)) {
45120
+ if (fs16.existsSync(worktree_path)) {
43246
45121
  logger.debug(`Manually removing worktree directory`);
43247
- fs15.rmSync(worktree_path, { recursive: true, force: true });
45122
+ fs16.rmSync(worktree_path, { recursive: true, force: true });
43248
45123
  }
43249
45124
  }
43250
45125
  this.activeWorktrees.delete(worktreeId);
@@ -43254,19 +45129,19 @@ var init_worktree_manager = __esm({
43254
45129
  * Save worktree metadata
43255
45130
  */
43256
45131
  async saveMetadata(worktreePath, metadata) {
43257
- const metadataPath = path18.join(worktreePath, ".visor-metadata.json");
43258
- fs15.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf8");
45132
+ const metadataPath = path19.join(worktreePath, ".visor-metadata.json");
45133
+ fs16.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf8");
43259
45134
  }
43260
45135
  /**
43261
45136
  * Load worktree metadata
43262
45137
  */
43263
45138
  async loadMetadata(worktreePath) {
43264
- const metadataPath = path18.join(worktreePath, ".visor-metadata.json");
43265
- if (!fs15.existsSync(metadataPath)) {
45139
+ const metadataPath = path19.join(worktreePath, ".visor-metadata.json");
45140
+ if (!fs16.existsSync(metadataPath)) {
43266
45141
  return null;
43267
45142
  }
43268
45143
  try {
43269
- const content = fs15.readFileSync(metadataPath, "utf8");
45144
+ const content = fs16.readFileSync(metadataPath, "utf8");
43270
45145
  return JSON.parse(content);
43271
45146
  } catch (error) {
43272
45147
  logger.warn(`Failed to load metadata: ${error}`);
@@ -43278,14 +45153,14 @@ var init_worktree_manager = __esm({
43278
45153
  */
43279
45154
  async listWorktrees() {
43280
45155
  const worktreesDir = this.getWorktreesDir();
43281
- if (!fs15.existsSync(worktreesDir)) {
45156
+ if (!fs16.existsSync(worktreesDir)) {
43282
45157
  return [];
43283
45158
  }
43284
- const entries = fs15.readdirSync(worktreesDir, { withFileTypes: true });
45159
+ const entries = fs16.readdirSync(worktreesDir, { withFileTypes: true });
43285
45160
  const worktrees = [];
43286
45161
  for (const entry of entries) {
43287
45162
  if (!entry.isDirectory()) continue;
43288
- const worktreePath = path18.join(worktreesDir, entry.name);
45163
+ const worktreePath = path19.join(worktreesDir, entry.name);
43289
45164
  const metadata = await this.loadMetadata(worktreePath);
43290
45165
  if (metadata) {
43291
45166
  worktrees.push({
@@ -43417,8 +45292,8 @@ var init_worktree_manager = __esm({
43417
45292
  * Validate path to prevent directory traversal
43418
45293
  */
43419
45294
  validatePath(userPath) {
43420
- const resolvedPath = path18.resolve(userPath);
43421
- if (!path18.isAbsolute(resolvedPath)) {
45295
+ const resolvedPath = path19.resolve(userPath);
45296
+ if (!path19.isAbsolute(resolvedPath)) {
43422
45297
  throw new Error("Path must be absolute");
43423
45298
  }
43424
45299
  const sensitivePatterns = [
@@ -45127,6 +47002,7 @@ async function executeSingleCheck(checkId, context2, state, emitEvent, transitio
45127
47002
  ...checkConfig,
45128
47003
  eventContext: context2.prInfo?.eventContext || {},
45129
47004
  __outputHistory: outputHistory,
47005
+ __globalTools: context2.config.tools || {},
45130
47006
  // Propagate workflow inputs for template access via {{ inputs.* }}
45131
47007
  workflowInputs,
45132
47008
  ai: {
@@ -45444,23 +47320,23 @@ __export(renderer_schema_exports, {
45444
47320
  });
45445
47321
  async function loadRendererSchema(name) {
45446
47322
  try {
45447
- const fs25 = await import("fs/promises");
45448
- const path29 = await import("path");
47323
+ const fs26 = await import("fs/promises");
47324
+ const path30 = await import("path");
45449
47325
  const sanitized = String(name).replace(/[^a-zA-Z0-9-]/g, "");
45450
47326
  if (!sanitized) return void 0;
45451
47327
  const candidates = [
45452
47328
  // When bundled with ncc, __dirname is dist/ and output/ is at dist/output/
45453
- path29.join(__dirname, "output", sanitized, "schema.json"),
47329
+ path30.join(__dirname, "output", sanitized, "schema.json"),
45454
47330
  // When running from source, __dirname is src/state-machine/dispatch/ and output/ is at output/
45455
- path29.join(__dirname, "..", "..", "output", sanitized, "schema.json"),
47331
+ path30.join(__dirname, "..", "..", "output", sanitized, "schema.json"),
45456
47332
  // When running from a checkout with output/ folder copied to CWD
45457
- path29.join(process.cwd(), "output", sanitized, "schema.json"),
47333
+ path30.join(process.cwd(), "output", sanitized, "schema.json"),
45458
47334
  // Fallback: cwd/dist/output/
45459
- path29.join(process.cwd(), "dist", "output", sanitized, "schema.json")
47335
+ path30.join(process.cwd(), "dist", "output", sanitized, "schema.json")
45460
47336
  ];
45461
47337
  for (const p of candidates) {
45462
47338
  try {
45463
- const raw = await fs25.readFile(p, "utf-8");
47339
+ const raw = await fs26.readFile(p, "utf-8");
45464
47340
  return JSON.parse(raw);
45465
47341
  } catch {
45466
47342
  }
@@ -47879,8 +49755,8 @@ function updateStats2(results, state, isForEachIteration = false) {
47879
49755
  async function renderTemplateContent2(checkId, checkConfig, reviewSummary) {
47880
49756
  try {
47881
49757
  const { createExtendedLiquid: createExtendedLiquid2 } = await Promise.resolve().then(() => (init_liquid_extensions(), liquid_extensions_exports));
47882
- const fs25 = await import("fs/promises");
47883
- const path29 = await import("path");
49758
+ const fs26 = await import("fs/promises");
49759
+ const path30 = await import("path");
47884
49760
  const schemaRaw = checkConfig.schema || "plain";
47885
49761
  const schema = typeof schemaRaw === "string" && !schemaRaw.includes("{{") && !schemaRaw.includes("{%") ? schemaRaw : typeof schemaRaw === "object" ? "code-review" : "plain";
47886
49762
  let templateContent;
@@ -47889,27 +49765,27 @@ async function renderTemplateContent2(checkId, checkConfig, reviewSummary) {
47889
49765
  logger.debug(`[LevelDispatch] Using inline template for ${checkId}`);
47890
49766
  } else if (checkConfig.template && checkConfig.template.file) {
47891
49767
  const file = String(checkConfig.template.file);
47892
- const resolved = path29.resolve(process.cwd(), file);
47893
- templateContent = await fs25.readFile(resolved, "utf-8");
49768
+ const resolved = path30.resolve(process.cwd(), file);
49769
+ templateContent = await fs26.readFile(resolved, "utf-8");
47894
49770
  logger.debug(`[LevelDispatch] Using template file for ${checkId}: ${resolved}`);
47895
49771
  } else if (schema && schema !== "plain") {
47896
49772
  const sanitized = String(schema).replace(/[^a-zA-Z0-9-]/g, "");
47897
49773
  if (sanitized) {
47898
49774
  const candidatePaths = [
47899
- path29.join(__dirname, "output", sanitized, "template.liquid"),
49775
+ path30.join(__dirname, "output", sanitized, "template.liquid"),
47900
49776
  // bundled: dist/output/
47901
- path29.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
49777
+ path30.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
47902
49778
  // source (from state-machine/states)
47903
- path29.join(__dirname, "..", "..", "..", "output", sanitized, "template.liquid"),
49779
+ path30.join(__dirname, "..", "..", "..", "output", sanitized, "template.liquid"),
47904
49780
  // source (alternate)
47905
- path29.join(process.cwd(), "output", sanitized, "template.liquid"),
49781
+ path30.join(process.cwd(), "output", sanitized, "template.liquid"),
47906
49782
  // fallback: cwd/output/
47907
- path29.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
49783
+ path30.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
47908
49784
  // fallback: cwd/dist/output/
47909
49785
  ];
47910
49786
  for (const p of candidatePaths) {
47911
49787
  try {
47912
- templateContent = await fs25.readFile(p, "utf-8");
49788
+ templateContent = await fs26.readFile(p, "utf-8");
47913
49789
  if (templateContent) {
47914
49790
  logger.debug(`[LevelDispatch] Using schema template for ${checkId}: ${p}`);
47915
49791
  break;
@@ -48516,14 +50392,14 @@ var init_runner = __esm({
48516
50392
  });
48517
50393
 
48518
50394
  // src/sandbox/docker-image-sandbox.ts
48519
- var import_util2, import_child_process2, import_fs5, import_path8, import_os, import_crypto2, execFileAsync, EXEC_MAX_BUFFER, DockerImageSandbox;
50395
+ var import_util2, import_child_process2, import_fs5, import_path9, import_os, import_crypto2, execFileAsync, EXEC_MAX_BUFFER, DockerImageSandbox;
48520
50396
  var init_docker_image_sandbox = __esm({
48521
50397
  "src/sandbox/docker-image-sandbox.ts"() {
48522
50398
  "use strict";
48523
50399
  import_util2 = require("util");
48524
50400
  import_child_process2 = require("child_process");
48525
50401
  import_fs5 = require("fs");
48526
- import_path8 = require("path");
50402
+ import_path9 = require("path");
48527
50403
  import_os = require("os");
48528
50404
  import_crypto2 = require("crypto");
48529
50405
  init_logger();
@@ -48568,8 +50444,8 @@ var init_docker_image_sandbox = __esm({
48568
50444
  `Sandbox '${this.name}' has invalid dockerfile_inline: must contain a FROM instruction`
48569
50445
  );
48570
50446
  }
48571
- const tmpDir = (0, import_fs5.mkdtempSync)((0, import_path8.join)((0, import_os.tmpdir)(), "visor-build-"));
48572
- const dockerfilePath = (0, import_path8.join)(tmpDir, "Dockerfile");
50447
+ const tmpDir = (0, import_fs5.mkdtempSync)((0, import_path9.join)((0, import_os.tmpdir)(), "visor-build-"));
50448
+ const dockerfilePath = (0, import_path9.join)(tmpDir, "Dockerfile");
48573
50449
  (0, import_fs5.writeFileSync)(dockerfilePath, this.config.dockerfile_inline, "utf8");
48574
50450
  try {
48575
50451
  logger.info(`Building sandbox image '${imageName}' from inline Dockerfile`);
@@ -49006,11 +50882,11 @@ var init_cache_volume_manager = __esm({
49006
50882
  });
49007
50883
 
49008
50884
  // src/sandbox/sandbox-manager.ts
49009
- var import_path9, import_fs6, SandboxManager;
50885
+ var import_path10, import_fs6, SandboxManager;
49010
50886
  var init_sandbox_manager = __esm({
49011
50887
  "src/sandbox/sandbox-manager.ts"() {
49012
50888
  "use strict";
49013
- import_path9 = require("path");
50889
+ import_path10 = require("path");
49014
50890
  import_fs6 = require("fs");
49015
50891
  init_docker_image_sandbox();
49016
50892
  init_docker_compose_sandbox();
@@ -49030,10 +50906,10 @@ var init_sandbox_manager = __esm({
49030
50906
  }
49031
50907
  constructor(sandboxDefs, repoPath, gitBranch) {
49032
50908
  this.sandboxDefs = sandboxDefs;
49033
- this.repoPath = (0, import_path9.resolve)(repoPath);
50909
+ this.repoPath = (0, import_path10.resolve)(repoPath);
49034
50910
  this.gitBranch = gitBranch;
49035
50911
  this.cacheManager = new CacheVolumeManager();
49036
- this.visorDistPath = (0, import_fs6.existsSync)((0, import_path9.join)(__dirname, "index.js")) ? __dirname : (0, import_path9.resolve)((0, import_path9.dirname)(__dirname));
50912
+ this.visorDistPath = (0, import_fs6.existsSync)((0, import_path10.join)(__dirname, "index.js")) ? __dirname : (0, import_path10.resolve)((0, import_path10.dirname)(__dirname));
49037
50913
  }
49038
50914
  /**
49039
50915
  * Resolve which sandbox a check should use.
@@ -49150,13 +51026,13 @@ var init_sandbox_manager = __esm({
49150
51026
  });
49151
51027
 
49152
51028
  // src/utils/file-exclusion.ts
49153
- var import_ignore, fs16, path19, DEFAULT_EXCLUSION_PATTERNS, FileExclusionHelper;
51029
+ var import_ignore, fs17, path20, DEFAULT_EXCLUSION_PATTERNS, FileExclusionHelper;
49154
51030
  var init_file_exclusion = __esm({
49155
51031
  "src/utils/file-exclusion.ts"() {
49156
51032
  "use strict";
49157
51033
  import_ignore = __toESM(require("ignore"));
49158
- fs16 = __toESM(require("fs"));
49159
- path19 = __toESM(require("path"));
51034
+ fs17 = __toESM(require("fs"));
51035
+ path20 = __toESM(require("path"));
49160
51036
  DEFAULT_EXCLUSION_PATTERNS = [
49161
51037
  "dist/",
49162
51038
  "build/",
@@ -49175,7 +51051,7 @@ var init_file_exclusion = __esm({
49175
51051
  * @param additionalPatterns - Additional patterns to include (optional, defaults to common build artifacts)
49176
51052
  */
49177
51053
  constructor(workingDirectory = process.cwd(), additionalPatterns = DEFAULT_EXCLUSION_PATTERNS) {
49178
- const normalizedPath = path19.resolve(workingDirectory);
51054
+ const normalizedPath = path20.resolve(workingDirectory);
49179
51055
  if (normalizedPath.includes("\0")) {
49180
51056
  throw new Error("Invalid workingDirectory: contains null bytes");
49181
51057
  }
@@ -49187,11 +51063,11 @@ var init_file_exclusion = __esm({
49187
51063
  * @param additionalPatterns - Additional patterns to add to gitignore rules
49188
51064
  */
49189
51065
  loadGitignore(additionalPatterns) {
49190
- const gitignorePath = path19.resolve(this.workingDirectory, ".gitignore");
49191
- const resolvedWorkingDir = path19.resolve(this.workingDirectory);
51066
+ const gitignorePath = path20.resolve(this.workingDirectory, ".gitignore");
51067
+ const resolvedWorkingDir = path20.resolve(this.workingDirectory);
49192
51068
  try {
49193
- const relativePath = path19.relative(resolvedWorkingDir, gitignorePath);
49194
- if (relativePath.startsWith("..") || path19.isAbsolute(relativePath)) {
51069
+ const relativePath = path20.relative(resolvedWorkingDir, gitignorePath);
51070
+ if (relativePath.startsWith("..") || path20.isAbsolute(relativePath)) {
49195
51071
  throw new Error("Invalid gitignore path: path traversal detected");
49196
51072
  }
49197
51073
  if (relativePath !== ".gitignore") {
@@ -49201,8 +51077,8 @@ var init_file_exclusion = __esm({
49201
51077
  if (additionalPatterns && additionalPatterns.length > 0) {
49202
51078
  this.gitignore.add(additionalPatterns);
49203
51079
  }
49204
- if (fs16.existsSync(gitignorePath)) {
49205
- const rawContent = fs16.readFileSync(gitignorePath, "utf8");
51080
+ if (fs17.existsSync(gitignorePath)) {
51081
+ const rawContent = fs17.readFileSync(gitignorePath, "utf8");
49206
51082
  const gitignoreContent = rawContent.replace(/[\r\n]+/g, "\n").replace(/[\x00-\x09\x0B-\x1F\x7F]/g, "").split("\n").filter((line) => line.length < 1e3).join("\n").trim();
49207
51083
  this.gitignore.add(gitignoreContent);
49208
51084
  if (process.env.VISOR_DEBUG === "true") {
@@ -49234,13 +51110,13 @@ var git_repository_analyzer_exports = {};
49234
51110
  __export(git_repository_analyzer_exports, {
49235
51111
  GitRepositoryAnalyzer: () => GitRepositoryAnalyzer
49236
51112
  });
49237
- var import_simple_git2, path20, fs17, MAX_PATCH_SIZE, GitRepositoryAnalyzer;
51113
+ var import_simple_git2, path21, fs18, MAX_PATCH_SIZE, GitRepositoryAnalyzer;
49238
51114
  var init_git_repository_analyzer = __esm({
49239
51115
  "src/git-repository-analyzer.ts"() {
49240
51116
  "use strict";
49241
51117
  import_simple_git2 = require("simple-git");
49242
- path20 = __toESM(require("path"));
49243
- fs17 = __toESM(require("fs"));
51118
+ path21 = __toESM(require("path"));
51119
+ fs18 = __toESM(require("fs"));
49244
51120
  init_file_exclusion();
49245
51121
  MAX_PATCH_SIZE = 50 * 1024;
49246
51122
  GitRepositoryAnalyzer = class {
@@ -49429,7 +51305,7 @@ ${file.patch}`).join("\n\n");
49429
51305
  console.error(`\u23ED\uFE0F Skipping excluded file: ${file}`);
49430
51306
  continue;
49431
51307
  }
49432
- const filePath = path20.join(this.cwd, file);
51308
+ const filePath = path21.join(this.cwd, file);
49433
51309
  const fileChange = await this.analyzeFileChange(file, status2, filePath, includeContext);
49434
51310
  changes.push(fileChange);
49435
51311
  }
@@ -49505,7 +51381,7 @@ ${file.patch}`).join("\n\n");
49505
51381
  let content;
49506
51382
  let truncated = false;
49507
51383
  try {
49508
- if (includeContext && status !== "added" && fs17.existsSync(filePath)) {
51384
+ if (includeContext && status !== "added" && fs18.existsSync(filePath)) {
49509
51385
  const diff = await this.git.diff(["--", filename]).catch(() => "");
49510
51386
  if (diff) {
49511
51387
  const result = this.truncatePatch(diff, filename);
@@ -49515,7 +51391,7 @@ ${file.patch}`).join("\n\n");
49515
51391
  additions = lines.filter((line) => line.startsWith("+")).length;
49516
51392
  deletions = lines.filter((line) => line.startsWith("-")).length;
49517
51393
  }
49518
- } else if (status !== "added" && fs17.existsSync(filePath)) {
51394
+ } else if (status !== "added" && fs18.existsSync(filePath)) {
49519
51395
  const diff = await this.git.diff(["--", filename]).catch(() => "");
49520
51396
  if (diff) {
49521
51397
  const lines = diff.split("\n");
@@ -49523,17 +51399,17 @@ ${file.patch}`).join("\n\n");
49523
51399
  deletions = lines.filter((line) => line.startsWith("-")).length;
49524
51400
  }
49525
51401
  }
49526
- if (status === "added" && fs17.existsSync(filePath)) {
51402
+ if (status === "added" && fs18.existsSync(filePath)) {
49527
51403
  try {
49528
- const stats = fs17.statSync(filePath);
51404
+ const stats = fs18.statSync(filePath);
49529
51405
  if (stats.isFile() && stats.size < 1024 * 1024) {
49530
51406
  if (includeContext) {
49531
- content = fs17.readFileSync(filePath, "utf8");
51407
+ content = fs18.readFileSync(filePath, "utf8");
49532
51408
  const result = this.truncatePatch(content, filename);
49533
51409
  patch = result.patch;
49534
51410
  truncated = result.truncated;
49535
51411
  }
49536
- const fileContent = includeContext ? content : fs17.readFileSync(filePath, "utf8");
51412
+ const fileContent = includeContext ? content : fs18.readFileSync(filePath, "utf8");
49537
51413
  additions = fileContent.split("\n").length;
49538
51414
  }
49539
51415
  } catch {
@@ -49624,12 +51500,12 @@ function shellEscape(str) {
49624
51500
  function sanitizePathComponent(name) {
49625
51501
  return name.replace(/\.\./g, "").replace(/[\/\\]/g, "-").replace(/^\.+/, "").trim() || "unnamed";
49626
51502
  }
49627
- var fsp2, path21, WorkspaceManager;
51503
+ var fsp2, path22, WorkspaceManager;
49628
51504
  var init_workspace_manager = __esm({
49629
51505
  "src/utils/workspace-manager.ts"() {
49630
51506
  "use strict";
49631
51507
  fsp2 = __toESM(require("fs/promises"));
49632
- path21 = __toESM(require("path"));
51508
+ path22 = __toESM(require("path"));
49633
51509
  init_command_executor();
49634
51510
  init_logger();
49635
51511
  WorkspaceManager = class _WorkspaceManager {
@@ -49663,7 +51539,7 @@ var init_workspace_manager = __esm({
49663
51539
  };
49664
51540
  this.basePath = this.config.basePath;
49665
51541
  const workspaceDirName = sanitizePathComponent(this.config.name || this.sessionId);
49666
- this.workspacePath = path21.join(this.basePath, workspaceDirName);
51542
+ this.workspacePath = path22.join(this.basePath, workspaceDirName);
49667
51543
  }
49668
51544
  /**
49669
51545
  * Get or create a WorkspaceManager instance for a session
@@ -49758,7 +51634,7 @@ var init_workspace_manager = __esm({
49758
51634
  configuredMainProjectName || this.extractProjectName(this.originalPath)
49759
51635
  );
49760
51636
  this.usedNames.add(mainProjectName);
49761
- const mainProjectPath = path21.join(this.workspacePath, mainProjectName);
51637
+ const mainProjectPath = path22.join(this.workspacePath, mainProjectName);
49762
51638
  const isGitRepo = await this.isGitRepository(this.originalPath);
49763
51639
  if (isGitRepo) {
49764
51640
  await this.createMainProjectWorktree(mainProjectPath);
@@ -49799,7 +51675,7 @@ var init_workspace_manager = __esm({
49799
51675
  let projectName = sanitizePathComponent(description || this.extractRepoName(repository));
49800
51676
  projectName = this.getUniqueName(projectName);
49801
51677
  this.usedNames.add(projectName);
49802
- const workspacePath = path21.join(this.workspacePath, projectName);
51678
+ const workspacePath = path22.join(this.workspacePath, projectName);
49803
51679
  await fsp2.rm(workspacePath, { recursive: true, force: true });
49804
51680
  try {
49805
51681
  await fsp2.symlink(worktreePath, workspacePath);
@@ -49938,7 +51814,7 @@ var init_workspace_manager = __esm({
49938
51814
  * Extract project name from path
49939
51815
  */
49940
51816
  extractProjectName(dirPath) {
49941
- return path21.basename(dirPath);
51817
+ return path22.basename(dirPath);
49942
51818
  }
49943
51819
  /**
49944
51820
  * Extract repository name from owner/repo format
@@ -50155,13 +52031,13 @@ var validator_exports = {};
50155
52031
  __export(validator_exports, {
50156
52032
  LicenseValidator: () => LicenseValidator
50157
52033
  });
50158
- var crypto2, fs18, path22, LicenseValidator;
52034
+ var crypto2, fs19, path23, LicenseValidator;
50159
52035
  var init_validator = __esm({
50160
52036
  "src/enterprise/license/validator.ts"() {
50161
52037
  "use strict";
50162
52038
  crypto2 = __toESM(require("crypto"));
50163
- fs18 = __toESM(require("fs"));
50164
- path22 = __toESM(require("path"));
52039
+ fs19 = __toESM(require("fs"));
52040
+ path23 = __toESM(require("path"));
50165
52041
  LicenseValidator = class _LicenseValidator {
50166
52042
  /** Ed25519 public key for license verification (PEM format). */
50167
52043
  static PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAI/Zd08EFmgIdrDm/HXd0l3/5GBt7R1PrdvhdmEXhJlU=\n-----END PUBLIC KEY-----\n";
@@ -50214,28 +52090,28 @@ var init_validator = __esm({
50214
52090
  return process.env.VISOR_LICENSE.trim();
50215
52091
  }
50216
52092
  if (process.env.VISOR_LICENSE_FILE) {
50217
- const resolved = path22.resolve(process.env.VISOR_LICENSE_FILE);
52093
+ const resolved = path23.resolve(process.env.VISOR_LICENSE_FILE);
50218
52094
  const home2 = process.env.HOME || process.env.USERPROFILE || "";
50219
- const allowedPrefixes = [path22.normalize(process.cwd())];
50220
- if (home2) allowedPrefixes.push(path22.normalize(path22.join(home2, ".config", "visor")));
52095
+ const allowedPrefixes = [path23.normalize(process.cwd())];
52096
+ if (home2) allowedPrefixes.push(path23.normalize(path23.join(home2, ".config", "visor")));
50221
52097
  let realPath;
50222
52098
  try {
50223
- realPath = fs18.realpathSync(resolved);
52099
+ realPath = fs19.realpathSync(resolved);
50224
52100
  } catch {
50225
52101
  return null;
50226
52102
  }
50227
52103
  const isSafe = allowedPrefixes.some(
50228
- (prefix) => realPath === prefix || realPath.startsWith(prefix + path22.sep)
52104
+ (prefix) => realPath === prefix || realPath.startsWith(prefix + path23.sep)
50229
52105
  );
50230
52106
  if (!isSafe) return null;
50231
52107
  return this.readFile(realPath);
50232
52108
  }
50233
- const cwdPath = path22.join(process.cwd(), ".visor-license");
52109
+ const cwdPath = path23.join(process.cwd(), ".visor-license");
50234
52110
  const cwdToken = this.readFile(cwdPath);
50235
52111
  if (cwdToken) return cwdToken;
50236
52112
  const home = process.env.HOME || process.env.USERPROFILE || "";
50237
52113
  if (home) {
50238
- const configPath = path22.join(home, ".config", "visor", ".visor-license");
52114
+ const configPath = path23.join(home, ".config", "visor", ".visor-license");
50239
52115
  const configToken = this.readFile(configPath);
50240
52116
  if (configToken) return configToken;
50241
52117
  }
@@ -50243,7 +52119,7 @@ var init_validator = __esm({
50243
52119
  }
50244
52120
  readFile(filePath) {
50245
52121
  try {
50246
- return fs18.readFileSync(filePath, "utf-8").trim();
52122
+ return fs19.readFileSync(filePath, "utf-8").trim();
50247
52123
  } catch {
50248
52124
  return null;
50249
52125
  }
@@ -50282,17 +52158,17 @@ var init_validator = __esm({
50282
52158
  });
50283
52159
 
50284
52160
  // src/enterprise/policy/opa-compiler.ts
50285
- var fs19, path23, os, crypto3, import_child_process5, OpaCompiler;
52161
+ var fs20, path24, os, crypto3, import_child_process5, OpaCompiler;
50286
52162
  var init_opa_compiler = __esm({
50287
52163
  "src/enterprise/policy/opa-compiler.ts"() {
50288
52164
  "use strict";
50289
- fs19 = __toESM(require("fs"));
50290
- path23 = __toESM(require("path"));
52165
+ fs20 = __toESM(require("fs"));
52166
+ path24 = __toESM(require("path"));
50291
52167
  os = __toESM(require("os"));
50292
52168
  crypto3 = __toESM(require("crypto"));
50293
52169
  import_child_process5 = require("child_process");
50294
52170
  OpaCompiler = class _OpaCompiler {
50295
- static CACHE_DIR = path23.join(os.tmpdir(), "visor-opa-cache");
52171
+ static CACHE_DIR = path24.join(os.tmpdir(), "visor-opa-cache");
50296
52172
  /**
50297
52173
  * Resolve the input paths to WASM bytes.
50298
52174
  *
@@ -50304,24 +52180,24 @@ var init_opa_compiler = __esm({
50304
52180
  async resolveWasmBytes(paths) {
50305
52181
  const regoFiles = [];
50306
52182
  for (const p of paths) {
50307
- const resolved = path23.resolve(p);
50308
- if (path23.normalize(resolved).includes("..")) {
52183
+ const resolved = path24.resolve(p);
52184
+ if (path24.normalize(resolved).includes("..")) {
50309
52185
  throw new Error(`Policy path contains traversal sequences: ${p}`);
50310
52186
  }
50311
- if (resolved.endsWith(".wasm") && fs19.existsSync(resolved)) {
50312
- return fs19.readFileSync(resolved);
52187
+ if (resolved.endsWith(".wasm") && fs20.existsSync(resolved)) {
52188
+ return fs20.readFileSync(resolved);
50313
52189
  }
50314
- if (!fs19.existsSync(resolved)) continue;
50315
- const stat = fs19.statSync(resolved);
52190
+ if (!fs20.existsSync(resolved)) continue;
52191
+ const stat = fs20.statSync(resolved);
50316
52192
  if (stat.isDirectory()) {
50317
- const wasmCandidate = path23.join(resolved, "policy.wasm");
50318
- if (fs19.existsSync(wasmCandidate)) {
50319
- return fs19.readFileSync(wasmCandidate);
52193
+ const wasmCandidate = path24.join(resolved, "policy.wasm");
52194
+ if (fs20.existsSync(wasmCandidate)) {
52195
+ return fs20.readFileSync(wasmCandidate);
50320
52196
  }
50321
- const files = fs19.readdirSync(resolved);
52197
+ const files = fs20.readdirSync(resolved);
50322
52198
  for (const f of files) {
50323
52199
  if (f.endsWith(".rego")) {
50324
- regoFiles.push(path23.join(resolved, f));
52200
+ regoFiles.push(path24.join(resolved, f));
50325
52201
  }
50326
52202
  }
50327
52203
  } else if (resolved.endsWith(".rego")) {
@@ -50351,17 +52227,17 @@ var init_opa_compiler = __esm({
50351
52227
  }
50352
52228
  const hash = crypto3.createHash("sha256");
50353
52229
  for (const f of regoFiles.sort()) {
50354
- hash.update(fs19.readFileSync(f));
52230
+ hash.update(fs20.readFileSync(f));
50355
52231
  hash.update(f);
50356
52232
  }
50357
52233
  const cacheKey = hash.digest("hex").slice(0, 16);
50358
52234
  const cacheDir = _OpaCompiler.CACHE_DIR;
50359
- const cachedWasm = path23.join(cacheDir, `${cacheKey}.wasm`);
50360
- if (fs19.existsSync(cachedWasm)) {
50361
- return fs19.readFileSync(cachedWasm);
52235
+ const cachedWasm = path24.join(cacheDir, `${cacheKey}.wasm`);
52236
+ if (fs20.existsSync(cachedWasm)) {
52237
+ return fs20.readFileSync(cachedWasm);
50362
52238
  }
50363
- fs19.mkdirSync(cacheDir, { recursive: true });
50364
- const bundleTar = path23.join(cacheDir, `${cacheKey}-bundle.tar.gz`);
52239
+ fs20.mkdirSync(cacheDir, { recursive: true });
52240
+ const bundleTar = path24.join(cacheDir, `${cacheKey}-bundle.tar.gz`);
50365
52241
  try {
50366
52242
  const args = [
50367
52243
  "build",
@@ -50390,43 +52266,43 @@ Ensure your .rego files are valid and the \`opa\` CLI is installed.`
50390
52266
  (0, import_child_process5.execFileSync)("tar", ["-xzf", bundleTar, "-C", cacheDir, "/policy.wasm"], {
50391
52267
  stdio: "pipe"
50392
52268
  });
50393
- const extractedWasm = path23.join(cacheDir, "policy.wasm");
50394
- if (fs19.existsSync(extractedWasm)) {
50395
- fs19.renameSync(extractedWasm, cachedWasm);
52269
+ const extractedWasm = path24.join(cacheDir, "policy.wasm");
52270
+ if (fs20.existsSync(extractedWasm)) {
52271
+ fs20.renameSync(extractedWasm, cachedWasm);
50396
52272
  }
50397
52273
  } catch {
50398
52274
  try {
50399
52275
  (0, import_child_process5.execFileSync)("tar", ["-xzf", bundleTar, "-C", cacheDir, "policy.wasm"], {
50400
52276
  stdio: "pipe"
50401
52277
  });
50402
- const extractedWasm = path23.join(cacheDir, "policy.wasm");
50403
- if (fs19.existsSync(extractedWasm)) {
50404
- fs19.renameSync(extractedWasm, cachedWasm);
52278
+ const extractedWasm = path24.join(cacheDir, "policy.wasm");
52279
+ if (fs20.existsSync(extractedWasm)) {
52280
+ fs20.renameSync(extractedWasm, cachedWasm);
50405
52281
  }
50406
52282
  } catch (err2) {
50407
52283
  throw new Error(`Failed to extract policy.wasm from OPA bundle: ${err2?.message || err2}`);
50408
52284
  }
50409
52285
  }
50410
52286
  try {
50411
- fs19.unlinkSync(bundleTar);
52287
+ fs20.unlinkSync(bundleTar);
50412
52288
  } catch {
50413
52289
  }
50414
- if (!fs19.existsSync(cachedWasm)) {
52290
+ if (!fs20.existsSync(cachedWasm)) {
50415
52291
  throw new Error("OPA build succeeded but policy.wasm was not found in the bundle");
50416
52292
  }
50417
- return fs19.readFileSync(cachedWasm);
52293
+ return fs20.readFileSync(cachedWasm);
50418
52294
  }
50419
52295
  };
50420
52296
  }
50421
52297
  });
50422
52298
 
50423
52299
  // src/enterprise/policy/opa-wasm-evaluator.ts
50424
- var fs20, path24, OpaWasmEvaluator;
52300
+ var fs21, path25, OpaWasmEvaluator;
50425
52301
  var init_opa_wasm_evaluator = __esm({
50426
52302
  "src/enterprise/policy/opa-wasm-evaluator.ts"() {
50427
52303
  "use strict";
50428
- fs20 = __toESM(require("fs"));
50429
- path24 = __toESM(require("path"));
52304
+ fs21 = __toESM(require("fs"));
52305
+ path25 = __toESM(require("path"));
50430
52306
  init_opa_compiler();
50431
52307
  OpaWasmEvaluator = class {
50432
52308
  policy = null;
@@ -50459,18 +52335,18 @@ var init_opa_wasm_evaluator = __esm({
50459
52335
  * making it available in Rego via `data.<key>`.
50460
52336
  */
50461
52337
  loadData(dataPath) {
50462
- const resolved = path24.resolve(dataPath);
50463
- if (path24.normalize(resolved).includes("..")) {
52338
+ const resolved = path25.resolve(dataPath);
52339
+ if (path25.normalize(resolved).includes("..")) {
50464
52340
  throw new Error(`Data path contains traversal sequences: ${dataPath}`);
50465
52341
  }
50466
- if (!fs20.existsSync(resolved)) {
52342
+ if (!fs21.existsSync(resolved)) {
50467
52343
  throw new Error(`OPA data file not found: ${resolved}`);
50468
52344
  }
50469
- const stat = fs20.statSync(resolved);
52345
+ const stat = fs21.statSync(resolved);
50470
52346
  if (stat.size > 10 * 1024 * 1024) {
50471
52347
  throw new Error(`OPA data file exceeds 10MB limit: ${resolved} (${stat.size} bytes)`);
50472
52348
  }
50473
- const raw = fs20.readFileSync(resolved, "utf-8");
52349
+ const raw = fs21.readFileSync(resolved, "utf-8");
50474
52350
  try {
50475
52351
  const parsed = JSON.parse(raw);
50476
52352
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
@@ -50991,12 +52867,12 @@ function toInsertRow(schedule) {
50991
52867
  previous_response: schedule.previousResponse ?? null
50992
52868
  };
50993
52869
  }
50994
- var fs21, path25, import_uuid2, KnexStoreBackend;
52870
+ var fs22, path26, import_uuid2, KnexStoreBackend;
50995
52871
  var init_knex_store = __esm({
50996
52872
  "src/enterprise/scheduler/knex-store.ts"() {
50997
52873
  "use strict";
50998
- fs21 = __toESM(require("fs"));
50999
- path25 = __toESM(require("path"));
52874
+ fs22 = __toESM(require("fs"));
52875
+ path26 = __toESM(require("path"));
51000
52876
  import_uuid2 = require("uuid");
51001
52877
  init_logger();
51002
52878
  KnexStoreBackend = class {
@@ -51082,24 +52958,24 @@ var init_knex_store = __esm({
51082
52958
  };
51083
52959
  if (ssl.ca) {
51084
52960
  const caPath = this.validateSslPath(ssl.ca, "CA certificate");
51085
- result.ca = fs21.readFileSync(caPath, "utf8");
52961
+ result.ca = fs22.readFileSync(caPath, "utf8");
51086
52962
  }
51087
52963
  if (ssl.cert) {
51088
52964
  const certPath = this.validateSslPath(ssl.cert, "client certificate");
51089
- result.cert = fs21.readFileSync(certPath, "utf8");
52965
+ result.cert = fs22.readFileSync(certPath, "utf8");
51090
52966
  }
51091
52967
  if (ssl.key) {
51092
52968
  const keyPath = this.validateSslPath(ssl.key, "client key");
51093
- result.key = fs21.readFileSync(keyPath, "utf8");
52969
+ result.key = fs22.readFileSync(keyPath, "utf8");
51094
52970
  }
51095
52971
  return result;
51096
52972
  }
51097
52973
  validateSslPath(filePath, label) {
51098
- const resolved = path25.resolve(filePath);
51099
- if (resolved !== path25.normalize(resolved)) {
52974
+ const resolved = path26.resolve(filePath);
52975
+ if (resolved !== path26.normalize(resolved)) {
51100
52976
  throw new Error(`SSL ${label} path contains invalid sequences: ${filePath}`);
51101
52977
  }
51102
- if (!fs21.existsSync(resolved)) {
52978
+ if (!fs22.existsSync(resolved)) {
51103
52979
  throw new Error(`SSL ${label} not found: ${filePath}`);
51104
52980
  }
51105
52981
  return resolved;
@@ -51430,12 +53306,12 @@ var ndjson_sink_exports = {};
51430
53306
  __export(ndjson_sink_exports, {
51431
53307
  NdjsonSink: () => NdjsonSink
51432
53308
  });
51433
- var import_fs7, import_path10, NdjsonSink;
53309
+ var import_fs7, import_path11, NdjsonSink;
51434
53310
  var init_ndjson_sink = __esm({
51435
53311
  "src/frontends/ndjson-sink.ts"() {
51436
53312
  "use strict";
51437
53313
  import_fs7 = __toESM(require("fs"));
51438
- import_path10 = __toESM(require("path"));
53314
+ import_path11 = __toESM(require("path"));
51439
53315
  NdjsonSink = class {
51440
53316
  name = "ndjson-sink";
51441
53317
  cfg;
@@ -51467,8 +53343,8 @@ var init_ndjson_sink = __esm({
51467
53343
  this.unsub = void 0;
51468
53344
  }
51469
53345
  resolveFile(p) {
51470
- if (import_path10.default.isAbsolute(p)) return p;
51471
- return import_path10.default.join(process.cwd(), p);
53346
+ if (import_path11.default.isAbsolute(p)) return p;
53347
+ return import_path11.default.join(process.cwd(), p);
51472
53348
  }
51473
53349
  };
51474
53350
  }
@@ -53195,16 +55071,16 @@ function extractMermaidDiagrams(text) {
53195
55071
  }
53196
55072
  async function renderMermaidToPng(mermaidCode) {
53197
55073
  const tmpDir = os2.tmpdir();
53198
- const inputFile = path27.join(
55074
+ const inputFile = path28.join(
53199
55075
  tmpDir,
53200
55076
  `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}.mmd`
53201
55077
  );
53202
- const outputFile = path27.join(
55078
+ const outputFile = path28.join(
53203
55079
  tmpDir,
53204
55080
  `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}.png`
53205
55081
  );
53206
55082
  try {
53207
- fs23.writeFileSync(inputFile, mermaidCode, "utf-8");
55083
+ fs24.writeFileSync(inputFile, mermaidCode, "utf-8");
53208
55084
  const chromiumPaths = [
53209
55085
  "/usr/bin/chromium",
53210
55086
  "/usr/bin/chromium-browser",
@@ -53213,7 +55089,7 @@ async function renderMermaidToPng(mermaidCode) {
53213
55089
  ];
53214
55090
  let chromiumPath;
53215
55091
  for (const p of chromiumPaths) {
53216
- if (fs23.existsSync(p)) {
55092
+ if (fs24.existsSync(p)) {
53217
55093
  chromiumPath = p;
53218
55094
  break;
53219
55095
  }
@@ -53265,19 +55141,19 @@ async function renderMermaidToPng(mermaidCode) {
53265
55141
  console.warn(`Mermaid rendering failed: ${result.error}`);
53266
55142
  return null;
53267
55143
  }
53268
- if (!fs23.existsSync(outputFile)) {
55144
+ if (!fs24.existsSync(outputFile)) {
53269
55145
  console.warn("Mermaid output file not created");
53270
55146
  return null;
53271
55147
  }
53272
- const pngBuffer = fs23.readFileSync(outputFile);
55148
+ const pngBuffer = fs24.readFileSync(outputFile);
53273
55149
  return pngBuffer;
53274
55150
  } catch (e) {
53275
55151
  console.warn(`Mermaid rendering error: ${e instanceof Error ? e.message : String(e)}`);
53276
55152
  return null;
53277
55153
  } finally {
53278
55154
  try {
53279
- if (fs23.existsSync(inputFile)) fs23.unlinkSync(inputFile);
53280
- if (fs23.existsSync(outputFile)) fs23.unlinkSync(outputFile);
55155
+ if (fs24.existsSync(inputFile)) fs24.unlinkSync(inputFile);
55156
+ if (fs24.existsSync(outputFile)) fs24.unlinkSync(outputFile);
53281
55157
  } catch {
53282
55158
  }
53283
55159
  }
@@ -53382,13 +55258,13 @@ function replaceFileSections(text, sections, replacement = (idx) => `_(See file:
53382
55258
  function formatSlackText(text) {
53383
55259
  return markdownToSlack(text);
53384
55260
  }
53385
- var import_child_process6, fs23, path27, os2;
55261
+ var import_child_process6, fs24, path28, os2;
53386
55262
  var init_markdown = __esm({
53387
55263
  "src/slack/markdown.ts"() {
53388
55264
  "use strict";
53389
55265
  import_child_process6 = require("child_process");
53390
- fs23 = __toESM(require("fs"));
53391
- path27 = __toESM(require("path"));
55266
+ fs24 = __toESM(require("fs"));
55267
+ path28 = __toESM(require("path"));
53392
55268
  os2 = __toESM(require("os"));
53393
55269
  }
53394
55270
  });
@@ -54365,15 +56241,15 @@ function serializeRunState(state) {
54365
56241
  ])
54366
56242
  };
54367
56243
  }
54368
- var path28, fs24, StateMachineExecutionEngine;
56244
+ var path29, fs25, StateMachineExecutionEngine;
54369
56245
  var init_state_machine_execution_engine = __esm({
54370
56246
  "src/state-machine-execution-engine.ts"() {
54371
56247
  "use strict";
54372
56248
  init_runner();
54373
56249
  init_logger();
54374
56250
  init_sandbox_manager();
54375
- path28 = __toESM(require("path"));
54376
- fs24 = __toESM(require("fs"));
56251
+ path29 = __toESM(require("path"));
56252
+ fs25 = __toESM(require("fs"));
54377
56253
  StateMachineExecutionEngine = class _StateMachineExecutionEngine {
54378
56254
  workingDirectory;
54379
56255
  executionContext;
@@ -54563,6 +56439,15 @@ var init_state_machine_execution_engine = __esm({
54563
56439
  ...config,
54564
56440
  tag_filter: tagFilter
54565
56441
  } : config;
56442
+ try {
56443
+ const { CheckProviderRegistry: CheckProviderRegistry2 } = await Promise.resolve().then(() => (init_check_provider_registry(), check_provider_registry_exports));
56444
+ const registry = CheckProviderRegistry2.getInstance();
56445
+ registry.setCustomTools(configWithTagFilter.tools || {});
56446
+ } catch (error) {
56447
+ logger.warn(
56448
+ `[StateMachine] Failed to register custom tools: ${error instanceof Error ? error.message : String(error)}`
56449
+ );
56450
+ }
54566
56451
  const context2 = this.buildEngineContext(
54567
56452
  configWithTagFilter,
54568
56453
  prInfo,
@@ -54749,9 +56634,9 @@ var init_state_machine_execution_engine = __esm({
54749
56634
  }
54750
56635
  const checkId = String(ev?.checkId || "unknown");
54751
56636
  const threadKey = ev?.threadKey || (channel && threadTs ? `${channel}:${threadTs}` : "session");
54752
- const baseDir = process.env.VISOR_SNAPSHOT_DIR || path28.resolve(process.cwd(), ".visor", "snapshots");
54753
- fs24.mkdirSync(baseDir, { recursive: true });
54754
- const filePath = path28.join(baseDir, `${threadKey}-${checkId}.json`);
56637
+ const baseDir = process.env.VISOR_SNAPSHOT_DIR || path29.resolve(process.cwd(), ".visor", "snapshots");
56638
+ fs25.mkdirSync(baseDir, { recursive: true });
56639
+ const filePath = path29.join(baseDir, `${threadKey}-${checkId}.json`);
54755
56640
  await this.saveSnapshotToFile(filePath);
54756
56641
  logger.info(`[Snapshot] Saved run snapshot: ${filePath}`);
54757
56642
  try {
@@ -54892,7 +56777,7 @@ var init_state_machine_execution_engine = __esm({
54892
56777
  * Does not include secrets. Intended for debugging and future resume support.
54893
56778
  */
54894
56779
  async saveSnapshotToFile(filePath) {
54895
- const fs25 = await import("fs/promises");
56780
+ const fs26 = await import("fs/promises");
54896
56781
  const ctx = this._lastContext;
54897
56782
  const runner = this._lastRunner;
54898
56783
  if (!ctx || !runner) {
@@ -54912,14 +56797,14 @@ var init_state_machine_execution_engine = __esm({
54912
56797
  journal: entries,
54913
56798
  requestedChecks: ctx.requestedChecks || []
54914
56799
  };
54915
- await fs25.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
56800
+ await fs26.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
54916
56801
  }
54917
56802
  /**
54918
56803
  * Load a snapshot JSON from file and return it. Resume support can build on this.
54919
56804
  */
54920
56805
  async loadSnapshotFromFile(filePath) {
54921
- const fs25 = await import("fs/promises");
54922
- const raw = await fs25.readFile(filePath, "utf8");
56806
+ const fs26 = await import("fs/promises");
56807
+ const raw = await fs26.readFile(filePath, "utf8");
54923
56808
  return JSON.parse(raw);
54924
56809
  }
54925
56810
  /**