@probelabs/visor 0.1.131 → 0.1.132-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 (122) hide show
  1. package/README.md +460 -596
  2. package/action.yml +2 -2
  3. package/dist/ai-review-service.d.ts +3 -0
  4. package/dist/ai-review-service.d.ts.map +1 -1
  5. package/dist/cli-main.d.ts.map +1 -1
  6. package/dist/config/config-watcher.d.ts +15 -1
  7. package/dist/config/config-watcher.d.ts.map +1 -1
  8. package/dist/enterprise/policy/policy-input-builder.d.ts +2 -0
  9. package/dist/enterprise/policy/policy-input-builder.d.ts.map +1 -1
  10. package/dist/frontends/slack-frontend.d.ts.map +1 -1
  11. package/dist/generated/config-schema.d.ts +404 -96
  12. package/dist/generated/config-schema.d.ts.map +1 -1
  13. package/dist/generated/config-schema.json +2875 -0
  14. package/dist/index.js +24782 -8528
  15. package/dist/providers/ai-check-provider.d.ts +12 -0
  16. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  17. package/dist/providers/workflow-check-provider.d.ts.map +1 -1
  18. package/dist/providers/workflow-tool-executor.d.ts +5 -1
  19. package/dist/providers/workflow-tool-executor.d.ts.map +1 -1
  20. package/dist/sdk/{check-provider-registry-S7BMQ2FC.mjs → check-provider-registry-7TCA3NSG.mjs} +6 -6
  21. package/dist/sdk/{check-provider-registry-ZOLEYDKM.mjs → check-provider-registry-KUDVEKAC.mjs} +7 -7
  22. package/dist/sdk/{chunk-OMFPM576.mjs → chunk-27RV5RR2.mjs} +2 -2
  23. package/dist/sdk/{chunk-6ZZ4DPAA.mjs → chunk-2RNTEWOA.mjs} +236 -47
  24. package/dist/sdk/chunk-2RNTEWOA.mjs.map +1 -0
  25. package/dist/sdk/{chunk-2GCSK3PD.mjs → chunk-BGBXLPLL.mjs} +3 -3
  26. package/dist/sdk/{chunk-LQ5B4T6L.mjs → chunk-U3BLLEW3.mjs} +431 -82
  27. package/dist/sdk/chunk-U3BLLEW3.mjs.map +1 -0
  28. package/dist/sdk/{chunk-N4I6ZDCJ.mjs → chunk-VG7FWDC2.mjs} +3 -3
  29. package/dist/sdk/{chunk-RI77TA6V.mjs.map → chunk-VG7FWDC2.mjs.map} +1 -1
  30. package/dist/sdk/{chunk-MQ57AB4U.mjs → chunk-XGI47XIH.mjs} +260 -55
  31. package/dist/sdk/chunk-XGI47XIH.mjs.map +1 -0
  32. package/dist/sdk/{config-4EG7IQIU.mjs → config-FMIIATKX.mjs} +2 -2
  33. package/dist/sdk/{failure-condition-evaluator-GLHZZF47.mjs → failure-condition-evaluator-PNONVBXD.mjs} +3 -3
  34. package/dist/sdk/{github-frontend-F4TE2JY7.mjs → github-frontend-WR4S3NG5.mjs} +3 -3
  35. package/dist/sdk/{host-SAT6RHDX.mjs → host-TROSAWTE.mjs} +3 -3
  36. package/dist/sdk/{host-VA3ET7N6.mjs → host-U7V54J2H.mjs} +3 -3
  37. package/dist/sdk/knex-store-HPXJILBL.mjs +411 -0
  38. package/dist/sdk/knex-store-HPXJILBL.mjs.map +1 -0
  39. package/dist/sdk/loader-ZC5G3JGJ.mjs +89 -0
  40. package/dist/sdk/loader-ZC5G3JGJ.mjs.map +1 -0
  41. package/dist/sdk/opa-policy-engine-S2S2ULEI.mjs +655 -0
  42. package/dist/sdk/opa-policy-engine-S2S2ULEI.mjs.map +1 -0
  43. package/dist/sdk/{routing-KFYQGOYU.mjs → routing-F4FOWVKF.mjs} +4 -4
  44. package/dist/sdk/{schedule-tool-handler-OQF57URO.mjs → schedule-tool-handler-ULNF7TZW.mjs} +7 -7
  45. package/dist/sdk/{schedule-tool-handler-PJVKWSYX.mjs → schedule-tool-handler-VFES42DD.mjs} +6 -6
  46. package/dist/sdk/sdk.d.mts +56 -38
  47. package/dist/sdk/sdk.d.ts +56 -38
  48. package/dist/sdk/sdk.js +2275 -388
  49. package/dist/sdk/sdk.js.map +1 -1
  50. package/dist/sdk/sdk.mjs +6 -6
  51. package/dist/sdk/{slack-frontend-LAY45IBR.mjs → slack-frontend-JS2VAZWB.mjs} +95 -4
  52. package/dist/sdk/slack-frontend-JS2VAZWB.mjs.map +1 -0
  53. package/dist/sdk/{trace-helpers-R2ETIEC2.mjs → trace-helpers-RDPXIN4S.mjs} +2 -2
  54. package/dist/sdk/validator-XTZJZZJH.mjs +134 -0
  55. package/dist/sdk/validator-XTZJZZJH.mjs.map +1 -0
  56. package/dist/sdk/{workflow-check-provider-LRWD52WN.mjs → workflow-check-provider-4NFWH6YO.mjs} +6 -6
  57. package/dist/sdk/{workflow-check-provider-N2DRFQDB.mjs → workflow-check-provider-5XS62BCJ.mjs} +7 -7
  58. package/dist/slack/adapter.d.ts +2 -0
  59. package/dist/slack/adapter.d.ts.map +1 -1
  60. package/dist/slack/client.d.ts +3 -0
  61. package/dist/slack/client.d.ts.map +1 -1
  62. package/dist/slack/markdown.d.ts +29 -0
  63. package/dist/slack/markdown.d.ts.map +1 -1
  64. package/dist/slack/socket-runner.d.ts +2 -0
  65. package/dist/slack/socket-runner.d.ts.map +1 -1
  66. package/dist/tui/chat-tui.d.ts +7 -0
  67. package/dist/tui/chat-tui.d.ts.map +1 -1
  68. package/dist/tui/components/input-bar.d.ts +11 -0
  69. package/dist/tui/components/input-bar.d.ts.map +1 -1
  70. package/dist/tui/components/trace-viewer.d.ts +25 -1
  71. package/dist/tui/components/trace-viewer.d.ts.map +1 -1
  72. package/dist/types/bot.d.ts +12 -0
  73. package/dist/types/bot.d.ts.map +1 -1
  74. package/dist/types/config.d.ts +4 -1
  75. package/dist/types/config.d.ts.map +1 -1
  76. package/package.json +3 -3
  77. package/dist/defaults/.visor.yaml +0 -420
  78. package/dist/output/traces/run-2026-02-15T19-14-20-379Z.ndjson +0 -138
  79. package/dist/output/traces/run-2026-02-15T19-15-09-410Z.ndjson +0 -1357
  80. package/dist/sdk/check-provider-registry-AAPPJ4CP.mjs +0 -28
  81. package/dist/sdk/chunk-6ZZ4DPAA.mjs.map +0 -1
  82. package/dist/sdk/chunk-EBTD2D4L.mjs +0 -739
  83. package/dist/sdk/chunk-LDFUW34H.mjs +0 -39912
  84. package/dist/sdk/chunk-LDFUW34H.mjs.map +0 -1
  85. package/dist/sdk/chunk-LQ5B4T6L.mjs.map +0 -1
  86. package/dist/sdk/chunk-MQ57AB4U.mjs.map +0 -1
  87. package/dist/sdk/chunk-N4I6ZDCJ.mjs.map +0 -1
  88. package/dist/sdk/chunk-OMFPM576.mjs.map +0 -1
  89. package/dist/sdk/chunk-RI77TA6V.mjs +0 -436
  90. package/dist/sdk/chunk-VO4N6TEL.mjs +0 -1502
  91. package/dist/sdk/chunk-VO4N6TEL.mjs.map +0 -1
  92. package/dist/sdk/failure-condition-evaluator-KN55WXRO.mjs +0 -17
  93. package/dist/sdk/github-frontend-HCOKL53D.mjs +0 -1356
  94. package/dist/sdk/github-frontend-HCOKL53D.mjs.map +0 -1
  95. package/dist/sdk/routing-OXQKETSA.mjs +0 -25
  96. package/dist/sdk/schedule-tool-handler-G353DHS6.mjs +0 -38
  97. package/dist/sdk/schedule-tool-handler-PJVKWSYX.mjs.map +0 -1
  98. package/dist/sdk/slack-frontend-LAY45IBR.mjs.map +0 -1
  99. package/dist/sdk/trace-helpers-LOPBHYYX.mjs +0 -25
  100. package/dist/sdk/trace-helpers-LOPBHYYX.mjs.map +0 -1
  101. package/dist/sdk/trace-helpers-R2ETIEC2.mjs.map +0 -1
  102. package/dist/sdk/workflow-check-provider-57KAR4Y4.mjs +0 -28
  103. package/dist/sdk/workflow-check-provider-57KAR4Y4.mjs.map +0 -1
  104. package/dist/sdk/workflow-check-provider-LRWD52WN.mjs.map +0 -1
  105. package/dist/sdk/workflow-check-provider-N2DRFQDB.mjs.map +0 -1
  106. package/dist/traces/run-2026-02-15T19-14-20-379Z.ndjson +0 -138
  107. package/dist/traces/run-2026-02-15T19-15-09-410Z.ndjson +0 -1357
  108. /package/dist/sdk/{check-provider-registry-AAPPJ4CP.mjs.map → check-provider-registry-7TCA3NSG.mjs.map} +0 -0
  109. /package/dist/sdk/{check-provider-registry-S7BMQ2FC.mjs.map → check-provider-registry-KUDVEKAC.mjs.map} +0 -0
  110. /package/dist/sdk/{chunk-EBTD2D4L.mjs.map → chunk-27RV5RR2.mjs.map} +0 -0
  111. /package/dist/sdk/{chunk-2GCSK3PD.mjs.map → chunk-BGBXLPLL.mjs.map} +0 -0
  112. /package/dist/sdk/{check-provider-registry-ZOLEYDKM.mjs.map → config-FMIIATKX.mjs.map} +0 -0
  113. /package/dist/sdk/{config-4EG7IQIU.mjs.map → failure-condition-evaluator-PNONVBXD.mjs.map} +0 -0
  114. /package/dist/sdk/{github-frontend-F4TE2JY7.mjs.map → github-frontend-WR4S3NG5.mjs.map} +0 -0
  115. /package/dist/sdk/{host-SAT6RHDX.mjs.map → host-TROSAWTE.mjs.map} +0 -0
  116. /package/dist/sdk/{host-VA3ET7N6.mjs.map → host-U7V54J2H.mjs.map} +0 -0
  117. /package/dist/sdk/{failure-condition-evaluator-GLHZZF47.mjs.map → routing-F4FOWVKF.mjs.map} +0 -0
  118. /package/dist/sdk/{failure-condition-evaluator-KN55WXRO.mjs.map → schedule-tool-handler-ULNF7TZW.mjs.map} +0 -0
  119. /package/dist/sdk/{routing-KFYQGOYU.mjs.map → schedule-tool-handler-VFES42DD.mjs.map} +0 -0
  120. /package/dist/sdk/{routing-OXQKETSA.mjs.map → trace-helpers-RDPXIN4S.mjs.map} +0 -0
  121. /package/dist/sdk/{schedule-tool-handler-G353DHS6.mjs.map → workflow-check-provider-4NFWH6YO.mjs.map} +0 -0
  122. /package/dist/sdk/{schedule-tool-handler-OQF57URO.mjs.map → workflow-check-provider-5XS62BCJ.mjs.map} +0 -0
package/dist/sdk/sdk.js CHANGED
@@ -646,7 +646,7 @@ var require_package = __commonJS({
646
646
  "package.json"(exports2, module2) {
647
647
  module2.exports = {
648
648
  name: "@probelabs/visor",
649
- version: "0.1.131",
649
+ version: "0.1.42",
650
650
  main: "dist/index.js",
651
651
  bin: {
652
652
  visor: "./dist/index.js"
@@ -731,7 +731,7 @@ var require_package = __commonJS({
731
731
  ],
732
732
  author: "Probe Labs",
733
733
  license: "MIT",
734
- description: "AI-powered code review tool for GitHub Pull Requests - CLI and GitHub Action",
734
+ description: "AI workflow engine for code review, assistants, and automation \u2014 orchestrate checks, MCP tools, and AI providers with YAML-driven pipelines",
735
735
  repository: {
736
736
  type: "git",
737
737
  url: "git+https://github.com/probelabs/visor.git"
@@ -748,7 +748,7 @@ var require_package = __commonJS({
748
748
  "@octokit/auth-app": "^8.1.0",
749
749
  "@octokit/core": "^7.0.3",
750
750
  "@octokit/rest": "^22.0.0",
751
- "@probelabs/probe": "^0.6.0-rc232",
751
+ "@probelabs/probe": "^0.6.0-rc245",
752
752
  "@types/commander": "^2.12.0",
753
753
  "@types/uuid": "^10.0.0",
754
754
  ajv: "^8.17.1",
@@ -857,11 +857,11 @@ function getTracer() {
857
857
  }
858
858
  async function withActiveSpan(name, attrs, fn) {
859
859
  const tracer = getTracer();
860
- return await new Promise((resolve11, reject) => {
860
+ return await new Promise((resolve15, reject) => {
861
861
  const callback = async (span) => {
862
862
  try {
863
863
  const res = await fn(span);
864
- resolve11(res);
864
+ resolve15(res);
865
865
  } catch (err) {
866
866
  try {
867
867
  if (err instanceof Error) span.recordException(err);
@@ -938,19 +938,19 @@ function __getOrCreateNdjsonPath() {
938
938
  try {
939
939
  if (process.env.VISOR_TELEMETRY_SINK && process.env.VISOR_TELEMETRY_SINK !== "file")
940
940
  return null;
941
- const path25 = require("path");
942
- const fs21 = require("fs");
941
+ const path29 = require("path");
942
+ const fs25 = require("fs");
943
943
  if (process.env.VISOR_FALLBACK_TRACE_FILE) {
944
944
  __ndjsonPath = process.env.VISOR_FALLBACK_TRACE_FILE;
945
- const dir = path25.dirname(__ndjsonPath);
946
- if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
945
+ const dir = path29.dirname(__ndjsonPath);
946
+ if (!fs25.existsSync(dir)) fs25.mkdirSync(dir, { recursive: true });
947
947
  return __ndjsonPath;
948
948
  }
949
- const outDir = process.env.VISOR_TRACE_DIR || path25.join(process.cwd(), "output", "traces");
950
- if (!fs21.existsSync(outDir)) fs21.mkdirSync(outDir, { recursive: true });
949
+ const outDir = process.env.VISOR_TRACE_DIR || path29.join(process.cwd(), "output", "traces");
950
+ if (!fs25.existsSync(outDir)) fs25.mkdirSync(outDir, { recursive: true });
951
951
  if (!__ndjsonPath) {
952
952
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
953
- __ndjsonPath = path25.join(outDir, `${ts}.ndjson`);
953
+ __ndjsonPath = path29.join(outDir, `${ts}.ndjson`);
954
954
  }
955
955
  return __ndjsonPath;
956
956
  } catch {
@@ -959,11 +959,11 @@ function __getOrCreateNdjsonPath() {
959
959
  }
960
960
  function _appendRunMarker() {
961
961
  try {
962
- const fs21 = require("fs");
962
+ const fs25 = require("fs");
963
963
  const p = __getOrCreateNdjsonPath();
964
964
  if (!p) return;
965
965
  const line = { name: "visor.run", attributes: { started: true } };
966
- fs21.appendFileSync(p, JSON.stringify(line) + "\n", "utf8");
966
+ fs25.appendFileSync(p, JSON.stringify(line) + "\n", "utf8");
967
967
  } catch {
968
968
  }
969
969
  }
@@ -3156,7 +3156,7 @@ var init_failure_condition_evaluator = __esm({
3156
3156
  */
3157
3157
  evaluateExpression(condition, context2) {
3158
3158
  try {
3159
- const normalize4 = (expr) => {
3159
+ const normalize8 = (expr) => {
3160
3160
  const trimmed = expr.trim();
3161
3161
  if (!/[\n;]/.test(trimmed)) return trimmed;
3162
3162
  const parts = trimmed.split(/[\n;]+/).map((s) => s.trim()).filter((s) => s.length > 0 && !s.startsWith("//"));
@@ -3314,7 +3314,7 @@ var init_failure_condition_evaluator = __esm({
3314
3314
  try {
3315
3315
  exec2 = this.sandbox.compile(`return (${raw});`);
3316
3316
  } catch {
3317
- const normalizedExpr = normalize4(condition);
3317
+ const normalizedExpr = normalize8(condition);
3318
3318
  exec2 = this.sandbox.compile(`return (${normalizedExpr});`);
3319
3319
  }
3320
3320
  const result = exec2(scope).run();
@@ -3697,9 +3697,9 @@ function configureLiquidWithExtensions(liquid) {
3697
3697
  });
3698
3698
  liquid.registerFilter("get", (obj, pathExpr) => {
3699
3699
  if (obj == null) return void 0;
3700
- const path25 = typeof pathExpr === "string" ? pathExpr : String(pathExpr || "");
3701
- if (!path25) return obj;
3702
- const parts = path25.split(".");
3700
+ const path29 = typeof pathExpr === "string" ? pathExpr : String(pathExpr || "");
3701
+ if (!path29) return obj;
3702
+ const parts = path29.split(".");
3703
3703
  let cur = obj;
3704
3704
  for (const p of parts) {
3705
3705
  if (cur == null) return void 0;
@@ -3818,9 +3818,9 @@ function configureLiquidWithExtensions(liquid) {
3818
3818
  }
3819
3819
  }
3820
3820
  const defaultRole = typeof rolesCfg.default === "string" && rolesCfg.default.trim() ? rolesCfg.default.trim() : void 0;
3821
- const getNested = (obj, path25) => {
3822
- if (!obj || !path25) return void 0;
3823
- const parts = path25.split(".");
3821
+ const getNested = (obj, path29) => {
3822
+ if (!obj || !path29) return void 0;
3823
+ const parts = path29.split(".");
3824
3824
  let cur = obj;
3825
3825
  for (const p of parts) {
3826
3826
  if (cur == null) return void 0;
@@ -6372,8 +6372,8 @@ var init_dependency_gating = __esm({
6372
6372
  async function renderTemplateContent(checkId, checkConfig, reviewSummary) {
6373
6373
  try {
6374
6374
  const { createExtendedLiquid: createExtendedLiquid2 } = await Promise.resolve().then(() => (init_liquid_extensions(), liquid_extensions_exports));
6375
- const fs21 = await import("fs/promises");
6376
- const path25 = await import("path");
6375
+ const fs25 = await import("fs/promises");
6376
+ const path29 = await import("path");
6377
6377
  const schemaRaw = checkConfig.schema || "plain";
6378
6378
  const schema = typeof schemaRaw === "string" ? schemaRaw : "code-review";
6379
6379
  let templateContent;
@@ -6381,24 +6381,24 @@ async function renderTemplateContent(checkId, checkConfig, reviewSummary) {
6381
6381
  templateContent = String(checkConfig.template.content);
6382
6382
  } else if (checkConfig.template && checkConfig.template.file) {
6383
6383
  const file = String(checkConfig.template.file);
6384
- const resolved = path25.resolve(process.cwd(), file);
6385
- templateContent = await fs21.readFile(resolved, "utf-8");
6384
+ const resolved = path29.resolve(process.cwd(), file);
6385
+ templateContent = await fs25.readFile(resolved, "utf-8");
6386
6386
  } else if (schema && schema !== "plain") {
6387
6387
  const sanitized = String(schema).replace(/[^a-zA-Z0-9-]/g, "");
6388
6388
  if (sanitized) {
6389
6389
  const candidatePaths = [
6390
- path25.join(__dirname, "output", sanitized, "template.liquid"),
6390
+ path29.join(__dirname, "output", sanitized, "template.liquid"),
6391
6391
  // bundled: dist/output/
6392
- path25.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
6392
+ path29.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
6393
6393
  // source: output/
6394
- path25.join(process.cwd(), "output", sanitized, "template.liquid"),
6394
+ path29.join(process.cwd(), "output", sanitized, "template.liquid"),
6395
6395
  // fallback: cwd/output/
6396
- path25.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
6396
+ path29.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
6397
6397
  // fallback: cwd/dist/output/
6398
6398
  ];
6399
6399
  for (const p of candidatePaths) {
6400
6400
  try {
6401
- templateContent = await fs21.readFile(p, "utf-8");
6401
+ templateContent = await fs25.readFile(p, "utf-8");
6402
6402
  if (templateContent) break;
6403
6403
  } catch {
6404
6404
  }
@@ -6803,7 +6803,7 @@ async function processDiffWithOutline(diffContent) {
6803
6803
  }
6804
6804
  try {
6805
6805
  const originalProbePath = process.env.PROBE_PATH;
6806
- const fs21 = require("fs");
6806
+ const fs25 = require("fs");
6807
6807
  const possiblePaths = [
6808
6808
  // Relative to current working directory (most common in production)
6809
6809
  path6.join(process.cwd(), "node_modules/@probelabs/probe/bin/probe-binary"),
@@ -6814,7 +6814,7 @@ async function processDiffWithOutline(diffContent) {
6814
6814
  ];
6815
6815
  let probeBinaryPath;
6816
6816
  for (const candidatePath of possiblePaths) {
6817
- if (fs21.existsSync(candidatePath)) {
6817
+ if (fs25.existsSync(candidatePath)) {
6818
6818
  probeBinaryPath = candidatePath;
6819
6819
  break;
6820
6820
  }
@@ -7739,7 +7739,7 @@ ${this.escapeXml(processedFallbackDiff)}
7739
7739
  <user>${this.escapeXml(String(m.user || ""))}</user>
7740
7740
  <text>${this.escapeXml(String(m.text || ""))}</text>
7741
7741
  <timestamp>${this.escapeXml(String(m.timestamp || ""))}</timestamp>
7742
- <origin>${this.escapeXml(String(m.origin || ""))}</origin>
7742
+ <origin>${this.escapeXml(String(m.origin || ""))}</origin>${this.formatFilesXml(m.files)}
7743
7743
  </message>`;
7744
7744
  }
7745
7745
  xml += `
@@ -7751,7 +7751,7 @@ ${this.escapeXml(processedFallbackDiff)}
7751
7751
  <user>${this.escapeXml(String(current.user || ""))}</user>
7752
7752
  <text>${this.escapeXml(String(current.text || ""))}</text>
7753
7753
  <timestamp>${this.escapeXml(String(current.timestamp || ""))}</timestamp>
7754
- <origin>${this.escapeXml(String(current.origin || ""))}</origin>
7754
+ <origin>${this.escapeXml(String(current.origin || ""))}</origin>${this.formatFilesXml(current.files)}
7755
7755
  </current>
7756
7756
  </slack_context>`;
7757
7757
  return xml;
@@ -7759,6 +7759,25 @@ ${this.escapeXml(processedFallbackDiff)}
7759
7759
  return "";
7760
7760
  }
7761
7761
  }
7762
+ /** Render file attachment metadata as XML fragment for a message. */
7763
+ formatFilesXml(files) {
7764
+ if (!Array.isArray(files) || files.length === 0) return "";
7765
+ let xml = `
7766
+ <files>`;
7767
+ for (const f of files) {
7768
+ xml += `
7769
+ <file>
7770
+ <name>${this.escapeXml(String(f.name || ""))}</name>
7771
+ <mimetype>${this.escapeXml(String(f.mimetype || ""))}</mimetype>
7772
+ <filetype>${this.escapeXml(String(f.filetype || ""))}</filetype>
7773
+ <url>${this.escapeXml(String(f.url_private || f.permalink || ""))}</url>
7774
+ <size>${f.size || 0}</size>
7775
+ </file>`;
7776
+ }
7777
+ xml += `
7778
+ </files>`;
7779
+ return xml;
7780
+ }
7762
7781
  /**
7763
7782
  * Build a normalized ConversationContext for GitHub (PR/issue + comments)
7764
7783
  * using the same contract as Slack's ConversationContext. This is exposed
@@ -7881,8 +7900,8 @@ ${schemaString}`);
7881
7900
  }
7882
7901
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
7883
7902
  try {
7884
- const fs21 = require("fs");
7885
- const path25 = require("path");
7903
+ const fs25 = require("fs");
7904
+ const path29 = require("path");
7886
7905
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7887
7906
  const provider = this.config.provider || "auto";
7888
7907
  const model = this.config.model || "default";
@@ -7996,20 +8015,20 @@ ${"=".repeat(60)}
7996
8015
  `;
7997
8016
  readableVersion += `${"=".repeat(60)}
7998
8017
  `;
7999
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path25.join(process.cwd(), "debug-artifacts");
8000
- if (!fs21.existsSync(debugArtifactsDir)) {
8001
- fs21.mkdirSync(debugArtifactsDir, { recursive: true });
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 });
8002
8021
  }
8003
- const debugFile = path25.join(
8022
+ const debugFile = path29.join(
8004
8023
  debugArtifactsDir,
8005
8024
  `prompt-${_checkName || "unknown"}-${timestamp}.json`
8006
8025
  );
8007
- fs21.writeFileSync(debugFile, debugJson, "utf-8");
8008
- const readableFile = path25.join(
8026
+ fs25.writeFileSync(debugFile, debugJson, "utf-8");
8027
+ const readableFile = path29.join(
8009
8028
  debugArtifactsDir,
8010
8029
  `prompt-${_checkName || "unknown"}-${timestamp}.txt`
8011
8030
  );
8012
- fs21.writeFileSync(readableFile, readableVersion, "utf-8");
8031
+ fs25.writeFileSync(readableFile, readableVersion, "utf-8");
8013
8032
  log(`
8014
8033
  \u{1F4BE} Full debug info saved to:`);
8015
8034
  log(` JSON: ${debugFile}`);
@@ -8042,8 +8061,8 @@ ${"=".repeat(60)}
8042
8061
  log(`\u{1F4E4} Response length: ${response.length} characters`);
8043
8062
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8044
8063
  try {
8045
- const fs21 = require("fs");
8046
- const path25 = require("path");
8064
+ const fs25 = require("fs");
8065
+ const path29 = require("path");
8047
8066
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8048
8067
  const agentAny2 = agent;
8049
8068
  let fullHistory = [];
@@ -8054,8 +8073,8 @@ ${"=".repeat(60)}
8054
8073
  } else if (agentAny2._messages) {
8055
8074
  fullHistory = agentAny2._messages;
8056
8075
  }
8057
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path25.join(process.cwd(), "debug-artifacts");
8058
- const sessionBase = path25.join(
8076
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path29.join(process.cwd(), "debug-artifacts");
8077
+ const sessionBase = path29.join(
8059
8078
  debugArtifactsDir,
8060
8079
  `session-${_checkName || "unknown"}-${timestamp}`
8061
8080
  );
@@ -8067,7 +8086,7 @@ ${"=".repeat(60)}
8067
8086
  schema: effectiveSchema,
8068
8087
  totalMessages: fullHistory.length
8069
8088
  };
8070
- fs21.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
8089
+ fs25.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
8071
8090
  let readable = `=============================================================
8072
8091
  `;
8073
8092
  readable += `COMPLETE AI SESSION HISTORY (AFTER RESPONSE)
@@ -8094,7 +8113,7 @@ ${"=".repeat(60)}
8094
8113
  `;
8095
8114
  readable += content + "\n";
8096
8115
  });
8097
- fs21.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
8116
+ fs25.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
8098
8117
  log(`\u{1F4BE} Complete session history saved:`);
8099
8118
  log(` - Contains ALL ${fullHistory.length} messages (prompts + responses)`);
8100
8119
  } catch (error) {
@@ -8103,11 +8122,11 @@ ${"=".repeat(60)}
8103
8122
  }
8104
8123
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8105
8124
  try {
8106
- const fs21 = require("fs");
8107
- const path25 = require("path");
8125
+ const fs25 = require("fs");
8126
+ const path29 = require("path");
8108
8127
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8109
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path25.join(process.cwd(), "debug-artifacts");
8110
- const responseFile = path25.join(
8128
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path29.join(process.cwd(), "debug-artifacts");
8129
+ const responseFile = path29.join(
8111
8130
  debugArtifactsDir,
8112
8131
  `response-${_checkName || "unknown"}-${timestamp}.txt`
8113
8132
  );
@@ -8140,7 +8159,7 @@ ${"=".repeat(60)}
8140
8159
  `;
8141
8160
  responseContent += `${"=".repeat(60)}
8142
8161
  `;
8143
- fs21.writeFileSync(responseFile, responseContent, "utf-8");
8162
+ fs25.writeFileSync(responseFile, responseContent, "utf-8");
8144
8163
  log(`\u{1F4BE} Response saved to: ${responseFile}`);
8145
8164
  } catch (error) {
8146
8165
  log(`\u26A0\uFE0F Could not save response file: ${error}`);
@@ -8156,9 +8175,9 @@ ${"=".repeat(60)}
8156
8175
  await agentAny._telemetryConfig.shutdown();
8157
8176
  log(`\u{1F4CA} OpenTelemetry trace saved to: ${agentAny._traceFilePath}`);
8158
8177
  if (process.env.GITHUB_ACTIONS) {
8159
- const fs21 = require("fs");
8160
- if (fs21.existsSync(agentAny._traceFilePath)) {
8161
- const stats = fs21.statSync(agentAny._traceFilePath);
8178
+ const fs25 = require("fs");
8179
+ if (fs25.existsSync(agentAny._traceFilePath)) {
8180
+ const stats = fs25.statSync(agentAny._traceFilePath);
8162
8181
  console.log(
8163
8182
  `::notice title=AI Trace Saved::${agentAny._traceFilePath} (${stats.size} bytes)`
8164
8183
  );
@@ -8264,6 +8283,9 @@ ${"=".repeat(60)}
8264
8283
  if (this.config.enableTasks !== void 0) {
8265
8284
  options.enableTasks = this.config.enableTasks;
8266
8285
  }
8286
+ if (this.config.enableExecutePlan !== void 0) {
8287
+ options.enableExecutePlan = this.config.enableExecutePlan;
8288
+ }
8267
8289
  if (this.config.retry) {
8268
8290
  options.retry = this.config.retry;
8269
8291
  }
@@ -8356,9 +8378,9 @@ ${schemaString}`);
8356
8378
  const model = this.config.model || "default";
8357
8379
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8358
8380
  try {
8359
- const fs21 = require("fs");
8360
- const path25 = require("path");
8361
- const os2 = require("os");
8381
+ const fs25 = require("fs");
8382
+ const path29 = require("path");
8383
+ const os3 = require("os");
8362
8384
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8363
8385
  const debugData = {
8364
8386
  timestamp,
@@ -8430,19 +8452,19 @@ ${"=".repeat(60)}
8430
8452
  `;
8431
8453
  readableVersion += `${"=".repeat(60)}
8432
8454
  `;
8433
- const tempDir = os2.tmpdir();
8434
- const promptFile = path25.join(tempDir, `visor-prompt-${timestamp}.txt`);
8435
- fs21.writeFileSync(promptFile, prompt, "utf-8");
8455
+ const tempDir = os3.tmpdir();
8456
+ const promptFile = path29.join(tempDir, `visor-prompt-${timestamp}.txt`);
8457
+ fs25.writeFileSync(promptFile, prompt, "utf-8");
8436
8458
  log(`
8437
8459
  \u{1F4BE} Prompt saved to: ${promptFile}`);
8438
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path25.join(process.cwd(), "debug-artifacts");
8460
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path29.join(process.cwd(), "debug-artifacts");
8439
8461
  try {
8440
- const base = path25.join(
8462
+ const base = path29.join(
8441
8463
  debugArtifactsDir,
8442
8464
  `prompt-${_checkName || "unknown"}-${timestamp}`
8443
8465
  );
8444
- fs21.writeFileSync(base + ".json", debugJson, "utf-8");
8445
- fs21.writeFileSync(base + ".summary.txt", readableVersion, "utf-8");
8466
+ fs25.writeFileSync(base + ".json", debugJson, "utf-8");
8467
+ fs25.writeFileSync(base + ".summary.txt", readableVersion, "utf-8");
8446
8468
  log(`
8447
8469
  \u{1F4BE} Full debug info saved to directory: ${debugArtifactsDir}`);
8448
8470
  } catch {
@@ -8487,8 +8509,8 @@ $ ${cliCommand}
8487
8509
  log(`\u{1F4E4} Response length: ${response.length} characters`);
8488
8510
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8489
8511
  try {
8490
- const fs21 = require("fs");
8491
- const path25 = require("path");
8512
+ const fs25 = require("fs");
8513
+ const path29 = require("path");
8492
8514
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8493
8515
  const agentAny = agent;
8494
8516
  let fullHistory = [];
@@ -8499,8 +8521,8 @@ $ ${cliCommand}
8499
8521
  } else if (agentAny._messages) {
8500
8522
  fullHistory = agentAny._messages;
8501
8523
  }
8502
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path25.join(process.cwd(), "debug-artifacts");
8503
- const sessionBase = path25.join(
8524
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path29.join(process.cwd(), "debug-artifacts");
8525
+ const sessionBase = path29.join(
8504
8526
  debugArtifactsDir,
8505
8527
  `session-${_checkName || "unknown"}-${timestamp}`
8506
8528
  );
@@ -8512,7 +8534,7 @@ $ ${cliCommand}
8512
8534
  schema: effectiveSchema,
8513
8535
  totalMessages: fullHistory.length
8514
8536
  };
8515
- fs21.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
8537
+ fs25.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
8516
8538
  let readable = `=============================================================
8517
8539
  `;
8518
8540
  readable += `COMPLETE AI SESSION HISTORY (AFTER RESPONSE)
@@ -8539,7 +8561,7 @@ ${"=".repeat(60)}
8539
8561
  `;
8540
8562
  readable += content + "\n";
8541
8563
  });
8542
- fs21.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
8564
+ fs25.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
8543
8565
  log(`\u{1F4BE} Complete session history saved:`);
8544
8566
  log(` - Contains ALL ${fullHistory.length} messages (prompts + responses)`);
8545
8567
  } catch (error) {
@@ -8548,11 +8570,11 @@ ${"=".repeat(60)}
8548
8570
  }
8549
8571
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8550
8572
  try {
8551
- const fs21 = require("fs");
8552
- const path25 = require("path");
8573
+ const fs25 = require("fs");
8574
+ const path29 = require("path");
8553
8575
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8554
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path25.join(process.cwd(), "debug-artifacts");
8555
- const responseFile = path25.join(
8576
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path29.join(process.cwd(), "debug-artifacts");
8577
+ const responseFile = path29.join(
8556
8578
  debugArtifactsDir,
8557
8579
  `response-${_checkName || "unknown"}-${timestamp}.txt`
8558
8580
  );
@@ -8585,7 +8607,7 @@ ${"=".repeat(60)}
8585
8607
  `;
8586
8608
  responseContent += `${"=".repeat(60)}
8587
8609
  `;
8588
- fs21.writeFileSync(responseFile, responseContent, "utf-8");
8610
+ fs25.writeFileSync(responseFile, responseContent, "utf-8");
8589
8611
  log(`\u{1F4BE} Response saved to: ${responseFile}`);
8590
8612
  } catch (error) {
8591
8613
  log(`\u26A0\uFE0F Could not save response file: ${error}`);
@@ -8603,9 +8625,9 @@ ${"=".repeat(60)}
8603
8625
  await telemetry.shutdown();
8604
8626
  log(`\u{1F4CA} OpenTelemetry trace saved to: ${traceFilePath}`);
8605
8627
  if (process.env.GITHUB_ACTIONS) {
8606
- const fs21 = require("fs");
8607
- if (fs21.existsSync(traceFilePath)) {
8608
- const stats = fs21.statSync(traceFilePath);
8628
+ const fs25 = require("fs");
8629
+ if (fs25.existsSync(traceFilePath)) {
8630
+ const stats = fs25.statSync(traceFilePath);
8609
8631
  console.log(
8610
8632
  `::notice title=AI Trace Saved::OpenTelemetry trace file size: ${stats.size} bytes`
8611
8633
  );
@@ -8643,8 +8665,8 @@ ${"=".repeat(60)}
8643
8665
  * Load schema content from schema files or inline definitions
8644
8666
  */
8645
8667
  async loadSchemaContent(schema) {
8646
- const fs21 = require("fs").promises;
8647
- const path25 = require("path");
8668
+ const fs25 = require("fs").promises;
8669
+ const path29 = require("path");
8648
8670
  if (typeof schema === "object" && schema !== null) {
8649
8671
  log("\u{1F4CB} Using inline schema object from configuration");
8650
8672
  return JSON.stringify(schema);
@@ -8657,14 +8679,14 @@ ${"=".repeat(60)}
8657
8679
  }
8658
8680
  } catch {
8659
8681
  }
8660
- if ((schema.startsWith("./") || schema.includes(".json")) && !path25.isAbsolute(schema)) {
8682
+ if ((schema.startsWith("./") || schema.includes(".json")) && !path29.isAbsolute(schema)) {
8661
8683
  if (schema.includes("..") || schema.includes("\0")) {
8662
8684
  throw new Error("Invalid schema path: path traversal not allowed");
8663
8685
  }
8664
8686
  try {
8665
- const schemaPath = path25.resolve(process.cwd(), schema);
8687
+ const schemaPath = path29.resolve(process.cwd(), schema);
8666
8688
  log(`\u{1F4CB} Loading custom schema from file: ${schemaPath}`);
8667
- const schemaContent = await fs21.readFile(schemaPath, "utf-8");
8689
+ const schemaContent = await fs25.readFile(schemaPath, "utf-8");
8668
8690
  return schemaContent.trim();
8669
8691
  } catch (error) {
8670
8692
  throw new Error(
@@ -8678,22 +8700,22 @@ ${"=".repeat(60)}
8678
8700
  }
8679
8701
  const candidatePaths = [
8680
8702
  // GitHub Action bundle location
8681
- path25.join(__dirname, "output", sanitizedSchemaName, "schema.json"),
8703
+ path29.join(__dirname, "output", sanitizedSchemaName, "schema.json"),
8682
8704
  // Historical fallback when src/output was inadvertently bundled as output1/
8683
- path25.join(__dirname, "output1", sanitizedSchemaName, "schema.json"),
8705
+ path29.join(__dirname, "output1", sanitizedSchemaName, "schema.json"),
8684
8706
  // Local dev (repo root)
8685
- path25.join(process.cwd(), "output", sanitizedSchemaName, "schema.json")
8707
+ path29.join(process.cwd(), "output", sanitizedSchemaName, "schema.json")
8686
8708
  ];
8687
8709
  for (const schemaPath of candidatePaths) {
8688
8710
  try {
8689
- const schemaContent = await fs21.readFile(schemaPath, "utf-8");
8711
+ const schemaContent = await fs25.readFile(schemaPath, "utf-8");
8690
8712
  return schemaContent.trim();
8691
8713
  } catch {
8692
8714
  }
8693
8715
  }
8694
- const distPath = path25.join(__dirname, "output", sanitizedSchemaName, "schema.json");
8695
- const distAltPath = path25.join(__dirname, "output1", sanitizedSchemaName, "schema.json");
8696
- const cwdPath = path25.join(process.cwd(), "output", sanitizedSchemaName, "schema.json");
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");
8697
8719
  throw new Error(
8698
8720
  `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.`
8699
8721
  );
@@ -8711,7 +8733,22 @@ ${"=".repeat(60)}
8711
8733
  log("\u{1F4CB} Full response preview:", response);
8712
8734
  }
8713
8735
  try {
8714
- let reviewData;
8736
+ let reviewData = void 0;
8737
+ const RAW_OUTPUT_RE = /\n<<<RAW_OUTPUT>>>\n([\s\S]*?)\n<<<END_RAW_OUTPUT>>>/g;
8738
+ const rawOutputBlocks = [];
8739
+ let responseForParsing = response;
8740
+ {
8741
+ let rawMatch;
8742
+ while ((rawMatch = RAW_OUTPUT_RE.exec(response)) !== null) {
8743
+ rawOutputBlocks.push(rawMatch[1]);
8744
+ }
8745
+ if (rawOutputBlocks.length > 0) {
8746
+ responseForParsing = response.replace(RAW_OUTPUT_RE, "");
8747
+ log(
8748
+ `\u{1F4E6} Extracted ${rawOutputBlocks.length} RAW_OUTPUT blocks (${rawOutputBlocks.reduce((s, b) => s + b.length, 0)} chars) from response`
8749
+ );
8750
+ }
8751
+ }
8715
8752
  if (_schema === "plain" || !_schema) {
8716
8753
  log(
8717
8754
  `\u{1F4CB} ${_schema === "plain" ? "Plain" : "No"} schema detected - treating raw response as text output`
@@ -8728,7 +8765,7 @@ ${"=".repeat(60)}
8728
8765
  }
8729
8766
  {
8730
8767
  log("\u{1F50D} Extracting JSON from AI response...");
8731
- const sanitizedResponse = response.replace(/^\uFEFF/, "").replace(/[\u200B-\u200D\uFEFF\u00A0]/g, "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").trim();
8768
+ const sanitizedResponse = responseForParsing.replace(/^\uFEFF/, "").replace(/[\u200B-\u200D\uFEFF\u00A0]/g, "").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").trim();
8732
8769
  try {
8733
8770
  reviewData = JSON.parse(sanitizedResponse);
8734
8771
  log("\u2705 Successfully parsed direct JSON response");
@@ -8736,22 +8773,48 @@ ${"=".repeat(60)}
8736
8773
  } catch (parseErr) {
8737
8774
  const errMsg = parseErr instanceof Error ? parseErr.message : String(parseErr);
8738
8775
  log(`\u{1F50D} Direct JSON parsing failed: ${errMsg}`);
8739
- if (response.toLowerCase().includes("i cannot") || response.toLowerCase().includes("unable to")) {
8740
- console.error("\u{1F6AB} AI refused to analyze - returning refusal as output");
8741
- const trimmed2 = response.trim();
8776
+ let recovered = false;
8777
+ const trailingMatch = errMsg.match(/after JSON at position (\d+)/);
8778
+ if (trailingMatch) {
8779
+ const pos = parseInt(trailingMatch[1], 10);
8780
+ try {
8781
+ reviewData = JSON.parse(sanitizedResponse.substring(0, pos));
8782
+ const trailing = sanitizedResponse.substring(pos).trim();
8783
+ if (trailing && reviewData && typeof reviewData === "object" && typeof reviewData.text === "string") {
8784
+ reviewData.text = reviewData.text + "\n\n" + trailing;
8785
+ log(
8786
+ `\u2705 Recovered JSON and appended ${trailing.length} chars of trailing content to text field`
8787
+ );
8788
+ } else {
8789
+ log(`\u2705 Recovered JSON by trimming trailing content at position ${pos}`);
8790
+ }
8791
+ if (debugInfo) debugInfo.jsonParseSuccess = true;
8792
+ recovered = true;
8793
+ } catch {
8794
+ }
8795
+ }
8796
+ if (!recovered) {
8797
+ if (response.toLowerCase().includes("i cannot") || response.toLowerCase().includes("unable to")) {
8798
+ console.error("\u{1F6AB} AI refused to analyze - returning refusal as output");
8799
+ const trimmed2 = responseForParsing.trim();
8800
+ const out = trimmed2 ? { text: trimmed2 } : {};
8801
+ if (rawOutputBlocks.length > 0) out._rawOutput = rawOutputBlocks.join("\n\n");
8802
+ return {
8803
+ issues: [],
8804
+ output: out,
8805
+ debug: debugInfo
8806
+ };
8807
+ }
8808
+ log("\u{1F527} Treating response as plain text (no JSON extraction)");
8809
+ const trimmed = responseForParsing.trim();
8810
+ const fallbackOut = { text: trimmed };
8811
+ if (rawOutputBlocks.length > 0) fallbackOut._rawOutput = rawOutputBlocks.join("\n\n");
8742
8812
  return {
8743
8813
  issues: [],
8744
- output: trimmed2 ? { text: trimmed2 } : {},
8814
+ output: fallbackOut,
8745
8815
  debug: debugInfo
8746
8816
  };
8747
8817
  }
8748
- log("\u{1F527} Treating response as plain text (no JSON extraction)");
8749
- const trimmed = response.trim();
8750
- return {
8751
- issues: [],
8752
- output: { text: trimmed },
8753
- debug: debugInfo
8754
- };
8755
8818
  }
8756
8819
  }
8757
8820
  const looksLikeTextOutput = reviewData && typeof reviewData === "object" && typeof reviewData.text === "string" && String(reviewData.text).trim().length > 0;
@@ -8799,6 +8862,9 @@ ${"=".repeat(60)}
8799
8862
  out.text = fallbackText;
8800
8863
  }
8801
8864
  }
8865
+ if (rawOutputBlocks.length > 0) {
8866
+ out._rawOutput = rawOutputBlocks.join("\n\n");
8867
+ }
8802
8868
  const result2 = {
8803
8869
  // Keep issues empty for custom-schema rendering; consumers read from output.*
8804
8870
  issues: [],
@@ -8884,7 +8950,7 @@ ${"=".repeat(60)}
8884
8950
  * Generate mock response for testing
8885
8951
  */
8886
8952
  async generateMockResponse(_prompt, _checkName, _schema) {
8887
- await new Promise((resolve11) => setTimeout(resolve11, 500));
8953
+ await new Promise((resolve15) => setTimeout(resolve15, 500));
8888
8954
  const name = (_checkName || "").toLowerCase();
8889
8955
  if (name.includes("extract-facts")) {
8890
8956
  const arr = Array.from({ length: 6 }, (_, i) => ({
@@ -9245,7 +9311,7 @@ var init_command_executor = __esm({
9245
9311
  * Execute command with stdin input
9246
9312
  */
9247
9313
  executeWithStdin(command, options) {
9248
- return new Promise((resolve11, reject) => {
9314
+ return new Promise((resolve15, reject) => {
9249
9315
  const childProcess = (0, import_child_process.exec)(
9250
9316
  command,
9251
9317
  {
@@ -9257,7 +9323,7 @@ var init_command_executor = __esm({
9257
9323
  if (error && error.killed && (error.code === "ETIMEDOUT" || error.signal === "SIGTERM")) {
9258
9324
  reject(new Error(`Command timed out after ${options.timeout || 3e4}ms`));
9259
9325
  } else {
9260
- resolve11({
9326
+ resolve15({
9261
9327
  stdout: stdout || "",
9262
9328
  stderr: stderr || "",
9263
9329
  exitCode: error ? error.code || 1 : 0
@@ -11112,6 +11178,10 @@ var init_config_schema = __esm({
11112
11178
  type: "number",
11113
11179
  description: "Maximum number of checks to run in parallel (default: 3)"
11114
11180
  },
11181
+ max_ai_concurrency: {
11182
+ type: "number",
11183
+ description: "Maximum total concurrent AI API calls across all checks (default: unlimited). When set, creates a shared concurrency limiter that gates every LLM request across all ProbeAgent instances in this run."
11184
+ },
11115
11185
  fail_fast: {
11116
11186
  type: "boolean",
11117
11187
  description: "Stop execution when any check fails (default: false)"
@@ -11158,6 +11228,18 @@ var init_config_schema = __esm({
11158
11228
  $ref: "#/definitions/WorkspaceConfig",
11159
11229
  description: "Workspace isolation configuration for sandboxed execution"
11160
11230
  },
11231
+ sandbox: {
11232
+ type: "string",
11233
+ description: "Workspace-level default sandbox name (all checks use this unless overridden)"
11234
+ },
11235
+ sandboxes: {
11236
+ $ref: "#/definitions/Record%3Cstring%2CSandboxConfig%3E",
11237
+ description: "Named sandbox environment definitions"
11238
+ },
11239
+ sandbox_defaults: {
11240
+ $ref: "#/definitions/SandboxDefaults",
11241
+ description: "Workspace-level sandbox defaults (env allowlist, etc.)"
11242
+ },
11161
11243
  slack: {
11162
11244
  $ref: "#/definitions/SlackConfig",
11163
11245
  description: "Slack configuration"
@@ -11168,7 +11250,7 @@ var init_config_schema = __esm({
11168
11250
  },
11169
11251
  policy: {
11170
11252
  $ref: "#/definitions/PolicyConfig",
11171
- description: "Enterprise policy engine configuration (EE feature)"
11253
+ description: "Enterprise policy engine configuration"
11172
11254
  }
11173
11255
  },
11174
11256
  required: ["version"],
@@ -11471,7 +11553,7 @@ var init_config_schema = __esm({
11471
11553
  },
11472
11554
  ai_bash_config_js: {
11473
11555
  type: "string",
11474
- description: "JavaScript expression to dynamically compute bash configuration for this AI check. Expression has access to: outputs, inputs, pr, files, env, memory. Must return a BashConfig object with optional allow/deny string arrays.\n\nExample: ``` return outputs['build-config']?.bash_config ?? {}; ```"
11556
+ description: "JavaScript expression to dynamically compute bash configuration for this AI check. Expression has access to: outputs, inputs, pr, files, env, memory Must return a BashConfig object with allow/deny arrays.\n\nExample: ``` return outputs['build-config']?.bash_config ?? {}; ```"
11475
11557
  },
11476
11558
  claude_code: {
11477
11559
  $ref: "#/definitions/ClaudeCodeConfig",
@@ -11737,7 +11819,7 @@ var init_config_schema = __esm({
11737
11819
  description: "Arguments/inputs for the workflow"
11738
11820
  },
11739
11821
  overrides: {
11740
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-12605-26099-src_types_config.ts-0-46407%3E%3E",
11822
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-27516-src_types_config.ts-0-51381%3E%3E",
11741
11823
  description: "Override specific step configurations in the workflow"
11742
11824
  },
11743
11825
  output_mapping: {
@@ -11753,7 +11835,7 @@ var init_config_schema = __esm({
11753
11835
  description: "Config file path - alternative to workflow ID (loads a Visor config file as workflow)"
11754
11836
  },
11755
11837
  workflow_overrides: {
11756
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-12605-26099-src_types_config.ts-0-46407%3E%3E",
11838
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-27516-src_types_config.ts-0-51381%3E%3E",
11757
11839
  description: "Alias for overrides - workflow step overrides (backward compatibility)"
11758
11840
  },
11759
11841
  ref: {
@@ -11823,6 +11905,10 @@ var init_config_schema = __esm({
11823
11905
  type: "boolean",
11824
11906
  description: "Keep worktree after workflow completion (default: false)"
11825
11907
  },
11908
+ sandbox: {
11909
+ type: "string",
11910
+ description: "Sandbox name to use for this check (overrides workspace-level default)"
11911
+ },
11826
11912
  policy: {
11827
11913
  $ref: "#/definitions/StepPolicyOverride",
11828
11914
  description: "Per-step policy override (enterprise)"
@@ -11967,6 +12053,14 @@ var init_config_schema = __esm({
11967
12053
  completion_prompt: {
11968
12054
  type: "string",
11969
12055
  description: "Completion prompt for post-completion validation/review (runs after attempt_completion)"
12056
+ },
12057
+ enable_scheduler: {
12058
+ type: "boolean",
12059
+ description: "Enable the schedule tool for scheduling workflow executions (requires scheduler configuration)"
12060
+ },
12061
+ enableExecutePlan: {
12062
+ type: "boolean",
12063
+ description: "Enable the execute_plan DSL orchestration tool (replaces analyze_all when enabled)"
11970
12064
  }
11971
12065
  },
11972
12066
  additionalProperties: false,
@@ -12429,7 +12523,7 @@ var init_config_schema = __esm({
12429
12523
  description: "Custom output name (defaults to workflow name)"
12430
12524
  },
12431
12525
  overrides: {
12432
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-12605-26099-src_types_config.ts-0-46407%3E%3E",
12526
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-27516-src_types_config.ts-0-51381%3E%3E",
12433
12527
  description: "Step overrides"
12434
12528
  },
12435
12529
  output_mapping: {
@@ -12444,13 +12538,13 @@ var init_config_schema = __esm({
12444
12538
  "^x-": {}
12445
12539
  }
12446
12540
  },
12447
- "Record<string,Partial<interface-src_types_config.ts-12605-26099-src_types_config.ts-0-46407>>": {
12541
+ "Record<string,Partial<interface-src_types_config.ts-13489-27516-src_types_config.ts-0-51381>>": {
12448
12542
  type: "object",
12449
12543
  additionalProperties: {
12450
- $ref: "#/definitions/Partial%3Cinterface-src_types_config.ts-12605-26099-src_types_config.ts-0-46407%3E"
12544
+ $ref: "#/definitions/Partial%3Cinterface-src_types_config.ts-13489-27516-src_types_config.ts-0-51381%3E"
12451
12545
  }
12452
12546
  },
12453
- "Partial<interface-src_types_config.ts-12605-26099-src_types_config.ts-0-46407>": {
12547
+ "Partial<interface-src_types_config.ts-13489-27516-src_types_config.ts-0-51381>": {
12454
12548
  type: "object",
12455
12549
  additionalProperties: false
12456
12550
  },
@@ -12564,9 +12658,9 @@ var init_config_schema = __esm({
12564
12658
  run: {
12565
12659
  type: "array",
12566
12660
  items: {
12567
- type: "string"
12661
+ $ref: "#/definitions/OnSuccessRunItem"
12568
12662
  },
12569
- description: "Post-success steps to run"
12663
+ description: "Post-success steps to run - can be step names or rich invocations with arguments"
12570
12664
  },
12571
12665
  goto: {
12572
12666
  type: "string",
@@ -12598,6 +12692,20 @@ var init_config_schema = __esm({
12598
12692
  "^x-": {}
12599
12693
  }
12600
12694
  },
12695
+ OnSuccessRunItem: {
12696
+ anyOf: [
12697
+ {
12698
+ type: "string"
12699
+ },
12700
+ {
12701
+ $ref: "#/definitions/OnInitStepInvocation"
12702
+ },
12703
+ {
12704
+ $ref: "#/definitions/OnInitWorkflowInvocation"
12705
+ }
12706
+ ],
12707
+ description: "Success routing run item - can be step name, step with args, or workflow with args"
12708
+ },
12601
12709
  OnFinishConfig: {
12602
12710
  type: "object",
12603
12711
  properties: {
@@ -12638,6 +12746,40 @@ var init_config_schema = __esm({
12638
12746
  "^x-": {}
12639
12747
  }
12640
12748
  },
12749
+ StepPolicyOverride: {
12750
+ type: "object",
12751
+ properties: {
12752
+ require: {
12753
+ anyOf: [
12754
+ {
12755
+ type: "string"
12756
+ },
12757
+ {
12758
+ type: "array",
12759
+ items: {
12760
+ type: "string"
12761
+ }
12762
+ }
12763
+ ],
12764
+ description: "Required role(s) - any of these roles suffices"
12765
+ },
12766
+ deny: {
12767
+ type: "array",
12768
+ items: {
12769
+ type: "string"
12770
+ },
12771
+ description: "Explicit deny for roles"
12772
+ },
12773
+ rule: {
12774
+ type: "string",
12775
+ description: "Custom OPA rule path for this step"
12776
+ }
12777
+ },
12778
+ additionalProperties: false,
12779
+ patternProperties: {
12780
+ "^x-": {}
12781
+ }
12782
+ },
12641
12783
  OutputConfig: {
12642
12784
  type: "object",
12643
12785
  properties: {
@@ -13044,6 +13186,141 @@ var init_config_schema = __esm({
13044
13186
  "^x-": {}
13045
13187
  }
13046
13188
  },
13189
+ "Record<string,SandboxConfig>": {
13190
+ type: "object",
13191
+ additionalProperties: {
13192
+ $ref: "#/definitions/SandboxConfig"
13193
+ }
13194
+ },
13195
+ SandboxConfig: {
13196
+ type: "object",
13197
+ properties: {
13198
+ image: {
13199
+ type: "string",
13200
+ description: 'Docker image to use (e.g., "node:20-alpine")'
13201
+ },
13202
+ dockerfile: {
13203
+ type: "string",
13204
+ description: "Path to Dockerfile (relative to config file or absolute)"
13205
+ },
13206
+ dockerfile_inline: {
13207
+ type: "string",
13208
+ description: "Inline Dockerfile content"
13209
+ },
13210
+ compose: {
13211
+ type: "string",
13212
+ description: "Path to docker-compose file"
13213
+ },
13214
+ service: {
13215
+ type: "string",
13216
+ description: "Service name within the compose file"
13217
+ },
13218
+ workdir: {
13219
+ type: "string",
13220
+ description: "Working directory inside container (default: /workspace)"
13221
+ },
13222
+ env_passthrough: {
13223
+ type: "array",
13224
+ items: {
13225
+ type: "string"
13226
+ },
13227
+ description: "Glob patterns for host env vars to forward into sandbox"
13228
+ },
13229
+ network: {
13230
+ type: "boolean",
13231
+ description: "Enable/disable network access (default: true)"
13232
+ },
13233
+ read_only: {
13234
+ type: "boolean",
13235
+ description: "Mount repo as read-only (default: false)"
13236
+ },
13237
+ resources: {
13238
+ $ref: "#/definitions/SandboxResourceConfig",
13239
+ description: "Resource limits"
13240
+ },
13241
+ visor_path: {
13242
+ type: "string",
13243
+ description: "Where visor is mounted inside container (default: /opt/visor)"
13244
+ },
13245
+ cache: {
13246
+ $ref: "#/definitions/SandboxCacheConfig",
13247
+ description: "Cache volume configuration"
13248
+ }
13249
+ },
13250
+ additionalProperties: false,
13251
+ description: "Configuration for a single sandbox environment",
13252
+ patternProperties: {
13253
+ "^x-": {}
13254
+ }
13255
+ },
13256
+ SandboxResourceConfig: {
13257
+ type: "object",
13258
+ properties: {
13259
+ memory: {
13260
+ type: "string",
13261
+ description: 'Memory limit (e.g., "512m", "2g")'
13262
+ },
13263
+ cpu: {
13264
+ type: "number",
13265
+ description: "CPU limit (e.g., 1.0, 0.5)"
13266
+ }
13267
+ },
13268
+ additionalProperties: false,
13269
+ description: "Resource limits for sandbox containers",
13270
+ patternProperties: {
13271
+ "^x-": {}
13272
+ }
13273
+ },
13274
+ SandboxCacheConfig: {
13275
+ type: "object",
13276
+ properties: {
13277
+ prefix: {
13278
+ type: "string",
13279
+ description: "Liquid template for cache scope prefix (default: git branch)"
13280
+ },
13281
+ fallback_prefix: {
13282
+ type: "string",
13283
+ description: "Fallback prefix when current prefix has no cache"
13284
+ },
13285
+ paths: {
13286
+ type: "array",
13287
+ items: {
13288
+ type: "string"
13289
+ },
13290
+ description: "Paths inside the container to cache"
13291
+ },
13292
+ ttl: {
13293
+ type: "string",
13294
+ description: 'Time-to-live for cache volumes (e.g., "7d", "24h")'
13295
+ },
13296
+ max_scopes: {
13297
+ type: "number",
13298
+ description: "Maximum number of cache scopes to keep"
13299
+ }
13300
+ },
13301
+ required: ["paths"],
13302
+ additionalProperties: false,
13303
+ description: "Cache configuration for sandbox volumes",
13304
+ patternProperties: {
13305
+ "^x-": {}
13306
+ }
13307
+ },
13308
+ SandboxDefaults: {
13309
+ type: "object",
13310
+ properties: {
13311
+ env_passthrough: {
13312
+ type: "array",
13313
+ items: {
13314
+ type: "string"
13315
+ },
13316
+ description: "Base env var patterns for all sandboxes (replaces hardcoded defaults when set)"
13317
+ }
13318
+ },
13319
+ additionalProperties: false,
13320
+ patternProperties: {
13321
+ "^x-": {}
13322
+ }
13323
+ },
13047
13324
  SlackConfig: {
13048
13325
  type: "object",
13049
13326
  properties: {
@@ -13103,7 +13380,16 @@ var init_config_schema = __esm({
13103
13380
  properties: {
13104
13381
  path: {
13105
13382
  type: "string",
13106
- description: "Path to schedules JSON file (default: .visor/schedules.json)"
13383
+ description: "Path to schedules JSON file (legacy, triggers auto-migration)"
13384
+ },
13385
+ driver: {
13386
+ type: "string",
13387
+ enum: ["sqlite", "postgresql", "mysql", "mssql"],
13388
+ description: "Database driver (default: 'sqlite')"
13389
+ },
13390
+ connection: {
13391
+ $ref: "#/definitions/SchedulerStorageConnectionConfig",
13392
+ description: "Database connection configuration"
13107
13393
  }
13108
13394
  },
13109
13395
  additionalProperties: false,
@@ -13112,6 +13398,10 @@ var init_config_schema = __esm({
13112
13398
  "^x-": {}
13113
13399
  }
13114
13400
  },
13401
+ ha: {
13402
+ $ref: "#/definitions/SchedulerHAConfig",
13403
+ description: "High-availability configuration for multi-node deployments"
13404
+ },
13115
13405
  limits: {
13116
13406
  $ref: "#/definitions/SchedulerLimitsConfig",
13117
13407
  description: "Limits for dynamic schedules"
@@ -13139,44 +13429,123 @@ var init_config_schema = __esm({
13139
13429
  "^x-": {}
13140
13430
  }
13141
13431
  },
13142
- PolicyConfig: {
13432
+ SchedulerStorageConnectionConfig: {
13143
13433
  type: "object",
13144
13434
  properties: {
13145
- engine: {
13435
+ filename: {
13146
13436
  type: "string",
13147
- enum: ["local", "remote", "disabled"],
13148
- description: "Policy engine mode: 'local' (WASM), 'remote' (HTTP OPA server), or 'disabled'"
13437
+ description: "SQLite database file path (default: '.visor/schedules.db')"
13149
13438
  },
13150
- rules: {
13151
- anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
13152
- description: "Path to .rego files or .wasm bundle (local mode)"
13439
+ host: {
13440
+ type: "string",
13441
+ description: "Database host (PostgreSQL/MySQL/MSSQL)"
13153
13442
  },
13154
- data: {
13443
+ port: {
13444
+ type: "number",
13445
+ description: "Database port (PostgreSQL/MySQL/MSSQL)"
13446
+ },
13447
+ database: {
13155
13448
  type: "string",
13156
- description: "Path to a JSON file to load as OPA data document (local mode)"
13449
+ description: "Database name (PostgreSQL/MySQL/MSSQL)"
13157
13450
  },
13158
- url: {
13451
+ user: {
13159
13452
  type: "string",
13160
- description: "OPA server URL (remote mode)"
13453
+ description: "Database user (PostgreSQL/MySQL/MSSQL)"
13161
13454
  },
13162
- fallback: {
13455
+ password: {
13163
13456
  type: "string",
13164
- enum: ["allow", "deny", "warn"],
13165
- description: "Default decision when policy evaluation fails (default: 'deny'). Use 'warn' for audit mode: violations are logged but not enforced."
13457
+ description: "Database password (PostgreSQL/MySQL/MSSQL)"
13166
13458
  },
13167
- timeout: {
13168
- type: "number",
13169
- description: "Evaluation timeout in milliseconds (default: 5000)"
13459
+ ssl: {
13460
+ anyOf: [
13461
+ {
13462
+ type: "boolean"
13463
+ },
13464
+ {
13465
+ $ref: "#/definitions/SchedulerSslConfig"
13466
+ }
13467
+ ],
13468
+ description: "SSL/TLS configuration (PostgreSQL/MySQL/MSSQL)"
13170
13469
  },
13171
- roles: {
13470
+ connection_string: {
13471
+ type: "string",
13472
+ description: "Connection string URL (e.g., postgresql://user:pass@host/db)"
13473
+ },
13474
+ pool: {
13172
13475
  type: "object",
13173
- additionalProperties: {
13174
- $ref: "#/definitions/PolicyRoleConfig"
13476
+ properties: {
13477
+ min: {
13478
+ type: "number"
13479
+ },
13480
+ max: {
13481
+ type: "number"
13482
+ }
13175
13483
  },
13176
- description: "Role definitions: map role names to conditions"
13484
+ additionalProperties: false,
13485
+ description: "Connection pool configuration",
13486
+ patternProperties: {
13487
+ "^x-": {}
13488
+ }
13177
13489
  }
13178
13490
  },
13179
13491
  additionalProperties: false,
13492
+ description: "Scheduler storage connection configuration",
13493
+ patternProperties: {
13494
+ "^x-": {}
13495
+ }
13496
+ },
13497
+ SchedulerSslConfig: {
13498
+ type: "object",
13499
+ properties: {
13500
+ enabled: {
13501
+ type: "boolean",
13502
+ description: "Enable SSL (default: true when SslConfig object is provided)"
13503
+ },
13504
+ reject_unauthorized: {
13505
+ type: "boolean",
13506
+ description: "Reject unauthorized certificates (default: true)"
13507
+ },
13508
+ ca: {
13509
+ type: "string",
13510
+ description: "Path to CA certificate PEM file"
13511
+ },
13512
+ cert: {
13513
+ type: "string",
13514
+ description: "Path to client certificate PEM file"
13515
+ },
13516
+ key: {
13517
+ type: "string",
13518
+ description: "Path to client key PEM file"
13519
+ }
13520
+ },
13521
+ additionalProperties: false,
13522
+ description: "SSL/TLS configuration for scheduler database connections",
13523
+ patternProperties: {
13524
+ "^x-": {}
13525
+ }
13526
+ },
13527
+ SchedulerHAConfig: {
13528
+ type: "object",
13529
+ properties: {
13530
+ enabled: {
13531
+ type: "boolean",
13532
+ description: "Enable distributed locking for multi-node deployments (default: false)"
13533
+ },
13534
+ node_id: {
13535
+ type: "string",
13536
+ description: "Unique node identifier (default: hostname-pid)"
13537
+ },
13538
+ lock_ttl: {
13539
+ type: "number",
13540
+ description: "Lock time-to-live in seconds (default: 60)"
13541
+ },
13542
+ heartbeat_interval: {
13543
+ type: "number",
13544
+ description: "Heartbeat interval for lock renewal in seconds (default: 15)"
13545
+ }
13546
+ },
13547
+ additionalProperties: false,
13548
+ description: "Scheduler high-availability configuration",
13180
13549
  patternProperties: {
13181
13550
  "^x-": {}
13182
13551
  }
@@ -13239,45 +13608,6 @@ var init_config_schema = __esm({
13239
13608
  "^x-": {}
13240
13609
  }
13241
13610
  },
13242
- PolicyRoleConfig: {
13243
- type: "object",
13244
- properties: {
13245
- author_association: {
13246
- type: "array",
13247
- items: { type: "string" },
13248
- description: "GitHub author associations that map to this role"
13249
- },
13250
- teams: {
13251
- type: "array",
13252
- items: { type: "string" },
13253
- description: "GitHub team slugs"
13254
- },
13255
- users: {
13256
- type: "array",
13257
- items: { type: "string" },
13258
- description: "Explicit GitHub usernames"
13259
- },
13260
- slack_users: {
13261
- type: "array",
13262
- items: { type: "string" },
13263
- description: "Slack user IDs (e.g., U0123ABC)"
13264
- },
13265
- emails: {
13266
- type: "array",
13267
- items: { type: "string" },
13268
- description: "Email addresses for identity matching"
13269
- },
13270
- slack_channels: {
13271
- type: "array",
13272
- items: { type: "string" },
13273
- description: "Slack channel IDs \u2014 role only applies when triggered from these channels"
13274
- }
13275
- },
13276
- additionalProperties: false,
13277
- patternProperties: {
13278
- "^x-": {}
13279
- }
13280
- },
13281
13611
  "Record<string,StaticCronJob>": {
13282
13612
  type: "object",
13283
13613
  additionalProperties: {
@@ -13343,21 +13673,106 @@ var init_config_schema = __esm({
13343
13673
  "^x-": {}
13344
13674
  }
13345
13675
  },
13346
- StepPolicyOverride: {
13676
+ PolicyConfig: {
13347
13677
  type: "object",
13348
13678
  properties: {
13349
- require: {
13350
- anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
13351
- description: "Required role(s) \u2014 any of these roles suffices"
13679
+ engine: {
13680
+ type: "string",
13681
+ enum: ["local", "remote", "disabled"],
13682
+ description: "Policy engine mode"
13352
13683
  },
13353
- deny: {
13354
- type: "array",
13355
- items: { type: "string" },
13356
- description: "Explicit deny for roles"
13684
+ rules: {
13685
+ anyOf: [
13686
+ {
13687
+ type: "string"
13688
+ },
13689
+ {
13690
+ type: "array",
13691
+ items: {
13692
+ type: "string"
13693
+ }
13694
+ }
13695
+ ],
13696
+ description: "Path to .rego files or .wasm bundle (local mode)"
13357
13697
  },
13358
- rule: {
13698
+ data: {
13359
13699
  type: "string",
13360
- description: "Custom OPA rule path for this step"
13700
+ description: "Path to a JSON file to load as OPA data document"
13701
+ },
13702
+ url: {
13703
+ type: "string",
13704
+ description: "OPA server URL (remote mode)"
13705
+ },
13706
+ fallback: {
13707
+ type: "string",
13708
+ enum: ["allow", "deny", "warn"],
13709
+ description: "Default decision when policy evaluation fails"
13710
+ },
13711
+ timeout: {
13712
+ type: "number",
13713
+ description: "Evaluation timeout in ms (default: 5000)"
13714
+ },
13715
+ roles: {
13716
+ $ref: "#/definitions/Record%3Cstring%2CPolicyRoleConfig%3E",
13717
+ description: "Role definitions: map role names to conditions"
13718
+ }
13719
+ },
13720
+ required: ["engine"],
13721
+ additionalProperties: false,
13722
+ patternProperties: {
13723
+ "^x-": {}
13724
+ }
13725
+ },
13726
+ "Record<string,PolicyRoleConfig>": {
13727
+ type: "object",
13728
+ additionalProperties: {
13729
+ $ref: "#/definitions/PolicyRoleConfig"
13730
+ }
13731
+ },
13732
+ PolicyRoleConfig: {
13733
+ type: "object",
13734
+ properties: {
13735
+ author_association: {
13736
+ type: "array",
13737
+ items: {
13738
+ type: "string"
13739
+ },
13740
+ description: "GitHub author associations that map to this role"
13741
+ },
13742
+ teams: {
13743
+ type: "array",
13744
+ items: {
13745
+ type: "string"
13746
+ },
13747
+ description: "GitHub team slugs (requires GitHub API)"
13748
+ },
13749
+ users: {
13750
+ type: "array",
13751
+ items: {
13752
+ type: "string"
13753
+ },
13754
+ description: "Explicit GitHub usernames"
13755
+ },
13756
+ slack_users: {
13757
+ type: "array",
13758
+ items: {
13759
+ type: "string"
13760
+ },
13761
+ description: 'Slack user IDs (e.g., ["U0123ABC"])'
13762
+ },
13763
+ emails: {
13764
+ type: "array",
13765
+ items: {
13766
+ type: "string"
13767
+ },
13768
+ description: 'Email addresses for identity matching (e.g., ["alice@co.com"])'
13769
+ },
13770
+ slack_channels: {
13771
+ type: "array",
13772
+ items: {
13773
+ type: "string"
13774
+ },
13775
+ description: "Slack channel IDs \u2014 role only applies when triggered from these channels"
13361
13776
  }
13362
13777
  },
13363
13778
  additionalProperties: false,
@@ -15517,6 +15932,20 @@ var init_workflow_check_provider = __esm({
15517
15932
  `[WorkflowProvider] Workflow '${workflow.id}' has null/undefined outputs: [${nullOutputs.join(", ")}]. This may indicate value_js expressions are not finding expected data.`
15518
15933
  );
15519
15934
  }
15935
+ if (!outputs._rawOutput) {
15936
+ const rawParts = [];
15937
+ for (const stepOutput of Object.values(outputsMap)) {
15938
+ if (stepOutput && typeof stepOutput === "object" && typeof stepOutput._rawOutput === "string") {
15939
+ rawParts.push(stepOutput._rawOutput);
15940
+ }
15941
+ }
15942
+ if (rawParts.length > 0) {
15943
+ outputs._rawOutput = rawParts.join("\n\n");
15944
+ logger.debug(
15945
+ `[WorkflowProvider] Propagated _rawOutput from steps (${rawParts.length} blocks, ${outputs._rawOutput.length} chars)`
15946
+ );
15947
+ }
15948
+ }
15520
15949
  return outputs;
15521
15950
  }
15522
15951
  /**
@@ -15580,17 +16009,17 @@ var init_workflow_check_provider = __esm({
15580
16009
  * so it can be executed by the state machine as a nested workflow.
15581
16010
  */
15582
16011
  async loadWorkflowFromConfigPath(sourcePath, baseDir) {
15583
- const path25 = require("path");
15584
- const fs21 = require("fs");
16012
+ const path29 = require("path");
16013
+ const fs25 = require("fs");
15585
16014
  const yaml5 = require("js-yaml");
15586
- const resolved = path25.isAbsolute(sourcePath) ? sourcePath : path25.resolve(baseDir, sourcePath);
15587
- if (!fs21.existsSync(resolved)) {
16015
+ const resolved = path29.isAbsolute(sourcePath) ? sourcePath : path29.resolve(baseDir, sourcePath);
16016
+ if (!fs25.existsSync(resolved)) {
15588
16017
  throw new Error(`Workflow config not found at: ${resolved}`);
15589
16018
  }
15590
- const rawContent = fs21.readFileSync(resolved, "utf8");
16019
+ const rawContent = fs25.readFileSync(resolved, "utf8");
15591
16020
  const rawData = yaml5.load(rawContent);
15592
16021
  if (rawData.imports && Array.isArray(rawData.imports)) {
15593
- const configDir = path25.dirname(resolved);
16022
+ const configDir = path29.dirname(resolved);
15594
16023
  for (const source of rawData.imports) {
15595
16024
  const results = await this.registry.import(source, {
15596
16025
  basePath: configDir,
@@ -15620,8 +16049,8 @@ ${errors}`);
15620
16049
  if (!steps || Object.keys(steps).length === 0) {
15621
16050
  throw new Error(`Config '${resolved}' does not contain any steps to execute as a workflow`);
15622
16051
  }
15623
- const id = path25.basename(resolved).replace(/\.(ya?ml)$/i, "");
15624
- const name = loaded.name || `Workflow from ${path25.basename(resolved)}`;
16052
+ const id = path29.basename(resolved).replace(/\.(ya?ml)$/i, "");
16053
+ const name = loaded.name || `Workflow from ${path29.basename(resolved)}`;
15625
16054
  const workflowDef = {
15626
16055
  id,
15627
16056
  name,
@@ -15689,7 +16118,7 @@ function workflowInputsToJsonSchema(inputs) {
15689
16118
  required: required.length > 0 ? required : void 0
15690
16119
  };
15691
16120
  }
15692
- function createWorkflowToolDefinition(workflow, argsOverrides) {
16121
+ function createWorkflowToolDefinition(workflow, argsOverrides, nameOverride) {
15693
16122
  const baseSchema = workflowInputsToJsonSchema(workflow.inputs);
15694
16123
  let inputSchema = baseSchema;
15695
16124
  if (argsOverrides && baseSchema && typeof baseSchema === "object") {
@@ -15707,7 +16136,7 @@ function createWorkflowToolDefinition(workflow, argsOverrides) {
15707
16136
  };
15708
16137
  }
15709
16138
  return {
15710
- name: workflow.id,
16139
+ name: nameOverride || workflow.id,
15711
16140
  description: workflow.description || `Execute the ${workflow.name} workflow`,
15712
16141
  inputSchema,
15713
16142
  // Workflow tools don't have an exec command - they're executed specially
@@ -15757,6 +16186,16 @@ async function executeWorkflowAsTool(workflowId, args, context2, argsOverrides)
15757
16186
  );
15758
16187
  logger.debug(`[WorkflowToolExecutor] Workflow '${workflowId}' output preview: ${outputPreview}`);
15759
16188
  if (output !== void 0) {
16189
+ if (output && typeof output === "object" && typeof output._rawOutput === "string") {
16190
+ const rawOutput = output._rawOutput;
16191
+ const cleanOutput = { ...output };
16192
+ delete cleanOutput._rawOutput;
16193
+ const jsonStr = JSON.stringify(cleanOutput, null, 2);
16194
+ logger.debug(
16195
+ `[WorkflowToolExecutor] Wrapping _rawOutput (${rawOutput.length} chars) in RAW_OUTPUT delimiters for '${workflowId}'`
16196
+ );
16197
+ return jsonStr + "\n<<<RAW_OUTPUT>>>\n" + rawOutput + "\n<<<END_RAW_OUTPUT>>>";
16198
+ }
15760
16199
  return output;
15761
16200
  }
15762
16201
  if (result.content) {
@@ -15781,7 +16220,7 @@ function resolveWorkflowToolFromItem(item) {
15781
16220
  logger.warn(`[WorkflowToolExecutor] Workflow '${item.workflow}' not found in registry`);
15782
16221
  return void 0;
15783
16222
  }
15784
- return createWorkflowToolDefinition(workflow, item.args);
16223
+ return createWorkflowToolDefinition(workflow, item.args, item.name);
15785
16224
  }
15786
16225
  return void 0;
15787
16226
  }
@@ -16240,8 +16679,8 @@ async function createStoreBackend(storageConfig, haConfig) {
16240
16679
  case "mssql": {
16241
16680
  try {
16242
16681
  const loaderPath = "../../enterprise/loader";
16243
- const { loadEnterpriseStoreBackend } = await import(loaderPath);
16244
- return await loadEnterpriseStoreBackend(driver, storageConfig, haConfig);
16682
+ const { loadEnterpriseStoreBackend: loadEnterpriseStoreBackend2 } = await import(loaderPath);
16683
+ return await loadEnterpriseStoreBackend2(driver, storageConfig, haConfig);
16245
16684
  } catch (err) {
16246
16685
  const msg = err instanceof Error ? err.message : String(err);
16247
16686
  logger.error(`[StoreFactory] Failed to load enterprise ${driver} backend: ${msg}`);
@@ -17508,7 +17947,7 @@ var init_mcp_custom_sse_server = __esm({
17508
17947
  * Returns the actual bound port number
17509
17948
  */
17510
17949
  async start() {
17511
- return new Promise((resolve11, reject) => {
17950
+ return new Promise((resolve15, reject) => {
17512
17951
  try {
17513
17952
  this.server = import_http.default.createServer((req, res) => {
17514
17953
  this.handleRequest(req, res).catch((error) => {
@@ -17542,7 +17981,7 @@ var init_mcp_custom_sse_server = __esm({
17542
17981
  );
17543
17982
  }
17544
17983
  this.startKeepalive();
17545
- resolve11(this.port);
17984
+ resolve15(this.port);
17546
17985
  });
17547
17986
  } catch (error) {
17548
17987
  reject(error);
@@ -17605,7 +18044,7 @@ var init_mcp_custom_sse_server = __esm({
17605
18044
  logger.debug(
17606
18045
  `[CustomToolsSSEServer:${this.sessionId}] Grace period before stop: ${waitMs}ms (activeToolCalls=${this.activeToolCalls})`
17607
18046
  );
17608
- await new Promise((resolve11) => setTimeout(resolve11, waitMs));
18047
+ await new Promise((resolve15) => setTimeout(resolve15, waitMs));
17609
18048
  }
17610
18049
  }
17611
18050
  if (this.activeToolCalls > 0) {
@@ -17614,7 +18053,7 @@ var init_mcp_custom_sse_server = __esm({
17614
18053
  `[CustomToolsSSEServer:${this.sessionId}] Waiting for ${this.activeToolCalls} active tool call(s) before stop`
17615
18054
  );
17616
18055
  while (this.activeToolCalls > 0 && Date.now() - startedAt < effectiveDrainTimeoutMs) {
17617
- await new Promise((resolve11) => setTimeout(resolve11, 250));
18056
+ await new Promise((resolve15) => setTimeout(resolve15, 250));
17618
18057
  }
17619
18058
  if (this.activeToolCalls > 0) {
17620
18059
  logger.warn(
@@ -17639,21 +18078,21 @@ var init_mcp_custom_sse_server = __esm({
17639
18078
  }
17640
18079
  this.connections.clear();
17641
18080
  if (this.server) {
17642
- await new Promise((resolve11, reject) => {
18081
+ await new Promise((resolve15, reject) => {
17643
18082
  const timeout = setTimeout(() => {
17644
18083
  if (this.debug) {
17645
18084
  logger.debug(
17646
18085
  `[CustomToolsSSEServer:${this.sessionId}] Force closing server after timeout`
17647
18086
  );
17648
18087
  }
17649
- this.server?.close(() => resolve11());
18088
+ this.server?.close(() => resolve15());
17650
18089
  }, 5e3);
17651
18090
  this.server.close((error) => {
17652
18091
  clearTimeout(timeout);
17653
18092
  if (error) {
17654
18093
  reject(error);
17655
18094
  } else {
17656
- resolve11();
18095
+ resolve15();
17657
18096
  }
17658
18097
  });
17659
18098
  });
@@ -18067,7 +18506,7 @@ var init_mcp_custom_sse_server = __esm({
18067
18506
  logger.warn(
18068
18507
  `[CustomToolsSSEServer:${this.sessionId}] Tool ${toolName} failed (attempt ${attempt + 1}/${retryCount + 1}): ${errorMsg}. Retrying in ${delay}ms`
18069
18508
  );
18070
- await new Promise((resolve11) => setTimeout(resolve11, delay));
18509
+ await new Promise((resolve15) => setTimeout(resolve15, delay));
18071
18510
  attempt++;
18072
18511
  }
18073
18512
  }
@@ -18162,7 +18601,7 @@ var init_ai_check_provider = __esm({
18162
18601
  init_sandbox();
18163
18602
  init_schedule_tool();
18164
18603
  init_schedule_tool_handler();
18165
- AICheckProvider = class extends CheckProvider {
18604
+ AICheckProvider = class _AICheckProvider extends CheckProvider {
18166
18605
  aiReviewService;
18167
18606
  liquidEngine;
18168
18607
  sandbox = null;
@@ -18332,9 +18771,9 @@ var init_ai_check_provider = __esm({
18332
18771
  } else {
18333
18772
  resolvedPath = import_path6.default.resolve(process.cwd(), str);
18334
18773
  }
18335
- const fs21 = require("fs").promises;
18774
+ const fs25 = require("fs").promises;
18336
18775
  try {
18337
- const stat = await fs21.stat(resolvedPath);
18776
+ const stat = await fs25.stat(resolvedPath);
18338
18777
  return stat.isFile();
18339
18778
  } catch {
18340
18779
  return hasFileExtension && (isRelativePath || isAbsolutePath || hasPathSeparators);
@@ -18744,6 +19183,9 @@ ${preview}`);
18744
19183
  if (aiAny2.enableTasks !== void 0) {
18745
19184
  aiConfig.enableTasks = aiAny2.enableTasks;
18746
19185
  }
19186
+ if (aiAny2.enableExecutePlan !== void 0) {
19187
+ aiConfig.enableExecutePlan = aiAny2.enableExecutePlan;
19188
+ }
18747
19189
  if (aiAny2.allowEdit !== void 0) {
18748
19190
  aiConfig.allowEdit = aiAny2.allowEdit;
18749
19191
  }
@@ -18889,13 +19331,10 @@ ${preview}`);
18889
19331
  if (decision.capabilities.allowEdit === false) aiConfig.allowEdit = false;
18890
19332
  if (decision.capabilities.allowBash === false) aiConfig.allowBash = false;
18891
19333
  if (decision.capabilities.allowedTools) {
18892
- if (aiConfig.allowedTools) {
18893
- aiConfig.allowedTools = aiConfig.allowedTools.filter(
18894
- (t) => decision.capabilities.allowedTools.includes(t)
18895
- );
18896
- } else {
18897
- aiConfig.allowedTools = decision.capabilities.allowedTools;
18898
- }
19334
+ aiConfig.allowedTools = _AICheckProvider.intersectAllowedTools(
19335
+ aiConfig.allowedTools,
19336
+ decision.capabilities.allowedTools
19337
+ );
18899
19338
  }
18900
19339
  }
18901
19340
  } catch (err) {
@@ -19016,7 +19455,8 @@ ${preview}`);
19016
19455
  if (cfg.workflow && typeof cfg.workflow === "string") {
19017
19456
  workflowEntriesFromMcp.push({
19018
19457
  workflow: cfg.workflow,
19019
- args: cfg.inputs
19458
+ args: cfg.inputs,
19459
+ name: serverName
19020
19460
  });
19021
19461
  mcpEntriesToRemove.push(serverName);
19022
19462
  logger.debug(
@@ -19157,6 +19597,27 @@ ${preview}`);
19157
19597
  );
19158
19598
  }
19159
19599
  }
19600
+ const allowedToolsJsExpr = config.ai_allowed_tools_js;
19601
+ if (allowedToolsJsExpr && _dependencyResults) {
19602
+ try {
19603
+ const dynamicAllowedTools = this.evaluateAllowedToolsJs(
19604
+ allowedToolsJsExpr,
19605
+ prInfo,
19606
+ _dependencyResults,
19607
+ config
19608
+ );
19609
+ if (dynamicAllowedTools !== null) {
19610
+ aiConfig.allowedTools = dynamicAllowedTools;
19611
+ this.logDebug(
19612
+ `[AI Provider] ai_allowed_tools_js evaluated to: ${JSON.stringify(dynamicAllowedTools)}`
19613
+ );
19614
+ }
19615
+ } catch (error) {
19616
+ logger.error(
19617
+ `[AICheckProvider] Failed to evaluate ai_allowed_tools_js: ${error instanceof Error ? error.message : "Unknown error"}`
19618
+ );
19619
+ }
19620
+ }
19160
19621
  const templateContext = {
19161
19622
  pr: {
19162
19623
  number: prInfo.number,
@@ -19660,6 +20121,65 @@ ${processedPrompt}` : processedPrompt;
19660
20121
  return {};
19661
20122
  }
19662
20123
  }
20124
+ /**
20125
+ * Evaluate ai_allowed_tools_js expression to dynamically compute allowed tools list.
20126
+ * Returns a string array of tool names, or null if the expression returns a non-array.
20127
+ */
20128
+ evaluateAllowedToolsJs(expression, prInfo, dependencyResults, config) {
20129
+ if (!this.sandbox) {
20130
+ this.sandbox = createSecureSandbox();
20131
+ }
20132
+ const outputs = {};
20133
+ for (const [checkId, result] of dependencyResults.entries()) {
20134
+ const summary = result;
20135
+ outputs[checkId] = summary.output !== void 0 ? summary.output : summary;
20136
+ }
20137
+ const jsContext = {
20138
+ outputs,
20139
+ inputs: config.inputs || {},
20140
+ pr: {
20141
+ number: prInfo.number,
20142
+ title: prInfo.title,
20143
+ description: prInfo.body,
20144
+ author: prInfo.author,
20145
+ branch: prInfo.head,
20146
+ base: prInfo.base,
20147
+ authorAssociation: prInfo.authorAssociation
20148
+ },
20149
+ files: prInfo.files?.map((f) => ({
20150
+ filename: f.filename,
20151
+ status: f.status,
20152
+ additions: f.additions,
20153
+ deletions: f.deletions,
20154
+ changes: f.changes
20155
+ })) || [],
20156
+ env: this.buildSafeEnv(),
20157
+ memory: config.__memoryAccessor || {}
20158
+ };
20159
+ try {
20160
+ const result = compileAndRun(this.sandbox, expression, jsContext, {
20161
+ injectLog: true,
20162
+ wrapFunction: true,
20163
+ logPrefix: "[ai_allowed_tools_js]"
20164
+ });
20165
+ if (!Array.isArray(result)) {
20166
+ logger.warn(
20167
+ `[AICheckProvider] ai_allowed_tools_js must return an array, got ${typeof result}`
20168
+ );
20169
+ return null;
20170
+ }
20171
+ const tools = result.filter((item) => typeof item === "string");
20172
+ logger.debug(
20173
+ `[AICheckProvider] ai_allowed_tools_js evaluated to ${tools.length} tools: ${tools.join(", ")}`
20174
+ );
20175
+ return tools;
20176
+ } catch (error) {
20177
+ logger.error(
20178
+ `[AICheckProvider] Failed to evaluate ai_allowed_tools_js: ${error instanceof Error ? error.message : "Unknown error"}`
20179
+ );
20180
+ return null;
20181
+ }
20182
+ }
19663
20183
  /**
19664
20184
  * Build a safe subset of environment variables for sandbox access.
19665
20185
  * Excludes sensitive keys like API keys, secrets, tokens.
@@ -19721,6 +20241,22 @@ ${processedPrompt}` : processedPrompt;
19721
20241
  }
19722
20242
  return tools;
19723
20243
  }
20244
+ /**
20245
+ * Intersect config-level allowedTools with policy-level allowedTools.
20246
+ * When the config list contains glob patterns ("*", "!" exclusions),
20247
+ * it is passed through unchanged — ProbeAgent resolves those patterns.
20248
+ * Literal tool name lists are intersected normally.
20249
+ */
20250
+ static intersectAllowedTools(configTools, policyTools) {
20251
+ if (!configTools) {
20252
+ return policyTools;
20253
+ }
20254
+ const hasGlobs = configTools.some((t) => t === "*" || t.startsWith("!"));
20255
+ if (hasGlobs) {
20256
+ return configTools;
20257
+ }
20258
+ return configTools.filter((t) => policyTools.includes(t));
20259
+ }
19724
20260
  getSupportedConfigKeys() {
19725
20261
  return [
19726
20262
  "type",
@@ -19736,6 +20272,7 @@ ${processedPrompt}` : processedPrompt;
19736
20272
  "ai.mcpServers",
19737
20273
  "ai.enableDelegate",
19738
20274
  "ai.enableTasks",
20275
+ "ai.enableExecutePlan",
19739
20276
  // legacy persona/prompt keys supported in config
19740
20277
  "ai_persona",
19741
20278
  "ai_prompt_type",
@@ -19757,6 +20294,7 @@ ${processedPrompt}` : processedPrompt;
19757
20294
  "ai_custom_tools",
19758
20295
  "ai_custom_tools_js",
19759
20296
  "ai_bash_config_js",
20297
+ "ai_allowed_tools_js",
19760
20298
  "env"
19761
20299
  ];
19762
20300
  }
@@ -24115,14 +24653,14 @@ var require_util = __commonJS({
24115
24653
  }
24116
24654
  const port = url.port != null ? url.port : url.protocol === "https:" ? 443 : 80;
24117
24655
  let origin = url.origin != null ? url.origin : `${url.protocol}//${url.hostname}:${port}`;
24118
- let path25 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
24656
+ let path29 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
24119
24657
  if (origin.endsWith("/")) {
24120
24658
  origin = origin.substring(0, origin.length - 1);
24121
24659
  }
24122
- if (path25 && !path25.startsWith("/")) {
24123
- path25 = `/${path25}`;
24660
+ if (path29 && !path29.startsWith("/")) {
24661
+ path29 = `/${path29}`;
24124
24662
  }
24125
- url = new URL(origin + path25);
24663
+ url = new URL(origin + path29);
24126
24664
  }
24127
24665
  return url;
24128
24666
  }
@@ -25736,20 +26274,20 @@ var require_parseParams = __commonJS({
25736
26274
  var require_basename = __commonJS({
25737
26275
  "node_modules/@fastify/busboy/lib/utils/basename.js"(exports2, module2) {
25738
26276
  "use strict";
25739
- module2.exports = function basename4(path25) {
25740
- if (typeof path25 !== "string") {
26277
+ module2.exports = function basename4(path29) {
26278
+ if (typeof path29 !== "string") {
25741
26279
  return "";
25742
26280
  }
25743
- for (var i = path25.length - 1; i >= 0; --i) {
25744
- switch (path25.charCodeAt(i)) {
26281
+ for (var i = path29.length - 1; i >= 0; --i) {
26282
+ switch (path29.charCodeAt(i)) {
25745
26283
  case 47:
25746
26284
  // '/'
25747
26285
  case 92:
25748
- path25 = path25.slice(i + 1);
25749
- return path25 === ".." || path25 === "." ? "" : path25;
26286
+ path29 = path29.slice(i + 1);
26287
+ return path29 === ".." || path29 === "." ? "" : path29;
25750
26288
  }
25751
26289
  }
25752
- return path25 === ".." || path25 === "." ? "" : path25;
26290
+ return path29 === ".." || path29 === "." ? "" : path29;
25753
26291
  };
25754
26292
  }
25755
26293
  });
@@ -26753,11 +27291,11 @@ var require_util2 = __commonJS({
26753
27291
  var assert = require("assert");
26754
27292
  var { isUint8Array } = require("util/types");
26755
27293
  var supportedHashes = [];
26756
- var crypto2;
27294
+ var crypto4;
26757
27295
  try {
26758
- crypto2 = require("crypto");
27296
+ crypto4 = require("crypto");
26759
27297
  const possibleRelevantHashes = ["sha256", "sha384", "sha512"];
26760
- supportedHashes = crypto2.getHashes().filter((hash) => possibleRelevantHashes.includes(hash));
27298
+ supportedHashes = crypto4.getHashes().filter((hash) => possibleRelevantHashes.includes(hash));
26761
27299
  } catch {
26762
27300
  }
26763
27301
  function responseURL(response) {
@@ -27034,7 +27572,7 @@ var require_util2 = __commonJS({
27034
27572
  }
27035
27573
  }
27036
27574
  function bytesMatch(bytes, metadataList) {
27037
- if (crypto2 === void 0) {
27575
+ if (crypto4 === void 0) {
27038
27576
  return true;
27039
27577
  }
27040
27578
  const parsedMetadata = parseMetadata(metadataList);
@@ -27049,7 +27587,7 @@ var require_util2 = __commonJS({
27049
27587
  for (const item of metadata) {
27050
27588
  const algorithm = item.algo;
27051
27589
  const expectedValue = item.hash;
27052
- let actualValue = crypto2.createHash(algorithm).update(bytes).digest("base64");
27590
+ let actualValue = crypto4.createHash(algorithm).update(bytes).digest("base64");
27053
27591
  if (actualValue[actualValue.length - 1] === "=") {
27054
27592
  if (actualValue[actualValue.length - 2] === "=") {
27055
27593
  actualValue = actualValue.slice(0, -2);
@@ -27142,8 +27680,8 @@ var require_util2 = __commonJS({
27142
27680
  function createDeferredPromise() {
27143
27681
  let res;
27144
27682
  let rej;
27145
- const promise = new Promise((resolve11, reject) => {
27146
- res = resolve11;
27683
+ const promise = new Promise((resolve15, reject) => {
27684
+ res = resolve15;
27147
27685
  rej = reject;
27148
27686
  });
27149
27687
  return { promise, resolve: res, reject: rej };
@@ -28396,8 +28934,8 @@ var require_body = __commonJS({
28396
28934
  var { parseMIMEType, serializeAMimeType } = require_dataURL();
28397
28935
  var random;
28398
28936
  try {
28399
- const crypto2 = require("crypto");
28400
- random = (max) => crypto2.randomInt(0, max);
28937
+ const crypto4 = require("crypto");
28938
+ random = (max) => crypto4.randomInt(0, max);
28401
28939
  } catch {
28402
28940
  random = (max) => Math.floor(Math.random(max));
28403
28941
  }
@@ -28648,8 +29186,8 @@ Content-Type: ${value.type || "application/octet-stream"}\r
28648
29186
  });
28649
29187
  }
28650
29188
  });
28651
- const busboyResolve = new Promise((resolve11, reject) => {
28652
- busboy.on("finish", resolve11);
29189
+ const busboyResolve = new Promise((resolve15, reject) => {
29190
+ busboy.on("finish", resolve15);
28653
29191
  busboy.on("error", (err) => reject(new TypeError(err)));
28654
29192
  });
28655
29193
  if (this.body !== null) for await (const chunk of consumeBody(this[kState].body)) busboy.write(chunk);
@@ -28780,7 +29318,7 @@ var require_request = __commonJS({
28780
29318
  }
28781
29319
  var Request = class _Request {
28782
29320
  constructor(origin, {
28783
- path: path25,
29321
+ path: path29,
28784
29322
  method,
28785
29323
  body,
28786
29324
  headers,
@@ -28794,11 +29332,11 @@ var require_request = __commonJS({
28794
29332
  throwOnError,
28795
29333
  expectContinue
28796
29334
  }, handler) {
28797
- if (typeof path25 !== "string") {
29335
+ if (typeof path29 !== "string") {
28798
29336
  throw new InvalidArgumentError("path must be a string");
28799
- } else if (path25[0] !== "/" && !(path25.startsWith("http://") || path25.startsWith("https://")) && method !== "CONNECT") {
29337
+ } else if (path29[0] !== "/" && !(path29.startsWith("http://") || path29.startsWith("https://")) && method !== "CONNECT") {
28800
29338
  throw new InvalidArgumentError("path must be an absolute URL or start with a slash");
28801
- } else if (invalidPathRegex.exec(path25) !== null) {
29339
+ } else if (invalidPathRegex.exec(path29) !== null) {
28802
29340
  throw new InvalidArgumentError("invalid request path");
28803
29341
  }
28804
29342
  if (typeof method !== "string") {
@@ -28861,7 +29399,7 @@ var require_request = __commonJS({
28861
29399
  this.completed = false;
28862
29400
  this.aborted = false;
28863
29401
  this.upgrade = upgrade || null;
28864
- this.path = query ? util.buildURL(path25, query) : path25;
29402
+ this.path = query ? util.buildURL(path29, query) : path29;
28865
29403
  this.origin = origin;
28866
29404
  this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent;
28867
29405
  this.blocking = blocking == null ? false : blocking;
@@ -29183,9 +29721,9 @@ var require_dispatcher_base = __commonJS({
29183
29721
  }
29184
29722
  close(callback) {
29185
29723
  if (callback === void 0) {
29186
- return new Promise((resolve11, reject) => {
29724
+ return new Promise((resolve15, reject) => {
29187
29725
  this.close((err, data) => {
29188
- return err ? reject(err) : resolve11(data);
29726
+ return err ? reject(err) : resolve15(data);
29189
29727
  });
29190
29728
  });
29191
29729
  }
@@ -29223,12 +29761,12 @@ var require_dispatcher_base = __commonJS({
29223
29761
  err = null;
29224
29762
  }
29225
29763
  if (callback === void 0) {
29226
- return new Promise((resolve11, reject) => {
29764
+ return new Promise((resolve15, reject) => {
29227
29765
  this.destroy(err, (err2, data) => {
29228
29766
  return err2 ? (
29229
29767
  /* istanbul ignore next: should never error */
29230
29768
  reject(err2)
29231
- ) : resolve11(data);
29769
+ ) : resolve15(data);
29232
29770
  });
29233
29771
  });
29234
29772
  }
@@ -29869,9 +30407,9 @@ var require_RedirectHandler = __commonJS({
29869
30407
  return this.handler.onHeaders(statusCode, headers, resume, statusText);
29870
30408
  }
29871
30409
  const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)));
29872
- const path25 = search ? `${pathname}${search}` : pathname;
30410
+ const path29 = search ? `${pathname}${search}` : pathname;
29873
30411
  this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin);
29874
- this.opts.path = path25;
30412
+ this.opts.path = path29;
29875
30413
  this.opts.origin = origin;
29876
30414
  this.opts.maxRedirections = 0;
29877
30415
  this.opts.query = null;
@@ -30290,16 +30828,16 @@ var require_client = __commonJS({
30290
30828
  return this[kNeedDrain] < 2;
30291
30829
  }
30292
30830
  async [kClose]() {
30293
- return new Promise((resolve11) => {
30831
+ return new Promise((resolve15) => {
30294
30832
  if (!this[kSize]) {
30295
- resolve11(null);
30833
+ resolve15(null);
30296
30834
  } else {
30297
- this[kClosedResolve] = resolve11;
30835
+ this[kClosedResolve] = resolve15;
30298
30836
  }
30299
30837
  });
30300
30838
  }
30301
30839
  async [kDestroy](err) {
30302
- return new Promise((resolve11) => {
30840
+ return new Promise((resolve15) => {
30303
30841
  const requests = this[kQueue].splice(this[kPendingIdx]);
30304
30842
  for (let i = 0; i < requests.length; i++) {
30305
30843
  const request = requests[i];
@@ -30310,7 +30848,7 @@ var require_client = __commonJS({
30310
30848
  this[kClosedResolve]();
30311
30849
  this[kClosedResolve] = null;
30312
30850
  }
30313
- resolve11();
30851
+ resolve15();
30314
30852
  };
30315
30853
  if (this[kHTTP2Session] != null) {
30316
30854
  util.destroy(this[kHTTP2Session], err);
@@ -30890,7 +31428,7 @@ var require_client = __commonJS({
30890
31428
  });
30891
31429
  }
30892
31430
  try {
30893
- const socket = await new Promise((resolve11, reject) => {
31431
+ const socket = await new Promise((resolve15, reject) => {
30894
31432
  client[kConnector]({
30895
31433
  host,
30896
31434
  hostname,
@@ -30902,7 +31440,7 @@ var require_client = __commonJS({
30902
31440
  if (err) {
30903
31441
  reject(err);
30904
31442
  } else {
30905
- resolve11(socket2);
31443
+ resolve15(socket2);
30906
31444
  }
30907
31445
  });
30908
31446
  });
@@ -31113,7 +31651,7 @@ var require_client = __commonJS({
31113
31651
  writeH2(client, client[kHTTP2Session], request);
31114
31652
  return;
31115
31653
  }
31116
- const { body, method, path: path25, host, upgrade, headers, blocking, reset } = request;
31654
+ const { body, method, path: path29, host, upgrade, headers, blocking, reset } = request;
31117
31655
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
31118
31656
  if (body && typeof body.read === "function") {
31119
31657
  body.read(0);
@@ -31163,7 +31701,7 @@ var require_client = __commonJS({
31163
31701
  if (blocking) {
31164
31702
  socket[kBlocking] = true;
31165
31703
  }
31166
- let header = `${method} ${path25} HTTP/1.1\r
31704
+ let header = `${method} ${path29} HTTP/1.1\r
31167
31705
  `;
31168
31706
  if (typeof host === "string") {
31169
31707
  header += `host: ${host}\r
@@ -31226,7 +31764,7 @@ upgrade: ${upgrade}\r
31226
31764
  return true;
31227
31765
  }
31228
31766
  function writeH2(client, session, request) {
31229
- const { body, method, path: path25, host, upgrade, expectContinue, signal, headers: reqHeaders } = request;
31767
+ const { body, method, path: path29, host, upgrade, expectContinue, signal, headers: reqHeaders } = request;
31230
31768
  let headers;
31231
31769
  if (typeof reqHeaders === "string") headers = Request[kHTTP2CopyHeaders](reqHeaders.trim());
31232
31770
  else headers = reqHeaders;
@@ -31269,7 +31807,7 @@ upgrade: ${upgrade}\r
31269
31807
  });
31270
31808
  return true;
31271
31809
  }
31272
- headers[HTTP2_HEADER_PATH] = path25;
31810
+ headers[HTTP2_HEADER_PATH] = path29;
31273
31811
  headers[HTTP2_HEADER_SCHEME] = "https";
31274
31812
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
31275
31813
  if (body && typeof body.read === "function") {
@@ -31526,12 +32064,12 @@ upgrade: ${upgrade}\r
31526
32064
  cb();
31527
32065
  }
31528
32066
  }
31529
- const waitForDrain = () => new Promise((resolve11, reject) => {
32067
+ const waitForDrain = () => new Promise((resolve15, reject) => {
31530
32068
  assert(callback === null);
31531
32069
  if (socket[kError]) {
31532
32070
  reject(socket[kError]);
31533
32071
  } else {
31534
- callback = resolve11;
32072
+ callback = resolve15;
31535
32073
  }
31536
32074
  });
31537
32075
  if (client[kHTTPConnVersion] === "h2") {
@@ -31877,8 +32415,8 @@ var require_pool_base = __commonJS({
31877
32415
  if (this[kQueue].isEmpty()) {
31878
32416
  return Promise.all(this[kClients].map((c) => c.close()));
31879
32417
  } else {
31880
- return new Promise((resolve11) => {
31881
- this[kClosedResolve] = resolve11;
32418
+ return new Promise((resolve15) => {
32419
+ this[kClosedResolve] = resolve15;
31882
32420
  });
31883
32421
  }
31884
32422
  }
@@ -32456,7 +32994,7 @@ var require_readable = __commonJS({
32456
32994
  if (this.closed) {
32457
32995
  return Promise.resolve(null);
32458
32996
  }
32459
- return new Promise((resolve11, reject) => {
32997
+ return new Promise((resolve15, reject) => {
32460
32998
  const signalListenerCleanup = signal ? util.addAbortListener(signal, () => {
32461
32999
  this.destroy();
32462
33000
  }) : noop;
@@ -32465,7 +33003,7 @@ var require_readable = __commonJS({
32465
33003
  if (signal && signal.aborted) {
32466
33004
  reject(signal.reason || Object.assign(new Error("The operation was aborted"), { name: "AbortError" }));
32467
33005
  } else {
32468
- resolve11(null);
33006
+ resolve15(null);
32469
33007
  }
32470
33008
  }).on("error", noop).on("data", function(chunk) {
32471
33009
  limit -= chunk.length;
@@ -32487,11 +33025,11 @@ var require_readable = __commonJS({
32487
33025
  throw new TypeError("unusable");
32488
33026
  }
32489
33027
  assert(!stream[kConsume]);
32490
- return new Promise((resolve11, reject) => {
33028
+ return new Promise((resolve15, reject) => {
32491
33029
  stream[kConsume] = {
32492
33030
  type,
32493
33031
  stream,
32494
- resolve: resolve11,
33032
+ resolve: resolve15,
32495
33033
  reject,
32496
33034
  length: 0,
32497
33035
  body: []
@@ -32526,12 +33064,12 @@ var require_readable = __commonJS({
32526
33064
  }
32527
33065
  }
32528
33066
  function consumeEnd(consume2) {
32529
- const { type, body, resolve: resolve11, stream, length } = consume2;
33067
+ const { type, body, resolve: resolve15, stream, length } = consume2;
32530
33068
  try {
32531
33069
  if (type === "text") {
32532
- resolve11(toUSVString(Buffer.concat(body)));
33070
+ resolve15(toUSVString(Buffer.concat(body)));
32533
33071
  } else if (type === "json") {
32534
- resolve11(JSON.parse(Buffer.concat(body)));
33072
+ resolve15(JSON.parse(Buffer.concat(body)));
32535
33073
  } else if (type === "arrayBuffer") {
32536
33074
  const dst = new Uint8Array(length);
32537
33075
  let pos = 0;
@@ -32539,12 +33077,12 @@ var require_readable = __commonJS({
32539
33077
  dst.set(buf, pos);
32540
33078
  pos += buf.byteLength;
32541
33079
  }
32542
- resolve11(dst.buffer);
33080
+ resolve15(dst.buffer);
32543
33081
  } else if (type === "blob") {
32544
33082
  if (!Blob2) {
32545
33083
  Blob2 = require("buffer").Blob;
32546
33084
  }
32547
- resolve11(new Blob2(body, { type: stream[kContentType] }));
33085
+ resolve15(new Blob2(body, { type: stream[kContentType] }));
32548
33086
  }
32549
33087
  consumeFinish(consume2);
32550
33088
  } catch (err) {
@@ -32801,9 +33339,9 @@ var require_api_request = __commonJS({
32801
33339
  };
32802
33340
  function request(opts, callback) {
32803
33341
  if (callback === void 0) {
32804
- return new Promise((resolve11, reject) => {
33342
+ return new Promise((resolve15, reject) => {
32805
33343
  request.call(this, opts, (err, data) => {
32806
- return err ? reject(err) : resolve11(data);
33344
+ return err ? reject(err) : resolve15(data);
32807
33345
  });
32808
33346
  });
32809
33347
  }
@@ -32976,9 +33514,9 @@ var require_api_stream = __commonJS({
32976
33514
  };
32977
33515
  function stream(opts, factory, callback) {
32978
33516
  if (callback === void 0) {
32979
- return new Promise((resolve11, reject) => {
33517
+ return new Promise((resolve15, reject) => {
32980
33518
  stream.call(this, opts, factory, (err, data) => {
32981
- return err ? reject(err) : resolve11(data);
33519
+ return err ? reject(err) : resolve15(data);
32982
33520
  });
32983
33521
  });
32984
33522
  }
@@ -33259,9 +33797,9 @@ var require_api_upgrade = __commonJS({
33259
33797
  };
33260
33798
  function upgrade(opts, callback) {
33261
33799
  if (callback === void 0) {
33262
- return new Promise((resolve11, reject) => {
33800
+ return new Promise((resolve15, reject) => {
33263
33801
  upgrade.call(this, opts, (err, data) => {
33264
- return err ? reject(err) : resolve11(data);
33802
+ return err ? reject(err) : resolve15(data);
33265
33803
  });
33266
33804
  });
33267
33805
  }
@@ -33350,9 +33888,9 @@ var require_api_connect = __commonJS({
33350
33888
  };
33351
33889
  function connect(opts, callback) {
33352
33890
  if (callback === void 0) {
33353
- return new Promise((resolve11, reject) => {
33891
+ return new Promise((resolve15, reject) => {
33354
33892
  connect.call(this, opts, (err, data) => {
33355
- return err ? reject(err) : resolve11(data);
33893
+ return err ? reject(err) : resolve15(data);
33356
33894
  });
33357
33895
  });
33358
33896
  }
@@ -33512,20 +34050,20 @@ var require_mock_utils = __commonJS({
33512
34050
  }
33513
34051
  return true;
33514
34052
  }
33515
- function safeUrl(path25) {
33516
- if (typeof path25 !== "string") {
33517
- return path25;
34053
+ function safeUrl(path29) {
34054
+ if (typeof path29 !== "string") {
34055
+ return path29;
33518
34056
  }
33519
- const pathSegments = path25.split("?");
34057
+ const pathSegments = path29.split("?");
33520
34058
  if (pathSegments.length !== 2) {
33521
- return path25;
34059
+ return path29;
33522
34060
  }
33523
34061
  const qp = new URLSearchParams(pathSegments.pop());
33524
34062
  qp.sort();
33525
34063
  return [...pathSegments, qp.toString()].join("?");
33526
34064
  }
33527
- function matchKey(mockDispatch2, { path: path25, method, body, headers }) {
33528
- const pathMatch = matchValue(mockDispatch2.path, path25);
34065
+ function matchKey(mockDispatch2, { path: path29, method, body, headers }) {
34066
+ const pathMatch = matchValue(mockDispatch2.path, path29);
33529
34067
  const methodMatch = matchValue(mockDispatch2.method, method);
33530
34068
  const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true;
33531
34069
  const headersMatch = matchHeaders(mockDispatch2, headers);
@@ -33543,7 +34081,7 @@ var require_mock_utils = __commonJS({
33543
34081
  function getMockDispatch(mockDispatches, key) {
33544
34082
  const basePath = key.query ? buildURL(key.path, key.query) : key.path;
33545
34083
  const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath;
33546
- let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path25 }) => matchValue(safeUrl(path25), resolvedPath));
34084
+ let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path29 }) => matchValue(safeUrl(path29), resolvedPath));
33547
34085
  if (matchedMockDispatches.length === 0) {
33548
34086
  throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`);
33549
34087
  }
@@ -33580,9 +34118,9 @@ var require_mock_utils = __commonJS({
33580
34118
  }
33581
34119
  }
33582
34120
  function buildKey(opts) {
33583
- const { path: path25, method, body, headers, query } = opts;
34121
+ const { path: path29, method, body, headers, query } = opts;
33584
34122
  return {
33585
- path: path25,
34123
+ path: path29,
33586
34124
  method,
33587
34125
  body,
33588
34126
  headers,
@@ -34031,10 +34569,10 @@ var require_pending_interceptors_formatter = __commonJS({
34031
34569
  }
34032
34570
  format(pendingInterceptors) {
34033
34571
  const withPrettyHeaders = pendingInterceptors.map(
34034
- ({ method, path: path25, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
34572
+ ({ method, path: path29, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
34035
34573
  Method: method,
34036
34574
  Origin: origin,
34037
- Path: path25,
34575
+ Path: path29,
34038
34576
  "Status code": statusCode,
34039
34577
  Persistent: persist ? "\u2705" : "\u274C",
34040
34578
  Invocations: timesInvoked,
@@ -36975,7 +37513,7 @@ var require_fetch = __commonJS({
36975
37513
  async function dispatch({ body }) {
36976
37514
  const url = requestCurrentURL(request);
36977
37515
  const agent = fetchParams.controller.dispatcher;
36978
- return new Promise((resolve11, reject) => agent.dispatch(
37516
+ return new Promise((resolve15, reject) => agent.dispatch(
36979
37517
  {
36980
37518
  path: url.pathname + url.search,
36981
37519
  origin: url.origin,
@@ -37051,7 +37589,7 @@ var require_fetch = __commonJS({
37051
37589
  }
37052
37590
  }
37053
37591
  }
37054
- resolve11({
37592
+ resolve15({
37055
37593
  status,
37056
37594
  statusText,
37057
37595
  headersList: headers[kHeadersList],
@@ -37094,7 +37632,7 @@ var require_fetch = __commonJS({
37094
37632
  const val = headersList[n + 1].toString("latin1");
37095
37633
  headers[kHeadersList].append(key, val);
37096
37634
  }
37097
- resolve11({
37635
+ resolve15({
37098
37636
  status,
37099
37637
  statusText: STATUS_CODES[status],
37100
37638
  headersList: headers[kHeadersList],
@@ -38655,8 +39193,8 @@ var require_util6 = __commonJS({
38655
39193
  }
38656
39194
  }
38657
39195
  }
38658
- function validateCookiePath(path25) {
38659
- for (const char of path25) {
39196
+ function validateCookiePath(path29) {
39197
+ for (const char of path29) {
38660
39198
  const code = char.charCodeAt(0);
38661
39199
  if (code < 33 || char === ";") {
38662
39200
  throw new Error("Invalid cookie path");
@@ -39453,9 +39991,9 @@ var require_connection = __commonJS({
39453
39991
  channels.open = diagnosticsChannel.channel("undici:websocket:open");
39454
39992
  channels.close = diagnosticsChannel.channel("undici:websocket:close");
39455
39993
  channels.socketError = diagnosticsChannel.channel("undici:websocket:socket_error");
39456
- var crypto2;
39994
+ var crypto4;
39457
39995
  try {
39458
- crypto2 = require("crypto");
39996
+ crypto4 = require("crypto");
39459
39997
  } catch {
39460
39998
  }
39461
39999
  function establishWebSocketConnection(url, protocols, ws, onEstablish, options) {
@@ -39474,7 +40012,7 @@ var require_connection = __commonJS({
39474
40012
  const headersList = new Headers(options.headers)[kHeadersList];
39475
40013
  request.headersList = headersList;
39476
40014
  }
39477
- const keyValue = crypto2.randomBytes(16).toString("base64");
40015
+ const keyValue = crypto4.randomBytes(16).toString("base64");
39478
40016
  request.headersList.append("sec-websocket-key", keyValue);
39479
40017
  request.headersList.append("sec-websocket-version", "13");
39480
40018
  for (const protocol of protocols) {
@@ -39503,7 +40041,7 @@ var require_connection = __commonJS({
39503
40041
  return;
39504
40042
  }
39505
40043
  const secWSAccept = response.headersList.get("Sec-WebSocket-Accept");
39506
- const digest = crypto2.createHash("sha1").update(keyValue + uid).digest("base64");
40044
+ const digest = crypto4.createHash("sha1").update(keyValue + uid).digest("base64");
39507
40045
  if (secWSAccept !== digest) {
39508
40046
  failWebsocketConnection(ws, "Incorrect hash received in Sec-WebSocket-Accept header.");
39509
40047
  return;
@@ -39583,9 +40121,9 @@ var require_frame = __commonJS({
39583
40121
  "node_modules/undici/lib/websocket/frame.js"(exports2, module2) {
39584
40122
  "use strict";
39585
40123
  var { maxUnsigned16Bit } = require_constants5();
39586
- var crypto2;
40124
+ var crypto4;
39587
40125
  try {
39588
- crypto2 = require("crypto");
40126
+ crypto4 = require("crypto");
39589
40127
  } catch {
39590
40128
  }
39591
40129
  var WebsocketFrameSend = class {
@@ -39594,7 +40132,7 @@ var require_frame = __commonJS({
39594
40132
  */
39595
40133
  constructor(data) {
39596
40134
  this.frameData = data;
39597
- this.maskKey = crypto2.randomBytes(4);
40135
+ this.maskKey = crypto4.randomBytes(4);
39598
40136
  }
39599
40137
  createFrame(opcode) {
39600
40138
  const bodyLength = this.frameData?.byteLength ?? 0;
@@ -40336,11 +40874,11 @@ var require_undici = __commonJS({
40336
40874
  if (typeof opts.path !== "string") {
40337
40875
  throw new InvalidArgumentError("invalid opts.path");
40338
40876
  }
40339
- let path25 = opts.path;
40877
+ let path29 = opts.path;
40340
40878
  if (!opts.path.startsWith("/")) {
40341
- path25 = `/${path25}`;
40879
+ path29 = `/${path29}`;
40342
40880
  }
40343
- url = new URL(util.parseOrigin(url).origin + path25);
40881
+ url = new URL(util.parseOrigin(url).origin + path29);
40344
40882
  } else {
40345
40883
  if (!opts) {
40346
40884
  opts = typeof url === "object" ? url : {};
@@ -40867,7 +41405,7 @@ var init_mcp_check_provider = __esm({
40867
41405
  logger.warn(
40868
41406
  `MCP ${transportName} failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`
40869
41407
  );
40870
- await new Promise((resolve11) => setTimeout(resolve11, delay));
41408
+ await new Promise((resolve15) => setTimeout(resolve15, delay));
40871
41409
  attempt += 1;
40872
41410
  } finally {
40873
41411
  try {
@@ -41136,7 +41674,7 @@ async function acquirePromptLock() {
41136
41674
  activePrompt = true;
41137
41675
  return;
41138
41676
  }
41139
- await new Promise((resolve11) => waiters.push(resolve11));
41677
+ await new Promise((resolve15) => waiters.push(resolve15));
41140
41678
  activePrompt = true;
41141
41679
  }
41142
41680
  function releasePromptLock() {
@@ -41146,7 +41684,7 @@ function releasePromptLock() {
41146
41684
  }
41147
41685
  async function interactivePrompt(options) {
41148
41686
  await acquirePromptLock();
41149
- return new Promise((resolve11, reject) => {
41687
+ return new Promise((resolve15, reject) => {
41150
41688
  const dbg = process.env.VISOR_DEBUG === "true";
41151
41689
  try {
41152
41690
  if (dbg) {
@@ -41233,12 +41771,12 @@ async function interactivePrompt(options) {
41233
41771
  };
41234
41772
  const finish = (value) => {
41235
41773
  cleanup();
41236
- resolve11(value);
41774
+ resolve15(value);
41237
41775
  };
41238
41776
  if (options.timeout && options.timeout > 0) {
41239
41777
  timeoutId = setTimeout(() => {
41240
41778
  cleanup();
41241
- if (defaultValue !== void 0) return resolve11(defaultValue);
41779
+ if (defaultValue !== void 0) return resolve15(defaultValue);
41242
41780
  return reject(new Error("Input timeout"));
41243
41781
  }, options.timeout);
41244
41782
  }
@@ -41370,7 +41908,7 @@ async function interactivePrompt(options) {
41370
41908
  });
41371
41909
  }
41372
41910
  async function simplePrompt(prompt) {
41373
- return new Promise((resolve11) => {
41911
+ return new Promise((resolve15) => {
41374
41912
  const rl = readline.createInterface({
41375
41913
  input: process.stdin,
41376
41914
  output: process.stdout
@@ -41386,7 +41924,7 @@ async function simplePrompt(prompt) {
41386
41924
  rl.question(`${prompt}
41387
41925
  > `, (answer) => {
41388
41926
  rl.close();
41389
- resolve11(answer.trim());
41927
+ resolve15(answer.trim());
41390
41928
  });
41391
41929
  });
41392
41930
  }
@@ -41554,7 +42092,7 @@ function isStdinAvailable() {
41554
42092
  return !process.stdin.isTTY;
41555
42093
  }
41556
42094
  async function readStdin(timeout, maxSize = 1024 * 1024) {
41557
- return new Promise((resolve11, reject) => {
42095
+ return new Promise((resolve15, reject) => {
41558
42096
  let data = "";
41559
42097
  let timeoutId;
41560
42098
  if (timeout) {
@@ -41581,7 +42119,7 @@ async function readStdin(timeout, maxSize = 1024 * 1024) {
41581
42119
  };
41582
42120
  const onEnd = () => {
41583
42121
  cleanup();
41584
- resolve11(data.trim());
42122
+ resolve15(data.trim());
41585
42123
  };
41586
42124
  const onError = (err) => {
41587
42125
  cleanup();
@@ -44906,23 +45444,23 @@ __export(renderer_schema_exports, {
44906
45444
  });
44907
45445
  async function loadRendererSchema(name) {
44908
45446
  try {
44909
- const fs21 = await import("fs/promises");
44910
- const path25 = await import("path");
45447
+ const fs25 = await import("fs/promises");
45448
+ const path29 = await import("path");
44911
45449
  const sanitized = String(name).replace(/[^a-zA-Z0-9-]/g, "");
44912
45450
  if (!sanitized) return void 0;
44913
45451
  const candidates = [
44914
45452
  // When bundled with ncc, __dirname is dist/ and output/ is at dist/output/
44915
- path25.join(__dirname, "output", sanitized, "schema.json"),
45453
+ path29.join(__dirname, "output", sanitized, "schema.json"),
44916
45454
  // When running from source, __dirname is src/state-machine/dispatch/ and output/ is at output/
44917
- path25.join(__dirname, "..", "..", "output", sanitized, "schema.json"),
45455
+ path29.join(__dirname, "..", "..", "output", sanitized, "schema.json"),
44918
45456
  // When running from a checkout with output/ folder copied to CWD
44919
- path25.join(process.cwd(), "output", sanitized, "schema.json"),
45457
+ path29.join(process.cwd(), "output", sanitized, "schema.json"),
44920
45458
  // Fallback: cwd/dist/output/
44921
- path25.join(process.cwd(), "dist", "output", sanitized, "schema.json")
45459
+ path29.join(process.cwd(), "dist", "output", sanitized, "schema.json")
44922
45460
  ];
44923
45461
  for (const p of candidates) {
44924
45462
  try {
44925
- const raw = await fs21.readFile(p, "utf-8");
45463
+ const raw = await fs25.readFile(p, "utf-8");
44926
45464
  return JSON.parse(raw);
44927
45465
  } catch {
44928
45466
  }
@@ -47341,37 +47879,37 @@ function updateStats2(results, state, isForEachIteration = false) {
47341
47879
  async function renderTemplateContent2(checkId, checkConfig, reviewSummary) {
47342
47880
  try {
47343
47881
  const { createExtendedLiquid: createExtendedLiquid2 } = await Promise.resolve().then(() => (init_liquid_extensions(), liquid_extensions_exports));
47344
- const fs21 = await import("fs/promises");
47345
- const path25 = await import("path");
47882
+ const fs25 = await import("fs/promises");
47883
+ const path29 = await import("path");
47346
47884
  const schemaRaw = checkConfig.schema || "plain";
47347
- const schema = typeof schemaRaw === "string" ? schemaRaw : "code-review";
47885
+ const schema = typeof schemaRaw === "string" && !schemaRaw.includes("{{") && !schemaRaw.includes("{%") ? schemaRaw : typeof schemaRaw === "object" ? "code-review" : "plain";
47348
47886
  let templateContent;
47349
47887
  if (checkConfig.template && checkConfig.template.content) {
47350
47888
  templateContent = String(checkConfig.template.content);
47351
47889
  logger.debug(`[LevelDispatch] Using inline template for ${checkId}`);
47352
47890
  } else if (checkConfig.template && checkConfig.template.file) {
47353
47891
  const file = String(checkConfig.template.file);
47354
- const resolved = path25.resolve(process.cwd(), file);
47355
- templateContent = await fs21.readFile(resolved, "utf-8");
47892
+ const resolved = path29.resolve(process.cwd(), file);
47893
+ templateContent = await fs25.readFile(resolved, "utf-8");
47356
47894
  logger.debug(`[LevelDispatch] Using template file for ${checkId}: ${resolved}`);
47357
47895
  } else if (schema && schema !== "plain") {
47358
47896
  const sanitized = String(schema).replace(/[^a-zA-Z0-9-]/g, "");
47359
47897
  if (sanitized) {
47360
47898
  const candidatePaths = [
47361
- path25.join(__dirname, "output", sanitized, "template.liquid"),
47899
+ path29.join(__dirname, "output", sanitized, "template.liquid"),
47362
47900
  // bundled: dist/output/
47363
- path25.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
47901
+ path29.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
47364
47902
  // source (from state-machine/states)
47365
- path25.join(__dirname, "..", "..", "..", "output", sanitized, "template.liquid"),
47903
+ path29.join(__dirname, "..", "..", "..", "output", sanitized, "template.liquid"),
47366
47904
  // source (alternate)
47367
- path25.join(process.cwd(), "output", sanitized, "template.liquid"),
47905
+ path29.join(process.cwd(), "output", sanitized, "template.liquid"),
47368
47906
  // fallback: cwd/output/
47369
- path25.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
47907
+ path29.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
47370
47908
  // fallback: cwd/dist/output/
47371
47909
  ];
47372
47910
  for (const p of candidatePaths) {
47373
47911
  try {
47374
- templateContent = await fs21.readFile(p, "utf-8");
47912
+ templateContent = await fs25.readFile(p, "utf-8");
47375
47913
  if (templateContent) {
47376
47914
  logger.debug(`[LevelDispatch] Using schema template for ${checkId}: ${p}`);
47377
47915
  break;
@@ -49172,8 +49710,8 @@ var init_workspace_manager = __esm({
49172
49710
  );
49173
49711
  if (this.cleanupRequested && this.activeOperations === 0) {
49174
49712
  logger.debug(`[Workspace] All references released, proceeding with deferred cleanup`);
49175
- for (const resolve11 of this.cleanupResolvers) {
49176
- resolve11();
49713
+ for (const resolve15 of this.cleanupResolvers) {
49714
+ resolve15();
49177
49715
  }
49178
49716
  this.cleanupResolvers = [];
49179
49717
  }
@@ -49298,19 +49836,19 @@ var init_workspace_manager = __esm({
49298
49836
  );
49299
49837
  this.cleanupRequested = true;
49300
49838
  await Promise.race([
49301
- new Promise((resolve11) => {
49839
+ new Promise((resolve15) => {
49302
49840
  if (this.activeOperations === 0) {
49303
- resolve11();
49841
+ resolve15();
49304
49842
  } else {
49305
- this.cleanupResolvers.push(resolve11);
49843
+ this.cleanupResolvers.push(resolve15);
49306
49844
  }
49307
49845
  }),
49308
- new Promise((resolve11) => {
49846
+ new Promise((resolve15) => {
49309
49847
  setTimeout(() => {
49310
49848
  logger.warn(
49311
49849
  `[Workspace] Cleanup timeout after ${timeout}ms, proceeding anyway (${this.activeOperations} operations still active)`
49312
49850
  );
49313
- resolve11();
49851
+ resolve15();
49314
49852
  }, timeout);
49315
49853
  })
49316
49854
  ]);
@@ -49589,6 +50127,1264 @@ var init_build_engine_context = __esm({
49589
50127
  }
49590
50128
  });
49591
50129
 
50130
+ // src/policy/default-engine.ts
50131
+ var DefaultPolicyEngine;
50132
+ var init_default_engine = __esm({
50133
+ "src/policy/default-engine.ts"() {
50134
+ "use strict";
50135
+ DefaultPolicyEngine = class {
50136
+ async initialize(_config) {
50137
+ }
50138
+ async evaluateCheckExecution(_checkId, _checkConfig) {
50139
+ return { allowed: true };
50140
+ }
50141
+ async evaluateToolInvocation(_serverName, _methodName, _transport) {
50142
+ return { allowed: true };
50143
+ }
50144
+ async evaluateCapabilities(_checkId, _capabilities) {
50145
+ return { allowed: true };
50146
+ }
50147
+ async shutdown() {
50148
+ }
50149
+ };
50150
+ }
50151
+ });
50152
+
50153
+ // src/enterprise/license/validator.ts
50154
+ var validator_exports = {};
50155
+ __export(validator_exports, {
50156
+ LicenseValidator: () => LicenseValidator
50157
+ });
50158
+ var crypto2, fs18, path22, LicenseValidator;
50159
+ var init_validator = __esm({
50160
+ "src/enterprise/license/validator.ts"() {
50161
+ "use strict";
50162
+ crypto2 = __toESM(require("crypto"));
50163
+ fs18 = __toESM(require("fs"));
50164
+ path22 = __toESM(require("path"));
50165
+ LicenseValidator = class _LicenseValidator {
50166
+ /** Ed25519 public key for license verification (PEM format). */
50167
+ static PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAI/Zd08EFmgIdrDm/HXd0l3/5GBt7R1PrdvhdmEXhJlU=\n-----END PUBLIC KEY-----\n";
50168
+ cache = null;
50169
+ static CACHE_TTL = 5 * 60 * 1e3;
50170
+ // 5 minutes
50171
+ static GRACE_PERIOD = 72 * 3600 * 1e3;
50172
+ // 72 hours after expiry
50173
+ /**
50174
+ * Load and validate license from environment or file.
50175
+ *
50176
+ * Resolution order:
50177
+ * 1. VISOR_LICENSE env var (JWT string)
50178
+ * 2. VISOR_LICENSE_FILE env var (path to file)
50179
+ * 3. .visor-license in project root (cwd)
50180
+ * 4. .visor-license in ~/.config/visor/
50181
+ */
50182
+ async loadAndValidate() {
50183
+ if (this.cache && Date.now() - this.cache.validatedAt < _LicenseValidator.CACHE_TTL) {
50184
+ return this.cache.payload;
50185
+ }
50186
+ const token = this.resolveToken();
50187
+ if (!token) return null;
50188
+ const payload = this.verifyAndDecode(token);
50189
+ if (!payload) return null;
50190
+ this.cache = { payload, validatedAt: Date.now() };
50191
+ return payload;
50192
+ }
50193
+ /** Check if a specific feature is licensed */
50194
+ hasFeature(feature) {
50195
+ if (!this.cache) return false;
50196
+ return this.cache.payload.features.includes(feature);
50197
+ }
50198
+ /** Check if license is valid (with grace period) */
50199
+ isValid() {
50200
+ if (!this.cache) return false;
50201
+ const now = Date.now();
50202
+ const expiryMs = this.cache.payload.exp * 1e3;
50203
+ return now < expiryMs + _LicenseValidator.GRACE_PERIOD;
50204
+ }
50205
+ /** Check if the license is within its grace period (expired but still valid) */
50206
+ isInGracePeriod() {
50207
+ if (!this.cache) return false;
50208
+ const now = Date.now();
50209
+ const expiryMs = this.cache.payload.exp * 1e3;
50210
+ return now >= expiryMs && now < expiryMs + _LicenseValidator.GRACE_PERIOD;
50211
+ }
50212
+ resolveToken() {
50213
+ if (process.env.VISOR_LICENSE) {
50214
+ return process.env.VISOR_LICENSE.trim();
50215
+ }
50216
+ if (process.env.VISOR_LICENSE_FILE) {
50217
+ const resolved = path22.resolve(process.env.VISOR_LICENSE_FILE);
50218
+ 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")));
50221
+ let realPath;
50222
+ try {
50223
+ realPath = fs18.realpathSync(resolved);
50224
+ } catch {
50225
+ return null;
50226
+ }
50227
+ const isSafe = allowedPrefixes.some(
50228
+ (prefix) => realPath === prefix || realPath.startsWith(prefix + path22.sep)
50229
+ );
50230
+ if (!isSafe) return null;
50231
+ return this.readFile(realPath);
50232
+ }
50233
+ const cwdPath = path22.join(process.cwd(), ".visor-license");
50234
+ const cwdToken = this.readFile(cwdPath);
50235
+ if (cwdToken) return cwdToken;
50236
+ const home = process.env.HOME || process.env.USERPROFILE || "";
50237
+ if (home) {
50238
+ const configPath = path22.join(home, ".config", "visor", ".visor-license");
50239
+ const configToken = this.readFile(configPath);
50240
+ if (configToken) return configToken;
50241
+ }
50242
+ return null;
50243
+ }
50244
+ readFile(filePath) {
50245
+ try {
50246
+ return fs18.readFileSync(filePath, "utf-8").trim();
50247
+ } catch {
50248
+ return null;
50249
+ }
50250
+ }
50251
+ verifyAndDecode(token) {
50252
+ try {
50253
+ const parts = token.split(".");
50254
+ if (parts.length !== 3) return null;
50255
+ const [headerB64, payloadB64, signatureB64] = parts;
50256
+ const header = JSON.parse(Buffer.from(headerB64, "base64url").toString());
50257
+ if (header.alg !== "EdDSA") return null;
50258
+ const data = `${headerB64}.${payloadB64}`;
50259
+ const signature = Buffer.from(signatureB64, "base64url");
50260
+ const publicKey = crypto2.createPublicKey(_LicenseValidator.PUBLIC_KEY);
50261
+ if (publicKey.asymmetricKeyType !== "ed25519") {
50262
+ return null;
50263
+ }
50264
+ const isValid = crypto2.verify(null, Buffer.from(data), publicKey, signature);
50265
+ if (!isValid) return null;
50266
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
50267
+ if (!payload.org || !Array.isArray(payload.features) || typeof payload.exp !== "number" || typeof payload.iat !== "number" || !payload.sub) {
50268
+ return null;
50269
+ }
50270
+ const now = Date.now();
50271
+ const expiryMs = payload.exp * 1e3;
50272
+ if (now >= expiryMs + _LicenseValidator.GRACE_PERIOD) {
50273
+ return null;
50274
+ }
50275
+ return payload;
50276
+ } catch {
50277
+ return null;
50278
+ }
50279
+ }
50280
+ };
50281
+ }
50282
+ });
50283
+
50284
+ // src/enterprise/policy/opa-compiler.ts
50285
+ var fs19, path23, os, crypto3, import_child_process5, OpaCompiler;
50286
+ var init_opa_compiler = __esm({
50287
+ "src/enterprise/policy/opa-compiler.ts"() {
50288
+ "use strict";
50289
+ fs19 = __toESM(require("fs"));
50290
+ path23 = __toESM(require("path"));
50291
+ os = __toESM(require("os"));
50292
+ crypto3 = __toESM(require("crypto"));
50293
+ import_child_process5 = require("child_process");
50294
+ OpaCompiler = class _OpaCompiler {
50295
+ static CACHE_DIR = path23.join(os.tmpdir(), "visor-opa-cache");
50296
+ /**
50297
+ * Resolve the input paths to WASM bytes.
50298
+ *
50299
+ * Strategy:
50300
+ * 1. If any path is a .wasm file, read it directly
50301
+ * 2. If a directory contains policy.wasm, read it
50302
+ * 3. Otherwise, collect all .rego files and auto-compile via `opa build`
50303
+ */
50304
+ async resolveWasmBytes(paths) {
50305
+ const regoFiles = [];
50306
+ for (const p of paths) {
50307
+ const resolved = path23.resolve(p);
50308
+ if (path23.normalize(resolved).includes("..")) {
50309
+ throw new Error(`Policy path contains traversal sequences: ${p}`);
50310
+ }
50311
+ if (resolved.endsWith(".wasm") && fs19.existsSync(resolved)) {
50312
+ return fs19.readFileSync(resolved);
50313
+ }
50314
+ if (!fs19.existsSync(resolved)) continue;
50315
+ const stat = fs19.statSync(resolved);
50316
+ if (stat.isDirectory()) {
50317
+ const wasmCandidate = path23.join(resolved, "policy.wasm");
50318
+ if (fs19.existsSync(wasmCandidate)) {
50319
+ return fs19.readFileSync(wasmCandidate);
50320
+ }
50321
+ const files = fs19.readdirSync(resolved);
50322
+ for (const f of files) {
50323
+ if (f.endsWith(".rego")) {
50324
+ regoFiles.push(path23.join(resolved, f));
50325
+ }
50326
+ }
50327
+ } else if (resolved.endsWith(".rego")) {
50328
+ regoFiles.push(resolved);
50329
+ }
50330
+ }
50331
+ if (regoFiles.length === 0) {
50332
+ throw new Error(
50333
+ `OPA WASM evaluator: no .wasm bundle or .rego files found in: ${paths.join(", ")}`
50334
+ );
50335
+ }
50336
+ return this.compileRego(regoFiles);
50337
+ }
50338
+ /**
50339
+ * Auto-compile .rego files to a WASM bundle using the `opa` CLI.
50340
+ *
50341
+ * Caches the compiled bundle based on a content hash of all input .rego files
50342
+ * so subsequent runs skip compilation if policies haven't changed.
50343
+ */
50344
+ compileRego(regoFiles) {
50345
+ try {
50346
+ (0, import_child_process5.execFileSync)("opa", ["version"], { stdio: "pipe" });
50347
+ } catch {
50348
+ throw new Error(
50349
+ "OPA CLI (`opa`) not found on PATH. Install it from https://www.openpolicyagent.org/docs/latest/#running-opa\nOr pre-compile your .rego files: opa build -t wasm -e visor -o bundle.tar.gz " + regoFiles.join(" ")
50350
+ );
50351
+ }
50352
+ const hash = crypto3.createHash("sha256");
50353
+ for (const f of regoFiles.sort()) {
50354
+ hash.update(fs19.readFileSync(f));
50355
+ hash.update(f);
50356
+ }
50357
+ const cacheKey = hash.digest("hex").slice(0, 16);
50358
+ const cacheDir = _OpaCompiler.CACHE_DIR;
50359
+ const cachedWasm = path23.join(cacheDir, `${cacheKey}.wasm`);
50360
+ if (fs19.existsSync(cachedWasm)) {
50361
+ return fs19.readFileSync(cachedWasm);
50362
+ }
50363
+ fs19.mkdirSync(cacheDir, { recursive: true });
50364
+ const bundleTar = path23.join(cacheDir, `${cacheKey}-bundle.tar.gz`);
50365
+ try {
50366
+ const args = [
50367
+ "build",
50368
+ "-t",
50369
+ "wasm",
50370
+ "-e",
50371
+ "visor",
50372
+ // entrypoint: the visor package tree
50373
+ "-o",
50374
+ bundleTar,
50375
+ ...regoFiles
50376
+ ];
50377
+ (0, import_child_process5.execFileSync)("opa", args, {
50378
+ stdio: "pipe",
50379
+ timeout: 3e4
50380
+ });
50381
+ } catch (err) {
50382
+ const stderr = err?.stderr?.toString() || "";
50383
+ throw new Error(
50384
+ `Failed to compile .rego files to WASM:
50385
+ ${stderr}
50386
+ Ensure your .rego files are valid and the \`opa\` CLI is installed.`
50387
+ );
50388
+ }
50389
+ try {
50390
+ (0, import_child_process5.execFileSync)("tar", ["-xzf", bundleTar, "-C", cacheDir, "/policy.wasm"], {
50391
+ stdio: "pipe"
50392
+ });
50393
+ const extractedWasm = path23.join(cacheDir, "policy.wasm");
50394
+ if (fs19.existsSync(extractedWasm)) {
50395
+ fs19.renameSync(extractedWasm, cachedWasm);
50396
+ }
50397
+ } catch {
50398
+ try {
50399
+ (0, import_child_process5.execFileSync)("tar", ["-xzf", bundleTar, "-C", cacheDir, "policy.wasm"], {
50400
+ stdio: "pipe"
50401
+ });
50402
+ const extractedWasm = path23.join(cacheDir, "policy.wasm");
50403
+ if (fs19.existsSync(extractedWasm)) {
50404
+ fs19.renameSync(extractedWasm, cachedWasm);
50405
+ }
50406
+ } catch (err2) {
50407
+ throw new Error(`Failed to extract policy.wasm from OPA bundle: ${err2?.message || err2}`);
50408
+ }
50409
+ }
50410
+ try {
50411
+ fs19.unlinkSync(bundleTar);
50412
+ } catch {
50413
+ }
50414
+ if (!fs19.existsSync(cachedWasm)) {
50415
+ throw new Error("OPA build succeeded but policy.wasm was not found in the bundle");
50416
+ }
50417
+ return fs19.readFileSync(cachedWasm);
50418
+ }
50419
+ };
50420
+ }
50421
+ });
50422
+
50423
+ // src/enterprise/policy/opa-wasm-evaluator.ts
50424
+ var fs20, path24, OpaWasmEvaluator;
50425
+ var init_opa_wasm_evaluator = __esm({
50426
+ "src/enterprise/policy/opa-wasm-evaluator.ts"() {
50427
+ "use strict";
50428
+ fs20 = __toESM(require("fs"));
50429
+ path24 = __toESM(require("path"));
50430
+ init_opa_compiler();
50431
+ OpaWasmEvaluator = class {
50432
+ policy = null;
50433
+ dataDocument = {};
50434
+ compiler = new OpaCompiler();
50435
+ async initialize(rulesPath) {
50436
+ const paths = Array.isArray(rulesPath) ? rulesPath : [rulesPath];
50437
+ const wasmBytes = await this.compiler.resolveWasmBytes(paths);
50438
+ try {
50439
+ const { createRequire } = require("module");
50440
+ const runtimeRequire = createRequire(__filename);
50441
+ const opaWasm = runtimeRequire("@open-policy-agent/opa-wasm");
50442
+ const loadPolicy = opaWasm.loadPolicy || opaWasm.default?.loadPolicy;
50443
+ if (!loadPolicy) {
50444
+ throw new Error("loadPolicy not found in @open-policy-agent/opa-wasm");
50445
+ }
50446
+ this.policy = await loadPolicy(wasmBytes);
50447
+ } catch (err) {
50448
+ if (err?.code === "MODULE_NOT_FOUND" || err?.code === "ERR_MODULE_NOT_FOUND") {
50449
+ throw new Error(
50450
+ "OPA WASM evaluator requires @open-policy-agent/opa-wasm. Install it with: npm install @open-policy-agent/opa-wasm"
50451
+ );
50452
+ }
50453
+ throw err;
50454
+ }
50455
+ }
50456
+ /**
50457
+ * Load external data from a JSON file to use as the OPA data document.
50458
+ * The loaded data will be passed to `policy.setData()` during evaluation,
50459
+ * making it available in Rego via `data.<key>`.
50460
+ */
50461
+ loadData(dataPath) {
50462
+ const resolved = path24.resolve(dataPath);
50463
+ if (path24.normalize(resolved).includes("..")) {
50464
+ throw new Error(`Data path contains traversal sequences: ${dataPath}`);
50465
+ }
50466
+ if (!fs20.existsSync(resolved)) {
50467
+ throw new Error(`OPA data file not found: ${resolved}`);
50468
+ }
50469
+ const stat = fs20.statSync(resolved);
50470
+ if (stat.size > 10 * 1024 * 1024) {
50471
+ throw new Error(`OPA data file exceeds 10MB limit: ${resolved} (${stat.size} bytes)`);
50472
+ }
50473
+ const raw = fs20.readFileSync(resolved, "utf-8");
50474
+ try {
50475
+ const parsed = JSON.parse(raw);
50476
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
50477
+ throw new Error("OPA data file must contain a JSON object (not an array or primitive)");
50478
+ }
50479
+ this.dataDocument = parsed;
50480
+ } catch (err) {
50481
+ if (err.message.startsWith("OPA data file must")) {
50482
+ throw err;
50483
+ }
50484
+ throw new Error(`Failed to parse OPA data file ${resolved}: ${err.message}`);
50485
+ }
50486
+ }
50487
+ async evaluate(input) {
50488
+ if (!this.policy) {
50489
+ throw new Error("OPA WASM evaluator not initialized");
50490
+ }
50491
+ this.policy.setData(this.dataDocument);
50492
+ const resultSet = this.policy.evaluate(input);
50493
+ if (Array.isArray(resultSet) && resultSet.length > 0) {
50494
+ return resultSet[0].result;
50495
+ }
50496
+ return void 0;
50497
+ }
50498
+ async shutdown() {
50499
+ if (this.policy) {
50500
+ if (typeof this.policy.close === "function") {
50501
+ try {
50502
+ this.policy.close();
50503
+ } catch {
50504
+ }
50505
+ } else if (typeof this.policy.free === "function") {
50506
+ try {
50507
+ this.policy.free();
50508
+ } catch {
50509
+ }
50510
+ }
50511
+ }
50512
+ this.policy = null;
50513
+ }
50514
+ };
50515
+ }
50516
+ });
50517
+
50518
+ // src/enterprise/policy/opa-http-evaluator.ts
50519
+ var OpaHttpEvaluator;
50520
+ var init_opa_http_evaluator = __esm({
50521
+ "src/enterprise/policy/opa-http-evaluator.ts"() {
50522
+ "use strict";
50523
+ OpaHttpEvaluator = class {
50524
+ baseUrl;
50525
+ timeout;
50526
+ constructor(baseUrl, timeout = 5e3) {
50527
+ let parsed;
50528
+ try {
50529
+ parsed = new URL(baseUrl);
50530
+ } catch {
50531
+ throw new Error(`OPA HTTP evaluator: invalid URL: ${baseUrl}`);
50532
+ }
50533
+ if (!["http:", "https:"].includes(parsed.protocol)) {
50534
+ throw new Error(
50535
+ `OPA HTTP evaluator: url must use http:// or https:// protocol, got: ${baseUrl}`
50536
+ );
50537
+ }
50538
+ const hostname = parsed.hostname;
50539
+ if (this.isBlockedHostname(hostname)) {
50540
+ throw new Error(
50541
+ `OPA HTTP evaluator: url must not point to internal, loopback, or private network addresses`
50542
+ );
50543
+ }
50544
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
50545
+ this.timeout = timeout;
50546
+ }
50547
+ /**
50548
+ * Check if a hostname is blocked due to SSRF concerns.
50549
+ *
50550
+ * Blocks:
50551
+ * - Loopback addresses (127.x.x.x, localhost, 0.0.0.0, ::1)
50552
+ * - Link-local addresses (169.254.x.x)
50553
+ * - Private networks (10.x.x.x, 172.16-31.x.x, 192.168.x.x)
50554
+ * - IPv6 unique local addresses (fd00::/8)
50555
+ * - Cloud metadata services (*.internal)
50556
+ */
50557
+ isBlockedHostname(hostname) {
50558
+ if (!hostname) return true;
50559
+ const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
50560
+ if (normalized === "metadata.google.internal" || normalized.endsWith(".internal")) {
50561
+ return true;
50562
+ }
50563
+ if (normalized === "localhost" || normalized === "localhost.localdomain") {
50564
+ return true;
50565
+ }
50566
+ if (normalized === "::1" || normalized === "0:0:0:0:0:0:0:1") {
50567
+ return true;
50568
+ }
50569
+ const ipv4Pattern = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
50570
+ const ipv4Match = normalized.match(ipv4Pattern);
50571
+ if (ipv4Match) {
50572
+ const octets = ipv4Match.slice(1, 5).map(Number);
50573
+ if (octets.some((octet) => octet > 255)) {
50574
+ return false;
50575
+ }
50576
+ const [a, b] = octets;
50577
+ if (a === 127) {
50578
+ return true;
50579
+ }
50580
+ if (a === 0) {
50581
+ return true;
50582
+ }
50583
+ if (a === 169 && b === 254) {
50584
+ return true;
50585
+ }
50586
+ if (a === 10) {
50587
+ return true;
50588
+ }
50589
+ if (a === 172 && b >= 16 && b <= 31) {
50590
+ return true;
50591
+ }
50592
+ if (a === 192 && b === 168) {
50593
+ return true;
50594
+ }
50595
+ }
50596
+ if (normalized.startsWith("fd") || normalized.startsWith("fc")) {
50597
+ return true;
50598
+ }
50599
+ if (normalized.startsWith("fe80:")) {
50600
+ return true;
50601
+ }
50602
+ return false;
50603
+ }
50604
+ /**
50605
+ * Evaluate a policy rule against an input document via OPA REST API.
50606
+ *
50607
+ * @param input - The input document to evaluate
50608
+ * @param rulePath - OPA rule path (e.g., 'visor/check/execute')
50609
+ * @returns The result object from OPA, or undefined on error
50610
+ */
50611
+ async evaluate(input, rulePath) {
50612
+ const encodedPath = rulePath.split("/").map((s) => encodeURIComponent(s)).join("/");
50613
+ const url = `${this.baseUrl}/v1/data/${encodedPath}`;
50614
+ const controller = new AbortController();
50615
+ const timer = setTimeout(() => controller.abort(), this.timeout);
50616
+ try {
50617
+ const response = await fetch(url, {
50618
+ method: "POST",
50619
+ headers: { "Content-Type": "application/json" },
50620
+ body: JSON.stringify({ input }),
50621
+ signal: controller.signal
50622
+ });
50623
+ if (!response.ok) {
50624
+ throw new Error(`OPA HTTP ${response.status}: ${response.statusText}`);
50625
+ }
50626
+ let body;
50627
+ try {
50628
+ body = await response.json();
50629
+ } catch (jsonErr) {
50630
+ throw new Error(
50631
+ `OPA HTTP evaluator: failed to parse JSON response: ${jsonErr instanceof Error ? jsonErr.message : String(jsonErr)}`
50632
+ );
50633
+ }
50634
+ return body?.result;
50635
+ } finally {
50636
+ clearTimeout(timer);
50637
+ }
50638
+ }
50639
+ async shutdown() {
50640
+ }
50641
+ };
50642
+ }
50643
+ });
50644
+
50645
+ // src/enterprise/policy/policy-input-builder.ts
50646
+ var PolicyInputBuilder;
50647
+ var init_policy_input_builder = __esm({
50648
+ "src/enterprise/policy/policy-input-builder.ts"() {
50649
+ "use strict";
50650
+ PolicyInputBuilder = class {
50651
+ roles;
50652
+ actor;
50653
+ repository;
50654
+ pullRequest;
50655
+ constructor(policyConfig, actor, repository, pullRequest) {
50656
+ this.roles = policyConfig.roles || {};
50657
+ this.actor = actor;
50658
+ this.repository = repository;
50659
+ this.pullRequest = pullRequest;
50660
+ }
50661
+ /** Resolve which roles apply to the current actor. */
50662
+ resolveRoles() {
50663
+ const matched = [];
50664
+ for (const [roleName, roleConfig] of Object.entries(this.roles)) {
50665
+ let identityMatch = false;
50666
+ if (roleConfig.author_association && this.actor.authorAssociation && roleConfig.author_association.includes(this.actor.authorAssociation)) {
50667
+ identityMatch = true;
50668
+ }
50669
+ if (!identityMatch && roleConfig.users && this.actor.login && roleConfig.users.includes(this.actor.login)) {
50670
+ identityMatch = true;
50671
+ }
50672
+ if (!identityMatch && roleConfig.slack_users && this.actor.slack?.userId && roleConfig.slack_users.includes(this.actor.slack.userId)) {
50673
+ identityMatch = true;
50674
+ }
50675
+ if (!identityMatch && roleConfig.emails && this.actor.slack?.email) {
50676
+ const actorEmail = this.actor.slack.email.toLowerCase();
50677
+ if (roleConfig.emails.some((e) => e.toLowerCase() === actorEmail)) {
50678
+ identityMatch = true;
50679
+ }
50680
+ }
50681
+ if (!identityMatch) continue;
50682
+ if (roleConfig.slack_channels && roleConfig.slack_channels.length > 0) {
50683
+ if (!this.actor.slack?.channelId || !roleConfig.slack_channels.includes(this.actor.slack.channelId)) {
50684
+ continue;
50685
+ }
50686
+ }
50687
+ matched.push(roleName);
50688
+ }
50689
+ return matched;
50690
+ }
50691
+ buildActor() {
50692
+ return {
50693
+ authorAssociation: this.actor.authorAssociation,
50694
+ login: this.actor.login,
50695
+ roles: this.resolveRoles(),
50696
+ isLocalMode: this.actor.isLocalMode,
50697
+ ...this.actor.slack && { slack: this.actor.slack }
50698
+ };
50699
+ }
50700
+ forCheckExecution(check) {
50701
+ return {
50702
+ scope: "check.execute",
50703
+ check: {
50704
+ id: check.id,
50705
+ type: check.type,
50706
+ group: check.group,
50707
+ tags: check.tags,
50708
+ criticality: check.criticality,
50709
+ sandbox: check.sandbox,
50710
+ policy: check.policy
50711
+ },
50712
+ actor: this.buildActor(),
50713
+ repository: this.repository,
50714
+ pullRequest: this.pullRequest
50715
+ };
50716
+ }
50717
+ forToolInvocation(serverName, methodName, transport) {
50718
+ return {
50719
+ scope: "tool.invoke",
50720
+ tool: { serverName, methodName, transport },
50721
+ actor: this.buildActor(),
50722
+ repository: this.repository,
50723
+ pullRequest: this.pullRequest
50724
+ };
50725
+ }
50726
+ forCapabilityResolve(checkId, capabilities) {
50727
+ return {
50728
+ scope: "capability.resolve",
50729
+ check: { id: checkId, type: "ai" },
50730
+ capability: capabilities,
50731
+ actor: this.buildActor(),
50732
+ repository: this.repository,
50733
+ pullRequest: this.pullRequest
50734
+ };
50735
+ }
50736
+ };
50737
+ }
50738
+ });
50739
+
50740
+ // src/enterprise/policy/opa-policy-engine.ts
50741
+ var opa_policy_engine_exports = {};
50742
+ __export(opa_policy_engine_exports, {
50743
+ OpaPolicyEngine: () => OpaPolicyEngine
50744
+ });
50745
+ var OpaPolicyEngine;
50746
+ var init_opa_policy_engine = __esm({
50747
+ "src/enterprise/policy/opa-policy-engine.ts"() {
50748
+ "use strict";
50749
+ init_opa_wasm_evaluator();
50750
+ init_opa_http_evaluator();
50751
+ init_policy_input_builder();
50752
+ OpaPolicyEngine = class {
50753
+ evaluator = null;
50754
+ fallback;
50755
+ timeout;
50756
+ config;
50757
+ inputBuilder = null;
50758
+ logger = null;
50759
+ constructor(config) {
50760
+ this.config = config;
50761
+ this.fallback = config.fallback || "deny";
50762
+ this.timeout = config.timeout || 5e3;
50763
+ }
50764
+ async initialize(config) {
50765
+ try {
50766
+ this.logger = (init_logger(), __toCommonJS(logger_exports)).logger;
50767
+ } catch {
50768
+ }
50769
+ const actor = {
50770
+ authorAssociation: process.env.VISOR_AUTHOR_ASSOCIATION,
50771
+ login: process.env.VISOR_AUTHOR_LOGIN || process.env.GITHUB_ACTOR,
50772
+ isLocalMode: !process.env.GITHUB_ACTIONS
50773
+ };
50774
+ const repo = {
50775
+ owner: process.env.GITHUB_REPOSITORY_OWNER,
50776
+ name: process.env.GITHUB_REPOSITORY?.split("/")[1],
50777
+ branch: process.env.GITHUB_HEAD_REF,
50778
+ baseBranch: process.env.GITHUB_BASE_REF,
50779
+ event: process.env.GITHUB_EVENT_NAME
50780
+ };
50781
+ const prNum = process.env.GITHUB_PR_NUMBER ? parseInt(process.env.GITHUB_PR_NUMBER, 10) : void 0;
50782
+ const pullRequest = {
50783
+ number: prNum !== void 0 && Number.isFinite(prNum) ? prNum : void 0
50784
+ };
50785
+ this.inputBuilder = new PolicyInputBuilder(config, actor, repo, pullRequest);
50786
+ if (config.engine === "local") {
50787
+ if (!config.rules) {
50788
+ throw new Error("OPA local mode requires `policy.rules` path to .wasm or .rego files");
50789
+ }
50790
+ const wasm = new OpaWasmEvaluator();
50791
+ await wasm.initialize(config.rules);
50792
+ if (config.data) {
50793
+ wasm.loadData(config.data);
50794
+ }
50795
+ this.evaluator = wasm;
50796
+ } else if (config.engine === "remote") {
50797
+ if (!config.url) {
50798
+ throw new Error("OPA remote mode requires `policy.url` pointing to OPA server");
50799
+ }
50800
+ this.evaluator = new OpaHttpEvaluator(config.url, this.timeout);
50801
+ } else {
50802
+ this.evaluator = null;
50803
+ }
50804
+ }
50805
+ /**
50806
+ * Update actor/repo/PR context (e.g., after PR info becomes available).
50807
+ * Called by the enterprise loader when engine context is enriched.
50808
+ */
50809
+ setActorContext(actor, repo, pullRequest) {
50810
+ this.inputBuilder = new PolicyInputBuilder(this.config, actor, repo, pullRequest);
50811
+ }
50812
+ async evaluateCheckExecution(checkId, checkConfig) {
50813
+ if (!this.evaluator || !this.inputBuilder) return { allowed: true };
50814
+ const cfg = checkConfig && typeof checkConfig === "object" ? checkConfig : {};
50815
+ const policyOverride = cfg.policy;
50816
+ const input = this.inputBuilder.forCheckExecution({
50817
+ id: checkId,
50818
+ type: cfg.type || "ai",
50819
+ group: cfg.group,
50820
+ tags: cfg.tags,
50821
+ criticality: cfg.criticality,
50822
+ sandbox: cfg.sandbox,
50823
+ policy: policyOverride
50824
+ });
50825
+ return this.doEvaluate(input, this.resolveRulePath("check.execute", policyOverride?.rule));
50826
+ }
50827
+ async evaluateToolInvocation(serverName, methodName, transport) {
50828
+ if (!this.evaluator || !this.inputBuilder) return { allowed: true };
50829
+ const input = this.inputBuilder.forToolInvocation(serverName, methodName, transport);
50830
+ return this.doEvaluate(input, "visor/tool/invoke");
50831
+ }
50832
+ async evaluateCapabilities(checkId, capabilities) {
50833
+ if (!this.evaluator || !this.inputBuilder) return { allowed: true };
50834
+ const input = this.inputBuilder.forCapabilityResolve(checkId, capabilities);
50835
+ return this.doEvaluate(input, "visor/capability/resolve");
50836
+ }
50837
+ async shutdown() {
50838
+ if (this.evaluator && "shutdown" in this.evaluator) {
50839
+ await this.evaluator.shutdown();
50840
+ }
50841
+ this.evaluator = null;
50842
+ this.inputBuilder = null;
50843
+ }
50844
+ resolveRulePath(defaultScope, override) {
50845
+ if (override) {
50846
+ return override.startsWith("visor/") ? override : `visor/${override}`;
50847
+ }
50848
+ return `visor/${defaultScope.replace(/\./g, "/")}`;
50849
+ }
50850
+ async doEvaluate(input, rulePath) {
50851
+ try {
50852
+ this.logger?.debug(`[PolicyEngine] Evaluating ${rulePath}`, JSON.stringify(input));
50853
+ let timer;
50854
+ const timeoutPromise = new Promise((_resolve, reject) => {
50855
+ timer = setTimeout(() => reject(new Error("policy evaluation timeout")), this.timeout);
50856
+ });
50857
+ try {
50858
+ const result = await Promise.race([this.rawEvaluate(input, rulePath), timeoutPromise]);
50859
+ const decision = this.parseDecision(result);
50860
+ if (!decision.allowed && this.fallback === "warn") {
50861
+ decision.allowed = true;
50862
+ decision.warn = true;
50863
+ decision.reason = `audit: ${decision.reason || "policy denied"}`;
50864
+ }
50865
+ this.logger?.debug(
50866
+ `[PolicyEngine] Decision for ${rulePath}: allowed=${decision.allowed}, warn=${decision.warn || false}, reason=${decision.reason || "none"}`
50867
+ );
50868
+ return decision;
50869
+ } finally {
50870
+ if (timer) clearTimeout(timer);
50871
+ }
50872
+ } catch (err) {
50873
+ const msg = err instanceof Error ? err.message : String(err);
50874
+ this.logger?.warn(`[PolicyEngine] Evaluation failed for ${rulePath}: ${msg}`);
50875
+ return {
50876
+ allowed: this.fallback === "allow" || this.fallback === "warn",
50877
+ warn: this.fallback === "warn" ? true : void 0,
50878
+ reason: `policy evaluation failed, fallback=${this.fallback}`
50879
+ };
50880
+ }
50881
+ }
50882
+ async rawEvaluate(input, rulePath) {
50883
+ if (this.evaluator instanceof OpaWasmEvaluator) {
50884
+ const result = await this.evaluator.evaluate(input);
50885
+ return this.navigateWasmResult(result, rulePath);
50886
+ }
50887
+ return this.evaluator.evaluate(input, rulePath);
50888
+ }
50889
+ /**
50890
+ * Navigate nested OPA WASM result tree to reach the specific rule's output.
50891
+ * The WASM entrypoint `-e visor` means the result root IS the visor package,
50892
+ * so we strip the `visor/` prefix and walk the remaining segments.
50893
+ */
50894
+ navigateWasmResult(result, rulePath) {
50895
+ if (!result || typeof result !== "object") return result;
50896
+ const segments = rulePath.replace(/^visor\//, "").split("/");
50897
+ let current = result;
50898
+ for (const seg of segments) {
50899
+ if (current && typeof current === "object" && seg in current) {
50900
+ current = current[seg];
50901
+ } else {
50902
+ return void 0;
50903
+ }
50904
+ }
50905
+ return current;
50906
+ }
50907
+ parseDecision(result) {
50908
+ if (result === void 0 || result === null) {
50909
+ return {
50910
+ allowed: this.fallback === "allow" || this.fallback === "warn",
50911
+ warn: this.fallback === "warn" ? true : void 0,
50912
+ reason: this.fallback === "warn" ? "audit: no policy result" : "no policy result"
50913
+ };
50914
+ }
50915
+ const allowed = result.allowed !== false;
50916
+ const decision = {
50917
+ allowed,
50918
+ reason: result.reason
50919
+ };
50920
+ if (result.capabilities) {
50921
+ decision.capabilities = result.capabilities;
50922
+ }
50923
+ return decision;
50924
+ }
50925
+ };
50926
+ }
50927
+ });
50928
+
50929
+ // src/enterprise/scheduler/knex-store.ts
50930
+ var knex_store_exports = {};
50931
+ __export(knex_store_exports, {
50932
+ KnexStoreBackend: () => KnexStoreBackend
50933
+ });
50934
+ function toNum(val) {
50935
+ if (val === null || val === void 0) return void 0;
50936
+ return typeof val === "string" ? parseInt(val, 10) : val;
50937
+ }
50938
+ function safeJsonParse2(value) {
50939
+ if (!value) return void 0;
50940
+ try {
50941
+ return JSON.parse(value);
50942
+ } catch {
50943
+ return void 0;
50944
+ }
50945
+ }
50946
+ function fromDbRow2(row) {
50947
+ return {
50948
+ id: row.id,
50949
+ creatorId: row.creator_id,
50950
+ creatorContext: row.creator_context ?? void 0,
50951
+ creatorName: row.creator_name ?? void 0,
50952
+ timezone: row.timezone,
50953
+ schedule: row.schedule_expr,
50954
+ runAt: toNum(row.run_at),
50955
+ isRecurring: row.is_recurring === true || row.is_recurring === 1,
50956
+ originalExpression: row.original_expression,
50957
+ workflow: row.workflow ?? void 0,
50958
+ workflowInputs: safeJsonParse2(row.workflow_inputs),
50959
+ outputContext: safeJsonParse2(row.output_context),
50960
+ status: row.status,
50961
+ createdAt: toNum(row.created_at),
50962
+ lastRunAt: toNum(row.last_run_at),
50963
+ nextRunAt: toNum(row.next_run_at),
50964
+ runCount: row.run_count,
50965
+ failureCount: row.failure_count,
50966
+ lastError: row.last_error ?? void 0,
50967
+ previousResponse: row.previous_response ?? void 0
50968
+ };
50969
+ }
50970
+ function toInsertRow(schedule) {
50971
+ return {
50972
+ id: schedule.id,
50973
+ creator_id: schedule.creatorId,
50974
+ creator_context: schedule.creatorContext ?? null,
50975
+ creator_name: schedule.creatorName ?? null,
50976
+ timezone: schedule.timezone,
50977
+ schedule_expr: schedule.schedule,
50978
+ run_at: schedule.runAt ?? null,
50979
+ is_recurring: schedule.isRecurring,
50980
+ original_expression: schedule.originalExpression,
50981
+ workflow: schedule.workflow ?? null,
50982
+ workflow_inputs: schedule.workflowInputs ? JSON.stringify(schedule.workflowInputs) : null,
50983
+ output_context: schedule.outputContext ? JSON.stringify(schedule.outputContext) : null,
50984
+ status: schedule.status,
50985
+ created_at: schedule.createdAt,
50986
+ last_run_at: schedule.lastRunAt ?? null,
50987
+ next_run_at: schedule.nextRunAt ?? null,
50988
+ run_count: schedule.runCount,
50989
+ failure_count: schedule.failureCount,
50990
+ last_error: schedule.lastError ?? null,
50991
+ previous_response: schedule.previousResponse ?? null
50992
+ };
50993
+ }
50994
+ var fs21, path25, import_uuid2, KnexStoreBackend;
50995
+ var init_knex_store = __esm({
50996
+ "src/enterprise/scheduler/knex-store.ts"() {
50997
+ "use strict";
50998
+ fs21 = __toESM(require("fs"));
50999
+ path25 = __toESM(require("path"));
51000
+ import_uuid2 = require("uuid");
51001
+ init_logger();
51002
+ KnexStoreBackend = class {
51003
+ knex = null;
51004
+ driver;
51005
+ connection;
51006
+ constructor(driver, storageConfig, _haConfig) {
51007
+ this.driver = driver;
51008
+ this.connection = storageConfig.connection || {};
51009
+ }
51010
+ async initialize() {
51011
+ const { createRequire } = require("module");
51012
+ const runtimeRequire = createRequire(__filename);
51013
+ let knexFactory;
51014
+ try {
51015
+ knexFactory = runtimeRequire("knex");
51016
+ } catch (err) {
51017
+ const code = err?.code;
51018
+ if (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") {
51019
+ throw new Error(
51020
+ "knex is required for PostgreSQL/MySQL/MSSQL schedule storage. Install it with: npm install knex"
51021
+ );
51022
+ }
51023
+ throw err;
51024
+ }
51025
+ const clientMap = {
51026
+ postgresql: "pg",
51027
+ mysql: "mysql2",
51028
+ mssql: "tedious"
51029
+ };
51030
+ const client = clientMap[this.driver];
51031
+ let connection;
51032
+ if (this.connection.connection_string) {
51033
+ connection = this.connection.connection_string;
51034
+ } else if (this.driver === "mssql") {
51035
+ connection = this.buildMssqlConnection();
51036
+ } else {
51037
+ connection = this.buildStandardConnection();
51038
+ }
51039
+ this.knex = knexFactory({
51040
+ client,
51041
+ connection,
51042
+ pool: {
51043
+ min: this.connection.pool?.min ?? 0,
51044
+ max: this.connection.pool?.max ?? 10
51045
+ }
51046
+ });
51047
+ await this.migrateSchema();
51048
+ logger.info(`[KnexStore] Initialized (${this.driver})`);
51049
+ }
51050
+ buildStandardConnection() {
51051
+ return {
51052
+ host: this.connection.host || "localhost",
51053
+ port: this.connection.port,
51054
+ database: this.connection.database || "visor",
51055
+ user: this.connection.user,
51056
+ password: this.connection.password,
51057
+ ssl: this.resolveSslConfig()
51058
+ };
51059
+ }
51060
+ buildMssqlConnection() {
51061
+ const ssl = this.connection.ssl;
51062
+ const sslEnabled = ssl === true || typeof ssl === "object" && ssl.enabled !== false;
51063
+ return {
51064
+ server: this.connection.host || "localhost",
51065
+ port: this.connection.port,
51066
+ database: this.connection.database || "visor",
51067
+ user: this.connection.user,
51068
+ password: this.connection.password,
51069
+ options: {
51070
+ encrypt: sslEnabled,
51071
+ trustServerCertificate: typeof ssl === "object" ? ssl.reject_unauthorized === false : !sslEnabled
51072
+ }
51073
+ };
51074
+ }
51075
+ resolveSslConfig() {
51076
+ const ssl = this.connection.ssl;
51077
+ if (ssl === false || ssl === void 0) return false;
51078
+ if (ssl === true) return { rejectUnauthorized: true };
51079
+ if (ssl.enabled === false) return false;
51080
+ const result = {
51081
+ rejectUnauthorized: ssl.reject_unauthorized !== false
51082
+ };
51083
+ if (ssl.ca) {
51084
+ const caPath = this.validateSslPath(ssl.ca, "CA certificate");
51085
+ result.ca = fs21.readFileSync(caPath, "utf8");
51086
+ }
51087
+ if (ssl.cert) {
51088
+ const certPath = this.validateSslPath(ssl.cert, "client certificate");
51089
+ result.cert = fs21.readFileSync(certPath, "utf8");
51090
+ }
51091
+ if (ssl.key) {
51092
+ const keyPath = this.validateSslPath(ssl.key, "client key");
51093
+ result.key = fs21.readFileSync(keyPath, "utf8");
51094
+ }
51095
+ return result;
51096
+ }
51097
+ validateSslPath(filePath, label) {
51098
+ const resolved = path25.resolve(filePath);
51099
+ if (resolved !== path25.normalize(resolved)) {
51100
+ throw new Error(`SSL ${label} path contains invalid sequences: ${filePath}`);
51101
+ }
51102
+ if (!fs21.existsSync(resolved)) {
51103
+ throw new Error(`SSL ${label} not found: ${filePath}`);
51104
+ }
51105
+ return resolved;
51106
+ }
51107
+ async shutdown() {
51108
+ if (this.knex) {
51109
+ await this.knex.destroy();
51110
+ this.knex = null;
51111
+ }
51112
+ }
51113
+ async migrateSchema() {
51114
+ const knex = this.getKnex();
51115
+ const exists = await knex.schema.hasTable("schedules");
51116
+ if (!exists) {
51117
+ await knex.schema.createTable("schedules", (table) => {
51118
+ table.string("id", 36).primary();
51119
+ table.string("creator_id", 255).notNullable().index();
51120
+ table.string("creator_context", 255);
51121
+ table.string("creator_name", 255);
51122
+ table.string("timezone", 64).notNullable().defaultTo("UTC");
51123
+ table.string("schedule_expr", 255);
51124
+ table.bigInteger("run_at");
51125
+ table.boolean("is_recurring").notNullable();
51126
+ table.text("original_expression");
51127
+ table.string("workflow", 255);
51128
+ table.text("workflow_inputs");
51129
+ table.text("output_context");
51130
+ table.string("status", 20).notNullable().index();
51131
+ table.bigInteger("created_at").notNullable();
51132
+ table.bigInteger("last_run_at");
51133
+ table.bigInteger("next_run_at");
51134
+ table.integer("run_count").notNullable().defaultTo(0);
51135
+ table.integer("failure_count").notNullable().defaultTo(0);
51136
+ table.text("last_error");
51137
+ table.text("previous_response");
51138
+ table.index(["status", "next_run_at"]);
51139
+ });
51140
+ }
51141
+ const locksExist = await knex.schema.hasTable("scheduler_locks");
51142
+ if (!locksExist) {
51143
+ await knex.schema.createTable("scheduler_locks", (table) => {
51144
+ table.string("lock_id", 255).primary();
51145
+ table.string("node_id", 255).notNullable();
51146
+ table.string("lock_token", 36).notNullable();
51147
+ table.bigInteger("acquired_at").notNullable();
51148
+ table.bigInteger("expires_at").notNullable();
51149
+ });
51150
+ }
51151
+ }
51152
+ getKnex() {
51153
+ if (!this.knex) {
51154
+ throw new Error("[KnexStore] Not initialized. Call initialize() first.");
51155
+ }
51156
+ return this.knex;
51157
+ }
51158
+ // --- CRUD ---
51159
+ async create(schedule) {
51160
+ const knex = this.getKnex();
51161
+ const newSchedule = {
51162
+ ...schedule,
51163
+ id: (0, import_uuid2.v4)(),
51164
+ createdAt: Date.now(),
51165
+ runCount: 0,
51166
+ failureCount: 0,
51167
+ status: "active"
51168
+ };
51169
+ await knex("schedules").insert(toInsertRow(newSchedule));
51170
+ logger.info(`[KnexStore] Created schedule ${newSchedule.id} for user ${newSchedule.creatorId}`);
51171
+ return newSchedule;
51172
+ }
51173
+ async importSchedule(schedule) {
51174
+ const knex = this.getKnex();
51175
+ const existing = await knex("schedules").where("id", schedule.id).first();
51176
+ if (existing) return;
51177
+ await knex("schedules").insert(toInsertRow(schedule));
51178
+ }
51179
+ async get(id) {
51180
+ const knex = this.getKnex();
51181
+ const row = await knex("schedules").where("id", id).first();
51182
+ return row ? fromDbRow2(row) : void 0;
51183
+ }
51184
+ async update(id, patch) {
51185
+ const knex = this.getKnex();
51186
+ const existing = await knex("schedules").where("id", id).first();
51187
+ if (!existing) return void 0;
51188
+ const current = fromDbRow2(existing);
51189
+ const updated = { ...current, ...patch, id: current.id };
51190
+ const row = toInsertRow(updated);
51191
+ delete row.id;
51192
+ await knex("schedules").where("id", id).update(row);
51193
+ return updated;
51194
+ }
51195
+ async delete(id) {
51196
+ const knex = this.getKnex();
51197
+ const deleted = await knex("schedules").where("id", id).del();
51198
+ if (deleted > 0) {
51199
+ logger.info(`[KnexStore] Deleted schedule ${id}`);
51200
+ return true;
51201
+ }
51202
+ return false;
51203
+ }
51204
+ // --- Queries ---
51205
+ async getByCreator(creatorId) {
51206
+ const knex = this.getKnex();
51207
+ const rows = await knex("schedules").where("creator_id", creatorId);
51208
+ return rows.map((r) => fromDbRow2(r));
51209
+ }
51210
+ async getActiveSchedules() {
51211
+ const knex = this.getKnex();
51212
+ const rows = await knex("schedules").where("status", "active");
51213
+ return rows.map((r) => fromDbRow2(r));
51214
+ }
51215
+ async getDueSchedules(now) {
51216
+ const ts = now ?? Date.now();
51217
+ const knex = this.getKnex();
51218
+ const bFalse = this.driver === "mssql" ? 0 : false;
51219
+ const bTrue = this.driver === "mssql" ? 1 : true;
51220
+ const rows = await knex("schedules").where("status", "active").andWhere(function() {
51221
+ this.where(function() {
51222
+ this.where("is_recurring", bFalse).whereNotNull("run_at").where("run_at", "<=", ts);
51223
+ }).orWhere(function() {
51224
+ this.where("is_recurring", bTrue).whereNotNull("next_run_at").where("next_run_at", "<=", ts);
51225
+ });
51226
+ });
51227
+ return rows.map((r) => fromDbRow2(r));
51228
+ }
51229
+ async findByWorkflow(creatorId, workflowName) {
51230
+ const knex = this.getKnex();
51231
+ const escaped = workflowName.toLowerCase().replace(/[%_\\]/g, "\\$&");
51232
+ const pattern = `%${escaped}%`;
51233
+ const rows = await knex("schedules").where("creator_id", creatorId).where("status", "active").whereRaw("LOWER(workflow) LIKE ? ESCAPE '\\'", [pattern]);
51234
+ return rows.map((r) => fromDbRow2(r));
51235
+ }
51236
+ async getAll() {
51237
+ const knex = this.getKnex();
51238
+ const rows = await knex("schedules");
51239
+ return rows.map((r) => fromDbRow2(r));
51240
+ }
51241
+ async getStats() {
51242
+ const knex = this.getKnex();
51243
+ const boolTrue = this.driver === "mssql" ? "1" : "true";
51244
+ const boolFalse = this.driver === "mssql" ? "0" : "false";
51245
+ const result = await knex("schedules").select(
51246
+ knex.raw("COUNT(*) as total"),
51247
+ knex.raw("SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active"),
51248
+ knex.raw("SUM(CASE WHEN status = 'paused' THEN 1 ELSE 0 END) as paused"),
51249
+ knex.raw("SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed"),
51250
+ knex.raw("SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed"),
51251
+ knex.raw(`SUM(CASE WHEN is_recurring = ${boolTrue} THEN 1 ELSE 0 END) as recurring`),
51252
+ knex.raw(`SUM(CASE WHEN is_recurring = ${boolFalse} THEN 1 ELSE 0 END) as one_time`)
51253
+ ).first();
51254
+ return {
51255
+ total: Number(result.total) || 0,
51256
+ active: Number(result.active) || 0,
51257
+ paused: Number(result.paused) || 0,
51258
+ completed: Number(result.completed) || 0,
51259
+ failed: Number(result.failed) || 0,
51260
+ recurring: Number(result.recurring) || 0,
51261
+ oneTime: Number(result.one_time) || 0
51262
+ };
51263
+ }
51264
+ async validateLimits(creatorId, isRecurring, limits) {
51265
+ const knex = this.getKnex();
51266
+ if (limits.maxGlobal) {
51267
+ const result = await knex("schedules").count("* as cnt").first();
51268
+ if (Number(result?.cnt) >= limits.maxGlobal) {
51269
+ throw new Error(`Global schedule limit reached (${limits.maxGlobal})`);
51270
+ }
51271
+ }
51272
+ if (limits.maxPerUser) {
51273
+ const result = await knex("schedules").where("creator_id", creatorId).count("* as cnt").first();
51274
+ if (Number(result?.cnt) >= limits.maxPerUser) {
51275
+ throw new Error(`You have reached the maximum number of schedules (${limits.maxPerUser})`);
51276
+ }
51277
+ }
51278
+ if (isRecurring && limits.maxRecurringPerUser) {
51279
+ const bTrue = this.driver === "mssql" ? 1 : true;
51280
+ const result = await knex("schedules").where("creator_id", creatorId).where("is_recurring", bTrue).count("* as cnt").first();
51281
+ if (Number(result?.cnt) >= limits.maxRecurringPerUser) {
51282
+ throw new Error(
51283
+ `You have reached the maximum number of recurring schedules (${limits.maxRecurringPerUser})`
51284
+ );
51285
+ }
51286
+ }
51287
+ }
51288
+ // --- HA Distributed Locking (via scheduler_locks table) ---
51289
+ async tryAcquireLock(lockId, nodeId, ttlSeconds) {
51290
+ const knex = this.getKnex();
51291
+ const now = Date.now();
51292
+ const expiresAt = now + ttlSeconds * 1e3;
51293
+ const token = (0, import_uuid2.v4)();
51294
+ const updated = await knex("scheduler_locks").where("lock_id", lockId).where("expires_at", "<", now).update({
51295
+ node_id: nodeId,
51296
+ lock_token: token,
51297
+ acquired_at: now,
51298
+ expires_at: expiresAt
51299
+ });
51300
+ if (updated > 0) return token;
51301
+ try {
51302
+ await knex("scheduler_locks").insert({
51303
+ lock_id: lockId,
51304
+ node_id: nodeId,
51305
+ lock_token: token,
51306
+ acquired_at: now,
51307
+ expires_at: expiresAt
51308
+ });
51309
+ return token;
51310
+ } catch {
51311
+ return null;
51312
+ }
51313
+ }
51314
+ async releaseLock(lockId, lockToken) {
51315
+ const knex = this.getKnex();
51316
+ await knex("scheduler_locks").where("lock_id", lockId).where("lock_token", lockToken).del();
51317
+ }
51318
+ async renewLock(lockId, lockToken, ttlSeconds) {
51319
+ const knex = this.getKnex();
51320
+ const now = Date.now();
51321
+ const expiresAt = now + ttlSeconds * 1e3;
51322
+ const updated = await knex("scheduler_locks").where("lock_id", lockId).where("lock_token", lockToken).update({ acquired_at: now, expires_at: expiresAt });
51323
+ return updated > 0;
51324
+ }
51325
+ async flush() {
51326
+ }
51327
+ };
51328
+ }
51329
+ });
51330
+
51331
+ // src/enterprise/loader.ts
51332
+ var loader_exports = {};
51333
+ __export(loader_exports, {
51334
+ loadEnterprisePolicyEngine: () => loadEnterprisePolicyEngine,
51335
+ loadEnterpriseStoreBackend: () => loadEnterpriseStoreBackend
51336
+ });
51337
+ async function loadEnterprisePolicyEngine(config) {
51338
+ try {
51339
+ const { LicenseValidator: LicenseValidator2 } = await Promise.resolve().then(() => (init_validator(), validator_exports));
51340
+ const validator = new LicenseValidator2();
51341
+ const license = await validator.loadAndValidate();
51342
+ if (!license || !validator.hasFeature("policy")) {
51343
+ return new DefaultPolicyEngine();
51344
+ }
51345
+ if (validator.isInGracePeriod()) {
51346
+ console.warn(
51347
+ "[visor:enterprise] License has expired but is within the 72-hour grace period. Please renew your license."
51348
+ );
51349
+ }
51350
+ const { OpaPolicyEngine: OpaPolicyEngine2 } = await Promise.resolve().then(() => (init_opa_policy_engine(), opa_policy_engine_exports));
51351
+ const engine = new OpaPolicyEngine2(config);
51352
+ await engine.initialize(config);
51353
+ return engine;
51354
+ } catch (err) {
51355
+ const msg = err instanceof Error ? err.message : String(err);
51356
+ try {
51357
+ const { logger: logger2 } = (init_logger(), __toCommonJS(logger_exports));
51358
+ logger2.warn(`[PolicyEngine] Enterprise policy init failed, falling back to default: ${msg}`);
51359
+ } catch {
51360
+ }
51361
+ return new DefaultPolicyEngine();
51362
+ }
51363
+ }
51364
+ async function loadEnterpriseStoreBackend(driver, storageConfig, haConfig) {
51365
+ const { LicenseValidator: LicenseValidator2 } = await Promise.resolve().then(() => (init_validator(), validator_exports));
51366
+ const validator = new LicenseValidator2();
51367
+ const license = await validator.loadAndValidate();
51368
+ if (!license || !validator.hasFeature("scheduler-sql")) {
51369
+ throw new Error(
51370
+ `The ${driver} schedule storage driver requires a Visor Enterprise license with the 'scheduler-sql' feature. Please upgrade or use driver: 'sqlite' (default).`
51371
+ );
51372
+ }
51373
+ if (validator.isInGracePeriod()) {
51374
+ console.warn(
51375
+ "[visor:enterprise] License has expired but is within the 72-hour grace period. Please renew your license."
51376
+ );
51377
+ }
51378
+ const { KnexStoreBackend: KnexStoreBackend2 } = await Promise.resolve().then(() => (init_knex_store(), knex_store_exports));
51379
+ return new KnexStoreBackend2(driver, storageConfig, haConfig);
51380
+ }
51381
+ var init_loader = __esm({
51382
+ "src/enterprise/loader.ts"() {
51383
+ "use strict";
51384
+ init_default_engine();
51385
+ }
51386
+ });
51387
+
49592
51388
  // src/event-bus/event-bus.ts
49593
51389
  var event_bus_exports = {};
49594
51390
  __export(event_bus_exports, {
@@ -50495,8 +52291,8 @@ ${content}
50495
52291
  * Sleep utility
50496
52292
  */
50497
52293
  sleep(ms) {
50498
- return new Promise((resolve11) => {
50499
- const t = setTimeout(resolve11, ms);
52294
+ return new Promise((resolve15) => {
52295
+ const t = setTimeout(resolve15, ms);
50500
52296
  if (typeof t.unref === "function") {
50501
52297
  try {
50502
52298
  t.unref();
@@ -50770,8 +52566,8 @@ ${end}`);
50770
52566
  async updateGroupedComment(ctx, comments, group, changedIds) {
50771
52567
  const existingLock = this.updateLocks.get(group);
50772
52568
  let resolveLock;
50773
- const ourLock = new Promise((resolve11) => {
50774
- resolveLock = resolve11;
52569
+ const ourLock = new Promise((resolve15) => {
52570
+ resolveLock = resolve15;
50775
52571
  });
50776
52572
  this.updateLocks.set(group, ourLock);
50777
52573
  try {
@@ -51083,7 +52879,7 @@ ${blocks}
51083
52879
  * Sleep utility for enforcing delays
51084
52880
  */
51085
52881
  sleep(ms) {
51086
- return new Promise((resolve11) => setTimeout(resolve11, ms));
52882
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
51087
52883
  }
51088
52884
  };
51089
52885
  }
@@ -51281,7 +53077,8 @@ var init_client = __esm({
51281
53077
  user: m.user,
51282
53078
  text: m.text,
51283
53079
  bot_id: m.bot_id,
51284
- thread_ts: m.thread_ts
53080
+ thread_ts: m.thread_ts,
53081
+ files: Array.isArray(m.files) ? m.files : void 0
51285
53082
  }));
51286
53083
  } catch (e) {
51287
53084
  console.warn(
@@ -51304,9 +53101,9 @@ var init_client = __esm({
51304
53101
  initial_comment
51305
53102
  }) => {
51306
53103
  try {
51307
- const getUrlResp = await this.api("files.getUploadURLExternal", {
53104
+ const getUrlResp = await this.apiForm("files.getUploadURLExternal", {
51308
53105
  filename,
51309
- length: content.length
53106
+ length: String(content.length)
51310
53107
  });
51311
53108
  if (!getUrlResp || getUrlResp.ok !== true || !getUrlResp.upload_url) {
51312
53109
  console.warn(
@@ -51364,6 +53161,19 @@ var init_client = __esm({
51364
53161
  });
51365
53162
  return await res.json();
51366
53163
  }
53164
+ /** Send a Slack API request as application/x-www-form-urlencoded (required by some file methods). */
53165
+ async apiForm(method, params) {
53166
+ const body = new URLSearchParams(params);
53167
+ const res = await fetch(`https://slack.com/api/${method}`, {
53168
+ method: "POST",
53169
+ headers: {
53170
+ "Content-Type": "application/x-www-form-urlencoded",
53171
+ Authorization: `Bearer ${this.token}`
53172
+ },
53173
+ body: body.toString()
53174
+ });
53175
+ return await res.json();
53176
+ }
51367
53177
  };
51368
53178
  }
51369
53179
  });
@@ -51384,17 +53194,17 @@ function extractMermaidDiagrams(text) {
51384
53194
  return diagrams;
51385
53195
  }
51386
53196
  async function renderMermaidToPng(mermaidCode) {
51387
- const tmpDir = os.tmpdir();
51388
- const inputFile = path23.join(
53197
+ const tmpDir = os2.tmpdir();
53198
+ const inputFile = path27.join(
51389
53199
  tmpDir,
51390
53200
  `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}.mmd`
51391
53201
  );
51392
- const outputFile = path23.join(
53202
+ const outputFile = path27.join(
51393
53203
  tmpDir,
51394
53204
  `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}.png`
51395
53205
  );
51396
53206
  try {
51397
- fs19.writeFileSync(inputFile, mermaidCode, "utf-8");
53207
+ fs23.writeFileSync(inputFile, mermaidCode, "utf-8");
51398
53208
  const chromiumPaths = [
51399
53209
  "/usr/bin/chromium",
51400
53210
  "/usr/bin/chromium-browser",
@@ -51403,7 +53213,7 @@ async function renderMermaidToPng(mermaidCode) {
51403
53213
  ];
51404
53214
  let chromiumPath;
51405
53215
  for (const p of chromiumPaths) {
51406
- if (fs19.existsSync(p)) {
53216
+ if (fs23.existsSync(p)) {
51407
53217
  chromiumPath = p;
51408
53218
  break;
51409
53219
  }
@@ -51412,8 +53222,8 @@ async function renderMermaidToPng(mermaidCode) {
51412
53222
  if (chromiumPath) {
51413
53223
  env.PUPPETEER_EXECUTABLE_PATH = chromiumPath;
51414
53224
  }
51415
- const result = await new Promise((resolve11) => {
51416
- const proc = (0, import_child_process5.spawn)(
53225
+ const result = await new Promise((resolve15) => {
53226
+ const proc = (0, import_child_process6.spawn)(
51417
53227
  "npx",
51418
53228
  [
51419
53229
  "--yes",
@@ -51442,32 +53252,32 @@ async function renderMermaidToPng(mermaidCode) {
51442
53252
  });
51443
53253
  proc.on("close", (code) => {
51444
53254
  if (code === 0) {
51445
- resolve11({ success: true });
53255
+ resolve15({ success: true });
51446
53256
  } else {
51447
- resolve11({ success: false, error: stderr || `Exit code ${code}` });
53257
+ resolve15({ success: false, error: stderr || `Exit code ${code}` });
51448
53258
  }
51449
53259
  });
51450
53260
  proc.on("error", (err) => {
51451
- resolve11({ success: false, error: err.message });
53261
+ resolve15({ success: false, error: err.message });
51452
53262
  });
51453
53263
  });
51454
53264
  if (!result.success) {
51455
53265
  console.warn(`Mermaid rendering failed: ${result.error}`);
51456
53266
  return null;
51457
53267
  }
51458
- if (!fs19.existsSync(outputFile)) {
53268
+ if (!fs23.existsSync(outputFile)) {
51459
53269
  console.warn("Mermaid output file not created");
51460
53270
  return null;
51461
53271
  }
51462
- const pngBuffer = fs19.readFileSync(outputFile);
53272
+ const pngBuffer = fs23.readFileSync(outputFile);
51463
53273
  return pngBuffer;
51464
53274
  } catch (e) {
51465
53275
  console.warn(`Mermaid rendering error: ${e instanceof Error ? e.message : String(e)}`);
51466
53276
  return null;
51467
53277
  } finally {
51468
53278
  try {
51469
- if (fs19.existsSync(inputFile)) fs19.unlinkSync(inputFile);
51470
- if (fs19.existsSync(outputFile)) fs19.unlinkSync(outputFile);
53279
+ if (fs23.existsSync(inputFile)) fs23.unlinkSync(inputFile);
53280
+ if (fs23.existsSync(outputFile)) fs23.unlinkSync(outputFile);
51471
53281
  } catch {
51472
53282
  }
51473
53283
  }
@@ -51528,17 +53338,58 @@ function markdownToSlack(text) {
51528
53338
  out = lines.join("\n");
51529
53339
  return out;
51530
53340
  }
53341
+ function extractFileSections(text) {
53342
+ const sections = [];
53343
+ const delimRegex = /^--- ([\w][\w.\-]*\.\w+) ---$/gm;
53344
+ const delimiters = [];
53345
+ let m;
53346
+ while ((m = delimRegex.exec(text)) !== null) {
53347
+ delimiters.push({
53348
+ filename: m[1],
53349
+ start: m.index,
53350
+ end: m.index + m[0].length
53351
+ });
53352
+ }
53353
+ if (delimiters.length === 0) return sections;
53354
+ for (let i = 0; i < delimiters.length; i++) {
53355
+ const open = delimiters[i];
53356
+ const contentStart = open.end < text.length && text[open.end] === "\n" ? open.end + 1 : open.end;
53357
+ const sectionEnd = i + 1 < delimiters.length ? delimiters[i + 1].start : text.length;
53358
+ const content = text.substring(contentStart, sectionEnd).trim();
53359
+ if (content.length > 0) {
53360
+ sections.push({
53361
+ fullMatch: text.substring(open.start, sectionEnd),
53362
+ filename: open.filename,
53363
+ content,
53364
+ startIndex: open.start,
53365
+ endIndex: sectionEnd
53366
+ });
53367
+ }
53368
+ }
53369
+ return sections;
53370
+ }
53371
+ function replaceFileSections(text, sections, replacement = (idx) => `_(See file: ${sections[idx].filename} above)_`) {
53372
+ if (sections.length === 0) return text;
53373
+ const sorted = [...sections].sort((a, b) => b.startIndex - a.startIndex);
53374
+ let result = text;
53375
+ sorted.forEach((section, sortedIndex) => {
53376
+ const originalIndex = sections.length - 1 - sortedIndex;
53377
+ const rep = typeof replacement === "function" ? replacement(originalIndex) : replacement;
53378
+ result = result.slice(0, section.startIndex) + rep + result.slice(section.endIndex);
53379
+ });
53380
+ return result;
53381
+ }
51531
53382
  function formatSlackText(text) {
51532
53383
  return markdownToSlack(text);
51533
53384
  }
51534
- var import_child_process5, fs19, path23, os;
53385
+ var import_child_process6, fs23, path27, os2;
51535
53386
  var init_markdown = __esm({
51536
53387
  "src/slack/markdown.ts"() {
51537
53388
  "use strict";
51538
- import_child_process5 = require("child_process");
51539
- fs19 = __toESM(require("fs"));
51540
- path23 = __toESM(require("path"));
51541
- os = __toESM(require("os"));
53389
+ import_child_process6 = require("child_process");
53390
+ fs23 = __toESM(require("fs"));
53391
+ path27 = __toESM(require("path"));
53392
+ os2 = __toESM(require("os"));
51542
53393
  }
51543
53394
  });
51544
53395
 
@@ -51956,6 +53807,9 @@ ${message}`;
51956
53807
  text = String(out);
51957
53808
  }
51958
53809
  }
53810
+ if (out && typeof out._rawOutput === "string" && out._rawOutput.trim().length > 0) {
53811
+ text = (text || "") + "\n\n" + out._rawOutput.trim();
53812
+ }
51959
53813
  if (!text) {
51960
53814
  ctx.logger.info(
51961
53815
  `[slack-frontend] skip posting AI reply for ${checkId}: no renderable text in check output`
@@ -52014,6 +53868,39 @@ ${message}`;
52014
53868
  );
52015
53869
  }
52016
53870
  }
53871
+ processedText = processedText.replace(/\\n/g, "\n");
53872
+ const fileSections = extractFileSections(processedText);
53873
+ if (fileSections.length > 0) {
53874
+ const uploadedFileIndices = [];
53875
+ for (let i = 0; i < fileSections.length; i++) {
53876
+ const section = fileSections[i];
53877
+ try {
53878
+ const buffer = Buffer.from(section.content, "utf-8");
53879
+ const uploadResult = await slack.files.uploadV2({
53880
+ content: buffer,
53881
+ filename: section.filename,
53882
+ channel,
53883
+ thread_ts: threadTs,
53884
+ title: section.filename
53885
+ });
53886
+ if (uploadResult.ok) {
53887
+ uploadedFileIndices.push(i);
53888
+ ctx.logger.info(`[slack-frontend] uploaded file ${section.filename} to ${channel}`);
53889
+ } else {
53890
+ ctx.logger.warn(`[slack-frontend] upload failed for file ${section.filename}`);
53891
+ }
53892
+ } catch (e) {
53893
+ ctx.logger.warn(
53894
+ `[slack-frontend] failed to upload file ${section.filename}: ${e instanceof Error ? e.message : String(e)}`
53895
+ );
53896
+ }
53897
+ }
53898
+ processedText = replaceFileSections(
53899
+ processedText,
53900
+ fileSections,
53901
+ (idx) => uploadedFileIndices.includes(idx) ? `_(See file: ${fileSections[idx].filename} above)_` : `_(File upload failed: ${fileSections[idx].filename})_`
53902
+ );
53903
+ }
52017
53904
  let decoratedText = processedText;
52018
53905
  const telemetryEnabled = telemetryCfg === true || telemetryCfg && typeof telemetryCfg === "object" && telemetryCfg.enabled === true;
52019
53906
  if (telemetryEnabled) {
@@ -52478,15 +54365,15 @@ function serializeRunState(state) {
52478
54365
  ])
52479
54366
  };
52480
54367
  }
52481
- var path24, fs20, StateMachineExecutionEngine;
54368
+ var path28, fs24, StateMachineExecutionEngine;
52482
54369
  var init_state_machine_execution_engine = __esm({
52483
54370
  "src/state-machine-execution-engine.ts"() {
52484
54371
  "use strict";
52485
54372
  init_runner();
52486
54373
  init_logger();
52487
54374
  init_sandbox_manager();
52488
- path24 = __toESM(require("path"));
52489
- fs20 = __toESM(require("fs"));
54375
+ path28 = __toESM(require("path"));
54376
+ fs24 = __toESM(require("fs"));
52490
54377
  StateMachineExecutionEngine = class _StateMachineExecutionEngine {
52491
54378
  workingDirectory;
52492
54379
  executionContext;
@@ -52709,8 +54596,8 @@ var init_state_machine_execution_engine = __esm({
52709
54596
  logger.debug(
52710
54597
  `[PolicyEngine] Loading enterprise policy engine (engine=${configWithTagFilter.policy.engine})`
52711
54598
  );
52712
- const { loadEnterprisePolicyEngine } = await import("./enterprise/loader");
52713
- context2.policyEngine = await loadEnterprisePolicyEngine(configWithTagFilter.policy);
54599
+ const { loadEnterprisePolicyEngine: loadEnterprisePolicyEngine2 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
54600
+ context2.policyEngine = await loadEnterprisePolicyEngine2(configWithTagFilter.policy);
52714
54601
  logger.debug(
52715
54602
  `[PolicyEngine] Initialized: ${context2.policyEngine?.constructor?.name || "unknown"}`
52716
54603
  );
@@ -52862,9 +54749,9 @@ var init_state_machine_execution_engine = __esm({
52862
54749
  }
52863
54750
  const checkId = String(ev?.checkId || "unknown");
52864
54751
  const threadKey = ev?.threadKey || (channel && threadTs ? `${channel}:${threadTs}` : "session");
52865
- const baseDir = process.env.VISOR_SNAPSHOT_DIR || path24.resolve(process.cwd(), ".visor", "snapshots");
52866
- fs20.mkdirSync(baseDir, { recursive: true });
52867
- const filePath = path24.join(baseDir, `${threadKey}-${checkId}.json`);
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`);
52868
54755
  await this.saveSnapshotToFile(filePath);
52869
54756
  logger.info(`[Snapshot] Saved run snapshot: ${filePath}`);
52870
54757
  try {
@@ -53005,7 +54892,7 @@ var init_state_machine_execution_engine = __esm({
53005
54892
  * Does not include secrets. Intended for debugging and future resume support.
53006
54893
  */
53007
54894
  async saveSnapshotToFile(filePath) {
53008
- const fs21 = await import("fs/promises");
54895
+ const fs25 = await import("fs/promises");
53009
54896
  const ctx = this._lastContext;
53010
54897
  const runner = this._lastRunner;
53011
54898
  if (!ctx || !runner) {
@@ -53025,14 +54912,14 @@ var init_state_machine_execution_engine = __esm({
53025
54912
  journal: entries,
53026
54913
  requestedChecks: ctx.requestedChecks || []
53027
54914
  };
53028
- await fs21.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
54915
+ await fs25.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
53029
54916
  }
53030
54917
  /**
53031
54918
  * Load a snapshot JSON from file and return it. Resume support can build on this.
53032
54919
  */
53033
54920
  async loadSnapshotFromFile(filePath) {
53034
- const fs21 = await import("fs/promises");
53035
- const raw = await fs21.readFile(filePath, "utf8");
54921
+ const fs25 = await import("fs/promises");
54922
+ const raw = await fs25.readFile(filePath, "utf8");
53036
54923
  return JSON.parse(raw);
53037
54924
  }
53038
54925
  /**