@probelabs/visor 0.1.149-ee → 0.1.150

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 (131) hide show
  1. package/README.md +52 -1
  2. package/dist/909.index.js +27117 -0
  3. package/dist/ai-review-service.d.ts.map +1 -1
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/docs/assistant-workflows.md +805 -0
  6. package/dist/docs/event-triggers.md +25 -0
  7. package/dist/docs/scheduler.md +156 -0
  8. package/dist/docs/slack-integration.md +48 -0
  9. package/dist/enterprise/scheduler/knex-store.d.ts +7 -1
  10. package/dist/enterprise/scheduler/knex-store.d.ts.map +1 -1
  11. package/dist/examples/code-talk-as-tool.yaml +76 -0
  12. package/dist/examples/code-talk-workflow.yaml +68 -0
  13. package/dist/examples/intent-router-workflow.yaml +66 -0
  14. package/dist/examples/slack-message-triggers.yaml +270 -0
  15. package/dist/generated/config-schema.d.ts +102 -7
  16. package/dist/generated/config-schema.d.ts.map +1 -1
  17. package/dist/generated/config-schema.json +116 -7
  18. package/dist/git-repository-analyzer.d.ts +8 -0
  19. package/dist/git-repository-analyzer.d.ts.map +1 -1
  20. package/dist/index.js +4051 -6453
  21. package/dist/output/traces/run-2026-03-03T20-21-24-501Z.ndjson +138 -0
  22. package/dist/output/traces/run-2026-03-03T20-22-08-701Z.ndjson +2197 -0
  23. package/dist/scheduler/message-trigger.d.ts +47 -0
  24. package/dist/scheduler/message-trigger.d.ts.map +1 -0
  25. package/dist/scheduler/schedule-store.d.ts +31 -1
  26. package/dist/scheduler/schedule-store.d.ts.map +1 -1
  27. package/dist/scheduler/schedule-tool.d.ts +17 -1
  28. package/dist/scheduler/schedule-tool.d.ts.map +1 -1
  29. package/dist/scheduler/store/sqlite-store.d.ts +7 -1
  30. package/dist/scheduler/store/sqlite-store.d.ts.map +1 -1
  31. package/dist/scheduler/store/types.d.ts +45 -0
  32. package/dist/scheduler/store/types.d.ts.map +1 -1
  33. package/dist/sdk/{check-provider-registry-LVLC4EPF.mjs → check-provider-registry-6D5CAX44.mjs} +6 -6
  34. package/dist/sdk/{check-provider-registry-TJAJVSMY.mjs → check-provider-registry-RRYBG6AU.mjs} +6 -6
  35. package/dist/sdk/check-provider-registry-YFC5KSJY.mjs +29 -0
  36. package/dist/sdk/{chunk-V6GI4U2M.mjs → chunk-AADKUA6L.mjs} +585 -29
  37. package/dist/sdk/{chunk-NYFTDVG5.mjs.map → chunk-AADKUA6L.mjs.map} +1 -1
  38. package/dist/sdk/{chunk-NYFTDVG5.mjs → chunk-CKDFGNF4.mjs} +586 -30
  39. package/dist/sdk/{chunk-V6GI4U2M.mjs.map → chunk-CKDFGNF4.mjs.map} +1 -1
  40. package/dist/sdk/{chunk-YYZAN5NK.mjs → chunk-FYK2DJK6.mjs} +106 -9
  41. package/dist/sdk/chunk-FYK2DJK6.mjs.map +1 -0
  42. package/dist/sdk/{chunk-RJLJUTSU.mjs → chunk-FZEQ744M.mjs} +2 -2
  43. package/dist/sdk/{chunk-CISJ6DJW.mjs → chunk-GIAN7HCT.mjs} +3 -3
  44. package/dist/sdk/chunk-GLROSEYJ.mjs +1502 -0
  45. package/dist/sdk/chunk-GLROSEYJ.mjs.map +1 -0
  46. package/dist/sdk/chunk-H4HFH7HH.mjs +443 -0
  47. package/dist/sdk/chunk-H4HFH7HH.mjs.map +1 -0
  48. package/dist/sdk/{chunk-62TNF5PJ.mjs → chunk-PETLPNRA.mjs} +2 -2
  49. package/dist/sdk/{chunk-62TNF5PJ.mjs.map → chunk-PETLPNRA.mjs.map} +1 -1
  50. package/dist/sdk/chunk-SWO4W57C.mjs +739 -0
  51. package/dist/sdk/chunk-SWO4W57C.mjs.map +1 -0
  52. package/dist/sdk/chunk-YY3KADY2.mjs +43715 -0
  53. package/dist/sdk/chunk-YY3KADY2.mjs.map +1 -0
  54. package/dist/sdk/{config-KQH254CA.mjs → config-MTEIGCOQ.mjs} +2 -2
  55. package/dist/sdk/{failure-condition-evaluator-IVCTD4BZ.mjs → failure-condition-evaluator-P6QUFLIN.mjs} +3 -3
  56. package/dist/sdk/failure-condition-evaluator-XV2ZMFFY.mjs +17 -0
  57. package/dist/sdk/{git-repository-analyzer-QFMW6WIS.mjs → git-repository-analyzer-TWNJUN42.mjs} +34 -3
  58. package/dist/sdk/git-repository-analyzer-TWNJUN42.mjs.map +1 -0
  59. package/dist/sdk/{github-frontend-DFT5G32K.mjs → github-frontend-A2R7D4N6.mjs} +3 -3
  60. package/dist/sdk/github-frontend-GSB2P5PE.mjs +1368 -0
  61. package/dist/sdk/github-frontend-GSB2P5PE.mjs.map +1 -0
  62. package/dist/sdk/{host-H7IX4GBK.mjs → host-CLPM2WVQ.mjs} +2 -2
  63. package/dist/sdk/{host-NZXGBBJI.mjs → host-MR7L57QI.mjs} +2 -2
  64. package/dist/sdk/{routing-LU5PAREW.mjs → routing-KLVK2MJZ.mjs} +4 -4
  65. package/dist/sdk/routing-V3MYZAOH.mjs +25 -0
  66. package/dist/sdk/{schedule-tool-4U32CSH6.mjs → schedule-tool-HYM55K3H.mjs} +6 -6
  67. package/dist/sdk/{schedule-tool-NX75VKGA.mjs → schedule-tool-NYLNJUEW.mjs} +6 -6
  68. package/dist/sdk/schedule-tool-PRXFAV4K.mjs +35 -0
  69. package/dist/sdk/{schedule-tool-handler-6S2DNP26.mjs → schedule-tool-handler-EFQIYD3W.mjs} +6 -6
  70. package/dist/sdk/{schedule-tool-handler-KKN7XJYT.mjs → schedule-tool-handler-HVWCEGQ6.mjs} +6 -6
  71. package/dist/sdk/schedule-tool-handler-HVWCEGQ6.mjs.map +1 -0
  72. package/dist/sdk/schedule-tool-handler-M5YI573R.mjs +39 -0
  73. package/dist/sdk/schedule-tool-handler-M5YI573R.mjs.map +1 -0
  74. package/dist/sdk/sdk.d.mts +38 -1
  75. package/dist/sdk/sdk.d.ts +38 -1
  76. package/dist/sdk/sdk.js +964 -1538
  77. package/dist/sdk/sdk.js.map +1 -1
  78. package/dist/sdk/sdk.mjs +5 -5
  79. package/dist/sdk/{trace-helpers-6ROJR7N3.mjs → trace-helpers-6NSZBC35.mjs} +2 -2
  80. package/dist/sdk/trace-helpers-6NSZBC35.mjs.map +1 -0
  81. package/dist/sdk/trace-helpers-TORF3JD5.mjs +25 -0
  82. package/dist/sdk/trace-helpers-TORF3JD5.mjs.map +1 -0
  83. package/dist/sdk/{workflow-check-provider-AGZ5JY2I.mjs → workflow-check-provider-3F6CBHVD.mjs} +6 -6
  84. package/dist/sdk/workflow-check-provider-3F6CBHVD.mjs.map +1 -0
  85. package/dist/sdk/{workflow-check-provider-FDNGOBBG.mjs → workflow-check-provider-OXHMLICJ.mjs} +6 -6
  86. package/dist/sdk/workflow-check-provider-OXHMLICJ.mjs.map +1 -0
  87. package/dist/sdk/workflow-check-provider-XLH7N4FV.mjs +29 -0
  88. package/dist/sdk/workflow-check-provider-XLH7N4FV.mjs.map +1 -0
  89. package/dist/slack/socket-runner.d.ts +14 -0
  90. package/dist/slack/socket-runner.d.ts.map +1 -1
  91. package/dist/test-runner/core/flow-stage.d.ts.map +1 -1
  92. package/dist/test-runner/fixture-loader.d.ts +1 -1
  93. package/dist/test-runner/fixture-loader.d.ts.map +1 -1
  94. package/dist/test-runner/index.d.ts.map +1 -1
  95. package/dist/test-runner/validator.d.ts.map +1 -1
  96. package/dist/traces/run-2026-03-03T20-21-24-501Z.ndjson +138 -0
  97. package/dist/traces/run-2026-03-03T20-22-08-701Z.ndjson +2197 -0
  98. package/dist/types/config.d.ts +38 -1
  99. package/dist/types/config.d.ts.map +1 -1
  100. package/dist/utils/workspace-manager.d.ts +4 -0
  101. package/dist/utils/workspace-manager.d.ts.map +1 -1
  102. package/dist/utils/worktree-manager.d.ts +15 -1
  103. package/dist/utils/worktree-manager.d.ts.map +1 -1
  104. package/package.json +2 -2
  105. package/dist/sdk/chunk-YYZAN5NK.mjs.map +0 -1
  106. package/dist/sdk/git-repository-analyzer-QFMW6WIS.mjs.map +0 -1
  107. package/dist/sdk/knex-store-HPXJILBL.mjs +0 -411
  108. package/dist/sdk/knex-store-HPXJILBL.mjs.map +0 -1
  109. package/dist/sdk/loader-YSRMVXC3.mjs +0 -89
  110. package/dist/sdk/loader-YSRMVXC3.mjs.map +0 -1
  111. package/dist/sdk/opa-policy-engine-S2S2ULEI.mjs +0 -655
  112. package/dist/sdk/opa-policy-engine-S2S2ULEI.mjs.map +0 -1
  113. package/dist/sdk/validator-XTZJZZJH.mjs +0 -134
  114. package/dist/sdk/validator-XTZJZZJH.mjs.map +0 -1
  115. /package/dist/sdk/{check-provider-registry-LVLC4EPF.mjs.map → check-provider-registry-6D5CAX44.mjs.map} +0 -0
  116. /package/dist/sdk/{check-provider-registry-TJAJVSMY.mjs.map → check-provider-registry-RRYBG6AU.mjs.map} +0 -0
  117. /package/dist/sdk/{config-KQH254CA.mjs.map → check-provider-registry-YFC5KSJY.mjs.map} +0 -0
  118. /package/dist/sdk/{chunk-RJLJUTSU.mjs.map → chunk-FZEQ744M.mjs.map} +0 -0
  119. /package/dist/sdk/{chunk-CISJ6DJW.mjs.map → chunk-GIAN7HCT.mjs.map} +0 -0
  120. /package/dist/sdk/{failure-condition-evaluator-IVCTD4BZ.mjs.map → config-MTEIGCOQ.mjs.map} +0 -0
  121. /package/dist/sdk/{routing-LU5PAREW.mjs.map → failure-condition-evaluator-P6QUFLIN.mjs.map} +0 -0
  122. /package/dist/sdk/{schedule-tool-4U32CSH6.mjs.map → failure-condition-evaluator-XV2ZMFFY.mjs.map} +0 -0
  123. /package/dist/sdk/{github-frontend-DFT5G32K.mjs.map → github-frontend-A2R7D4N6.mjs.map} +0 -0
  124. /package/dist/sdk/{host-H7IX4GBK.mjs.map → host-CLPM2WVQ.mjs.map} +0 -0
  125. /package/dist/sdk/{host-NZXGBBJI.mjs.map → host-MR7L57QI.mjs.map} +0 -0
  126. /package/dist/sdk/{schedule-tool-NX75VKGA.mjs.map → routing-KLVK2MJZ.mjs.map} +0 -0
  127. /package/dist/sdk/{schedule-tool-handler-6S2DNP26.mjs.map → routing-V3MYZAOH.mjs.map} +0 -0
  128. /package/dist/sdk/{schedule-tool-handler-KKN7XJYT.mjs.map → schedule-tool-HYM55K3H.mjs.map} +0 -0
  129. /package/dist/sdk/{trace-helpers-6ROJR7N3.mjs.map → schedule-tool-NYLNJUEW.mjs.map} +0 -0
  130. /package/dist/sdk/{workflow-check-provider-AGZ5JY2I.mjs.map → schedule-tool-PRXFAV4K.mjs.map} +0 -0
  131. /package/dist/sdk/{workflow-check-provider-FDNGOBBG.mjs.map → schedule-tool-handler-EFQIYD3W.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.42",
649
+ version: "0.1.150",
650
650
  main: "dist/index.js",
651
651
  bin: {
652
652
  visor: "./dist/index.js"
@@ -760,7 +760,7 @@ var require_package = __commonJS({
760
760
  "@opentelemetry/sdk-node": "^0.203.0",
761
761
  "@opentelemetry/sdk-trace-base": "^1.30.1",
762
762
  "@opentelemetry/semantic-conventions": "^1.30.1",
763
- "@probelabs/probe": "^0.6.0-rc264",
763
+ "@probelabs/probe": "^0.6.0-rc266",
764
764
  "@types/commander": "^2.12.0",
765
765
  "@types/uuid": "^10.0.0",
766
766
  acorn: "^8.16.0",
@@ -864,11 +864,11 @@ function getTracer() {
864
864
  }
865
865
  async function withActiveSpan(name, attrs, fn) {
866
866
  const tracer = getTracer();
867
- return await new Promise((resolve18, reject) => {
867
+ return await new Promise((resolve14, reject) => {
868
868
  const callback = async (span) => {
869
869
  try {
870
870
  const res = await fn(span);
871
- resolve18(res);
871
+ resolve14(res);
872
872
  } catch (err) {
873
873
  try {
874
874
  if (err instanceof Error) span.recordException(err);
@@ -945,19 +945,19 @@ function __getOrCreateNdjsonPath() {
945
945
  try {
946
946
  if (process.env.VISOR_TELEMETRY_SINK && process.env.VISOR_TELEMETRY_SINK !== "file")
947
947
  return null;
948
- const path31 = require("path");
949
- const fs27 = require("fs");
948
+ const path27 = require("path");
949
+ const fs23 = require("fs");
950
950
  if (process.env.VISOR_FALLBACK_TRACE_FILE) {
951
951
  __ndjsonPath = process.env.VISOR_FALLBACK_TRACE_FILE;
952
- const dir = path31.dirname(__ndjsonPath);
953
- if (!fs27.existsSync(dir)) fs27.mkdirSync(dir, { recursive: true });
952
+ const dir = path27.dirname(__ndjsonPath);
953
+ if (!fs23.existsSync(dir)) fs23.mkdirSync(dir, { recursive: true });
954
954
  return __ndjsonPath;
955
955
  }
956
- const outDir = process.env.VISOR_TRACE_DIR || path31.join(process.cwd(), "output", "traces");
957
- if (!fs27.existsSync(outDir)) fs27.mkdirSync(outDir, { recursive: true });
956
+ const outDir = process.env.VISOR_TRACE_DIR || path27.join(process.cwd(), "output", "traces");
957
+ if (!fs23.existsSync(outDir)) fs23.mkdirSync(outDir, { recursive: true });
958
958
  if (!__ndjsonPath) {
959
959
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
960
- __ndjsonPath = path31.join(outDir, `${ts}.ndjson`);
960
+ __ndjsonPath = path27.join(outDir, `${ts}.ndjson`);
961
961
  }
962
962
  return __ndjsonPath;
963
963
  } catch {
@@ -966,11 +966,11 @@ function __getOrCreateNdjsonPath() {
966
966
  }
967
967
  function _appendRunMarker() {
968
968
  try {
969
- const fs27 = require("fs");
969
+ const fs23 = require("fs");
970
970
  const p = __getOrCreateNdjsonPath();
971
971
  if (!p) return;
972
972
  const line = { name: "visor.run", attributes: { started: true } };
973
- fs27.appendFileSync(p, JSON.stringify(line) + "\n", "utf8");
973
+ fs23.appendFileSync(p, JSON.stringify(line) + "\n", "utf8");
974
974
  } catch {
975
975
  }
976
976
  }
@@ -3193,7 +3193,7 @@ var init_failure_condition_evaluator = __esm({
3193
3193
  */
3194
3194
  evaluateExpression(condition, context2) {
3195
3195
  try {
3196
- const normalize8 = (expr) => {
3196
+ const normalize4 = (expr) => {
3197
3197
  const trimmed = expr.trim();
3198
3198
  if (!/[\n;]/.test(trimmed)) return trimmed;
3199
3199
  const parts = trimmed.split(/[\n;]+/).map((s) => s.trim()).filter((s) => s.length > 0 && !s.startsWith("//"));
@@ -3351,7 +3351,7 @@ var init_failure_condition_evaluator = __esm({
3351
3351
  try {
3352
3352
  exec2 = this.sandbox.compile(`return (${raw});`);
3353
3353
  } catch {
3354
- const normalizedExpr = normalize8(condition);
3354
+ const normalizedExpr = normalize4(condition);
3355
3355
  exec2 = this.sandbox.compile(`return (${normalizedExpr});`);
3356
3356
  }
3357
3357
  const result = exec2(scope).run();
@@ -3734,9 +3734,9 @@ function configureLiquidWithExtensions(liquid) {
3734
3734
  });
3735
3735
  liquid.registerFilter("get", (obj, pathExpr) => {
3736
3736
  if (obj == null) return void 0;
3737
- const path31 = typeof pathExpr === "string" ? pathExpr : String(pathExpr || "");
3738
- if (!path31) return obj;
3739
- const parts = path31.split(".");
3737
+ const path27 = typeof pathExpr === "string" ? pathExpr : String(pathExpr || "");
3738
+ if (!path27) return obj;
3739
+ const parts = path27.split(".");
3740
3740
  let cur = obj;
3741
3741
  for (const p of parts) {
3742
3742
  if (cur == null) return void 0;
@@ -3855,9 +3855,9 @@ function configureLiquidWithExtensions(liquid) {
3855
3855
  }
3856
3856
  }
3857
3857
  const defaultRole = typeof rolesCfg.default === "string" && rolesCfg.default.trim() ? rolesCfg.default.trim() : void 0;
3858
- const getNested = (obj, path31) => {
3859
- if (!obj || !path31) return void 0;
3860
- const parts = path31.split(".");
3858
+ const getNested = (obj, path27) => {
3859
+ if (!obj || !path27) return void 0;
3860
+ const parts = path27.split(".");
3861
3861
  let cur = obj;
3862
3862
  for (const p of parts) {
3863
3863
  if (cur == null) return void 0;
@@ -6409,8 +6409,8 @@ var init_dependency_gating = __esm({
6409
6409
  async function renderTemplateContent(checkId, checkConfig, reviewSummary) {
6410
6410
  try {
6411
6411
  const { createExtendedLiquid: createExtendedLiquid2 } = await Promise.resolve().then(() => (init_liquid_extensions(), liquid_extensions_exports));
6412
- const fs27 = await import("fs/promises");
6413
- const path31 = await import("path");
6412
+ const fs23 = await import("fs/promises");
6413
+ const path27 = await import("path");
6414
6414
  const schemaRaw = checkConfig.schema || "plain";
6415
6415
  const schema = typeof schemaRaw === "string" ? schemaRaw : "code-review";
6416
6416
  let templateContent;
@@ -6418,24 +6418,24 @@ async function renderTemplateContent(checkId, checkConfig, reviewSummary) {
6418
6418
  templateContent = String(checkConfig.template.content);
6419
6419
  } else if (checkConfig.template && checkConfig.template.file) {
6420
6420
  const file = String(checkConfig.template.file);
6421
- const resolved = path31.resolve(process.cwd(), file);
6422
- templateContent = await fs27.readFile(resolved, "utf-8");
6421
+ const resolved = path27.resolve(process.cwd(), file);
6422
+ templateContent = await fs23.readFile(resolved, "utf-8");
6423
6423
  } else if (schema && schema !== "plain") {
6424
6424
  const sanitized = String(schema).replace(/[^a-zA-Z0-9-]/g, "");
6425
6425
  if (sanitized) {
6426
6426
  const candidatePaths = [
6427
- path31.join(__dirname, "output", sanitized, "template.liquid"),
6427
+ path27.join(__dirname, "output", sanitized, "template.liquid"),
6428
6428
  // bundled: dist/output/
6429
- path31.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
6429
+ path27.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
6430
6430
  // source: output/
6431
- path31.join(process.cwd(), "output", sanitized, "template.liquid"),
6431
+ path27.join(process.cwd(), "output", sanitized, "template.liquid"),
6432
6432
  // fallback: cwd/output/
6433
- path31.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
6433
+ path27.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
6434
6434
  // fallback: cwd/dist/output/
6435
6435
  ];
6436
6436
  for (const p of candidatePaths) {
6437
6437
  try {
6438
- templateContent = await fs27.readFile(p, "utf-8");
6438
+ templateContent = await fs23.readFile(p, "utf-8");
6439
6439
  if (templateContent) break;
6440
6440
  } catch {
6441
6441
  }
@@ -6840,7 +6840,7 @@ async function processDiffWithOutline(diffContent) {
6840
6840
  }
6841
6841
  try {
6842
6842
  const originalProbePath = process.env.PROBE_PATH;
6843
- const fs27 = require("fs");
6843
+ const fs23 = require("fs");
6844
6844
  const possiblePaths = [
6845
6845
  // Relative to current working directory (most common in production)
6846
6846
  path6.join(process.cwd(), "node_modules/@probelabs/probe/bin/probe-binary"),
@@ -6851,7 +6851,7 @@ async function processDiffWithOutline(diffContent) {
6851
6851
  ];
6852
6852
  let probeBinaryPath;
6853
6853
  for (const candidatePath of possiblePaths) {
6854
- if (fs27.existsSync(candidatePath)) {
6854
+ if (fs23.existsSync(candidatePath)) {
6855
6855
  probeBinaryPath = candidatePath;
6856
6856
  break;
6857
6857
  }
@@ -6972,7 +6972,7 @@ async function renderMermaidToPng(mermaidCode) {
6972
6972
  if (chromiumPath) {
6973
6973
  env.PUPPETEER_EXECUTABLE_PATH = chromiumPath;
6974
6974
  }
6975
- const result = await new Promise((resolve18) => {
6975
+ const result = await new Promise((resolve14) => {
6976
6976
  const proc = (0, import_child_process.spawn)(
6977
6977
  "npx",
6978
6978
  [
@@ -7002,13 +7002,13 @@ async function renderMermaidToPng(mermaidCode) {
7002
7002
  });
7003
7003
  proc.on("close", (code) => {
7004
7004
  if (code === 0) {
7005
- resolve18({ success: true });
7005
+ resolve14({ success: true });
7006
7006
  } else {
7007
- resolve18({ success: false, error: stderr || `Exit code ${code}` });
7007
+ resolve14({ success: false, error: stderr || `Exit code ${code}` });
7008
7008
  }
7009
7009
  });
7010
7010
  proc.on("error", (err) => {
7011
- resolve18({ success: false, error: err.message });
7011
+ resolve14({ success: false, error: err.message });
7012
7012
  });
7013
7013
  });
7014
7014
  if (!result.success) {
@@ -7192,6 +7192,23 @@ function createProbeTracerAdapter(fallbackTracer) {
7192
7192
  }
7193
7193
  }
7194
7194
  },
7195
+ recordToolResult: (toolName, result, success, durationMs, metadata) => {
7196
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
7197
+ emitEvent("tool.result", {
7198
+ "tool.name": toolName,
7199
+ "tool.result": resultStr.substring(0, 1e4),
7200
+ "tool.result.length": resultStr.length,
7201
+ "tool.duration_ms": durationMs,
7202
+ "tool.success": success,
7203
+ ...metadata || {}
7204
+ });
7205
+ if (fallback && typeof fallback.recordToolResult === "function") {
7206
+ try {
7207
+ fallback.recordToolResult(toolName, result, success, durationMs, metadata);
7208
+ } catch {
7209
+ }
7210
+ }
7211
+ },
7195
7212
  recordDelegationEvent: (phase, attrs) => {
7196
7213
  emitEvent(`delegation.${phase}`, attrs);
7197
7214
  if (fallback && typeof fallback.recordDelegationEvent === "function") {
@@ -8153,8 +8170,8 @@ ${schemaString}`);
8153
8170
  }
8154
8171
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8155
8172
  try {
8156
- const fs27 = require("fs");
8157
- const path31 = require("path");
8173
+ const fs23 = require("fs");
8174
+ const path27 = require("path");
8158
8175
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8159
8176
  const provider = this.config.provider || "auto";
8160
8177
  const model = this.config.model || "default";
@@ -8268,20 +8285,20 @@ ${"=".repeat(60)}
8268
8285
  `;
8269
8286
  readableVersion += `${"=".repeat(60)}
8270
8287
  `;
8271
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path31.join(process.cwd(), "debug-artifacts");
8272
- if (!fs27.existsSync(debugArtifactsDir)) {
8273
- fs27.mkdirSync(debugArtifactsDir, { recursive: true });
8288
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path27.join(process.cwd(), "debug-artifacts");
8289
+ if (!fs23.existsSync(debugArtifactsDir)) {
8290
+ fs23.mkdirSync(debugArtifactsDir, { recursive: true });
8274
8291
  }
8275
- const debugFile = path31.join(
8292
+ const debugFile = path27.join(
8276
8293
  debugArtifactsDir,
8277
8294
  `prompt-${_checkName || "unknown"}-${timestamp}.json`
8278
8295
  );
8279
- fs27.writeFileSync(debugFile, debugJson, "utf-8");
8280
- const readableFile = path31.join(
8296
+ fs23.writeFileSync(debugFile, debugJson, "utf-8");
8297
+ const readableFile = path27.join(
8281
8298
  debugArtifactsDir,
8282
8299
  `prompt-${_checkName || "unknown"}-${timestamp}.txt`
8283
8300
  );
8284
- fs27.writeFileSync(readableFile, readableVersion, "utf-8");
8301
+ fs23.writeFileSync(readableFile, readableVersion, "utf-8");
8285
8302
  log(`
8286
8303
  \u{1F4BE} Full debug info saved to:`);
8287
8304
  log(` JSON: ${debugFile}`);
@@ -8314,8 +8331,8 @@ ${"=".repeat(60)}
8314
8331
  log(`\u{1F4E4} Response length: ${response.length} characters`);
8315
8332
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8316
8333
  try {
8317
- const fs27 = require("fs");
8318
- const path31 = require("path");
8334
+ const fs23 = require("fs");
8335
+ const path27 = require("path");
8319
8336
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8320
8337
  const agentAny2 = agent;
8321
8338
  let fullHistory = [];
@@ -8326,8 +8343,8 @@ ${"=".repeat(60)}
8326
8343
  } else if (agentAny2._messages) {
8327
8344
  fullHistory = agentAny2._messages;
8328
8345
  }
8329
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path31.join(process.cwd(), "debug-artifacts");
8330
- const sessionBase = path31.join(
8346
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path27.join(process.cwd(), "debug-artifacts");
8347
+ const sessionBase = path27.join(
8331
8348
  debugArtifactsDir,
8332
8349
  `session-${_checkName || "unknown"}-${timestamp}`
8333
8350
  );
@@ -8339,7 +8356,7 @@ ${"=".repeat(60)}
8339
8356
  schema: effectiveSchema,
8340
8357
  totalMessages: fullHistory.length
8341
8358
  };
8342
- fs27.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
8359
+ fs23.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
8343
8360
  let readable = `=============================================================
8344
8361
  `;
8345
8362
  readable += `COMPLETE AI SESSION HISTORY (AFTER RESPONSE)
@@ -8366,7 +8383,7 @@ ${"=".repeat(60)}
8366
8383
  `;
8367
8384
  readable += content + "\n";
8368
8385
  });
8369
- fs27.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
8386
+ fs23.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
8370
8387
  log(`\u{1F4BE} Complete session history saved:`);
8371
8388
  log(` - Contains ALL ${fullHistory.length} messages (prompts + responses)`);
8372
8389
  } catch (error) {
@@ -8375,11 +8392,11 @@ ${"=".repeat(60)}
8375
8392
  }
8376
8393
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8377
8394
  try {
8378
- const fs27 = require("fs");
8379
- const path31 = require("path");
8395
+ const fs23 = require("fs");
8396
+ const path27 = require("path");
8380
8397
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8381
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path31.join(process.cwd(), "debug-artifacts");
8382
- const responseFile = path31.join(
8398
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path27.join(process.cwd(), "debug-artifacts");
8399
+ const responseFile = path27.join(
8383
8400
  debugArtifactsDir,
8384
8401
  `response-${_checkName || "unknown"}-${timestamp}.txt`
8385
8402
  );
@@ -8412,7 +8429,7 @@ ${"=".repeat(60)}
8412
8429
  `;
8413
8430
  responseContent += `${"=".repeat(60)}
8414
8431
  `;
8415
- fs27.writeFileSync(responseFile, responseContent, "utf-8");
8432
+ fs23.writeFileSync(responseFile, responseContent, "utf-8");
8416
8433
  log(`\u{1F4BE} Response saved to: ${responseFile}`);
8417
8434
  } catch (error) {
8418
8435
  log(`\u26A0\uFE0F Could not save response file: ${error}`);
@@ -8428,9 +8445,9 @@ ${"=".repeat(60)}
8428
8445
  await agentAny._telemetryConfig.shutdown();
8429
8446
  log(`\u{1F4CA} OpenTelemetry trace saved to: ${agentAny._traceFilePath}`);
8430
8447
  if (process.env.GITHUB_ACTIONS) {
8431
- const fs27 = require("fs");
8432
- if (fs27.existsSync(agentAny._traceFilePath)) {
8433
- const stats = fs27.statSync(agentAny._traceFilePath);
8448
+ const fs23 = require("fs");
8449
+ if (fs23.existsSync(agentAny._traceFilePath)) {
8450
+ const stats = fs23.statSync(agentAny._traceFilePath);
8434
8451
  console.log(
8435
8452
  `::notice title=AI Trace Saved::${agentAny._traceFilePath} (${stats.size} bytes)`
8436
8453
  );
@@ -8637,9 +8654,9 @@ ${schemaString}`);
8637
8654
  const model = this.config.model || "default";
8638
8655
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8639
8656
  try {
8640
- const fs27 = require("fs");
8641
- const path31 = require("path");
8642
- const os3 = require("os");
8657
+ const fs23 = require("fs");
8658
+ const path27 = require("path");
8659
+ const os2 = require("os");
8643
8660
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8644
8661
  const debugData = {
8645
8662
  timestamp,
@@ -8711,19 +8728,19 @@ ${"=".repeat(60)}
8711
8728
  `;
8712
8729
  readableVersion += `${"=".repeat(60)}
8713
8730
  `;
8714
- const tempDir = os3.tmpdir();
8715
- const promptFile = path31.join(tempDir, `visor-prompt-${timestamp}.txt`);
8716
- fs27.writeFileSync(promptFile, prompt, "utf-8");
8731
+ const tempDir = os2.tmpdir();
8732
+ const promptFile = path27.join(tempDir, `visor-prompt-${timestamp}.txt`);
8733
+ fs23.writeFileSync(promptFile, prompt, "utf-8");
8717
8734
  log(`
8718
8735
  \u{1F4BE} Prompt saved to: ${promptFile}`);
8719
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path31.join(process.cwd(), "debug-artifacts");
8736
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path27.join(process.cwd(), "debug-artifacts");
8720
8737
  try {
8721
- const base = path31.join(
8738
+ const base = path27.join(
8722
8739
  debugArtifactsDir,
8723
8740
  `prompt-${_checkName || "unknown"}-${timestamp}`
8724
8741
  );
8725
- fs27.writeFileSync(base + ".json", debugJson, "utf-8");
8726
- fs27.writeFileSync(base + ".summary.txt", readableVersion, "utf-8");
8742
+ fs23.writeFileSync(base + ".json", debugJson, "utf-8");
8743
+ fs23.writeFileSync(base + ".summary.txt", readableVersion, "utf-8");
8727
8744
  log(`
8728
8745
  \u{1F4BE} Full debug info saved to directory: ${debugArtifactsDir}`);
8729
8746
  } catch {
@@ -8768,8 +8785,8 @@ $ ${cliCommand}
8768
8785
  log(`\u{1F4E4} Response length: ${response.length} characters`);
8769
8786
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8770
8787
  try {
8771
- const fs27 = require("fs");
8772
- const path31 = require("path");
8788
+ const fs23 = require("fs");
8789
+ const path27 = require("path");
8773
8790
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8774
8791
  const agentAny = agent;
8775
8792
  let fullHistory = [];
@@ -8780,8 +8797,8 @@ $ ${cliCommand}
8780
8797
  } else if (agentAny._messages) {
8781
8798
  fullHistory = agentAny._messages;
8782
8799
  }
8783
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path31.join(process.cwd(), "debug-artifacts");
8784
- const sessionBase = path31.join(
8800
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path27.join(process.cwd(), "debug-artifacts");
8801
+ const sessionBase = path27.join(
8785
8802
  debugArtifactsDir,
8786
8803
  `session-${_checkName || "unknown"}-${timestamp}`
8787
8804
  );
@@ -8793,7 +8810,7 @@ $ ${cliCommand}
8793
8810
  schema: effectiveSchema,
8794
8811
  totalMessages: fullHistory.length
8795
8812
  };
8796
- fs27.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
8813
+ fs23.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
8797
8814
  let readable = `=============================================================
8798
8815
  `;
8799
8816
  readable += `COMPLETE AI SESSION HISTORY (AFTER RESPONSE)
@@ -8820,7 +8837,7 @@ ${"=".repeat(60)}
8820
8837
  `;
8821
8838
  readable += content + "\n";
8822
8839
  });
8823
- fs27.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
8840
+ fs23.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
8824
8841
  log(`\u{1F4BE} Complete session history saved:`);
8825
8842
  log(` - Contains ALL ${fullHistory.length} messages (prompts + responses)`);
8826
8843
  } catch (error) {
@@ -8829,11 +8846,11 @@ ${"=".repeat(60)}
8829
8846
  }
8830
8847
  if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
8831
8848
  try {
8832
- const fs27 = require("fs");
8833
- const path31 = require("path");
8849
+ const fs23 = require("fs");
8850
+ const path27 = require("path");
8834
8851
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8835
- const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path31.join(process.cwd(), "debug-artifacts");
8836
- const responseFile = path31.join(
8852
+ const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path27.join(process.cwd(), "debug-artifacts");
8853
+ const responseFile = path27.join(
8837
8854
  debugArtifactsDir,
8838
8855
  `response-${_checkName || "unknown"}-${timestamp}.txt`
8839
8856
  );
@@ -8866,7 +8883,7 @@ ${"=".repeat(60)}
8866
8883
  `;
8867
8884
  responseContent += `${"=".repeat(60)}
8868
8885
  `;
8869
- fs27.writeFileSync(responseFile, responseContent, "utf-8");
8886
+ fs23.writeFileSync(responseFile, responseContent, "utf-8");
8870
8887
  log(`\u{1F4BE} Response saved to: ${responseFile}`);
8871
8888
  } catch (error) {
8872
8889
  log(`\u26A0\uFE0F Could not save response file: ${error}`);
@@ -8884,9 +8901,9 @@ ${"=".repeat(60)}
8884
8901
  await telemetry.shutdown();
8885
8902
  log(`\u{1F4CA} OpenTelemetry trace saved to: ${traceFilePath}`);
8886
8903
  if (process.env.GITHUB_ACTIONS) {
8887
- const fs27 = require("fs");
8888
- if (fs27.existsSync(traceFilePath)) {
8889
- const stats = fs27.statSync(traceFilePath);
8904
+ const fs23 = require("fs");
8905
+ if (fs23.existsSync(traceFilePath)) {
8906
+ const stats = fs23.statSync(traceFilePath);
8890
8907
  console.log(
8891
8908
  `::notice title=AI Trace Saved::OpenTelemetry trace file size: ${stats.size} bytes`
8892
8909
  );
@@ -8924,8 +8941,8 @@ ${"=".repeat(60)}
8924
8941
  * Load schema content from schema files or inline definitions
8925
8942
  */
8926
8943
  async loadSchemaContent(schema) {
8927
- const fs27 = require("fs").promises;
8928
- const path31 = require("path");
8944
+ const fs23 = require("fs").promises;
8945
+ const path27 = require("path");
8929
8946
  if (typeof schema === "object" && schema !== null) {
8930
8947
  log("\u{1F4CB} Using inline schema object from configuration");
8931
8948
  return JSON.stringify(schema);
@@ -8938,14 +8955,14 @@ ${"=".repeat(60)}
8938
8955
  }
8939
8956
  } catch {
8940
8957
  }
8941
- if ((schema.startsWith("./") || schema.includes(".json")) && !path31.isAbsolute(schema)) {
8958
+ if ((schema.startsWith("./") || schema.includes(".json")) && !path27.isAbsolute(schema)) {
8942
8959
  if (schema.includes("..") || schema.includes("\0")) {
8943
8960
  throw new Error("Invalid schema path: path traversal not allowed");
8944
8961
  }
8945
8962
  try {
8946
- const schemaPath = path31.resolve(process.cwd(), schema);
8963
+ const schemaPath = path27.resolve(process.cwd(), schema);
8947
8964
  log(`\u{1F4CB} Loading custom schema from file: ${schemaPath}`);
8948
- const schemaContent = await fs27.readFile(schemaPath, "utf-8");
8965
+ const schemaContent = await fs23.readFile(schemaPath, "utf-8");
8949
8966
  return schemaContent.trim();
8950
8967
  } catch (error) {
8951
8968
  throw new Error(
@@ -8959,22 +8976,22 @@ ${"=".repeat(60)}
8959
8976
  }
8960
8977
  const candidatePaths = [
8961
8978
  // GitHub Action bundle location
8962
- path31.join(__dirname, "output", sanitizedSchemaName, "schema.json"),
8979
+ path27.join(__dirname, "output", sanitizedSchemaName, "schema.json"),
8963
8980
  // Historical fallback when src/output was inadvertently bundled as output1/
8964
- path31.join(__dirname, "output1", sanitizedSchemaName, "schema.json"),
8981
+ path27.join(__dirname, "output1", sanitizedSchemaName, "schema.json"),
8965
8982
  // Local dev (repo root)
8966
- path31.join(process.cwd(), "output", sanitizedSchemaName, "schema.json")
8983
+ path27.join(process.cwd(), "output", sanitizedSchemaName, "schema.json")
8967
8984
  ];
8968
8985
  for (const schemaPath of candidatePaths) {
8969
8986
  try {
8970
- const schemaContent = await fs27.readFile(schemaPath, "utf-8");
8987
+ const schemaContent = await fs23.readFile(schemaPath, "utf-8");
8971
8988
  return schemaContent.trim();
8972
8989
  } catch {
8973
8990
  }
8974
8991
  }
8975
- const distPath = path31.join(__dirname, "output", sanitizedSchemaName, "schema.json");
8976
- const distAltPath = path31.join(__dirname, "output1", sanitizedSchemaName, "schema.json");
8977
- const cwdPath = path31.join(process.cwd(), "output", sanitizedSchemaName, "schema.json");
8992
+ const distPath = path27.join(__dirname, "output", sanitizedSchemaName, "schema.json");
8993
+ const distAltPath = path27.join(__dirname, "output1", sanitizedSchemaName, "schema.json");
8994
+ const cwdPath = path27.join(process.cwd(), "output", sanitizedSchemaName, "schema.json");
8978
8995
  throw new Error(
8979
8996
  `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.`
8980
8997
  );
@@ -9219,7 +9236,7 @@ ${"=".repeat(60)}
9219
9236
  * Generate mock response for testing
9220
9237
  */
9221
9238
  async generateMockResponse(_prompt, _checkName, _schema) {
9222
- await new Promise((resolve18) => setTimeout(resolve18, 500));
9239
+ await new Promise((resolve14) => setTimeout(resolve14, 500));
9223
9240
  const name = (_checkName || "").toLowerCase();
9224
9241
  if (name.includes("extract-facts")) {
9225
9242
  const arr = Array.from({ length: 6 }, (_, i) => ({
@@ -9580,7 +9597,7 @@ var init_command_executor = __esm({
9580
9597
  * Execute command with stdin input
9581
9598
  */
9582
9599
  executeWithStdin(command, options) {
9583
- return new Promise((resolve18, reject) => {
9600
+ return new Promise((resolve14, reject) => {
9584
9601
  const childProcess = (0, import_child_process2.exec)(
9585
9602
  command,
9586
9603
  {
@@ -9592,7 +9609,7 @@ var init_command_executor = __esm({
9592
9609
  if (error && error.killed && (error.code === "ETIMEDOUT" || error.signal === "SIGTERM")) {
9593
9610
  reject(new Error(`Command timed out after ${options.timeout || 3e4}ms`));
9594
9611
  } else {
9595
- resolve18({
9612
+ resolve14({
9596
9613
  stdout: stdout || "",
9597
9614
  stderr: stderr || "",
9598
9615
  exitCode: error ? error.code || 1 : 0
@@ -13146,7 +13163,7 @@ var init_config_schema = __esm({
13146
13163
  description: "Arguments/inputs for the workflow"
13147
13164
  },
13148
13165
  overrides: {
13149
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867%3E%3E",
13166
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13509-28103-src_types_config.ts-0-55255%3E%3E",
13150
13167
  description: "Override specific step configurations in the workflow"
13151
13168
  },
13152
13169
  output_mapping: {
@@ -13162,7 +13179,7 @@ var init_config_schema = __esm({
13162
13179
  description: "Config file path - alternative to workflow ID (loads a Visor config file as workflow)"
13163
13180
  },
13164
13181
  workflow_overrides: {
13165
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867%3E%3E",
13182
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13509-28103-src_types_config.ts-0-55255%3E%3E",
13166
13183
  description: "Alias for overrides - workflow step overrides (backward compatibility)"
13167
13184
  },
13168
13185
  ref: {
@@ -13344,7 +13361,8 @@ var init_config_schema = __esm({
13344
13361
  "issue_comment",
13345
13362
  "manual",
13346
13363
  "schedule",
13347
- "webhook_received"
13364
+ "webhook_received",
13365
+ "slack_message"
13348
13366
  ],
13349
13367
  description: "Valid event triggers for checks"
13350
13368
  },
@@ -13850,7 +13868,7 @@ var init_config_schema = __esm({
13850
13868
  description: "Custom output name (defaults to workflow name)"
13851
13869
  },
13852
13870
  overrides: {
13853
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867%3E%3E",
13871
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13509-28103-src_types_config.ts-0-55255%3E%3E",
13854
13872
  description: "Step overrides"
13855
13873
  },
13856
13874
  output_mapping: {
@@ -13865,13 +13883,13 @@ var init_config_schema = __esm({
13865
13883
  "^x-": {}
13866
13884
  }
13867
13885
  },
13868
- "Record<string,Partial<interface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867>>": {
13886
+ "Record<string,Partial<interface-src_types_config.ts-13509-28103-src_types_config.ts-0-55255>>": {
13869
13887
  type: "object",
13870
13888
  additionalProperties: {
13871
- $ref: "#/definitions/Partial%3Cinterface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867%3E"
13889
+ $ref: "#/definitions/Partial%3Cinterface-src_types_config.ts-13509-28103-src_types_config.ts-0-55255%3E"
13872
13890
  }
13873
13891
  },
13874
- "Partial<interface-src_types_config.ts-13489-28083-src_types_config.ts-0-53867>": {
13892
+ "Partial<interface-src_types_config.ts-13509-28103-src_types_config.ts-0-55255>": {
13875
13893
  type: "object",
13876
13894
  additionalProperties: false
13877
13895
  },
@@ -14753,6 +14771,10 @@ var init_config_schema = __esm({
14753
14771
  cron: {
14754
14772
  $ref: "#/definitions/Record%3Cstring%2CStaticCronJob%3E",
14755
14773
  description: "Static cron jobs defined in configuration (always executed)"
14774
+ },
14775
+ on_message: {
14776
+ $ref: "#/definitions/Record%3Cstring%2CSlackMessageTrigger%3E",
14777
+ description: "Slack message triggers for reactive workflow execution"
14756
14778
  }
14757
14779
  },
14758
14780
  additionalProperties: false,
@@ -15005,6 +15027,97 @@ var init_config_schema = __esm({
15005
15027
  "^x-": {}
15006
15028
  }
15007
15029
  },
15030
+ "Record<string,SlackMessageTrigger>": {
15031
+ type: "object",
15032
+ additionalProperties: {
15033
+ $ref: "#/definitions/SlackMessageTrigger"
15034
+ }
15035
+ },
15036
+ SlackMessageTrigger: {
15037
+ type: "object",
15038
+ properties: {
15039
+ channels: {
15040
+ type: "array",
15041
+ items: {
15042
+ type: "string"
15043
+ },
15044
+ description: 'Channel IDs to monitor (supports wildcard suffix, e.g., "CENG*")'
15045
+ },
15046
+ from: {
15047
+ type: "array",
15048
+ items: {
15049
+ type: "string"
15050
+ },
15051
+ description: "Only trigger on messages from these user IDs"
15052
+ },
15053
+ from_bots: {
15054
+ type: "boolean",
15055
+ description: "Allow bot messages to trigger (default: false)"
15056
+ },
15057
+ contains: {
15058
+ type: "array",
15059
+ items: {
15060
+ type: "string"
15061
+ },
15062
+ description: "Keyword match - any keyword triggers (case-insensitive OR)"
15063
+ },
15064
+ match: {
15065
+ type: "string",
15066
+ description: "Regex pattern to match against message text"
15067
+ },
15068
+ threads: {
15069
+ type: "string",
15070
+ enum: ["root_only", "thread_only", "any"],
15071
+ description: "Thread scope filter (default: 'any')"
15072
+ },
15073
+ workflow: {
15074
+ type: "string",
15075
+ description: "Workflow/check ID to execute"
15076
+ },
15077
+ inputs: {
15078
+ $ref: "#/definitions/Record%3Cstring%2Cunknown%3E",
15079
+ description: "Optional workflow inputs"
15080
+ },
15081
+ output: {
15082
+ type: "object",
15083
+ properties: {
15084
+ type: {
15085
+ type: "string",
15086
+ enum: ["slack", "github", "webhook", "none"],
15087
+ description: "Output type: slack, github, webhook, or none"
15088
+ },
15089
+ target: {
15090
+ type: "string",
15091
+ description: "Target (channel name, repo, URL)"
15092
+ },
15093
+ thread_id: {
15094
+ type: "string",
15095
+ description: "Thread ID for threaded outputs"
15096
+ }
15097
+ },
15098
+ required: ["type"],
15099
+ additionalProperties: false,
15100
+ description: "Output destination configuration",
15101
+ patternProperties: {
15102
+ "^x-": {}
15103
+ }
15104
+ },
15105
+ description: {
15106
+ type: "string",
15107
+ description: "Description for logging/display"
15108
+ },
15109
+ enabled: {
15110
+ type: "boolean",
15111
+ description: "Enable/disable this trigger (default: true)"
15112
+ }
15113
+ },
15114
+ required: ["workflow"],
15115
+ additionalProperties: false,
15116
+ description: "Slack message trigger for reactive workflow execution Triggers workflows based on Slack messages matching configured filters",
15117
+ patternProperties: {
15118
+ "^x-": {}
15119
+ }
15120
+ },
15008
15121
  PolicyConfig: {
15009
15122
  type: "object",
15010
15123
  properties: {
@@ -15146,7 +15259,8 @@ var init_config = __esm({
15146
15259
  "issue_comment",
15147
15260
  "manual",
15148
15261
  "schedule",
15149
- "webhook_received"
15262
+ "webhook_received",
15263
+ "slack_message"
15150
15264
  ];
15151
15265
  ConfigManager = class {
15152
15266
  validCheckTypes = [
@@ -17432,17 +17546,17 @@ var init_workflow_check_provider = __esm({
17432
17546
  * so it can be executed by the state machine as a nested workflow.
17433
17547
  */
17434
17548
  async loadWorkflowFromConfigPath(sourcePath, baseDir) {
17435
- const path31 = require("path");
17436
- const fs27 = require("fs");
17549
+ const path27 = require("path");
17550
+ const fs23 = require("fs");
17437
17551
  const yaml5 = require("js-yaml");
17438
- const resolved = path31.isAbsolute(sourcePath) ? sourcePath : path31.resolve(baseDir, sourcePath);
17439
- if (!fs27.existsSync(resolved)) {
17552
+ const resolved = path27.isAbsolute(sourcePath) ? sourcePath : path27.resolve(baseDir, sourcePath);
17553
+ if (!fs23.existsSync(resolved)) {
17440
17554
  throw new Error(`Workflow config not found at: ${resolved}`);
17441
17555
  }
17442
- const rawContent = fs27.readFileSync(resolved, "utf8");
17556
+ const rawContent = fs23.readFileSync(resolved, "utf8");
17443
17557
  const rawData = yaml5.load(rawContent);
17444
17558
  if (rawData.imports && Array.isArray(rawData.imports)) {
17445
- const configDir = path31.dirname(resolved);
17559
+ const configDir = path27.dirname(resolved);
17446
17560
  for (const source of rawData.imports) {
17447
17561
  const results = await this.registry.import(source, {
17448
17562
  basePath: configDir,
@@ -17472,8 +17586,8 @@ ${errors}`);
17472
17586
  if (!steps || Object.keys(steps).length === 0) {
17473
17587
  throw new Error(`Config '${resolved}' does not contain any steps to execute as a workflow`);
17474
17588
  }
17475
- const id = path31.basename(resolved).replace(/\.(ya?ml)$/i, "");
17476
- const name = loaded.name || `Workflow from ${path31.basename(resolved)}`;
17589
+ const id = path27.basename(resolved).replace(/\.(ya?ml)$/i, "");
17590
+ const name = loaded.name || `Workflow from ${path27.basename(resolved)}`;
17477
17591
  const workflowDef = {
17478
17592
  id,
17479
17593
  name,
@@ -17713,6 +17827,48 @@ function fromDbRow(row) {
17713
17827
  previousResponse: row.previous_response ?? void 0
17714
17828
  };
17715
17829
  }
17830
+ function toTriggerRow(trigger) {
17831
+ return {
17832
+ id: trigger.id,
17833
+ creator_id: trigger.creatorId,
17834
+ creator_context: trigger.creatorContext ?? null,
17835
+ creator_name: trigger.creatorName ?? null,
17836
+ description: trigger.description ?? null,
17837
+ channels: trigger.channels ? JSON.stringify(trigger.channels) : null,
17838
+ from_users: trigger.fromUsers ? JSON.stringify(trigger.fromUsers) : null,
17839
+ from_bots: trigger.fromBots ? 1 : 0,
17840
+ contains: trigger.contains ? JSON.stringify(trigger.contains) : null,
17841
+ match_pattern: trigger.matchPattern ?? null,
17842
+ threads: trigger.threads,
17843
+ workflow: trigger.workflow,
17844
+ inputs: trigger.inputs ? JSON.stringify(trigger.inputs) : null,
17845
+ output_context: trigger.outputContext ? JSON.stringify(trigger.outputContext) : null,
17846
+ status: trigger.status,
17847
+ enabled: trigger.enabled ? 1 : 0,
17848
+ created_at: trigger.createdAt
17849
+ };
17850
+ }
17851
+ function fromTriggerRow(row) {
17852
+ return {
17853
+ id: row.id,
17854
+ creatorId: row.creator_id,
17855
+ creatorContext: row.creator_context ?? void 0,
17856
+ creatorName: row.creator_name ?? void 0,
17857
+ description: row.description ?? void 0,
17858
+ channels: safeJsonParse(row.channels),
17859
+ fromUsers: safeJsonParse(row.from_users),
17860
+ fromBots: row.from_bots === 1,
17861
+ contains: safeJsonParse(row.contains),
17862
+ matchPattern: row.match_pattern ?? void 0,
17863
+ threads: row.threads,
17864
+ workflow: row.workflow,
17865
+ inputs: safeJsonParse(row.inputs),
17866
+ outputContext: safeJsonParse(row.output_context),
17867
+ status: row.status,
17868
+ enabled: row.enabled === 1,
17869
+ createdAt: row.created_at
17870
+ };
17871
+ }
17716
17872
  var import_path5, import_fs4, import_uuid, SqliteStoreBackend;
17717
17873
  var init_sqlite_store = __esm({
17718
17874
  "src/scheduler/store/sqlite-store.ts"() {
@@ -17805,6 +17961,32 @@ var init_sqlite_store = __esm({
17805
17961
  acquired_at BIGINT NOT NULL,
17806
17962
  expires_at BIGINT NOT NULL
17807
17963
  );
17964
+
17965
+ CREATE TABLE IF NOT EXISTS message_triggers (
17966
+ id VARCHAR(36) PRIMARY KEY,
17967
+ creator_id VARCHAR(255) NOT NULL,
17968
+ creator_context VARCHAR(255),
17969
+ creator_name VARCHAR(255),
17970
+ description TEXT,
17971
+ channels TEXT,
17972
+ from_users TEXT,
17973
+ from_bots BOOLEAN NOT NULL DEFAULT 0,
17974
+ contains TEXT,
17975
+ match_pattern TEXT,
17976
+ threads VARCHAR(20) NOT NULL DEFAULT 'any',
17977
+ workflow VARCHAR(255) NOT NULL,
17978
+ inputs TEXT,
17979
+ output_context TEXT,
17980
+ status VARCHAR(20) NOT NULL DEFAULT 'active',
17981
+ enabled BOOLEAN NOT NULL DEFAULT 1,
17982
+ created_at BIGINT NOT NULL
17983
+ );
17984
+
17985
+ CREATE INDEX IF NOT EXISTS idx_message_triggers_creator
17986
+ ON message_triggers(creator_id);
17987
+
17988
+ CREATE INDEX IF NOT EXISTS idx_message_triggers_status
17989
+ ON message_triggers(status);
17808
17990
  `);
17809
17991
  }
17810
17992
  // --- Helpers ---
@@ -18086,6 +18268,114 @@ var init_sqlite_store = __esm({
18086
18268
  }
18087
18269
  async flush() {
18088
18270
  }
18271
+ // --- Message Triggers ---
18272
+ async createTrigger(trigger) {
18273
+ const db = this.getDb();
18274
+ const newTrigger = {
18275
+ ...trigger,
18276
+ id: (0, import_uuid.v4)(),
18277
+ createdAt: Date.now()
18278
+ };
18279
+ const row = toTriggerRow(newTrigger);
18280
+ db.prepare(
18281
+ `INSERT INTO message_triggers (
18282
+ id, creator_id, creator_context, creator_name, description,
18283
+ channels, from_users, from_bots, contains, match_pattern,
18284
+ threads, workflow, inputs, output_context,
18285
+ status, enabled, created_at
18286
+ ) VALUES (
18287
+ ?, ?, ?, ?, ?,
18288
+ ?, ?, ?, ?, ?,
18289
+ ?, ?, ?, ?,
18290
+ ?, ?, ?
18291
+ )`
18292
+ ).run(
18293
+ row.id,
18294
+ row.creator_id,
18295
+ row.creator_context,
18296
+ row.creator_name,
18297
+ row.description,
18298
+ row.channels,
18299
+ row.from_users,
18300
+ row.from_bots,
18301
+ row.contains,
18302
+ row.match_pattern,
18303
+ row.threads,
18304
+ row.workflow,
18305
+ row.inputs,
18306
+ row.output_context,
18307
+ row.status,
18308
+ row.enabled,
18309
+ row.created_at
18310
+ );
18311
+ logger.info(
18312
+ `[SqliteStore] Created message trigger ${newTrigger.id} for user ${newTrigger.creatorId}`
18313
+ );
18314
+ return newTrigger;
18315
+ }
18316
+ async getTrigger(id) {
18317
+ const db = this.getDb();
18318
+ const row = db.prepare("SELECT * FROM message_triggers WHERE id = ?").get(id);
18319
+ return row ? fromTriggerRow(row) : void 0;
18320
+ }
18321
+ async updateTrigger(id, patch) {
18322
+ const db = this.getDb();
18323
+ const existing = db.prepare("SELECT * FROM message_triggers WHERE id = ?").get(id);
18324
+ if (!existing) return void 0;
18325
+ const current = fromTriggerRow(existing);
18326
+ const updated = {
18327
+ ...current,
18328
+ ...patch,
18329
+ id: current.id,
18330
+ createdAt: current.createdAt
18331
+ };
18332
+ const row = toTriggerRow(updated);
18333
+ db.prepare(
18334
+ `UPDATE message_triggers SET
18335
+ creator_id = ?, creator_context = ?, creator_name = ?, description = ?,
18336
+ channels = ?, from_users = ?, from_bots = ?, contains = ?, match_pattern = ?,
18337
+ threads = ?, workflow = ?, inputs = ?, output_context = ?,
18338
+ status = ?, enabled = ?
18339
+ WHERE id = ?`
18340
+ ).run(
18341
+ row.creator_id,
18342
+ row.creator_context,
18343
+ row.creator_name,
18344
+ row.description,
18345
+ row.channels,
18346
+ row.from_users,
18347
+ row.from_bots,
18348
+ row.contains,
18349
+ row.match_pattern,
18350
+ row.threads,
18351
+ row.workflow,
18352
+ row.inputs,
18353
+ row.output_context,
18354
+ row.status,
18355
+ row.enabled,
18356
+ row.id
18357
+ );
18358
+ return updated;
18359
+ }
18360
+ async deleteTrigger(id) {
18361
+ const db = this.getDb();
18362
+ const result = db.prepare("DELETE FROM message_triggers WHERE id = ?").run(id);
18363
+ if (result.changes > 0) {
18364
+ logger.info(`[SqliteStore] Deleted message trigger ${id}`);
18365
+ return true;
18366
+ }
18367
+ return false;
18368
+ }
18369
+ async getTriggersByCreator(creatorId) {
18370
+ const db = this.getDb();
18371
+ const rows = db.prepare("SELECT * FROM message_triggers WHERE creator_id = ?").all(creatorId);
18372
+ return rows.map(fromTriggerRow);
18373
+ }
18374
+ async getActiveTriggers() {
18375
+ const db = this.getDb();
18376
+ const rows = db.prepare("SELECT * FROM message_triggers WHERE status = 'active' AND enabled = 1").all();
18377
+ return rows.map(fromTriggerRow);
18378
+ }
18089
18379
  };
18090
18380
  }
18091
18381
  });
@@ -18103,8 +18393,8 @@ async function createStoreBackend(storageConfig, haConfig) {
18103
18393
  case "mssql": {
18104
18394
  try {
18105
18395
  const loaderPath = "../../enterprise/loader";
18106
- const { loadEnterpriseStoreBackend: loadEnterpriseStoreBackend2 } = await import(loaderPath);
18107
- return await loadEnterpriseStoreBackend2(driver, storageConfig, haConfig);
18396
+ const { loadEnterpriseStoreBackend } = await import(loaderPath);
18397
+ return await loadEnterpriseStoreBackend(driver, storageConfig, haConfig);
18108
18398
  } catch (err) {
18109
18399
  const msg = err instanceof Error ? err.message : String(err);
18110
18400
  logger.error(`[StoreFactory] Failed to load enterprise ${driver} backend: ${msg}`);
@@ -18205,11 +18495,19 @@ var init_schedule_store = __esm({
18205
18495
  init_json_migrator();
18206
18496
  ScheduleStore = class _ScheduleStore {
18207
18497
  static instance;
18498
+ static onTriggersChanged;
18208
18499
  backend = null;
18209
18500
  initialized = false;
18210
18501
  limits;
18211
18502
  config;
18212
18503
  externalBackend = null;
18504
+ /**
18505
+ * Register a callback to be invoked when message triggers change (create/update/delete).
18506
+ * Used by SlackSocketRunner to rebuild its evaluator.
18507
+ */
18508
+ static setTriggersChangedCallback(cb) {
18509
+ _ScheduleStore.onTriggersChanged = cb;
18510
+ }
18213
18511
  constructor(config, limits, backend) {
18214
18512
  this.config = config || {};
18215
18513
  this.limits = {
@@ -18371,6 +18669,53 @@ var init_schedule_store = __esm({
18371
18669
  }
18372
18670
  return this.backend;
18373
18671
  }
18672
+ // --- Message Trigger Methods ---
18673
+ /**
18674
+ * Create a new message trigger
18675
+ */
18676
+ async createTriggerAsync(trigger) {
18677
+ const result = await this.getBackend().createTrigger(trigger);
18678
+ _ScheduleStore.onTriggersChanged?.();
18679
+ return result;
18680
+ }
18681
+ /**
18682
+ * Get a trigger by ID
18683
+ */
18684
+ async getTriggerAsync(id) {
18685
+ return this.getBackend().getTrigger(id);
18686
+ }
18687
+ /**
18688
+ * Update a trigger
18689
+ */
18690
+ async updateTriggerAsync(id, patch) {
18691
+ const result = await this.getBackend().updateTrigger(id, patch);
18692
+ if (result) {
18693
+ _ScheduleStore.onTriggersChanged?.();
18694
+ }
18695
+ return result;
18696
+ }
18697
+ /**
18698
+ * Delete a trigger
18699
+ */
18700
+ async deleteTriggerAsync(id) {
18701
+ const result = await this.getBackend().deleteTrigger(id);
18702
+ if (result) {
18703
+ _ScheduleStore.onTriggersChanged?.();
18704
+ }
18705
+ return result;
18706
+ }
18707
+ /**
18708
+ * Get all triggers for a specific creator
18709
+ */
18710
+ async getTriggersByCreatorAsync(creatorId) {
18711
+ return this.getBackend().getTriggersByCreator(creatorId);
18712
+ }
18713
+ /**
18714
+ * Get all active triggers
18715
+ */
18716
+ async getActiveTriggersAsync() {
18717
+ return this.getBackend().getActiveTriggers();
18718
+ }
18374
18719
  /**
18375
18720
  * Shut down the backend cleanly
18376
18721
  */
@@ -19611,11 +19956,19 @@ async function handleScheduleAction(args, context2) {
19611
19956
  return handlePauseResume(args, context2, store, "paused");
19612
19957
  case "resume":
19613
19958
  return handlePauseResume(args, context2, store, "active");
19959
+ case "create_trigger":
19960
+ return handleCreateTrigger(args, context2, store);
19961
+ case "list_triggers":
19962
+ return handleListTriggers(context2, store);
19963
+ case "delete_trigger":
19964
+ return handleDeleteTrigger(args, context2, store);
19965
+ case "update_trigger":
19966
+ return handleUpdateTrigger(args, context2, store);
19614
19967
  default:
19615
19968
  return {
19616
19969
  success: false,
19617
19970
  message: `Unknown action: ${args.action}`,
19618
- error: `Supported actions: create, list, cancel, pause, resume`
19971
+ error: `Supported actions: create, list, cancel, pause, resume, create_trigger, list_triggers, delete_trigger, update_trigger`
19619
19972
  };
19620
19973
  }
19621
19974
  }
@@ -19858,6 +20211,172 @@ async function handlePauseResume(args, context2, store, newStatus) {
19858
20211
  schedule: updated
19859
20212
  };
19860
20213
  }
20214
+ function formatTrigger(trigger) {
20215
+ const channels = trigger.channels?.length ? trigger.channels.join(", ") : "all";
20216
+ const filters = [];
20217
+ if (trigger.contains?.length) filters.push(`contains: ${trigger.contains.join(", ")}`);
20218
+ if (trigger.matchPattern) filters.push(`match: /${trigger.matchPattern}/`);
20219
+ if (trigger.fromBots) filters.push("bots: yes");
20220
+ const filterStr = filters.length ? ` [${filters.join("; ")}]` : "";
20221
+ const status = trigger.enabled ? "" : " (disabled)";
20222
+ return `\`${trigger.id.substring(0, 8)}\` - channels: ${channels} \u2192 workflow: "${trigger.workflow}"${filterStr}${status}`;
20223
+ }
20224
+ async function handleCreateTrigger(args, context2, store) {
20225
+ if (!args.workflow) {
20226
+ return {
20227
+ success: false,
20228
+ message: "Missing workflow",
20229
+ error: "Please specify the workflow to run when the trigger fires."
20230
+ };
20231
+ }
20232
+ if (context2.availableWorkflows && !context2.availableWorkflows.includes(args.workflow)) {
20233
+ return {
20234
+ success: false,
20235
+ message: `Workflow "${args.workflow}" not found`,
20236
+ error: `Available workflows: ${context2.availableWorkflows.slice(0, 5).join(", ")}${context2.availableWorkflows.length > 5 ? "..." : ""}`
20237
+ };
20238
+ }
20239
+ if ((!args.trigger_channels || args.trigger_channels.length === 0) && (!args.trigger_contains || args.trigger_contains.length === 0) && !args.trigger_match) {
20240
+ return {
20241
+ success: false,
20242
+ message: "Missing trigger filters",
20243
+ error: "Please specify at least one filter: trigger_channels, trigger_contains, or trigger_match."
20244
+ };
20245
+ }
20246
+ const permissionCheck = checkSchedulePermissions(context2, args.workflow);
20247
+ if (!permissionCheck.allowed) {
20248
+ return {
20249
+ success: false,
20250
+ message: "Permission denied",
20251
+ error: permissionCheck.reason || "You do not have permission to create triggers for this workflow"
20252
+ };
20253
+ }
20254
+ try {
20255
+ const trigger = await store.createTriggerAsync({
20256
+ creatorId: context2.userId,
20257
+ creatorContext: context2.contextType,
20258
+ creatorName: context2.userName,
20259
+ description: args.trigger_description,
20260
+ channels: args.trigger_channels,
20261
+ fromBots: args.trigger_from_bots ?? false,
20262
+ contains: args.trigger_contains,
20263
+ matchPattern: args.trigger_match,
20264
+ threads: args.trigger_threads ?? "any",
20265
+ workflow: args.workflow,
20266
+ inputs: args.workflow_inputs,
20267
+ status: "active",
20268
+ enabled: true
20269
+ });
20270
+ logger.info(
20271
+ `[ScheduleTool] Created message trigger ${trigger.id} for user ${context2.userId}: workflow="${args.workflow}"`
20272
+ );
20273
+ return {
20274
+ success: true,
20275
+ message: `**Message trigger created!**
20276
+
20277
+ **Workflow**: ${trigger.workflow}
20278
+ **Channels**: ${trigger.channels?.join(", ") || "all"}
20279
+ ${trigger.contains?.length ? `**Contains**: ${trigger.contains.join(", ")}
20280
+ ` : ""}${trigger.matchPattern ? `**Pattern**: /${trigger.matchPattern}/
20281
+ ` : ""}${trigger.description ? `**Description**: ${trigger.description}
20282
+ ` : ""}
20283
+ ID: \`${trigger.id.substring(0, 8)}\``
20284
+ };
20285
+ } catch (error) {
20286
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
20287
+ logger.warn(`[ScheduleTool] Failed to create trigger: ${errorMsg}`);
20288
+ return {
20289
+ success: false,
20290
+ message: `Failed to create trigger: ${errorMsg}`,
20291
+ error: errorMsg
20292
+ };
20293
+ }
20294
+ }
20295
+ async function handleListTriggers(context2, store) {
20296
+ const triggers = await store.getTriggersByCreatorAsync(context2.userId);
20297
+ const active = triggers.filter((t) => t.status !== "deleted");
20298
+ if (active.length === 0) {
20299
+ return {
20300
+ success: true,
20301
+ message: `You don't have any message triggers.
20302
+
20303
+ To create one: "watch #channel for messages containing 'error' and run %my-workflow"`
20304
+ };
20305
+ }
20306
+ const lines = active.map((t, i) => `${i + 1}. ${formatTrigger(t)}`);
20307
+ return {
20308
+ success: true,
20309
+ message: `**Your message triggers:**
20310
+
20311
+ ${lines.join("\n")}
20312
+
20313
+ To delete: "delete trigger <id>"
20314
+ To disable: "disable trigger <id>"`
20315
+ };
20316
+ }
20317
+ async function handleDeleteTrigger(args, context2, store) {
20318
+ if (!args.trigger_id) {
20319
+ return {
20320
+ success: false,
20321
+ message: "Missing trigger ID",
20322
+ error: "Please specify which trigger to delete."
20323
+ };
20324
+ }
20325
+ const userTriggers = await store.getTriggersByCreatorAsync(context2.userId);
20326
+ let trigger = userTriggers.find((t) => t.id === args.trigger_id);
20327
+ if (!trigger) {
20328
+ trigger = userTriggers.find((t) => t.id.startsWith(args.trigger_id));
20329
+ }
20330
+ if (!trigger) {
20331
+ return {
20332
+ success: false,
20333
+ message: "Trigger not found",
20334
+ error: `Could not find trigger with ID "${args.trigger_id}" in your triggers. Use "list my triggers" to see your triggers.`
20335
+ };
20336
+ }
20337
+ await store.deleteTriggerAsync(trigger.id);
20338
+ logger.info(`[ScheduleTool] Deleted trigger ${trigger.id} for user ${context2.userId}`);
20339
+ return {
20340
+ success: true,
20341
+ message: `**Trigger deleted!**
20342
+
20343
+ Was: watching for "${trigger.workflow}" ${trigger.channels?.length ? `in ${trigger.channels.join(", ")}` : ""}`
20344
+ };
20345
+ }
20346
+ async function handleUpdateTrigger(args, context2, store) {
20347
+ if (!args.trigger_id) {
20348
+ return {
20349
+ success: false,
20350
+ message: "Missing trigger ID",
20351
+ error: "Please specify which trigger to update."
20352
+ };
20353
+ }
20354
+ const userTriggers = await store.getTriggersByCreatorAsync(context2.userId);
20355
+ let trigger = userTriggers.find((t) => t.id === args.trigger_id);
20356
+ if (!trigger) {
20357
+ trigger = userTriggers.find((t) => t.id.startsWith(args.trigger_id));
20358
+ }
20359
+ if (!trigger) {
20360
+ return {
20361
+ success: false,
20362
+ message: "Trigger not found",
20363
+ error: `Could not find trigger with ID "${args.trigger_id}" in your triggers.`
20364
+ };
20365
+ }
20366
+ const patch = {};
20367
+ if (args.trigger_enabled !== void 0) {
20368
+ patch.enabled = args.trigger_enabled;
20369
+ }
20370
+ await store.updateTriggerAsync(trigger.id, patch);
20371
+ const action = args.trigger_enabled === false ? "disabled" : "enabled";
20372
+ logger.info(`[ScheduleTool] ${action} trigger ${trigger.id} for user ${context2.userId}`);
20373
+ return {
20374
+ success: true,
20375
+ message: `**Trigger ${action}!**
20376
+
20377
+ "${trigger.workflow}" ${trigger.channels?.length ? `in ${trigger.channels.join(", ")}` : ""}`
20378
+ };
20379
+ }
19861
20380
  function getScheduleToolDefinition() {
19862
20381
  return {
19863
20382
  name: "schedule",
@@ -19877,6 +20396,17 @@ ACTIONS:
19877
20396
  - cancel: Remove a schedule by ID
19878
20397
  - pause/resume: Temporarily disable/enable a schedule
19879
20398
 
20399
+ MESSAGE-BASED TRIGGERS:
20400
+ In addition to time-based schedules, this tool can create/manage message-based triggers that react to
20401
+ Slack messages in specific channels. Use the create_trigger, list_triggers, delete_trigger, and update_trigger
20402
+ actions for this. Message triggers fire workflows based on message content, channel, sender, and thread scope.
20403
+
20404
+ TRIGGER ACTIONS:
20405
+ - create_trigger: Create a new message trigger (requires workflow + at least one filter)
20406
+ - list_triggers: Show user's message triggers
20407
+ - delete_trigger: Remove a trigger by ID
20408
+ - update_trigger: Enable/disable a trigger by ID
20409
+
19880
20410
  FOR CREATE ACTION - Extract these from user's request:
19881
20411
  1. WHAT:
19882
20412
  - If user says "schedule %some-workflow ...", populate 'workflow' with "some-workflow".
@@ -19964,14 +20494,36 @@ User: "list my schedules"
19964
20494
  \u2192 { "action": "list" }
19965
20495
 
19966
20496
  User: "cancel schedule abc123"
19967
- \u2192 { "action": "cancel", "schedule_id": "abc123" }`,
20497
+ \u2192 { "action": "cancel", "schedule_id": "abc123" }
20498
+
20499
+ User: "watch #cicd for messages containing 'failed' and run %handle-cicd"
20500
+ \u2192 { "action": "create_trigger", "trigger_channels": ["C0CICD"], "trigger_contains": ["failed"], "workflow": "handle-cicd" }
20501
+
20502
+ User: "list my message triggers"
20503
+ \u2192 { "action": "list_triggers" }
20504
+
20505
+ User: "delete trigger abc123"
20506
+ \u2192 { "action": "delete_trigger", "trigger_id": "abc123" }
20507
+
20508
+ User: "disable trigger abc123"
20509
+ \u2192 { "action": "update_trigger", "trigger_id": "abc123", "trigger_enabled": false }`,
19968
20510
  inputSchema: {
19969
20511
  type: "object",
19970
20512
  properties: {
19971
20513
  action: {
19972
20514
  type: "string",
19973
- enum: ["create", "list", "cancel", "pause", "resume"],
19974
- description: "What to do: create new, list existing, cancel/pause/resume by ID"
20515
+ enum: [
20516
+ "create",
20517
+ "list",
20518
+ "cancel",
20519
+ "pause",
20520
+ "resume",
20521
+ "create_trigger",
20522
+ "list_triggers",
20523
+ "delete_trigger",
20524
+ "update_trigger"
20525
+ ],
20526
+ description: "What to do: create/list/cancel/pause/resume for time-based schedules; create_trigger/list_triggers/delete_trigger/update_trigger for message-based triggers"
19975
20527
  },
19976
20528
  // WHAT to do
19977
20529
  reminder_text: {
@@ -20021,6 +20573,42 @@ User: "cancel schedule abc123"
20021
20573
  schedule_id: {
20022
20574
  type: "string",
20023
20575
  description: "For cancel/pause/resume: the schedule ID to act on (first 8 chars is enough)"
20576
+ },
20577
+ // For message trigger actions
20578
+ trigger_channels: {
20579
+ type: "array",
20580
+ items: { type: "string" },
20581
+ description: 'For create_trigger: Slack channel IDs to monitor (e.g., ["C0CICD"]). Supports wildcard suffix (e.g., "CENG*").'
20582
+ },
20583
+ trigger_from_bots: {
20584
+ type: "boolean",
20585
+ description: "For create_trigger: allow bot messages to trigger (default: false)"
20586
+ },
20587
+ trigger_contains: {
20588
+ type: "array",
20589
+ items: { type: "string" },
20590
+ description: 'For create_trigger: keywords to match (case-insensitive OR). E.g., ["failed", "error"].'
20591
+ },
20592
+ trigger_match: {
20593
+ type: "string",
20594
+ description: "For create_trigger: regex pattern to match against message text."
20595
+ },
20596
+ trigger_threads: {
20597
+ type: "string",
20598
+ enum: ["root_only", "thread_only", "any"],
20599
+ description: 'For create_trigger: thread scope filter (default: "any").'
20600
+ },
20601
+ trigger_description: {
20602
+ type: "string",
20603
+ description: "For create_trigger: human-readable description of the trigger."
20604
+ },
20605
+ trigger_id: {
20606
+ type: "string",
20607
+ description: "For delete_trigger/update_trigger: the trigger ID to act on (first 8 chars is enough)."
20608
+ },
20609
+ trigger_enabled: {
20610
+ type: "boolean",
20611
+ description: "For update_trigger: set to false to disable, true to enable."
20024
20612
  }
20025
20613
  },
20026
20614
  required: ["action"]
@@ -20350,7 +20938,7 @@ var init_mcp_custom_sse_server = __esm({
20350
20938
  * Returns the actual bound port number
20351
20939
  */
20352
20940
  async start() {
20353
- return new Promise((resolve18, reject) => {
20941
+ return new Promise((resolve14, reject) => {
20354
20942
  try {
20355
20943
  this.server = import_http.default.createServer((req, res) => {
20356
20944
  this.handleRequest(req, res).catch((error) => {
@@ -20384,7 +20972,7 @@ var init_mcp_custom_sse_server = __esm({
20384
20972
  );
20385
20973
  }
20386
20974
  this.startKeepalive();
20387
- resolve18(this.port);
20975
+ resolve14(this.port);
20388
20976
  });
20389
20977
  } catch (error) {
20390
20978
  reject(error);
@@ -20447,7 +21035,7 @@ var init_mcp_custom_sse_server = __esm({
20447
21035
  logger.debug(
20448
21036
  `[CustomToolsSSEServer:${this.sessionId}] Grace period before stop: ${waitMs}ms (activeToolCalls=${this.activeToolCalls})`
20449
21037
  );
20450
- await new Promise((resolve18) => setTimeout(resolve18, waitMs));
21038
+ await new Promise((resolve14) => setTimeout(resolve14, waitMs));
20451
21039
  }
20452
21040
  }
20453
21041
  if (this.activeToolCalls > 0) {
@@ -20456,7 +21044,7 @@ var init_mcp_custom_sse_server = __esm({
20456
21044
  `[CustomToolsSSEServer:${this.sessionId}] Waiting for ${this.activeToolCalls} active tool call(s) before stop`
20457
21045
  );
20458
21046
  while (this.activeToolCalls > 0 && Date.now() - startedAt < effectiveDrainTimeoutMs) {
20459
- await new Promise((resolve18) => setTimeout(resolve18, 250));
21047
+ await new Promise((resolve14) => setTimeout(resolve14, 250));
20460
21048
  }
20461
21049
  if (this.activeToolCalls > 0) {
20462
21050
  logger.warn(
@@ -20481,21 +21069,21 @@ var init_mcp_custom_sse_server = __esm({
20481
21069
  }
20482
21070
  this.connections.clear();
20483
21071
  if (this.server) {
20484
- await new Promise((resolve18, reject) => {
21072
+ await new Promise((resolve14, reject) => {
20485
21073
  const timeout = setTimeout(() => {
20486
21074
  if (this.debug) {
20487
21075
  logger.debug(
20488
21076
  `[CustomToolsSSEServer:${this.sessionId}] Force closing server after timeout`
20489
21077
  );
20490
21078
  }
20491
- this.server?.close(() => resolve18());
21079
+ this.server?.close(() => resolve14());
20492
21080
  }, 5e3);
20493
21081
  this.server.close((error) => {
20494
21082
  clearTimeout(timeout);
20495
21083
  if (error) {
20496
21084
  reject(error);
20497
21085
  } else {
20498
- resolve18();
21086
+ resolve14();
20499
21087
  }
20500
21088
  });
20501
21089
  });
@@ -20921,7 +21509,7 @@ var init_mcp_custom_sse_server = __esm({
20921
21509
  logger.warn(
20922
21510
  `[CustomToolsSSEServer:${this.sessionId}] Tool ${toolName} failed (attempt ${attempt + 1}/${retryCount + 1}): ${errorMsg}. Retrying in ${delay}ms`
20923
21511
  );
20924
- await new Promise((resolve18) => setTimeout(resolve18, delay));
21512
+ await new Promise((resolve14) => setTimeout(resolve14, delay));
20925
21513
  attempt++;
20926
21514
  }
20927
21515
  }
@@ -21234,9 +21822,9 @@ var init_ai_check_provider = __esm({
21234
21822
  } else {
21235
21823
  resolvedPath = import_path7.default.resolve(process.cwd(), str);
21236
21824
  }
21237
- const fs27 = require("fs").promises;
21825
+ const fs23 = require("fs").promises;
21238
21826
  try {
21239
- const stat2 = await fs27.stat(resolvedPath);
21827
+ const stat2 = await fs23.stat(resolvedPath);
21240
21828
  return stat2.isFile();
21241
21829
  } catch {
21242
21830
  return hasFileExtension && (isRelativePath || isAbsolutePath || hasPathSeparators);
@@ -27164,14 +27752,14 @@ var require_util = __commonJS({
27164
27752
  }
27165
27753
  const port = url.port != null ? url.port : url.protocol === "https:" ? 443 : 80;
27166
27754
  let origin = url.origin != null ? url.origin : `${url.protocol}//${url.hostname}:${port}`;
27167
- let path31 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
27755
+ let path27 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
27168
27756
  if (origin.endsWith("/")) {
27169
27757
  origin = origin.substring(0, origin.length - 1);
27170
27758
  }
27171
- if (path31 && !path31.startsWith("/")) {
27172
- path31 = `/${path31}`;
27759
+ if (path27 && !path27.startsWith("/")) {
27760
+ path27 = `/${path27}`;
27173
27761
  }
27174
- url = new URL(origin + path31);
27762
+ url = new URL(origin + path27);
27175
27763
  }
27176
27764
  return url;
27177
27765
  }
@@ -28785,20 +29373,20 @@ var require_parseParams = __commonJS({
28785
29373
  var require_basename = __commonJS({
28786
29374
  "node_modules/@fastify/busboy/lib/utils/basename.js"(exports2, module2) {
28787
29375
  "use strict";
28788
- module2.exports = function basename4(path31) {
28789
- if (typeof path31 !== "string") {
29376
+ module2.exports = function basename4(path27) {
29377
+ if (typeof path27 !== "string") {
28790
29378
  return "";
28791
29379
  }
28792
- for (var i = path31.length - 1; i >= 0; --i) {
28793
- switch (path31.charCodeAt(i)) {
29380
+ for (var i = path27.length - 1; i >= 0; --i) {
29381
+ switch (path27.charCodeAt(i)) {
28794
29382
  case 47:
28795
29383
  // '/'
28796
29384
  case 92:
28797
- path31 = path31.slice(i + 1);
28798
- return path31 === ".." || path31 === "." ? "" : path31;
29385
+ path27 = path27.slice(i + 1);
29386
+ return path27 === ".." || path27 === "." ? "" : path27;
28799
29387
  }
28800
29388
  }
28801
- return path31 === ".." || path31 === "." ? "" : path31;
29389
+ return path27 === ".." || path27 === "." ? "" : path27;
28802
29390
  };
28803
29391
  }
28804
29392
  });
@@ -29802,11 +30390,11 @@ var require_util2 = __commonJS({
29802
30390
  var assert = require("assert");
29803
30391
  var { isUint8Array } = require("util/types");
29804
30392
  var supportedHashes = [];
29805
- var crypto4;
30393
+ var crypto2;
29806
30394
  try {
29807
- crypto4 = require("crypto");
30395
+ crypto2 = require("crypto");
29808
30396
  const possibleRelevantHashes = ["sha256", "sha384", "sha512"];
29809
- supportedHashes = crypto4.getHashes().filter((hash) => possibleRelevantHashes.includes(hash));
30397
+ supportedHashes = crypto2.getHashes().filter((hash) => possibleRelevantHashes.includes(hash));
29810
30398
  } catch {
29811
30399
  }
29812
30400
  function responseURL(response) {
@@ -30083,7 +30671,7 @@ var require_util2 = __commonJS({
30083
30671
  }
30084
30672
  }
30085
30673
  function bytesMatch(bytes, metadataList) {
30086
- if (crypto4 === void 0) {
30674
+ if (crypto2 === void 0) {
30087
30675
  return true;
30088
30676
  }
30089
30677
  const parsedMetadata = parseMetadata(metadataList);
@@ -30098,7 +30686,7 @@ var require_util2 = __commonJS({
30098
30686
  for (const item of metadata) {
30099
30687
  const algorithm = item.algo;
30100
30688
  const expectedValue = item.hash;
30101
- let actualValue = crypto4.createHash(algorithm).update(bytes).digest("base64");
30689
+ let actualValue = crypto2.createHash(algorithm).update(bytes).digest("base64");
30102
30690
  if (actualValue[actualValue.length - 1] === "=") {
30103
30691
  if (actualValue[actualValue.length - 2] === "=") {
30104
30692
  actualValue = actualValue.slice(0, -2);
@@ -30191,8 +30779,8 @@ var require_util2 = __commonJS({
30191
30779
  function createDeferredPromise() {
30192
30780
  let res;
30193
30781
  let rej;
30194
- const promise = new Promise((resolve18, reject) => {
30195
- res = resolve18;
30782
+ const promise = new Promise((resolve14, reject) => {
30783
+ res = resolve14;
30196
30784
  rej = reject;
30197
30785
  });
30198
30786
  return { promise, resolve: res, reject: rej };
@@ -31445,8 +32033,8 @@ var require_body = __commonJS({
31445
32033
  var { parseMIMEType, serializeAMimeType } = require_dataURL();
31446
32034
  var random;
31447
32035
  try {
31448
- const crypto4 = require("crypto");
31449
- random = (max) => crypto4.randomInt(0, max);
32036
+ const crypto2 = require("crypto");
32037
+ random = (max) => crypto2.randomInt(0, max);
31450
32038
  } catch {
31451
32039
  random = (max) => Math.floor(Math.random(max));
31452
32040
  }
@@ -31697,8 +32285,8 @@ Content-Type: ${value.type || "application/octet-stream"}\r
31697
32285
  });
31698
32286
  }
31699
32287
  });
31700
- const busboyResolve = new Promise((resolve18, reject) => {
31701
- busboy.on("finish", resolve18);
32288
+ const busboyResolve = new Promise((resolve14, reject) => {
32289
+ busboy.on("finish", resolve14);
31702
32290
  busboy.on("error", (err) => reject(new TypeError(err)));
31703
32291
  });
31704
32292
  if (this.body !== null) for await (const chunk of consumeBody(this[kState].body)) busboy.write(chunk);
@@ -31829,7 +32417,7 @@ var require_request = __commonJS({
31829
32417
  }
31830
32418
  var Request = class _Request {
31831
32419
  constructor(origin, {
31832
- path: path31,
32420
+ path: path27,
31833
32421
  method,
31834
32422
  body,
31835
32423
  headers,
@@ -31843,11 +32431,11 @@ var require_request = __commonJS({
31843
32431
  throwOnError,
31844
32432
  expectContinue
31845
32433
  }, handler) {
31846
- if (typeof path31 !== "string") {
32434
+ if (typeof path27 !== "string") {
31847
32435
  throw new InvalidArgumentError("path must be a string");
31848
- } else if (path31[0] !== "/" && !(path31.startsWith("http://") || path31.startsWith("https://")) && method !== "CONNECT") {
32436
+ } else if (path27[0] !== "/" && !(path27.startsWith("http://") || path27.startsWith("https://")) && method !== "CONNECT") {
31849
32437
  throw new InvalidArgumentError("path must be an absolute URL or start with a slash");
31850
- } else if (invalidPathRegex.exec(path31) !== null) {
32438
+ } else if (invalidPathRegex.exec(path27) !== null) {
31851
32439
  throw new InvalidArgumentError("invalid request path");
31852
32440
  }
31853
32441
  if (typeof method !== "string") {
@@ -31910,7 +32498,7 @@ var require_request = __commonJS({
31910
32498
  this.completed = false;
31911
32499
  this.aborted = false;
31912
32500
  this.upgrade = upgrade || null;
31913
- this.path = query ? util.buildURL(path31, query) : path31;
32501
+ this.path = query ? util.buildURL(path27, query) : path27;
31914
32502
  this.origin = origin;
31915
32503
  this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent;
31916
32504
  this.blocking = blocking == null ? false : blocking;
@@ -32232,9 +32820,9 @@ var require_dispatcher_base = __commonJS({
32232
32820
  }
32233
32821
  close(callback) {
32234
32822
  if (callback === void 0) {
32235
- return new Promise((resolve18, reject) => {
32823
+ return new Promise((resolve14, reject) => {
32236
32824
  this.close((err, data) => {
32237
- return err ? reject(err) : resolve18(data);
32825
+ return err ? reject(err) : resolve14(data);
32238
32826
  });
32239
32827
  });
32240
32828
  }
@@ -32272,12 +32860,12 @@ var require_dispatcher_base = __commonJS({
32272
32860
  err = null;
32273
32861
  }
32274
32862
  if (callback === void 0) {
32275
- return new Promise((resolve18, reject) => {
32863
+ return new Promise((resolve14, reject) => {
32276
32864
  this.destroy(err, (err2, data) => {
32277
32865
  return err2 ? (
32278
32866
  /* istanbul ignore next: should never error */
32279
32867
  reject(err2)
32280
- ) : resolve18(data);
32868
+ ) : resolve14(data);
32281
32869
  });
32282
32870
  });
32283
32871
  }
@@ -32918,9 +33506,9 @@ var require_RedirectHandler = __commonJS({
32918
33506
  return this.handler.onHeaders(statusCode, headers, resume, statusText);
32919
33507
  }
32920
33508
  const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)));
32921
- const path31 = search ? `${pathname}${search}` : pathname;
33509
+ const path27 = search ? `${pathname}${search}` : pathname;
32922
33510
  this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin);
32923
- this.opts.path = path31;
33511
+ this.opts.path = path27;
32924
33512
  this.opts.origin = origin;
32925
33513
  this.opts.maxRedirections = 0;
32926
33514
  this.opts.query = null;
@@ -33339,16 +33927,16 @@ var require_client = __commonJS({
33339
33927
  return this[kNeedDrain] < 2;
33340
33928
  }
33341
33929
  async [kClose]() {
33342
- return new Promise((resolve18) => {
33930
+ return new Promise((resolve14) => {
33343
33931
  if (!this[kSize]) {
33344
- resolve18(null);
33932
+ resolve14(null);
33345
33933
  } else {
33346
- this[kClosedResolve] = resolve18;
33934
+ this[kClosedResolve] = resolve14;
33347
33935
  }
33348
33936
  });
33349
33937
  }
33350
33938
  async [kDestroy](err) {
33351
- return new Promise((resolve18) => {
33939
+ return new Promise((resolve14) => {
33352
33940
  const requests = this[kQueue].splice(this[kPendingIdx]);
33353
33941
  for (let i = 0; i < requests.length; i++) {
33354
33942
  const request = requests[i];
@@ -33359,7 +33947,7 @@ var require_client = __commonJS({
33359
33947
  this[kClosedResolve]();
33360
33948
  this[kClosedResolve] = null;
33361
33949
  }
33362
- resolve18();
33950
+ resolve14();
33363
33951
  };
33364
33952
  if (this[kHTTP2Session] != null) {
33365
33953
  util.destroy(this[kHTTP2Session], err);
@@ -33939,7 +34527,7 @@ var require_client = __commonJS({
33939
34527
  });
33940
34528
  }
33941
34529
  try {
33942
- const socket = await new Promise((resolve18, reject) => {
34530
+ const socket = await new Promise((resolve14, reject) => {
33943
34531
  client[kConnector]({
33944
34532
  host,
33945
34533
  hostname,
@@ -33951,7 +34539,7 @@ var require_client = __commonJS({
33951
34539
  if (err) {
33952
34540
  reject(err);
33953
34541
  } else {
33954
- resolve18(socket2);
34542
+ resolve14(socket2);
33955
34543
  }
33956
34544
  });
33957
34545
  });
@@ -34162,7 +34750,7 @@ var require_client = __commonJS({
34162
34750
  writeH2(client, client[kHTTP2Session], request);
34163
34751
  return;
34164
34752
  }
34165
- const { body, method, path: path31, host, upgrade, headers, blocking, reset } = request;
34753
+ const { body, method, path: path27, host, upgrade, headers, blocking, reset } = request;
34166
34754
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
34167
34755
  if (body && typeof body.read === "function") {
34168
34756
  body.read(0);
@@ -34212,7 +34800,7 @@ var require_client = __commonJS({
34212
34800
  if (blocking) {
34213
34801
  socket[kBlocking] = true;
34214
34802
  }
34215
- let header = `${method} ${path31} HTTP/1.1\r
34803
+ let header = `${method} ${path27} HTTP/1.1\r
34216
34804
  `;
34217
34805
  if (typeof host === "string") {
34218
34806
  header += `host: ${host}\r
@@ -34275,7 +34863,7 @@ upgrade: ${upgrade}\r
34275
34863
  return true;
34276
34864
  }
34277
34865
  function writeH2(client, session, request) {
34278
- const { body, method, path: path31, host, upgrade, expectContinue, signal, headers: reqHeaders } = request;
34866
+ const { body, method, path: path27, host, upgrade, expectContinue, signal, headers: reqHeaders } = request;
34279
34867
  let headers;
34280
34868
  if (typeof reqHeaders === "string") headers = Request[kHTTP2CopyHeaders](reqHeaders.trim());
34281
34869
  else headers = reqHeaders;
@@ -34318,7 +34906,7 @@ upgrade: ${upgrade}\r
34318
34906
  });
34319
34907
  return true;
34320
34908
  }
34321
- headers[HTTP2_HEADER_PATH] = path31;
34909
+ headers[HTTP2_HEADER_PATH] = path27;
34322
34910
  headers[HTTP2_HEADER_SCHEME] = "https";
34323
34911
  const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
34324
34912
  if (body && typeof body.read === "function") {
@@ -34575,12 +35163,12 @@ upgrade: ${upgrade}\r
34575
35163
  cb();
34576
35164
  }
34577
35165
  }
34578
- const waitForDrain = () => new Promise((resolve18, reject) => {
35166
+ const waitForDrain = () => new Promise((resolve14, reject) => {
34579
35167
  assert(callback === null);
34580
35168
  if (socket[kError]) {
34581
35169
  reject(socket[kError]);
34582
35170
  } else {
34583
- callback = resolve18;
35171
+ callback = resolve14;
34584
35172
  }
34585
35173
  });
34586
35174
  if (client[kHTTPConnVersion] === "h2") {
@@ -34926,8 +35514,8 @@ var require_pool_base = __commonJS({
34926
35514
  if (this[kQueue].isEmpty()) {
34927
35515
  return Promise.all(this[kClients].map((c) => c.close()));
34928
35516
  } else {
34929
- return new Promise((resolve18) => {
34930
- this[kClosedResolve] = resolve18;
35517
+ return new Promise((resolve14) => {
35518
+ this[kClosedResolve] = resolve14;
34931
35519
  });
34932
35520
  }
34933
35521
  }
@@ -35505,7 +36093,7 @@ var require_readable = __commonJS({
35505
36093
  if (this.closed) {
35506
36094
  return Promise.resolve(null);
35507
36095
  }
35508
- return new Promise((resolve18, reject) => {
36096
+ return new Promise((resolve14, reject) => {
35509
36097
  const signalListenerCleanup = signal ? util.addAbortListener(signal, () => {
35510
36098
  this.destroy();
35511
36099
  }) : noop;
@@ -35514,7 +36102,7 @@ var require_readable = __commonJS({
35514
36102
  if (signal && signal.aborted) {
35515
36103
  reject(signal.reason || Object.assign(new Error("The operation was aborted"), { name: "AbortError" }));
35516
36104
  } else {
35517
- resolve18(null);
36105
+ resolve14(null);
35518
36106
  }
35519
36107
  }).on("error", noop).on("data", function(chunk) {
35520
36108
  limit -= chunk.length;
@@ -35536,11 +36124,11 @@ var require_readable = __commonJS({
35536
36124
  throw new TypeError("unusable");
35537
36125
  }
35538
36126
  assert(!stream[kConsume]);
35539
- return new Promise((resolve18, reject) => {
36127
+ return new Promise((resolve14, reject) => {
35540
36128
  stream[kConsume] = {
35541
36129
  type,
35542
36130
  stream,
35543
- resolve: resolve18,
36131
+ resolve: resolve14,
35544
36132
  reject,
35545
36133
  length: 0,
35546
36134
  body: []
@@ -35575,12 +36163,12 @@ var require_readable = __commonJS({
35575
36163
  }
35576
36164
  }
35577
36165
  function consumeEnd(consume2) {
35578
- const { type, body, resolve: resolve18, stream, length } = consume2;
36166
+ const { type, body, resolve: resolve14, stream, length } = consume2;
35579
36167
  try {
35580
36168
  if (type === "text") {
35581
- resolve18(toUSVString(Buffer.concat(body)));
36169
+ resolve14(toUSVString(Buffer.concat(body)));
35582
36170
  } else if (type === "json") {
35583
- resolve18(JSON.parse(Buffer.concat(body)));
36171
+ resolve14(JSON.parse(Buffer.concat(body)));
35584
36172
  } else if (type === "arrayBuffer") {
35585
36173
  const dst = new Uint8Array(length);
35586
36174
  let pos = 0;
@@ -35588,12 +36176,12 @@ var require_readable = __commonJS({
35588
36176
  dst.set(buf, pos);
35589
36177
  pos += buf.byteLength;
35590
36178
  }
35591
- resolve18(dst.buffer);
36179
+ resolve14(dst.buffer);
35592
36180
  } else if (type === "blob") {
35593
36181
  if (!Blob2) {
35594
36182
  Blob2 = require("buffer").Blob;
35595
36183
  }
35596
- resolve18(new Blob2(body, { type: stream[kContentType] }));
36184
+ resolve14(new Blob2(body, { type: stream[kContentType] }));
35597
36185
  }
35598
36186
  consumeFinish(consume2);
35599
36187
  } catch (err) {
@@ -35850,9 +36438,9 @@ var require_api_request = __commonJS({
35850
36438
  };
35851
36439
  function request(opts, callback) {
35852
36440
  if (callback === void 0) {
35853
- return new Promise((resolve18, reject) => {
36441
+ return new Promise((resolve14, reject) => {
35854
36442
  request.call(this, opts, (err, data) => {
35855
- return err ? reject(err) : resolve18(data);
36443
+ return err ? reject(err) : resolve14(data);
35856
36444
  });
35857
36445
  });
35858
36446
  }
@@ -36025,9 +36613,9 @@ var require_api_stream = __commonJS({
36025
36613
  };
36026
36614
  function stream(opts, factory, callback) {
36027
36615
  if (callback === void 0) {
36028
- return new Promise((resolve18, reject) => {
36616
+ return new Promise((resolve14, reject) => {
36029
36617
  stream.call(this, opts, factory, (err, data) => {
36030
- return err ? reject(err) : resolve18(data);
36618
+ return err ? reject(err) : resolve14(data);
36031
36619
  });
36032
36620
  });
36033
36621
  }
@@ -36308,9 +36896,9 @@ var require_api_upgrade = __commonJS({
36308
36896
  };
36309
36897
  function upgrade(opts, callback) {
36310
36898
  if (callback === void 0) {
36311
- return new Promise((resolve18, reject) => {
36899
+ return new Promise((resolve14, reject) => {
36312
36900
  upgrade.call(this, opts, (err, data) => {
36313
- return err ? reject(err) : resolve18(data);
36901
+ return err ? reject(err) : resolve14(data);
36314
36902
  });
36315
36903
  });
36316
36904
  }
@@ -36399,9 +36987,9 @@ var require_api_connect = __commonJS({
36399
36987
  };
36400
36988
  function connect(opts, callback) {
36401
36989
  if (callback === void 0) {
36402
- return new Promise((resolve18, reject) => {
36990
+ return new Promise((resolve14, reject) => {
36403
36991
  connect.call(this, opts, (err, data) => {
36404
- return err ? reject(err) : resolve18(data);
36992
+ return err ? reject(err) : resolve14(data);
36405
36993
  });
36406
36994
  });
36407
36995
  }
@@ -36561,20 +37149,20 @@ var require_mock_utils = __commonJS({
36561
37149
  }
36562
37150
  return true;
36563
37151
  }
36564
- function safeUrl(path31) {
36565
- if (typeof path31 !== "string") {
36566
- return path31;
37152
+ function safeUrl(path27) {
37153
+ if (typeof path27 !== "string") {
37154
+ return path27;
36567
37155
  }
36568
- const pathSegments = path31.split("?");
37156
+ const pathSegments = path27.split("?");
36569
37157
  if (pathSegments.length !== 2) {
36570
- return path31;
37158
+ return path27;
36571
37159
  }
36572
37160
  const qp = new URLSearchParams(pathSegments.pop());
36573
37161
  qp.sort();
36574
37162
  return [...pathSegments, qp.toString()].join("?");
36575
37163
  }
36576
- function matchKey(mockDispatch2, { path: path31, method, body, headers }) {
36577
- const pathMatch = matchValue(mockDispatch2.path, path31);
37164
+ function matchKey(mockDispatch2, { path: path27, method, body, headers }) {
37165
+ const pathMatch = matchValue(mockDispatch2.path, path27);
36578
37166
  const methodMatch = matchValue(mockDispatch2.method, method);
36579
37167
  const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true;
36580
37168
  const headersMatch = matchHeaders(mockDispatch2, headers);
@@ -36592,7 +37180,7 @@ var require_mock_utils = __commonJS({
36592
37180
  function getMockDispatch(mockDispatches, key) {
36593
37181
  const basePath = key.query ? buildURL(key.path, key.query) : key.path;
36594
37182
  const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath;
36595
- let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path31 }) => matchValue(safeUrl(path31), resolvedPath));
37183
+ let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path27 }) => matchValue(safeUrl(path27), resolvedPath));
36596
37184
  if (matchedMockDispatches.length === 0) {
36597
37185
  throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`);
36598
37186
  }
@@ -36629,9 +37217,9 @@ var require_mock_utils = __commonJS({
36629
37217
  }
36630
37218
  }
36631
37219
  function buildKey(opts) {
36632
- const { path: path31, method, body, headers, query } = opts;
37220
+ const { path: path27, method, body, headers, query } = opts;
36633
37221
  return {
36634
- path: path31,
37222
+ path: path27,
36635
37223
  method,
36636
37224
  body,
36637
37225
  headers,
@@ -37080,10 +37668,10 @@ var require_pending_interceptors_formatter = __commonJS({
37080
37668
  }
37081
37669
  format(pendingInterceptors) {
37082
37670
  const withPrettyHeaders = pendingInterceptors.map(
37083
- ({ method, path: path31, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
37671
+ ({ method, path: path27, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
37084
37672
  Method: method,
37085
37673
  Origin: origin,
37086
- Path: path31,
37674
+ Path: path27,
37087
37675
  "Status code": statusCode,
37088
37676
  Persistent: persist ? "\u2705" : "\u274C",
37089
37677
  Invocations: timesInvoked,
@@ -40024,7 +40612,7 @@ var require_fetch = __commonJS({
40024
40612
  async function dispatch({ body }) {
40025
40613
  const url = requestCurrentURL(request);
40026
40614
  const agent = fetchParams.controller.dispatcher;
40027
- return new Promise((resolve18, reject) => agent.dispatch(
40615
+ return new Promise((resolve14, reject) => agent.dispatch(
40028
40616
  {
40029
40617
  path: url.pathname + url.search,
40030
40618
  origin: url.origin,
@@ -40100,7 +40688,7 @@ var require_fetch = __commonJS({
40100
40688
  }
40101
40689
  }
40102
40690
  }
40103
- resolve18({
40691
+ resolve14({
40104
40692
  status,
40105
40693
  statusText,
40106
40694
  headersList: headers[kHeadersList],
@@ -40143,7 +40731,7 @@ var require_fetch = __commonJS({
40143
40731
  const val = headersList[n + 1].toString("latin1");
40144
40732
  headers[kHeadersList].append(key, val);
40145
40733
  }
40146
- resolve18({
40734
+ resolve14({
40147
40735
  status,
40148
40736
  statusText: STATUS_CODES[status],
40149
40737
  headersList: headers[kHeadersList],
@@ -41704,8 +42292,8 @@ var require_util6 = __commonJS({
41704
42292
  }
41705
42293
  }
41706
42294
  }
41707
- function validateCookiePath(path31) {
41708
- for (const char of path31) {
42295
+ function validateCookiePath(path27) {
42296
+ for (const char of path27) {
41709
42297
  const code = char.charCodeAt(0);
41710
42298
  if (code < 33 || char === ";") {
41711
42299
  throw new Error("Invalid cookie path");
@@ -42502,9 +43090,9 @@ var require_connection = __commonJS({
42502
43090
  channels.open = diagnosticsChannel.channel("undici:websocket:open");
42503
43091
  channels.close = diagnosticsChannel.channel("undici:websocket:close");
42504
43092
  channels.socketError = diagnosticsChannel.channel("undici:websocket:socket_error");
42505
- var crypto4;
43093
+ var crypto2;
42506
43094
  try {
42507
- crypto4 = require("crypto");
43095
+ crypto2 = require("crypto");
42508
43096
  } catch {
42509
43097
  }
42510
43098
  function establishWebSocketConnection(url, protocols, ws, onEstablish, options) {
@@ -42523,7 +43111,7 @@ var require_connection = __commonJS({
42523
43111
  const headersList = new Headers(options.headers)[kHeadersList];
42524
43112
  request.headersList = headersList;
42525
43113
  }
42526
- const keyValue = crypto4.randomBytes(16).toString("base64");
43114
+ const keyValue = crypto2.randomBytes(16).toString("base64");
42527
43115
  request.headersList.append("sec-websocket-key", keyValue);
42528
43116
  request.headersList.append("sec-websocket-version", "13");
42529
43117
  for (const protocol of protocols) {
@@ -42552,7 +43140,7 @@ var require_connection = __commonJS({
42552
43140
  return;
42553
43141
  }
42554
43142
  const secWSAccept = response.headersList.get("Sec-WebSocket-Accept");
42555
- const digest = crypto4.createHash("sha1").update(keyValue + uid).digest("base64");
43143
+ const digest = crypto2.createHash("sha1").update(keyValue + uid).digest("base64");
42556
43144
  if (secWSAccept !== digest) {
42557
43145
  failWebsocketConnection(ws, "Incorrect hash received in Sec-WebSocket-Accept header.");
42558
43146
  return;
@@ -42632,9 +43220,9 @@ var require_frame = __commonJS({
42632
43220
  "node_modules/undici/lib/websocket/frame.js"(exports2, module2) {
42633
43221
  "use strict";
42634
43222
  var { maxUnsigned16Bit } = require_constants5();
42635
- var crypto4;
43223
+ var crypto2;
42636
43224
  try {
42637
- crypto4 = require("crypto");
43225
+ crypto2 = require("crypto");
42638
43226
  } catch {
42639
43227
  }
42640
43228
  var WebsocketFrameSend = class {
@@ -42643,7 +43231,7 @@ var require_frame = __commonJS({
42643
43231
  */
42644
43232
  constructor(data) {
42645
43233
  this.frameData = data;
42646
- this.maskKey = crypto4.randomBytes(4);
43234
+ this.maskKey = crypto2.randomBytes(4);
42647
43235
  }
42648
43236
  createFrame(opcode) {
42649
43237
  const bodyLength = this.frameData?.byteLength ?? 0;
@@ -43385,11 +43973,11 @@ var require_undici = __commonJS({
43385
43973
  if (typeof opts.path !== "string") {
43386
43974
  throw new InvalidArgumentError("invalid opts.path");
43387
43975
  }
43388
- let path31 = opts.path;
43976
+ let path27 = opts.path;
43389
43977
  if (!opts.path.startsWith("/")) {
43390
- path31 = `/${path31}`;
43978
+ path27 = `/${path27}`;
43391
43979
  }
43392
- url = new URL(util.parseOrigin(url).origin + path31);
43980
+ url = new URL(util.parseOrigin(url).origin + path27);
43393
43981
  } else {
43394
43982
  if (!opts) {
43395
43983
  opts = typeof url === "object" ? url : {};
@@ -43938,7 +44526,7 @@ var init_mcp_check_provider = __esm({
43938
44526
  logger.warn(
43939
44527
  `MCP ${transportName} failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`
43940
44528
  );
43941
- await new Promise((resolve18) => setTimeout(resolve18, delay));
44529
+ await new Promise((resolve14) => setTimeout(resolve14, delay));
43942
44530
  attempt += 1;
43943
44531
  } finally {
43944
44532
  try {
@@ -44220,7 +44808,7 @@ async function acquirePromptLock() {
44220
44808
  activePrompt = true;
44221
44809
  return;
44222
44810
  }
44223
- await new Promise((resolve18) => waiters.push(resolve18));
44811
+ await new Promise((resolve14) => waiters.push(resolve14));
44224
44812
  activePrompt = true;
44225
44813
  }
44226
44814
  function releasePromptLock() {
@@ -44230,7 +44818,7 @@ function releasePromptLock() {
44230
44818
  }
44231
44819
  async function interactivePrompt(options) {
44232
44820
  await acquirePromptLock();
44233
- return new Promise((resolve18, reject) => {
44821
+ return new Promise((resolve14, reject) => {
44234
44822
  const dbg = process.env.VISOR_DEBUG === "true";
44235
44823
  try {
44236
44824
  if (dbg) {
@@ -44317,12 +44905,12 @@ async function interactivePrompt(options) {
44317
44905
  };
44318
44906
  const finish = (value) => {
44319
44907
  cleanup();
44320
- resolve18(value);
44908
+ resolve14(value);
44321
44909
  };
44322
44910
  if (options.timeout && options.timeout > 0) {
44323
44911
  timeoutId = setTimeout(() => {
44324
44912
  cleanup();
44325
- if (defaultValue !== void 0) return resolve18(defaultValue);
44913
+ if (defaultValue !== void 0) return resolve14(defaultValue);
44326
44914
  return reject(new Error("Input timeout"));
44327
44915
  }, options.timeout);
44328
44916
  }
@@ -44454,7 +45042,7 @@ async function interactivePrompt(options) {
44454
45042
  });
44455
45043
  }
44456
45044
  async function simplePrompt(prompt) {
44457
- return new Promise((resolve18) => {
45045
+ return new Promise((resolve14) => {
44458
45046
  const rl = readline.createInterface({
44459
45047
  input: process.stdin,
44460
45048
  output: process.stdout
@@ -44470,7 +45058,7 @@ async function simplePrompt(prompt) {
44470
45058
  rl.question(`${prompt}
44471
45059
  > `, (answer) => {
44472
45060
  rl.close();
44473
- resolve18(answer.trim());
45061
+ resolve14(answer.trim());
44474
45062
  });
44475
45063
  });
44476
45064
  }
@@ -44638,7 +45226,7 @@ function isStdinAvailable() {
44638
45226
  return !process.stdin.isTTY;
44639
45227
  }
44640
45228
  async function readStdin(timeout, maxSize = 1024 * 1024) {
44641
- return new Promise((resolve18, reject) => {
45229
+ return new Promise((resolve14, reject) => {
44642
45230
  let data = "";
44643
45231
  let timeoutId;
44644
45232
  if (timeout) {
@@ -44665,7 +45253,7 @@ async function readStdin(timeout, maxSize = 1024 * 1024) {
44665
45253
  };
44666
45254
  const onEnd = () => {
44667
45255
  cleanup();
44668
- resolve18(data.trim());
45256
+ resolve14(data.trim());
44669
45257
  };
44670
45258
  const onError = (err) => {
44671
45259
  cleanup();
@@ -46314,7 +46902,7 @@ var init_worktree_manager = __esm({
46314
46902
  await this.saveMetadata(worktreePath, updatedMetadata);
46315
46903
  if (options.clean) {
46316
46904
  logger.debug(`Cleaning updated worktree`);
46317
- await this.cleanWorktree(worktreePath);
46905
+ await this.cleanWorktree(worktreePath, latestCommit);
46318
46906
  }
46319
46907
  this.activeWorktrees.set(worktreeId, updatedMetadata);
46320
46908
  return {
@@ -46333,7 +46921,7 @@ var init_worktree_manager = __esm({
46333
46921
  }
46334
46922
  if (options.clean) {
46335
46923
  logger.debug(`Cleaning existing worktree`);
46336
- await this.cleanWorktree(worktreePath);
46924
+ await this.cleanWorktree(worktreePath, metadata2.commit);
46337
46925
  }
46338
46926
  this.activeWorktrees.set(worktreeId, metadata2);
46339
46927
  return {
@@ -46375,7 +46963,7 @@ var init_worktree_manager = __esm({
46375
46963
  await this.saveMetadata(worktreePath, updatedMetadata);
46376
46964
  if (options.clean) {
46377
46965
  logger.debug(`Cleaning updated worktree`);
46378
- await this.cleanWorktree(worktreePath);
46966
+ await this.cleanWorktree(worktreePath, newCommit);
46379
46967
  }
46380
46968
  this.activeWorktrees.set(worktreeId, updatedMetadata);
46381
46969
  logger.info(`Successfully updated worktree to ${ref} (${newCommit})`);
@@ -46465,13 +47053,54 @@ var init_worktree_manager = __esm({
46465
47053
  return true;
46466
47054
  }
46467
47055
  /**
46468
- * Clean worktree (reset and remove untracked files)
46469
- */
46470
- async cleanWorktree(worktreePath) {
47056
+ * Clean worktree (reset and remove untracked files).
47057
+ *
47058
+ * When `expectedCommit` is provided the worktree is first forced back to a
47059
+ * detached HEAD at that commit. This is essential because AI agents or user
47060
+ * commands may have created local branches inside the worktree, switching
47061
+ * HEAD away from the detached state. A plain `reset --hard HEAD` would
47062
+ * then reset to the *wrong* commit. After resetting, any local branches
47063
+ * that were created inside the worktree are deleted so they cannot leak
47064
+ * into future runs or PRs.
47065
+ */
47066
+ async cleanWorktree(worktreePath, expectedCommit) {
47067
+ if (expectedCommit) {
47068
+ const detachCmd = `git -C ${this.escapeShellArg(worktreePath)} checkout --detach ${this.escapeShellArg(expectedCommit)}`;
47069
+ const detachResult = await this.executeGitCommand(detachCmd, { timeout: 3e4 });
47070
+ if (detachResult.exitCode !== 0) {
47071
+ await this.executeGitCommand(`git -C ${this.escapeShellArg(worktreePath)} reset --hard`, {
47072
+ timeout: 1e4
47073
+ });
47074
+ await this.executeGitCommand(detachCmd, { timeout: 3e4 });
47075
+ }
47076
+ }
46471
47077
  const resetCmd = `git -C ${this.escapeShellArg(worktreePath)} reset --hard HEAD`;
46472
47078
  await this.executeGitCommand(resetCmd);
46473
47079
  const cleanCmd = `git -C ${this.escapeShellArg(worktreePath)} clean -fdx`;
46474
47080
  await this.executeGitCommand(cleanCmd);
47081
+ await this.deleteLocalBranches(worktreePath);
47082
+ }
47083
+ /**
47084
+ * Delete all local branches in a worktree.
47085
+ * Worktrees are always used in detached HEAD state, so any local branches
47086
+ * were unintentionally created and should be cleaned up.
47087
+ */
47088
+ async deleteLocalBranches(worktreePath) {
47089
+ const listCmd = `git -C ${this.escapeShellArg(worktreePath)} branch --list --format='%(refname:short)'`;
47090
+ const listResult = await this.executeGitCommand(listCmd, { timeout: 1e4 });
47091
+ if (listResult.exitCode !== 0 || !listResult.stdout.trim()) {
47092
+ return;
47093
+ }
47094
+ const branches = listResult.stdout.trim().split("\n").map((b) => b.trim()).filter((b) => b.length > 0);
47095
+ for (const branch of branches) {
47096
+ const deleteCmd = `git -C ${this.escapeShellArg(worktreePath)} branch -D ${this.escapeShellArg(branch)}`;
47097
+ const deleteResult = await this.executeGitCommand(deleteCmd, { timeout: 1e4 });
47098
+ if (deleteResult.exitCode === 0) {
47099
+ logger.debug(`Deleted local branch '${branch}' from worktree`);
47100
+ } else {
47101
+ logger.warn(`Failed to delete branch '${branch}': ${deleteResult.stderr}`);
47102
+ }
47103
+ }
46475
47104
  }
46476
47105
  /**
46477
47106
  * Get commit SHA for a given ref inside a bare repository.
@@ -48742,23 +49371,23 @@ __export(renderer_schema_exports, {
48742
49371
  });
48743
49372
  async function loadRendererSchema(name) {
48744
49373
  try {
48745
- const fs27 = await import("fs/promises");
48746
- const path31 = await import("path");
49374
+ const fs23 = await import("fs/promises");
49375
+ const path27 = await import("path");
48747
49376
  const sanitized = String(name).replace(/[^a-zA-Z0-9-]/g, "");
48748
49377
  if (!sanitized) return void 0;
48749
49378
  const candidates = [
48750
49379
  // When bundled with ncc, __dirname is dist/ and output/ is at dist/output/
48751
- path31.join(__dirname, "output", sanitized, "schema.json"),
49380
+ path27.join(__dirname, "output", sanitized, "schema.json"),
48752
49381
  // When running from source, __dirname is src/state-machine/dispatch/ and output/ is at output/
48753
- path31.join(__dirname, "..", "..", "output", sanitized, "schema.json"),
49382
+ path27.join(__dirname, "..", "..", "output", sanitized, "schema.json"),
48754
49383
  // When running from a checkout with output/ folder copied to CWD
48755
- path31.join(process.cwd(), "output", sanitized, "schema.json"),
49384
+ path27.join(process.cwd(), "output", sanitized, "schema.json"),
48756
49385
  // Fallback: cwd/dist/output/
48757
- path31.join(process.cwd(), "dist", "output", sanitized, "schema.json")
49386
+ path27.join(process.cwd(), "dist", "output", sanitized, "schema.json")
48758
49387
  ];
48759
49388
  for (const p of candidates) {
48760
49389
  try {
48761
- const raw = await fs27.readFile(p, "utf-8");
49390
+ const raw = await fs23.readFile(p, "utf-8");
48762
49391
  return JSON.parse(raw);
48763
49392
  } catch {
48764
49393
  }
@@ -51177,8 +51806,8 @@ function updateStats2(results, state, isForEachIteration = false) {
51177
51806
  async function renderTemplateContent2(checkId, checkConfig, reviewSummary) {
51178
51807
  try {
51179
51808
  const { createExtendedLiquid: createExtendedLiquid2 } = await Promise.resolve().then(() => (init_liquid_extensions(), liquid_extensions_exports));
51180
- const fs27 = await import("fs/promises");
51181
- const path31 = await import("path");
51809
+ const fs23 = await import("fs/promises");
51810
+ const path27 = await import("path");
51182
51811
  const schemaRaw = checkConfig.schema || "plain";
51183
51812
  const schema = typeof schemaRaw === "string" && !schemaRaw.includes("{{") && !schemaRaw.includes("{%") ? schemaRaw : typeof schemaRaw === "object" ? "code-review" : "plain";
51184
51813
  let templateContent;
@@ -51187,27 +51816,27 @@ async function renderTemplateContent2(checkId, checkConfig, reviewSummary) {
51187
51816
  logger.debug(`[LevelDispatch] Using inline template for ${checkId}`);
51188
51817
  } else if (checkConfig.template && checkConfig.template.file) {
51189
51818
  const file = String(checkConfig.template.file);
51190
- const resolved = path31.resolve(process.cwd(), file);
51191
- templateContent = await fs27.readFile(resolved, "utf-8");
51819
+ const resolved = path27.resolve(process.cwd(), file);
51820
+ templateContent = await fs23.readFile(resolved, "utf-8");
51192
51821
  logger.debug(`[LevelDispatch] Using template file for ${checkId}: ${resolved}`);
51193
51822
  } else if (schema && schema !== "plain") {
51194
51823
  const sanitized = String(schema).replace(/[^a-zA-Z0-9-]/g, "");
51195
51824
  if (sanitized) {
51196
51825
  const candidatePaths = [
51197
- path31.join(__dirname, "output", sanitized, "template.liquid"),
51826
+ path27.join(__dirname, "output", sanitized, "template.liquid"),
51198
51827
  // bundled: dist/output/
51199
- path31.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
51828
+ path27.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
51200
51829
  // source (from state-machine/states)
51201
- path31.join(__dirname, "..", "..", "..", "output", sanitized, "template.liquid"),
51830
+ path27.join(__dirname, "..", "..", "..", "output", sanitized, "template.liquid"),
51202
51831
  // source (alternate)
51203
- path31.join(process.cwd(), "output", sanitized, "template.liquid"),
51832
+ path27.join(process.cwd(), "output", sanitized, "template.liquid"),
51204
51833
  // fallback: cwd/output/
51205
- path31.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
51834
+ path27.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
51206
51835
  // fallback: cwd/dist/output/
51207
51836
  ];
51208
51837
  for (const p of candidatePaths) {
51209
51838
  try {
51210
- templateContent = await fs27.readFile(p, "utf-8");
51839
+ templateContent = await fs23.readFile(p, "utf-8");
51211
51840
  if (templateContent) {
51212
51841
  logger.debug(`[LevelDispatch] Using schema template for ${checkId}: ${p}`);
51213
51842
  break;
@@ -53022,9 +53651,40 @@ ${file.patch}`).join("\n\n");
53022
53651
  /**
53023
53652
  * Get diff between current branch and base branch (for feature branch analysis)
53024
53653
  */
53654
+ /**
53655
+ * Resolve the base branch ref to a revision that exists locally.
53656
+ * In CI (shallow clones), the local branch may not exist, so we try:
53657
+ * 1. The branch name as-is (e.g. "main")
53658
+ * 2. The remote-tracking ref (e.g. "origin/main")
53659
+ * 3. Fetch from origin then retry
53660
+ */
53661
+ async resolveBaseBranchRef(baseBranch) {
53662
+ try {
53663
+ await this.git.revparse([baseBranch]);
53664
+ return baseBranch;
53665
+ } catch {
53666
+ }
53667
+ const remoteBranch = `origin/${baseBranch}`;
53668
+ try {
53669
+ await this.git.revparse([remoteBranch]);
53670
+ return remoteBranch;
53671
+ } catch {
53672
+ }
53673
+ try {
53674
+ await this.git.fetch(["origin", baseBranch]);
53675
+ await this.git.revparse([remoteBranch]);
53676
+ return remoteBranch;
53677
+ } catch {
53678
+ return baseBranch;
53679
+ }
53680
+ }
53025
53681
  async getBranchDiff(baseBranch, includeContext = true) {
53026
53682
  try {
53027
- const diffSummary = await this.git.diffSummary([baseBranch]);
53683
+ const resolvedBase = await this.resolveBaseBranchRef(baseBranch);
53684
+ if (resolvedBase !== baseBranch) {
53685
+ console.error(`\u{1F4CE} Resolved base branch: ${baseBranch} \u2192 ${resolvedBase}`);
53686
+ }
53687
+ const diffSummary = await this.git.diffSummary([resolvedBase]);
53028
53688
  const changes = [];
53029
53689
  if (!diffSummary || !diffSummary.files) {
53030
53690
  return [];
@@ -53052,7 +53712,7 @@ ${file.patch}`).join("\n\n");
53052
53712
  let truncated = false;
53053
53713
  if (includeContext && !isBinary) {
53054
53714
  try {
53055
- const rawPatch = await this.git.diff([baseBranch, "--", file.file]);
53715
+ const rawPatch = await this.git.diff([resolvedBase, "--", file.file]);
53056
53716
  if (rawPatch) {
53057
53717
  const result = this.truncatePatch(rawPatch, file.file);
53058
53718
  patch = result.patch;
@@ -53290,8 +53950,8 @@ var init_workspace_manager = __esm({
53290
53950
  );
53291
53951
  if (this.cleanupRequested && this.activeOperations === 0) {
53292
53952
  logger.debug(`[Workspace] All references released, proceeding with deferred cleanup`);
53293
- for (const resolve18 of this.cleanupResolvers) {
53294
- resolve18();
53953
+ for (const resolve14 of this.cleanupResolvers) {
53954
+ resolve14();
53295
53955
  }
53296
53956
  this.cleanupResolvers = [];
53297
53957
  }
@@ -53448,19 +54108,19 @@ var init_workspace_manager = __esm({
53448
54108
  );
53449
54109
  this.cleanupRequested = true;
53450
54110
  await Promise.race([
53451
- new Promise((resolve18) => {
54111
+ new Promise((resolve14) => {
53452
54112
  if (this.activeOperations === 0) {
53453
- resolve18();
54113
+ resolve14();
53454
54114
  } else {
53455
- this.cleanupResolvers.push(resolve18);
54115
+ this.cleanupResolvers.push(resolve14);
53456
54116
  }
53457
54117
  }),
53458
- new Promise((resolve18) => {
54118
+ new Promise((resolve14) => {
53459
54119
  setTimeout(() => {
53460
54120
  logger.warn(
53461
54121
  `[Workspace] Cleanup timeout after ${timeout}ms, proceeding anyway (${this.activeOperations} operations still active)`
53462
54122
  );
53463
- resolve18();
54123
+ resolve14();
53464
54124
  }, timeout);
53465
54125
  })
53466
54126
  ]);
@@ -53648,6 +54308,30 @@ var init_workspace_manager = __esm({
53648
54308
  if (cleanResult.exitCode !== 0) {
53649
54309
  logger.warn(`[Workspace] clean -fdx failed: ${cleanResult.stderr}`);
53650
54310
  }
54311
+ await this.deleteLocalBranches(worktreePath);
54312
+ }
54313
+ /**
54314
+ * Delete all local branches in a worktree.
54315
+ */
54316
+ async deleteLocalBranches(worktreePath) {
54317
+ const escapedPath = shellEscape(worktreePath);
54318
+ const listResult = await commandExecutor.execute(
54319
+ `git -C ${escapedPath} branch --list --format='%(refname:short)'`,
54320
+ { timeout: 1e4 }
54321
+ );
54322
+ if (listResult.exitCode !== 0 || !listResult.stdout.trim()) {
54323
+ return;
54324
+ }
54325
+ const branches = listResult.stdout.trim().split("\n").map((b) => b.trim()).filter((b) => b.length > 0);
54326
+ for (const branch of branches) {
54327
+ const deleteResult = await commandExecutor.execute(
54328
+ `git -C ${escapedPath} branch -D ${shellEscape(branch)}`,
54329
+ { timeout: 1e4 }
54330
+ );
54331
+ if (deleteResult.exitCode === 0) {
54332
+ logger.debug(`[Workspace] Deleted local branch '${branch}' from worktree`);
54333
+ }
54334
+ }
53651
54335
  }
53652
54336
  /**
53653
54337
  * Refresh an existing worktree to the latest upstream default branch
@@ -53914,1264 +54598,6 @@ var init_build_engine_context = __esm({
53914
54598
  }
53915
54599
  });
53916
54600
 
53917
- // src/policy/default-engine.ts
53918
- var DefaultPolicyEngine;
53919
- var init_default_engine = __esm({
53920
- "src/policy/default-engine.ts"() {
53921
- "use strict";
53922
- DefaultPolicyEngine = class {
53923
- async initialize(_config) {
53924
- }
53925
- async evaluateCheckExecution(_checkId, _checkConfig) {
53926
- return { allowed: true };
53927
- }
53928
- async evaluateToolInvocation(_serverName, _methodName, _transport) {
53929
- return { allowed: true };
53930
- }
53931
- async evaluateCapabilities(_checkId, _capabilities) {
53932
- return { allowed: true };
53933
- }
53934
- async shutdown() {
53935
- }
53936
- };
53937
- }
53938
- });
53939
-
53940
- // src/enterprise/license/validator.ts
53941
- var validator_exports = {};
53942
- __export(validator_exports, {
53943
- LicenseValidator: () => LicenseValidator
53944
- });
53945
- var crypto2, fs21, path25, LicenseValidator;
53946
- var init_validator = __esm({
53947
- "src/enterprise/license/validator.ts"() {
53948
- "use strict";
53949
- crypto2 = __toESM(require("crypto"));
53950
- fs21 = __toESM(require("fs"));
53951
- path25 = __toESM(require("path"));
53952
- LicenseValidator = class _LicenseValidator {
53953
- /** Ed25519 public key for license verification (PEM format). */
53954
- static PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAI/Zd08EFmgIdrDm/HXd0l3/5GBt7R1PrdvhdmEXhJlU=\n-----END PUBLIC KEY-----\n";
53955
- cache = null;
53956
- static CACHE_TTL = 5 * 60 * 1e3;
53957
- // 5 minutes
53958
- static GRACE_PERIOD = 72 * 3600 * 1e3;
53959
- // 72 hours after expiry
53960
- /**
53961
- * Load and validate license from environment or file.
53962
- *
53963
- * Resolution order:
53964
- * 1. VISOR_LICENSE env var (JWT string)
53965
- * 2. VISOR_LICENSE_FILE env var (path to file)
53966
- * 3. .visor-license in project root (cwd)
53967
- * 4. .visor-license in ~/.config/visor/
53968
- */
53969
- async loadAndValidate() {
53970
- if (this.cache && Date.now() - this.cache.validatedAt < _LicenseValidator.CACHE_TTL) {
53971
- return this.cache.payload;
53972
- }
53973
- const token = this.resolveToken();
53974
- if (!token) return null;
53975
- const payload = this.verifyAndDecode(token);
53976
- if (!payload) return null;
53977
- this.cache = { payload, validatedAt: Date.now() };
53978
- return payload;
53979
- }
53980
- /** Check if a specific feature is licensed */
53981
- hasFeature(feature) {
53982
- if (!this.cache) return false;
53983
- return this.cache.payload.features.includes(feature);
53984
- }
53985
- /** Check if license is valid (with grace period) */
53986
- isValid() {
53987
- if (!this.cache) return false;
53988
- const now = Date.now();
53989
- const expiryMs = this.cache.payload.exp * 1e3;
53990
- return now < expiryMs + _LicenseValidator.GRACE_PERIOD;
53991
- }
53992
- /** Check if the license is within its grace period (expired but still valid) */
53993
- isInGracePeriod() {
53994
- if (!this.cache) return false;
53995
- const now = Date.now();
53996
- const expiryMs = this.cache.payload.exp * 1e3;
53997
- return now >= expiryMs && now < expiryMs + _LicenseValidator.GRACE_PERIOD;
53998
- }
53999
- resolveToken() {
54000
- if (process.env.VISOR_LICENSE) {
54001
- return process.env.VISOR_LICENSE.trim();
54002
- }
54003
- if (process.env.VISOR_LICENSE_FILE) {
54004
- const resolved = path25.resolve(process.env.VISOR_LICENSE_FILE);
54005
- const home2 = process.env.HOME || process.env.USERPROFILE || "";
54006
- const allowedPrefixes = [path25.normalize(process.cwd())];
54007
- if (home2) allowedPrefixes.push(path25.normalize(path25.join(home2, ".config", "visor")));
54008
- let realPath;
54009
- try {
54010
- realPath = fs21.realpathSync(resolved);
54011
- } catch {
54012
- return null;
54013
- }
54014
- const isSafe = allowedPrefixes.some(
54015
- (prefix) => realPath === prefix || realPath.startsWith(prefix + path25.sep)
54016
- );
54017
- if (!isSafe) return null;
54018
- return this.readFile(realPath);
54019
- }
54020
- const cwdPath = path25.join(process.cwd(), ".visor-license");
54021
- const cwdToken = this.readFile(cwdPath);
54022
- if (cwdToken) return cwdToken;
54023
- const home = process.env.HOME || process.env.USERPROFILE || "";
54024
- if (home) {
54025
- const configPath = path25.join(home, ".config", "visor", ".visor-license");
54026
- const configToken = this.readFile(configPath);
54027
- if (configToken) return configToken;
54028
- }
54029
- return null;
54030
- }
54031
- readFile(filePath) {
54032
- try {
54033
- return fs21.readFileSync(filePath, "utf-8").trim();
54034
- } catch {
54035
- return null;
54036
- }
54037
- }
54038
- verifyAndDecode(token) {
54039
- try {
54040
- const parts = token.split(".");
54041
- if (parts.length !== 3) return null;
54042
- const [headerB64, payloadB64, signatureB64] = parts;
54043
- const header = JSON.parse(Buffer.from(headerB64, "base64url").toString());
54044
- if (header.alg !== "EdDSA") return null;
54045
- const data = `${headerB64}.${payloadB64}`;
54046
- const signature = Buffer.from(signatureB64, "base64url");
54047
- const publicKey = crypto2.createPublicKey(_LicenseValidator.PUBLIC_KEY);
54048
- if (publicKey.asymmetricKeyType !== "ed25519") {
54049
- return null;
54050
- }
54051
- const isValid = crypto2.verify(null, Buffer.from(data), publicKey, signature);
54052
- if (!isValid) return null;
54053
- const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
54054
- if (!payload.org || !Array.isArray(payload.features) || typeof payload.exp !== "number" || typeof payload.iat !== "number" || !payload.sub) {
54055
- return null;
54056
- }
54057
- const now = Date.now();
54058
- const expiryMs = payload.exp * 1e3;
54059
- if (now >= expiryMs + _LicenseValidator.GRACE_PERIOD) {
54060
- return null;
54061
- }
54062
- return payload;
54063
- } catch {
54064
- return null;
54065
- }
54066
- }
54067
- };
54068
- }
54069
- });
54070
-
54071
- // src/enterprise/policy/opa-compiler.ts
54072
- var fs22, path26, os2, crypto3, import_child_process8, OpaCompiler;
54073
- var init_opa_compiler = __esm({
54074
- "src/enterprise/policy/opa-compiler.ts"() {
54075
- "use strict";
54076
- fs22 = __toESM(require("fs"));
54077
- path26 = __toESM(require("path"));
54078
- os2 = __toESM(require("os"));
54079
- crypto3 = __toESM(require("crypto"));
54080
- import_child_process8 = require("child_process");
54081
- OpaCompiler = class _OpaCompiler {
54082
- static CACHE_DIR = path26.join(os2.tmpdir(), "visor-opa-cache");
54083
- /**
54084
- * Resolve the input paths to WASM bytes.
54085
- *
54086
- * Strategy:
54087
- * 1. If any path is a .wasm file, read it directly
54088
- * 2. If a directory contains policy.wasm, read it
54089
- * 3. Otherwise, collect all .rego files and auto-compile via `opa build`
54090
- */
54091
- async resolveWasmBytes(paths) {
54092
- const regoFiles = [];
54093
- for (const p of paths) {
54094
- const resolved = path26.resolve(p);
54095
- if (path26.normalize(resolved).includes("..")) {
54096
- throw new Error(`Policy path contains traversal sequences: ${p}`);
54097
- }
54098
- if (resolved.endsWith(".wasm") && fs22.existsSync(resolved)) {
54099
- return fs22.readFileSync(resolved);
54100
- }
54101
- if (!fs22.existsSync(resolved)) continue;
54102
- const stat2 = fs22.statSync(resolved);
54103
- if (stat2.isDirectory()) {
54104
- const wasmCandidate = path26.join(resolved, "policy.wasm");
54105
- if (fs22.existsSync(wasmCandidate)) {
54106
- return fs22.readFileSync(wasmCandidate);
54107
- }
54108
- const files = fs22.readdirSync(resolved);
54109
- for (const f of files) {
54110
- if (f.endsWith(".rego")) {
54111
- regoFiles.push(path26.join(resolved, f));
54112
- }
54113
- }
54114
- } else if (resolved.endsWith(".rego")) {
54115
- regoFiles.push(resolved);
54116
- }
54117
- }
54118
- if (regoFiles.length === 0) {
54119
- throw new Error(
54120
- `OPA WASM evaluator: no .wasm bundle or .rego files found in: ${paths.join(", ")}`
54121
- );
54122
- }
54123
- return this.compileRego(regoFiles);
54124
- }
54125
- /**
54126
- * Auto-compile .rego files to a WASM bundle using the `opa` CLI.
54127
- *
54128
- * Caches the compiled bundle based on a content hash of all input .rego files
54129
- * so subsequent runs skip compilation if policies haven't changed.
54130
- */
54131
- compileRego(regoFiles) {
54132
- try {
54133
- (0, import_child_process8.execFileSync)("opa", ["version"], { stdio: "pipe" });
54134
- } catch {
54135
- throw new Error(
54136
- "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(" ")
54137
- );
54138
- }
54139
- const hash = crypto3.createHash("sha256");
54140
- for (const f of regoFiles.sort()) {
54141
- hash.update(fs22.readFileSync(f));
54142
- hash.update(f);
54143
- }
54144
- const cacheKey = hash.digest("hex").slice(0, 16);
54145
- const cacheDir = _OpaCompiler.CACHE_DIR;
54146
- const cachedWasm = path26.join(cacheDir, `${cacheKey}.wasm`);
54147
- if (fs22.existsSync(cachedWasm)) {
54148
- return fs22.readFileSync(cachedWasm);
54149
- }
54150
- fs22.mkdirSync(cacheDir, { recursive: true });
54151
- const bundleTar = path26.join(cacheDir, `${cacheKey}-bundle.tar.gz`);
54152
- try {
54153
- const args = [
54154
- "build",
54155
- "-t",
54156
- "wasm",
54157
- "-e",
54158
- "visor",
54159
- // entrypoint: the visor package tree
54160
- "-o",
54161
- bundleTar,
54162
- ...regoFiles
54163
- ];
54164
- (0, import_child_process8.execFileSync)("opa", args, {
54165
- stdio: "pipe",
54166
- timeout: 3e4
54167
- });
54168
- } catch (err) {
54169
- const stderr = err?.stderr?.toString() || "";
54170
- throw new Error(
54171
- `Failed to compile .rego files to WASM:
54172
- ${stderr}
54173
- Ensure your .rego files are valid and the \`opa\` CLI is installed.`
54174
- );
54175
- }
54176
- try {
54177
- (0, import_child_process8.execFileSync)("tar", ["-xzf", bundleTar, "-C", cacheDir, "/policy.wasm"], {
54178
- stdio: "pipe"
54179
- });
54180
- const extractedWasm = path26.join(cacheDir, "policy.wasm");
54181
- if (fs22.existsSync(extractedWasm)) {
54182
- fs22.renameSync(extractedWasm, cachedWasm);
54183
- }
54184
- } catch {
54185
- try {
54186
- (0, import_child_process8.execFileSync)("tar", ["-xzf", bundleTar, "-C", cacheDir, "policy.wasm"], {
54187
- stdio: "pipe"
54188
- });
54189
- const extractedWasm = path26.join(cacheDir, "policy.wasm");
54190
- if (fs22.existsSync(extractedWasm)) {
54191
- fs22.renameSync(extractedWasm, cachedWasm);
54192
- }
54193
- } catch (err2) {
54194
- throw new Error(`Failed to extract policy.wasm from OPA bundle: ${err2?.message || err2}`);
54195
- }
54196
- }
54197
- try {
54198
- fs22.unlinkSync(bundleTar);
54199
- } catch {
54200
- }
54201
- if (!fs22.existsSync(cachedWasm)) {
54202
- throw new Error("OPA build succeeded but policy.wasm was not found in the bundle");
54203
- }
54204
- return fs22.readFileSync(cachedWasm);
54205
- }
54206
- };
54207
- }
54208
- });
54209
-
54210
- // src/enterprise/policy/opa-wasm-evaluator.ts
54211
- var fs23, path27, OpaWasmEvaluator;
54212
- var init_opa_wasm_evaluator = __esm({
54213
- "src/enterprise/policy/opa-wasm-evaluator.ts"() {
54214
- "use strict";
54215
- fs23 = __toESM(require("fs"));
54216
- path27 = __toESM(require("path"));
54217
- init_opa_compiler();
54218
- OpaWasmEvaluator = class {
54219
- policy = null;
54220
- dataDocument = {};
54221
- compiler = new OpaCompiler();
54222
- async initialize(rulesPath) {
54223
- const paths = Array.isArray(rulesPath) ? rulesPath : [rulesPath];
54224
- const wasmBytes = await this.compiler.resolveWasmBytes(paths);
54225
- try {
54226
- const { createRequire } = require("module");
54227
- const runtimeRequire = createRequire(__filename);
54228
- const opaWasm = runtimeRequire("@open-policy-agent/opa-wasm");
54229
- const loadPolicy = opaWasm.loadPolicy || opaWasm.default?.loadPolicy;
54230
- if (!loadPolicy) {
54231
- throw new Error("loadPolicy not found in @open-policy-agent/opa-wasm");
54232
- }
54233
- this.policy = await loadPolicy(wasmBytes);
54234
- } catch (err) {
54235
- if (err?.code === "MODULE_NOT_FOUND" || err?.code === "ERR_MODULE_NOT_FOUND") {
54236
- throw new Error(
54237
- "OPA WASM evaluator requires @open-policy-agent/opa-wasm. Install it with: npm install @open-policy-agent/opa-wasm"
54238
- );
54239
- }
54240
- throw err;
54241
- }
54242
- }
54243
- /**
54244
- * Load external data from a JSON file to use as the OPA data document.
54245
- * The loaded data will be passed to `policy.setData()` during evaluation,
54246
- * making it available in Rego via `data.<key>`.
54247
- */
54248
- loadData(dataPath) {
54249
- const resolved = path27.resolve(dataPath);
54250
- if (path27.normalize(resolved).includes("..")) {
54251
- throw new Error(`Data path contains traversal sequences: ${dataPath}`);
54252
- }
54253
- if (!fs23.existsSync(resolved)) {
54254
- throw new Error(`OPA data file not found: ${resolved}`);
54255
- }
54256
- const stat2 = fs23.statSync(resolved);
54257
- if (stat2.size > 10 * 1024 * 1024) {
54258
- throw new Error(`OPA data file exceeds 10MB limit: ${resolved} (${stat2.size} bytes)`);
54259
- }
54260
- const raw = fs23.readFileSync(resolved, "utf-8");
54261
- try {
54262
- const parsed = JSON.parse(raw);
54263
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
54264
- throw new Error("OPA data file must contain a JSON object (not an array or primitive)");
54265
- }
54266
- this.dataDocument = parsed;
54267
- } catch (err) {
54268
- if (err.message.startsWith("OPA data file must")) {
54269
- throw err;
54270
- }
54271
- throw new Error(`Failed to parse OPA data file ${resolved}: ${err.message}`);
54272
- }
54273
- }
54274
- async evaluate(input) {
54275
- if (!this.policy) {
54276
- throw new Error("OPA WASM evaluator not initialized");
54277
- }
54278
- this.policy.setData(this.dataDocument);
54279
- const resultSet = this.policy.evaluate(input);
54280
- if (Array.isArray(resultSet) && resultSet.length > 0) {
54281
- return resultSet[0].result;
54282
- }
54283
- return void 0;
54284
- }
54285
- async shutdown() {
54286
- if (this.policy) {
54287
- if (typeof this.policy.close === "function") {
54288
- try {
54289
- this.policy.close();
54290
- } catch {
54291
- }
54292
- } else if (typeof this.policy.free === "function") {
54293
- try {
54294
- this.policy.free();
54295
- } catch {
54296
- }
54297
- }
54298
- }
54299
- this.policy = null;
54300
- }
54301
- };
54302
- }
54303
- });
54304
-
54305
- // src/enterprise/policy/opa-http-evaluator.ts
54306
- var OpaHttpEvaluator;
54307
- var init_opa_http_evaluator = __esm({
54308
- "src/enterprise/policy/opa-http-evaluator.ts"() {
54309
- "use strict";
54310
- OpaHttpEvaluator = class {
54311
- baseUrl;
54312
- timeout;
54313
- constructor(baseUrl, timeout = 5e3) {
54314
- let parsed;
54315
- try {
54316
- parsed = new URL(baseUrl);
54317
- } catch {
54318
- throw new Error(`OPA HTTP evaluator: invalid URL: ${baseUrl}`);
54319
- }
54320
- if (!["http:", "https:"].includes(parsed.protocol)) {
54321
- throw new Error(
54322
- `OPA HTTP evaluator: url must use http:// or https:// protocol, got: ${baseUrl}`
54323
- );
54324
- }
54325
- const hostname = parsed.hostname;
54326
- if (this.isBlockedHostname(hostname)) {
54327
- throw new Error(
54328
- `OPA HTTP evaluator: url must not point to internal, loopback, or private network addresses`
54329
- );
54330
- }
54331
- this.baseUrl = baseUrl.replace(/\/+$/, "");
54332
- this.timeout = timeout;
54333
- }
54334
- /**
54335
- * Check if a hostname is blocked due to SSRF concerns.
54336
- *
54337
- * Blocks:
54338
- * - Loopback addresses (127.x.x.x, localhost, 0.0.0.0, ::1)
54339
- * - Link-local addresses (169.254.x.x)
54340
- * - Private networks (10.x.x.x, 172.16-31.x.x, 192.168.x.x)
54341
- * - IPv6 unique local addresses (fd00::/8)
54342
- * - Cloud metadata services (*.internal)
54343
- */
54344
- isBlockedHostname(hostname) {
54345
- if (!hostname) return true;
54346
- const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
54347
- if (normalized === "metadata.google.internal" || normalized.endsWith(".internal")) {
54348
- return true;
54349
- }
54350
- if (normalized === "localhost" || normalized === "localhost.localdomain") {
54351
- return true;
54352
- }
54353
- if (normalized === "::1" || normalized === "0:0:0:0:0:0:0:1") {
54354
- return true;
54355
- }
54356
- const ipv4Pattern = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
54357
- const ipv4Match = normalized.match(ipv4Pattern);
54358
- if (ipv4Match) {
54359
- const octets = ipv4Match.slice(1, 5).map(Number);
54360
- if (octets.some((octet) => octet > 255)) {
54361
- return false;
54362
- }
54363
- const [a, b] = octets;
54364
- if (a === 127) {
54365
- return true;
54366
- }
54367
- if (a === 0) {
54368
- return true;
54369
- }
54370
- if (a === 169 && b === 254) {
54371
- return true;
54372
- }
54373
- if (a === 10) {
54374
- return true;
54375
- }
54376
- if (a === 172 && b >= 16 && b <= 31) {
54377
- return true;
54378
- }
54379
- if (a === 192 && b === 168) {
54380
- return true;
54381
- }
54382
- }
54383
- if (normalized.startsWith("fd") || normalized.startsWith("fc")) {
54384
- return true;
54385
- }
54386
- if (normalized.startsWith("fe80:")) {
54387
- return true;
54388
- }
54389
- return false;
54390
- }
54391
- /**
54392
- * Evaluate a policy rule against an input document via OPA REST API.
54393
- *
54394
- * @param input - The input document to evaluate
54395
- * @param rulePath - OPA rule path (e.g., 'visor/check/execute')
54396
- * @returns The result object from OPA, or undefined on error
54397
- */
54398
- async evaluate(input, rulePath) {
54399
- const encodedPath = rulePath.split("/").map((s) => encodeURIComponent(s)).join("/");
54400
- const url = `${this.baseUrl}/v1/data/${encodedPath}`;
54401
- const controller = new AbortController();
54402
- const timer = setTimeout(() => controller.abort(), this.timeout);
54403
- try {
54404
- const response = await fetch(url, {
54405
- method: "POST",
54406
- headers: { "Content-Type": "application/json" },
54407
- body: JSON.stringify({ input }),
54408
- signal: controller.signal
54409
- });
54410
- if (!response.ok) {
54411
- throw new Error(`OPA HTTP ${response.status}: ${response.statusText}`);
54412
- }
54413
- let body;
54414
- try {
54415
- body = await response.json();
54416
- } catch (jsonErr) {
54417
- throw new Error(
54418
- `OPA HTTP evaluator: failed to parse JSON response: ${jsonErr instanceof Error ? jsonErr.message : String(jsonErr)}`
54419
- );
54420
- }
54421
- return body?.result;
54422
- } finally {
54423
- clearTimeout(timer);
54424
- }
54425
- }
54426
- async shutdown() {
54427
- }
54428
- };
54429
- }
54430
- });
54431
-
54432
- // src/enterprise/policy/policy-input-builder.ts
54433
- var PolicyInputBuilder;
54434
- var init_policy_input_builder = __esm({
54435
- "src/enterprise/policy/policy-input-builder.ts"() {
54436
- "use strict";
54437
- PolicyInputBuilder = class {
54438
- roles;
54439
- actor;
54440
- repository;
54441
- pullRequest;
54442
- constructor(policyConfig, actor, repository, pullRequest) {
54443
- this.roles = policyConfig.roles || {};
54444
- this.actor = actor;
54445
- this.repository = repository;
54446
- this.pullRequest = pullRequest;
54447
- }
54448
- /** Resolve which roles apply to the current actor. */
54449
- resolveRoles() {
54450
- const matched = [];
54451
- for (const [roleName, roleConfig] of Object.entries(this.roles)) {
54452
- let identityMatch = false;
54453
- if (roleConfig.author_association && this.actor.authorAssociation && roleConfig.author_association.includes(this.actor.authorAssociation)) {
54454
- identityMatch = true;
54455
- }
54456
- if (!identityMatch && roleConfig.users && this.actor.login && roleConfig.users.includes(this.actor.login)) {
54457
- identityMatch = true;
54458
- }
54459
- if (!identityMatch && roleConfig.slack_users && this.actor.slack?.userId && roleConfig.slack_users.includes(this.actor.slack.userId)) {
54460
- identityMatch = true;
54461
- }
54462
- if (!identityMatch && roleConfig.emails && this.actor.slack?.email) {
54463
- const actorEmail = this.actor.slack.email.toLowerCase();
54464
- if (roleConfig.emails.some((e) => e.toLowerCase() === actorEmail)) {
54465
- identityMatch = true;
54466
- }
54467
- }
54468
- if (!identityMatch) continue;
54469
- if (roleConfig.slack_channels && roleConfig.slack_channels.length > 0) {
54470
- if (!this.actor.slack?.channelId || !roleConfig.slack_channels.includes(this.actor.slack.channelId)) {
54471
- continue;
54472
- }
54473
- }
54474
- matched.push(roleName);
54475
- }
54476
- return matched;
54477
- }
54478
- buildActor() {
54479
- return {
54480
- authorAssociation: this.actor.authorAssociation,
54481
- login: this.actor.login,
54482
- roles: this.resolveRoles(),
54483
- isLocalMode: this.actor.isLocalMode,
54484
- ...this.actor.slack && { slack: this.actor.slack }
54485
- };
54486
- }
54487
- forCheckExecution(check) {
54488
- return {
54489
- scope: "check.execute",
54490
- check: {
54491
- id: check.id,
54492
- type: check.type,
54493
- group: check.group,
54494
- tags: check.tags,
54495
- criticality: check.criticality,
54496
- sandbox: check.sandbox,
54497
- policy: check.policy
54498
- },
54499
- actor: this.buildActor(),
54500
- repository: this.repository,
54501
- pullRequest: this.pullRequest
54502
- };
54503
- }
54504
- forToolInvocation(serverName, methodName, transport) {
54505
- return {
54506
- scope: "tool.invoke",
54507
- tool: { serverName, methodName, transport },
54508
- actor: this.buildActor(),
54509
- repository: this.repository,
54510
- pullRequest: this.pullRequest
54511
- };
54512
- }
54513
- forCapabilityResolve(checkId, capabilities) {
54514
- return {
54515
- scope: "capability.resolve",
54516
- check: { id: checkId, type: "ai" },
54517
- capability: capabilities,
54518
- actor: this.buildActor(),
54519
- repository: this.repository,
54520
- pullRequest: this.pullRequest
54521
- };
54522
- }
54523
- };
54524
- }
54525
- });
54526
-
54527
- // src/enterprise/policy/opa-policy-engine.ts
54528
- var opa_policy_engine_exports = {};
54529
- __export(opa_policy_engine_exports, {
54530
- OpaPolicyEngine: () => OpaPolicyEngine
54531
- });
54532
- var OpaPolicyEngine;
54533
- var init_opa_policy_engine = __esm({
54534
- "src/enterprise/policy/opa-policy-engine.ts"() {
54535
- "use strict";
54536
- init_opa_wasm_evaluator();
54537
- init_opa_http_evaluator();
54538
- init_policy_input_builder();
54539
- OpaPolicyEngine = class {
54540
- evaluator = null;
54541
- fallback;
54542
- timeout;
54543
- config;
54544
- inputBuilder = null;
54545
- logger = null;
54546
- constructor(config) {
54547
- this.config = config;
54548
- this.fallback = config.fallback || "deny";
54549
- this.timeout = config.timeout || 5e3;
54550
- }
54551
- async initialize(config) {
54552
- try {
54553
- this.logger = (init_logger(), __toCommonJS(logger_exports)).logger;
54554
- } catch {
54555
- }
54556
- const actor = {
54557
- authorAssociation: process.env.VISOR_AUTHOR_ASSOCIATION,
54558
- login: process.env.VISOR_AUTHOR_LOGIN || process.env.GITHUB_ACTOR,
54559
- isLocalMode: !process.env.GITHUB_ACTIONS
54560
- };
54561
- const repo = {
54562
- owner: process.env.GITHUB_REPOSITORY_OWNER,
54563
- name: process.env.GITHUB_REPOSITORY?.split("/")[1],
54564
- branch: process.env.GITHUB_HEAD_REF,
54565
- baseBranch: process.env.GITHUB_BASE_REF,
54566
- event: process.env.GITHUB_EVENT_NAME
54567
- };
54568
- const prNum = process.env.GITHUB_PR_NUMBER ? parseInt(process.env.GITHUB_PR_NUMBER, 10) : void 0;
54569
- const pullRequest = {
54570
- number: prNum !== void 0 && Number.isFinite(prNum) ? prNum : void 0
54571
- };
54572
- this.inputBuilder = new PolicyInputBuilder(config, actor, repo, pullRequest);
54573
- if (config.engine === "local") {
54574
- if (!config.rules) {
54575
- throw new Error("OPA local mode requires `policy.rules` path to .wasm or .rego files");
54576
- }
54577
- const wasm = new OpaWasmEvaluator();
54578
- await wasm.initialize(config.rules);
54579
- if (config.data) {
54580
- wasm.loadData(config.data);
54581
- }
54582
- this.evaluator = wasm;
54583
- } else if (config.engine === "remote") {
54584
- if (!config.url) {
54585
- throw new Error("OPA remote mode requires `policy.url` pointing to OPA server");
54586
- }
54587
- this.evaluator = new OpaHttpEvaluator(config.url, this.timeout);
54588
- } else {
54589
- this.evaluator = null;
54590
- }
54591
- }
54592
- /**
54593
- * Update actor/repo/PR context (e.g., after PR info becomes available).
54594
- * Called by the enterprise loader when engine context is enriched.
54595
- */
54596
- setActorContext(actor, repo, pullRequest) {
54597
- this.inputBuilder = new PolicyInputBuilder(this.config, actor, repo, pullRequest);
54598
- }
54599
- async evaluateCheckExecution(checkId, checkConfig) {
54600
- if (!this.evaluator || !this.inputBuilder) return { allowed: true };
54601
- const cfg = checkConfig && typeof checkConfig === "object" ? checkConfig : {};
54602
- const policyOverride = cfg.policy;
54603
- const input = this.inputBuilder.forCheckExecution({
54604
- id: checkId,
54605
- type: cfg.type || "ai",
54606
- group: cfg.group,
54607
- tags: cfg.tags,
54608
- criticality: cfg.criticality,
54609
- sandbox: cfg.sandbox,
54610
- policy: policyOverride
54611
- });
54612
- return this.doEvaluate(input, this.resolveRulePath("check.execute", policyOverride?.rule));
54613
- }
54614
- async evaluateToolInvocation(serverName, methodName, transport) {
54615
- if (!this.evaluator || !this.inputBuilder) return { allowed: true };
54616
- const input = this.inputBuilder.forToolInvocation(serverName, methodName, transport);
54617
- return this.doEvaluate(input, "visor/tool/invoke");
54618
- }
54619
- async evaluateCapabilities(checkId, capabilities) {
54620
- if (!this.evaluator || !this.inputBuilder) return { allowed: true };
54621
- const input = this.inputBuilder.forCapabilityResolve(checkId, capabilities);
54622
- return this.doEvaluate(input, "visor/capability/resolve");
54623
- }
54624
- async shutdown() {
54625
- if (this.evaluator && "shutdown" in this.evaluator) {
54626
- await this.evaluator.shutdown();
54627
- }
54628
- this.evaluator = null;
54629
- this.inputBuilder = null;
54630
- }
54631
- resolveRulePath(defaultScope, override) {
54632
- if (override) {
54633
- return override.startsWith("visor/") ? override : `visor/${override}`;
54634
- }
54635
- return `visor/${defaultScope.replace(/\./g, "/")}`;
54636
- }
54637
- async doEvaluate(input, rulePath) {
54638
- try {
54639
- this.logger?.debug(`[PolicyEngine] Evaluating ${rulePath}`, JSON.stringify(input));
54640
- let timer;
54641
- const timeoutPromise = new Promise((_resolve, reject) => {
54642
- timer = setTimeout(() => reject(new Error("policy evaluation timeout")), this.timeout);
54643
- });
54644
- try {
54645
- const result = await Promise.race([this.rawEvaluate(input, rulePath), timeoutPromise]);
54646
- const decision = this.parseDecision(result);
54647
- if (!decision.allowed && this.fallback === "warn") {
54648
- decision.allowed = true;
54649
- decision.warn = true;
54650
- decision.reason = `audit: ${decision.reason || "policy denied"}`;
54651
- }
54652
- this.logger?.debug(
54653
- `[PolicyEngine] Decision for ${rulePath}: allowed=${decision.allowed}, warn=${decision.warn || false}, reason=${decision.reason || "none"}`
54654
- );
54655
- return decision;
54656
- } finally {
54657
- if (timer) clearTimeout(timer);
54658
- }
54659
- } catch (err) {
54660
- const msg = err instanceof Error ? err.message : String(err);
54661
- this.logger?.warn(`[PolicyEngine] Evaluation failed for ${rulePath}: ${msg}`);
54662
- return {
54663
- allowed: this.fallback === "allow" || this.fallback === "warn",
54664
- warn: this.fallback === "warn" ? true : void 0,
54665
- reason: `policy evaluation failed, fallback=${this.fallback}`
54666
- };
54667
- }
54668
- }
54669
- async rawEvaluate(input, rulePath) {
54670
- if (this.evaluator instanceof OpaWasmEvaluator) {
54671
- const result = await this.evaluator.evaluate(input);
54672
- return this.navigateWasmResult(result, rulePath);
54673
- }
54674
- return this.evaluator.evaluate(input, rulePath);
54675
- }
54676
- /**
54677
- * Navigate nested OPA WASM result tree to reach the specific rule's output.
54678
- * The WASM entrypoint `-e visor` means the result root IS the visor package,
54679
- * so we strip the `visor/` prefix and walk the remaining segments.
54680
- */
54681
- navigateWasmResult(result, rulePath) {
54682
- if (!result || typeof result !== "object") return result;
54683
- const segments = rulePath.replace(/^visor\//, "").split("/");
54684
- let current = result;
54685
- for (const seg of segments) {
54686
- if (current && typeof current === "object" && seg in current) {
54687
- current = current[seg];
54688
- } else {
54689
- return void 0;
54690
- }
54691
- }
54692
- return current;
54693
- }
54694
- parseDecision(result) {
54695
- if (result === void 0 || result === null) {
54696
- return {
54697
- allowed: this.fallback === "allow" || this.fallback === "warn",
54698
- warn: this.fallback === "warn" ? true : void 0,
54699
- reason: this.fallback === "warn" ? "audit: no policy result" : "no policy result"
54700
- };
54701
- }
54702
- const allowed = result.allowed !== false;
54703
- const decision = {
54704
- allowed,
54705
- reason: result.reason
54706
- };
54707
- if (result.capabilities) {
54708
- decision.capabilities = result.capabilities;
54709
- }
54710
- return decision;
54711
- }
54712
- };
54713
- }
54714
- });
54715
-
54716
- // src/enterprise/scheduler/knex-store.ts
54717
- var knex_store_exports = {};
54718
- __export(knex_store_exports, {
54719
- KnexStoreBackend: () => KnexStoreBackend
54720
- });
54721
- function toNum(val) {
54722
- if (val === null || val === void 0) return void 0;
54723
- return typeof val === "string" ? parseInt(val, 10) : val;
54724
- }
54725
- function safeJsonParse2(value) {
54726
- if (!value) return void 0;
54727
- try {
54728
- return JSON.parse(value);
54729
- } catch {
54730
- return void 0;
54731
- }
54732
- }
54733
- function fromDbRow2(row) {
54734
- return {
54735
- id: row.id,
54736
- creatorId: row.creator_id,
54737
- creatorContext: row.creator_context ?? void 0,
54738
- creatorName: row.creator_name ?? void 0,
54739
- timezone: row.timezone,
54740
- schedule: row.schedule_expr,
54741
- runAt: toNum(row.run_at),
54742
- isRecurring: row.is_recurring === true || row.is_recurring === 1,
54743
- originalExpression: row.original_expression,
54744
- workflow: row.workflow ?? void 0,
54745
- workflowInputs: safeJsonParse2(row.workflow_inputs),
54746
- outputContext: safeJsonParse2(row.output_context),
54747
- status: row.status,
54748
- createdAt: toNum(row.created_at),
54749
- lastRunAt: toNum(row.last_run_at),
54750
- nextRunAt: toNum(row.next_run_at),
54751
- runCount: row.run_count,
54752
- failureCount: row.failure_count,
54753
- lastError: row.last_error ?? void 0,
54754
- previousResponse: row.previous_response ?? void 0
54755
- };
54756
- }
54757
- function toInsertRow(schedule) {
54758
- return {
54759
- id: schedule.id,
54760
- creator_id: schedule.creatorId,
54761
- creator_context: schedule.creatorContext ?? null,
54762
- creator_name: schedule.creatorName ?? null,
54763
- timezone: schedule.timezone,
54764
- schedule_expr: schedule.schedule,
54765
- run_at: schedule.runAt ?? null,
54766
- is_recurring: schedule.isRecurring,
54767
- original_expression: schedule.originalExpression,
54768
- workflow: schedule.workflow ?? null,
54769
- workflow_inputs: schedule.workflowInputs ? JSON.stringify(schedule.workflowInputs) : null,
54770
- output_context: schedule.outputContext ? JSON.stringify(schedule.outputContext) : null,
54771
- status: schedule.status,
54772
- created_at: schedule.createdAt,
54773
- last_run_at: schedule.lastRunAt ?? null,
54774
- next_run_at: schedule.nextRunAt ?? null,
54775
- run_count: schedule.runCount,
54776
- failure_count: schedule.failureCount,
54777
- last_error: schedule.lastError ?? null,
54778
- previous_response: schedule.previousResponse ?? null
54779
- };
54780
- }
54781
- var fs24, path28, import_uuid2, KnexStoreBackend;
54782
- var init_knex_store = __esm({
54783
- "src/enterprise/scheduler/knex-store.ts"() {
54784
- "use strict";
54785
- fs24 = __toESM(require("fs"));
54786
- path28 = __toESM(require("path"));
54787
- import_uuid2 = require("uuid");
54788
- init_logger();
54789
- KnexStoreBackend = class {
54790
- knex = null;
54791
- driver;
54792
- connection;
54793
- constructor(driver, storageConfig, _haConfig) {
54794
- this.driver = driver;
54795
- this.connection = storageConfig.connection || {};
54796
- }
54797
- async initialize() {
54798
- const { createRequire } = require("module");
54799
- const runtimeRequire = createRequire(__filename);
54800
- let knexFactory;
54801
- try {
54802
- knexFactory = runtimeRequire("knex");
54803
- } catch (err) {
54804
- const code = err?.code;
54805
- if (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") {
54806
- throw new Error(
54807
- "knex is required for PostgreSQL/MySQL/MSSQL schedule storage. Install it with: npm install knex"
54808
- );
54809
- }
54810
- throw err;
54811
- }
54812
- const clientMap = {
54813
- postgresql: "pg",
54814
- mysql: "mysql2",
54815
- mssql: "tedious"
54816
- };
54817
- const client = clientMap[this.driver];
54818
- let connection;
54819
- if (this.connection.connection_string) {
54820
- connection = this.connection.connection_string;
54821
- } else if (this.driver === "mssql") {
54822
- connection = this.buildMssqlConnection();
54823
- } else {
54824
- connection = this.buildStandardConnection();
54825
- }
54826
- this.knex = knexFactory({
54827
- client,
54828
- connection,
54829
- pool: {
54830
- min: this.connection.pool?.min ?? 0,
54831
- max: this.connection.pool?.max ?? 10
54832
- }
54833
- });
54834
- await this.migrateSchema();
54835
- logger.info(`[KnexStore] Initialized (${this.driver})`);
54836
- }
54837
- buildStandardConnection() {
54838
- return {
54839
- host: this.connection.host || "localhost",
54840
- port: this.connection.port,
54841
- database: this.connection.database || "visor",
54842
- user: this.connection.user,
54843
- password: this.connection.password,
54844
- ssl: this.resolveSslConfig()
54845
- };
54846
- }
54847
- buildMssqlConnection() {
54848
- const ssl = this.connection.ssl;
54849
- const sslEnabled = ssl === true || typeof ssl === "object" && ssl.enabled !== false;
54850
- return {
54851
- server: this.connection.host || "localhost",
54852
- port: this.connection.port,
54853
- database: this.connection.database || "visor",
54854
- user: this.connection.user,
54855
- password: this.connection.password,
54856
- options: {
54857
- encrypt: sslEnabled,
54858
- trustServerCertificate: typeof ssl === "object" ? ssl.reject_unauthorized === false : !sslEnabled
54859
- }
54860
- };
54861
- }
54862
- resolveSslConfig() {
54863
- const ssl = this.connection.ssl;
54864
- if (ssl === false || ssl === void 0) return false;
54865
- if (ssl === true) return { rejectUnauthorized: true };
54866
- if (ssl.enabled === false) return false;
54867
- const result = {
54868
- rejectUnauthorized: ssl.reject_unauthorized !== false
54869
- };
54870
- if (ssl.ca) {
54871
- const caPath = this.validateSslPath(ssl.ca, "CA certificate");
54872
- result.ca = fs24.readFileSync(caPath, "utf8");
54873
- }
54874
- if (ssl.cert) {
54875
- const certPath = this.validateSslPath(ssl.cert, "client certificate");
54876
- result.cert = fs24.readFileSync(certPath, "utf8");
54877
- }
54878
- if (ssl.key) {
54879
- const keyPath = this.validateSslPath(ssl.key, "client key");
54880
- result.key = fs24.readFileSync(keyPath, "utf8");
54881
- }
54882
- return result;
54883
- }
54884
- validateSslPath(filePath, label) {
54885
- const resolved = path28.resolve(filePath);
54886
- if (resolved !== path28.normalize(resolved)) {
54887
- throw new Error(`SSL ${label} path contains invalid sequences: ${filePath}`);
54888
- }
54889
- if (!fs24.existsSync(resolved)) {
54890
- throw new Error(`SSL ${label} not found: ${filePath}`);
54891
- }
54892
- return resolved;
54893
- }
54894
- async shutdown() {
54895
- if (this.knex) {
54896
- await this.knex.destroy();
54897
- this.knex = null;
54898
- }
54899
- }
54900
- async migrateSchema() {
54901
- const knex = this.getKnex();
54902
- const exists = await knex.schema.hasTable("schedules");
54903
- if (!exists) {
54904
- await knex.schema.createTable("schedules", (table) => {
54905
- table.string("id", 36).primary();
54906
- table.string("creator_id", 255).notNullable().index();
54907
- table.string("creator_context", 255);
54908
- table.string("creator_name", 255);
54909
- table.string("timezone", 64).notNullable().defaultTo("UTC");
54910
- table.string("schedule_expr", 255);
54911
- table.bigInteger("run_at");
54912
- table.boolean("is_recurring").notNullable();
54913
- table.text("original_expression");
54914
- table.string("workflow", 255);
54915
- table.text("workflow_inputs");
54916
- table.text("output_context");
54917
- table.string("status", 20).notNullable().index();
54918
- table.bigInteger("created_at").notNullable();
54919
- table.bigInteger("last_run_at");
54920
- table.bigInteger("next_run_at");
54921
- table.integer("run_count").notNullable().defaultTo(0);
54922
- table.integer("failure_count").notNullable().defaultTo(0);
54923
- table.text("last_error");
54924
- table.text("previous_response");
54925
- table.index(["status", "next_run_at"]);
54926
- });
54927
- }
54928
- const locksExist = await knex.schema.hasTable("scheduler_locks");
54929
- if (!locksExist) {
54930
- await knex.schema.createTable("scheduler_locks", (table) => {
54931
- table.string("lock_id", 255).primary();
54932
- table.string("node_id", 255).notNullable();
54933
- table.string("lock_token", 36).notNullable();
54934
- table.bigInteger("acquired_at").notNullable();
54935
- table.bigInteger("expires_at").notNullable();
54936
- });
54937
- }
54938
- }
54939
- getKnex() {
54940
- if (!this.knex) {
54941
- throw new Error("[KnexStore] Not initialized. Call initialize() first.");
54942
- }
54943
- return this.knex;
54944
- }
54945
- // --- CRUD ---
54946
- async create(schedule) {
54947
- const knex = this.getKnex();
54948
- const newSchedule = {
54949
- ...schedule,
54950
- id: (0, import_uuid2.v4)(),
54951
- createdAt: Date.now(),
54952
- runCount: 0,
54953
- failureCount: 0,
54954
- status: "active"
54955
- };
54956
- await knex("schedules").insert(toInsertRow(newSchedule));
54957
- logger.info(`[KnexStore] Created schedule ${newSchedule.id} for user ${newSchedule.creatorId}`);
54958
- return newSchedule;
54959
- }
54960
- async importSchedule(schedule) {
54961
- const knex = this.getKnex();
54962
- const existing = await knex("schedules").where("id", schedule.id).first();
54963
- if (existing) return;
54964
- await knex("schedules").insert(toInsertRow(schedule));
54965
- }
54966
- async get(id) {
54967
- const knex = this.getKnex();
54968
- const row = await knex("schedules").where("id", id).first();
54969
- return row ? fromDbRow2(row) : void 0;
54970
- }
54971
- async update(id, patch) {
54972
- const knex = this.getKnex();
54973
- const existing = await knex("schedules").where("id", id).first();
54974
- if (!existing) return void 0;
54975
- const current = fromDbRow2(existing);
54976
- const updated = { ...current, ...patch, id: current.id };
54977
- const row = toInsertRow(updated);
54978
- delete row.id;
54979
- await knex("schedules").where("id", id).update(row);
54980
- return updated;
54981
- }
54982
- async delete(id) {
54983
- const knex = this.getKnex();
54984
- const deleted = await knex("schedules").where("id", id).del();
54985
- if (deleted > 0) {
54986
- logger.info(`[KnexStore] Deleted schedule ${id}`);
54987
- return true;
54988
- }
54989
- return false;
54990
- }
54991
- // --- Queries ---
54992
- async getByCreator(creatorId) {
54993
- const knex = this.getKnex();
54994
- const rows = await knex("schedules").where("creator_id", creatorId);
54995
- return rows.map((r) => fromDbRow2(r));
54996
- }
54997
- async getActiveSchedules() {
54998
- const knex = this.getKnex();
54999
- const rows = await knex("schedules").where("status", "active");
55000
- return rows.map((r) => fromDbRow2(r));
55001
- }
55002
- async getDueSchedules(now) {
55003
- const ts = now ?? Date.now();
55004
- const knex = this.getKnex();
55005
- const bFalse = this.driver === "mssql" ? 0 : false;
55006
- const bTrue = this.driver === "mssql" ? 1 : true;
55007
- const rows = await knex("schedules").where("status", "active").andWhere(function() {
55008
- this.where(function() {
55009
- this.where("is_recurring", bFalse).whereNotNull("run_at").where("run_at", "<=", ts);
55010
- }).orWhere(function() {
55011
- this.where("is_recurring", bTrue).whereNotNull("next_run_at").where("next_run_at", "<=", ts);
55012
- });
55013
- });
55014
- return rows.map((r) => fromDbRow2(r));
55015
- }
55016
- async findByWorkflow(creatorId, workflowName) {
55017
- const knex = this.getKnex();
55018
- const escaped = workflowName.toLowerCase().replace(/[%_\\]/g, "\\$&");
55019
- const pattern = `%${escaped}%`;
55020
- const rows = await knex("schedules").where("creator_id", creatorId).where("status", "active").whereRaw("LOWER(workflow) LIKE ? ESCAPE '\\'", [pattern]);
55021
- return rows.map((r) => fromDbRow2(r));
55022
- }
55023
- async getAll() {
55024
- const knex = this.getKnex();
55025
- const rows = await knex("schedules");
55026
- return rows.map((r) => fromDbRow2(r));
55027
- }
55028
- async getStats() {
55029
- const knex = this.getKnex();
55030
- const boolTrue = this.driver === "mssql" ? "1" : "true";
55031
- const boolFalse = this.driver === "mssql" ? "0" : "false";
55032
- const result = await knex("schedules").select(
55033
- knex.raw("COUNT(*) as total"),
55034
- knex.raw("SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active"),
55035
- knex.raw("SUM(CASE WHEN status = 'paused' THEN 1 ELSE 0 END) as paused"),
55036
- knex.raw("SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed"),
55037
- knex.raw("SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed"),
55038
- knex.raw(`SUM(CASE WHEN is_recurring = ${boolTrue} THEN 1 ELSE 0 END) as recurring`),
55039
- knex.raw(`SUM(CASE WHEN is_recurring = ${boolFalse} THEN 1 ELSE 0 END) as one_time`)
55040
- ).first();
55041
- return {
55042
- total: Number(result.total) || 0,
55043
- active: Number(result.active) || 0,
55044
- paused: Number(result.paused) || 0,
55045
- completed: Number(result.completed) || 0,
55046
- failed: Number(result.failed) || 0,
55047
- recurring: Number(result.recurring) || 0,
55048
- oneTime: Number(result.one_time) || 0
55049
- };
55050
- }
55051
- async validateLimits(creatorId, isRecurring, limits) {
55052
- const knex = this.getKnex();
55053
- if (limits.maxGlobal) {
55054
- const result = await knex("schedules").count("* as cnt").first();
55055
- if (Number(result?.cnt) >= limits.maxGlobal) {
55056
- throw new Error(`Global schedule limit reached (${limits.maxGlobal})`);
55057
- }
55058
- }
55059
- if (limits.maxPerUser) {
55060
- const result = await knex("schedules").where("creator_id", creatorId).count("* as cnt").first();
55061
- if (Number(result?.cnt) >= limits.maxPerUser) {
55062
- throw new Error(`You have reached the maximum number of schedules (${limits.maxPerUser})`);
55063
- }
55064
- }
55065
- if (isRecurring && limits.maxRecurringPerUser) {
55066
- const bTrue = this.driver === "mssql" ? 1 : true;
55067
- const result = await knex("schedules").where("creator_id", creatorId).where("is_recurring", bTrue).count("* as cnt").first();
55068
- if (Number(result?.cnt) >= limits.maxRecurringPerUser) {
55069
- throw new Error(
55070
- `You have reached the maximum number of recurring schedules (${limits.maxRecurringPerUser})`
55071
- );
55072
- }
55073
- }
55074
- }
55075
- // --- HA Distributed Locking (via scheduler_locks table) ---
55076
- async tryAcquireLock(lockId, nodeId, ttlSeconds) {
55077
- const knex = this.getKnex();
55078
- const now = Date.now();
55079
- const expiresAt = now + ttlSeconds * 1e3;
55080
- const token = (0, import_uuid2.v4)();
55081
- const updated = await knex("scheduler_locks").where("lock_id", lockId).where("expires_at", "<", now).update({
55082
- node_id: nodeId,
55083
- lock_token: token,
55084
- acquired_at: now,
55085
- expires_at: expiresAt
55086
- });
55087
- if (updated > 0) return token;
55088
- try {
55089
- await knex("scheduler_locks").insert({
55090
- lock_id: lockId,
55091
- node_id: nodeId,
55092
- lock_token: token,
55093
- acquired_at: now,
55094
- expires_at: expiresAt
55095
- });
55096
- return token;
55097
- } catch {
55098
- return null;
55099
- }
55100
- }
55101
- async releaseLock(lockId, lockToken) {
55102
- const knex = this.getKnex();
55103
- await knex("scheduler_locks").where("lock_id", lockId).where("lock_token", lockToken).del();
55104
- }
55105
- async renewLock(lockId, lockToken, ttlSeconds) {
55106
- const knex = this.getKnex();
55107
- const now = Date.now();
55108
- const expiresAt = now + ttlSeconds * 1e3;
55109
- const updated = await knex("scheduler_locks").where("lock_id", lockId).where("lock_token", lockToken).update({ acquired_at: now, expires_at: expiresAt });
55110
- return updated > 0;
55111
- }
55112
- async flush() {
55113
- }
55114
- };
55115
- }
55116
- });
55117
-
55118
- // src/enterprise/loader.ts
55119
- var loader_exports = {};
55120
- __export(loader_exports, {
55121
- loadEnterprisePolicyEngine: () => loadEnterprisePolicyEngine,
55122
- loadEnterpriseStoreBackend: () => loadEnterpriseStoreBackend
55123
- });
55124
- async function loadEnterprisePolicyEngine(config) {
55125
- try {
55126
- const { LicenseValidator: LicenseValidator2 } = await Promise.resolve().then(() => (init_validator(), validator_exports));
55127
- const validator = new LicenseValidator2();
55128
- const license = await validator.loadAndValidate();
55129
- if (!license || !validator.hasFeature("policy")) {
55130
- return new DefaultPolicyEngine();
55131
- }
55132
- if (validator.isInGracePeriod()) {
55133
- console.warn(
55134
- "[visor:enterprise] License has expired but is within the 72-hour grace period. Please renew your license."
55135
- );
55136
- }
55137
- const { OpaPolicyEngine: OpaPolicyEngine2 } = await Promise.resolve().then(() => (init_opa_policy_engine(), opa_policy_engine_exports));
55138
- const engine = new OpaPolicyEngine2(config);
55139
- await engine.initialize(config);
55140
- return engine;
55141
- } catch (err) {
55142
- const msg = err instanceof Error ? err.message : String(err);
55143
- try {
55144
- const { logger: logger2 } = (init_logger(), __toCommonJS(logger_exports));
55145
- logger2.warn(`[PolicyEngine] Enterprise policy init failed, falling back to default: ${msg}`);
55146
- } catch {
55147
- }
55148
- return new DefaultPolicyEngine();
55149
- }
55150
- }
55151
- async function loadEnterpriseStoreBackend(driver, storageConfig, haConfig) {
55152
- const { LicenseValidator: LicenseValidator2 } = await Promise.resolve().then(() => (init_validator(), validator_exports));
55153
- const validator = new LicenseValidator2();
55154
- const license = await validator.loadAndValidate();
55155
- if (!license || !validator.hasFeature("scheduler-sql")) {
55156
- throw new Error(
55157
- `The ${driver} schedule storage driver requires a Visor Enterprise license with the 'scheduler-sql' feature. Please upgrade or use driver: 'sqlite' (default).`
55158
- );
55159
- }
55160
- if (validator.isInGracePeriod()) {
55161
- console.warn(
55162
- "[visor:enterprise] License has expired but is within the 72-hour grace period. Please renew your license."
55163
- );
55164
- }
55165
- const { KnexStoreBackend: KnexStoreBackend2 } = await Promise.resolve().then(() => (init_knex_store(), knex_store_exports));
55166
- return new KnexStoreBackend2(driver, storageConfig, haConfig);
55167
- }
55168
- var init_loader = __esm({
55169
- "src/enterprise/loader.ts"() {
55170
- "use strict";
55171
- init_default_engine();
55172
- }
55173
- });
55174
-
55175
54601
  // src/event-bus/event-bus.ts
55176
54602
  var event_bus_exports = {};
55177
54603
  __export(event_bus_exports, {
@@ -56078,8 +55504,8 @@ ${content}
56078
55504
  * Sleep utility
56079
55505
  */
56080
55506
  sleep(ms) {
56081
- return new Promise((resolve18) => {
56082
- const t = setTimeout(resolve18, ms);
55507
+ return new Promise((resolve14) => {
55508
+ const t = setTimeout(resolve14, ms);
56083
55509
  if (typeof t.unref === "function") {
56084
55510
  try {
56085
55511
  t.unref();
@@ -56364,8 +55790,8 @@ ${end}`);
56364
55790
  async updateGroupedComment(ctx, comments, group, changedIds) {
56365
55791
  const existingLock = this.updateLocks.get(group);
56366
55792
  let resolveLock;
56367
- const ourLock = new Promise((resolve18) => {
56368
- resolveLock = resolve18;
55793
+ const ourLock = new Promise((resolve14) => {
55794
+ resolveLock = resolve14;
56369
55795
  });
56370
55796
  this.updateLocks.set(group, ourLock);
56371
55797
  try {
@@ -56678,7 +56104,7 @@ ${blocks}
56678
56104
  * Sleep utility for enforcing delays
56679
56105
  */
56680
56106
  sleep(ms) {
56681
- return new Promise((resolve18) => setTimeout(resolve18, ms));
56107
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
56682
56108
  }
56683
56109
  };
56684
56110
  }
@@ -57949,15 +57375,15 @@ function serializeRunState(state) {
57949
57375
  ])
57950
57376
  };
57951
57377
  }
57952
- var path30, fs26, StateMachineExecutionEngine;
57378
+ var path26, fs22, StateMachineExecutionEngine;
57953
57379
  var init_state_machine_execution_engine = __esm({
57954
57380
  "src/state-machine-execution-engine.ts"() {
57955
57381
  "use strict";
57956
57382
  init_runner();
57957
57383
  init_logger();
57958
57384
  init_sandbox_manager();
57959
- path30 = __toESM(require("path"));
57960
- fs26 = __toESM(require("fs"));
57385
+ path26 = __toESM(require("path"));
57386
+ fs22 = __toESM(require("fs"));
57961
57387
  StateMachineExecutionEngine = class _StateMachineExecutionEngine {
57962
57388
  workingDirectory;
57963
57389
  executionContext;
@@ -58189,8 +57615,8 @@ var init_state_machine_execution_engine = __esm({
58189
57615
  logger.debug(
58190
57616
  `[PolicyEngine] Loading enterprise policy engine (engine=${configWithTagFilter.policy.engine})`
58191
57617
  );
58192
- const { loadEnterprisePolicyEngine: loadEnterprisePolicyEngine2 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
58193
- context2.policyEngine = await loadEnterprisePolicyEngine2(configWithTagFilter.policy);
57618
+ const { loadEnterprisePolicyEngine } = await import("./enterprise/loader");
57619
+ context2.policyEngine = await loadEnterprisePolicyEngine(configWithTagFilter.policy);
58194
57620
  logger.debug(
58195
57621
  `[PolicyEngine] Initialized: ${context2.policyEngine?.constructor?.name || "unknown"}`
58196
57622
  );
@@ -58342,9 +57768,9 @@ var init_state_machine_execution_engine = __esm({
58342
57768
  }
58343
57769
  const checkId = String(ev?.checkId || "unknown");
58344
57770
  const threadKey = ev?.threadKey || (channel && threadTs ? `${channel}:${threadTs}` : "session");
58345
- const baseDir = process.env.VISOR_SNAPSHOT_DIR || path30.resolve(process.cwd(), ".visor", "snapshots");
58346
- fs26.mkdirSync(baseDir, { recursive: true });
58347
- const filePath = path30.join(baseDir, `${threadKey}-${checkId}.json`);
57771
+ const baseDir = process.env.VISOR_SNAPSHOT_DIR || path26.resolve(process.cwd(), ".visor", "snapshots");
57772
+ fs22.mkdirSync(baseDir, { recursive: true });
57773
+ const filePath = path26.join(baseDir, `${threadKey}-${checkId}.json`);
58348
57774
  await this.saveSnapshotToFile(filePath);
58349
57775
  logger.info(`[Snapshot] Saved run snapshot: ${filePath}`);
58350
57776
  try {
@@ -58485,7 +57911,7 @@ var init_state_machine_execution_engine = __esm({
58485
57911
  * Does not include secrets. Intended for debugging and future resume support.
58486
57912
  */
58487
57913
  async saveSnapshotToFile(filePath) {
58488
- const fs27 = await import("fs/promises");
57914
+ const fs23 = await import("fs/promises");
58489
57915
  const ctx = this._lastContext;
58490
57916
  const runner = this._lastRunner;
58491
57917
  if (!ctx || !runner) {
@@ -58505,14 +57931,14 @@ var init_state_machine_execution_engine = __esm({
58505
57931
  journal: entries,
58506
57932
  requestedChecks: ctx.requestedChecks || []
58507
57933
  };
58508
- await fs27.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
57934
+ await fs23.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
58509
57935
  }
58510
57936
  /**
58511
57937
  * Load a snapshot JSON from file and return it. Resume support can build on this.
58512
57938
  */
58513
57939
  async loadSnapshotFromFile(filePath) {
58514
- const fs27 = await import("fs/promises");
58515
- const raw = await fs27.readFile(filePath, "utf8");
57940
+ const fs23 = await import("fs/promises");
57941
+ const raw = await fs23.readFile(filePath, "utf8");
58516
57942
  return JSON.parse(raw);
58517
57943
  }
58518
57944
  /**