@probelabs/visor 0.1.132 → 0.1.137
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/config-reloader.d.ts +1 -0
- package/dist/config/config-reloader.d.ts.map +1 -1
- package/dist/config/config-watcher.d.ts +1 -0
- package/dist/config/config-watcher.d.ts.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/docs/ai-custom-tools-usage.md +37 -0
- package/dist/docs/ai-custom-tools.md +43 -1
- package/dist/docs/custom-tools.md +70 -1
- package/dist/docs/script.md +542 -27
- package/dist/docs/testing/cookbook.md +47 -0
- package/dist/examples/README.md +4 -0
- package/dist/examples/api-tools-ai-example.yaml +63 -0
- package/dist/examples/api-tools-inline-overlay-example.yaml +126 -0
- package/dist/examples/api-tools-library.yaml +18 -0
- package/dist/examples/api-tools-mcp-example.yaml +55 -0
- package/dist/examples/openapi/profiles-overlay-rename.yaml +3 -0
- package/dist/examples/openapi/users-api.json +91 -0
- package/dist/examples/openapi/users-overlay-rename.yaml +3 -0
- package/dist/generated/config-schema.d.ts +223 -74
- package/dist/generated/config-schema.d.ts.map +1 -1
- package/dist/generated/config-schema.json +251 -79
- package/dist/index.js +45128 -25001
- package/dist/output/traces/{run-2026-02-18T11-06-48-673Z.ndjson → run-2026-02-23T08-59-32-321Z.ndjson} +84 -84
- package/dist/{traces/run-2026-02-18T11-07-37-310Z.ndjson → output/traces/run-2026-02-23T09-00-20-148Z.ndjson} +1148 -1063
- package/dist/providers/ai-check-provider.d.ts.map +1 -1
- package/dist/providers/api-tool-executor.d.ts +43 -0
- package/dist/providers/api-tool-executor.d.ts.map +1 -0
- package/dist/providers/command-check-provider.d.ts.map +1 -1
- package/dist/providers/custom-tool-executor.d.ts +21 -0
- package/dist/providers/custom-tool-executor.d.ts.map +1 -1
- package/dist/providers/mcp-check-provider.d.ts.map +1 -1
- package/dist/providers/mcp-custom-sse-server.d.ts.map +1 -1
- package/dist/providers/script-check-provider.d.ts +18 -2
- package/dist/providers/script-check-provider.d.ts.map +1 -1
- package/dist/sdk/{check-provider-registry-7TCA3NSG.mjs → check-provider-registry-BCGP62RY.mjs} +9 -8
- package/dist/sdk/{check-provider-registry-RRUZHGJI.mjs → check-provider-registry-SA2WHPLO.mjs} +9 -8
- package/dist/sdk/{check-provider-registry-4WLTLPMU.mjs → check-provider-registry-SCL4KP55.mjs} +9 -8
- package/dist/sdk/{chunk-LMJNI6RM.mjs → chunk-ALB3N4ZQ.mjs} +12 -5
- package/dist/sdk/{chunk-LMJNI6RM.mjs.map → chunk-ALB3N4ZQ.mjs.map} +1 -1
- package/dist/sdk/{chunk-27RV5RR2.mjs → chunk-BRD36I43.mjs} +3 -3
- package/dist/sdk/{chunk-5VY5QJTY.mjs → chunk-DFKP7LY6.mjs} +1896 -1762
- package/dist/sdk/chunk-DFKP7LY6.mjs.map +1 -0
- package/dist/sdk/{chunk-UBDHAGYY.mjs → chunk-E2N3U5HU.mjs} +5 -5
- package/dist/sdk/{chunk-XGI47XIH.mjs → chunk-F4K5WFSM.mjs} +1896 -1762
- package/dist/sdk/chunk-F4K5WFSM.mjs.map +1 -0
- package/dist/sdk/{chunk-BOGVSF57.mjs → chunk-J6F5K5EG.mjs} +1896 -1762
- package/dist/sdk/chunk-J6F5K5EG.mjs.map +1 -0
- package/dist/sdk/{chunk-U3BLLEW3.mjs → chunk-KPRFDKQX.mjs} +329 -80
- package/dist/sdk/chunk-KPRFDKQX.mjs.map +1 -0
- package/dist/sdk/{chunk-VF6XIUE4.mjs → chunk-LW3INISN.mjs} +32 -1
- package/dist/sdk/{chunk-VF6XIUE4.mjs.map → chunk-LW3INISN.mjs.map} +1 -1
- package/dist/sdk/{chunk-VG7FWDC2.mjs → chunk-QUEWQWDX.mjs} +11 -4
- package/dist/sdk/{chunk-VG7FWDC2.mjs.map → chunk-QUEWQWDX.mjs.map} +1 -1
- package/dist/sdk/{chunk-BGBXLPLL.mjs → chunk-UMFEBYCN.mjs} +5 -5
- package/dist/sdk/chunk-XKCER23W.mjs +1490 -0
- package/dist/sdk/chunk-XKCER23W.mjs.map +1 -0
- package/dist/sdk/{chunk-FAKITJ3J.mjs → chunk-YTAGJZHN.mjs} +3 -3
- package/dist/sdk/{chunk-XJQKTK6V.mjs → chunk-ZUEQNCKB.mjs} +2 -2
- package/dist/sdk/{config-FMIIATKX.mjs → config-3UIU4TMP.mjs} +3 -3
- package/dist/sdk/{failure-condition-evaluator-PNONVBXD.mjs → failure-condition-evaluator-3B3G5NYW.mjs} +4 -4
- package/dist/sdk/{failure-condition-evaluator-MUUAK7MN.mjs → failure-condition-evaluator-B5JJFYKU.mjs} +4 -4
- package/dist/sdk/{github-frontend-DWF6BLZH.mjs → github-frontend-VAWVSCNX.mjs} +4 -4
- package/dist/sdk/{github-frontend-WR4S3NG5.mjs → github-frontend-ZOVXPPHQ.mjs} +4 -4
- package/dist/sdk/{host-S3LSWESP.mjs → host-LOQWBHWT.mjs} +2 -2
- package/dist/sdk/{host-U7V54J2H.mjs → host-TEQ7HKKH.mjs} +2 -2
- package/dist/sdk/{liquid-extensions-YDIIH33Q.mjs → liquid-extensions-PLBOMRLI.mjs} +3 -3
- package/dist/sdk/{routing-MVDVJDYJ.mjs → routing-HR6N43RQ.mjs} +6 -6
- package/dist/sdk/{routing-F4FOWVKF.mjs → routing-SEQYM4N6.mjs} +6 -6
- package/dist/sdk/schedule-tool-2COUUTF7.mjs +18 -0
- package/dist/sdk/{schedule-tool-handler-FRN3KKRM.mjs → schedule-tool-handler-5BDMLHS5.mjs} +9 -8
- package/dist/sdk/{schedule-tool-handler-VFES42DD.mjs → schedule-tool-handler-OXGTPLST.mjs} +9 -8
- package/dist/sdk/{schedule-tool-handler-7DNEGDZC.mjs → schedule-tool-handler-Y2UABBXN.mjs} +9 -8
- package/dist/sdk/sdk.d.mts +55 -2
- package/dist/sdk/sdk.d.ts +55 -2
- package/dist/sdk/sdk.js +2367 -482
- package/dist/sdk/sdk.js.map +1 -1
- package/dist/sdk/sdk.mjs +8 -7
- package/dist/sdk/sdk.mjs.map +1 -1
- package/dist/sdk/{trace-helpers-RDPXIN4S.mjs → trace-helpers-FAAGLXBI.mjs} +2 -2
- package/dist/sdk/{trace-helpers-KSPGA24B.mjs → trace-helpers-IGMH7ZPP.mjs} +2 -2
- package/dist/sdk/{workflow-check-provider-BMVJ6X7N.mjs → workflow-check-provider-7SR7ZWSV.mjs} +9 -8
- package/dist/sdk/{workflow-check-provider-CPGIRZMH.mjs → workflow-check-provider-L2ZUOMJR.mjs} +9 -8
- package/dist/sdk/{workflow-check-provider-4NFWH6YO.mjs → workflow-check-provider-WLA7LO56.mjs} +9 -8
- package/dist/sdk/workflow-check-provider-WLA7LO56.mjs.map +1 -0
- package/dist/state-machine/dispatch/execution-invoker.d.ts.map +1 -1
- package/dist/state-machine-execution-engine.d.ts.map +1 -1
- package/dist/test-runner/core/test-execution-wrapper.d.ts.map +1 -1
- package/dist/test-runner/index.d.ts.map +1 -1
- package/dist/test-runner/validator.d.ts.map +1 -1
- package/dist/traces/{run-2026-02-18T11-06-48-673Z.ndjson → run-2026-02-23T08-59-32-321Z.ndjson} +84 -84
- package/dist/{output/traces/run-2026-02-18T11-07-37-310Z.ndjson → traces/run-2026-02-23T09-00-20-148Z.ndjson} +1148 -1063
- package/dist/types/config.d.ts +55 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/utils/config-loader.d.ts +5 -0
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/sandbox.d.ts +8 -0
- package/dist/utils/sandbox.d.ts.map +1 -1
- package/dist/utils/script-tool-environment.d.ts +90 -0
- package/dist/utils/script-tool-environment.d.ts.map +1 -0
- package/dist/utils/tool-resolver.d.ts +18 -0
- package/dist/utils/tool-resolver.d.ts.map +1 -0
- package/package.json +11 -4
- package/dist/sdk/chunk-5VY5QJTY.mjs.map +0 -1
- package/dist/sdk/chunk-BOGVSF57.mjs.map +0 -1
- package/dist/sdk/chunk-U3BLLEW3.mjs.map +0 -1
- package/dist/sdk/chunk-XGI47XIH.mjs.map +0 -1
- /package/dist/sdk/{check-provider-registry-4WLTLPMU.mjs.map → check-provider-registry-BCGP62RY.mjs.map} +0 -0
- /package/dist/sdk/{check-provider-registry-7TCA3NSG.mjs.map → check-provider-registry-SA2WHPLO.mjs.map} +0 -0
- /package/dist/sdk/{check-provider-registry-RRUZHGJI.mjs.map → check-provider-registry-SCL4KP55.mjs.map} +0 -0
- /package/dist/sdk/{chunk-27RV5RR2.mjs.map → chunk-BRD36I43.mjs.map} +0 -0
- /package/dist/sdk/{chunk-BGBXLPLL.mjs.map → chunk-E2N3U5HU.mjs.map} +0 -0
- /package/dist/sdk/{chunk-UBDHAGYY.mjs.map → chunk-UMFEBYCN.mjs.map} +0 -0
- /package/dist/sdk/{chunk-FAKITJ3J.mjs.map → chunk-YTAGJZHN.mjs.map} +0 -0
- /package/dist/sdk/{chunk-XJQKTK6V.mjs.map → chunk-ZUEQNCKB.mjs.map} +0 -0
- /package/dist/sdk/{config-FMIIATKX.mjs.map → config-3UIU4TMP.mjs.map} +0 -0
- /package/dist/sdk/{failure-condition-evaluator-MUUAK7MN.mjs.map → failure-condition-evaluator-3B3G5NYW.mjs.map} +0 -0
- /package/dist/sdk/{failure-condition-evaluator-PNONVBXD.mjs.map → failure-condition-evaluator-B5JJFYKU.mjs.map} +0 -0
- /package/dist/sdk/{github-frontend-DWF6BLZH.mjs.map → github-frontend-VAWVSCNX.mjs.map} +0 -0
- /package/dist/sdk/{github-frontend-WR4S3NG5.mjs.map → github-frontend-ZOVXPPHQ.mjs.map} +0 -0
- /package/dist/sdk/{host-S3LSWESP.mjs.map → host-LOQWBHWT.mjs.map} +0 -0
- /package/dist/sdk/{host-U7V54J2H.mjs.map → host-TEQ7HKKH.mjs.map} +0 -0
- /package/dist/sdk/{liquid-extensions-YDIIH33Q.mjs.map → liquid-extensions-PLBOMRLI.mjs.map} +0 -0
- /package/dist/sdk/{routing-F4FOWVKF.mjs.map → routing-HR6N43RQ.mjs.map} +0 -0
- /package/dist/sdk/{routing-MVDVJDYJ.mjs.map → routing-SEQYM4N6.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-handler-7DNEGDZC.mjs.map → schedule-tool-2COUUTF7.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-handler-FRN3KKRM.mjs.map → schedule-tool-handler-5BDMLHS5.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-handler-VFES42DD.mjs.map → schedule-tool-handler-OXGTPLST.mjs.map} +0 -0
- /package/dist/sdk/{trace-helpers-KSPGA24B.mjs.map → schedule-tool-handler-Y2UABBXN.mjs.map} +0 -0
- /package/dist/sdk/{trace-helpers-RDPXIN4S.mjs.map → trace-helpers-FAAGLXBI.mjs.map} +0 -0
- /package/dist/sdk/{workflow-check-provider-4NFWH6YO.mjs.map → trace-helpers-IGMH7ZPP.mjs.map} +0 -0
- /package/dist/sdk/{workflow-check-provider-BMVJ6X7N.mjs.map → workflow-check-provider-7SR7ZWSV.mjs.map} +0 -0
- /package/dist/sdk/{workflow-check-provider-CPGIRZMH.mjs.map → workflow-check-provider-L2ZUOMJR.mjs.map} +0 -0
|
@@ -28,7 +28,18 @@ import {
|
|
|
28
28
|
import {
|
|
29
29
|
config_exports,
|
|
30
30
|
init_config
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-KPRFDKQX.mjs";
|
|
32
|
+
import {
|
|
33
|
+
ScheduleStore,
|
|
34
|
+
buildScheduleToolContext,
|
|
35
|
+
getScheduleToolDefinition,
|
|
36
|
+
handleScheduleAction,
|
|
37
|
+
init_schedule_parser,
|
|
38
|
+
init_schedule_store,
|
|
39
|
+
init_schedule_tool,
|
|
40
|
+
init_store,
|
|
41
|
+
isScheduleTool
|
|
42
|
+
} from "./chunk-XKCER23W.mjs";
|
|
32
43
|
import {
|
|
33
44
|
ExecutionJournal,
|
|
34
45
|
checkLoopBudget,
|
|
@@ -37,11 +48,11 @@ import {
|
|
|
37
48
|
init_routing,
|
|
38
49
|
init_snapshot_store,
|
|
39
50
|
snapshot_store_exports
|
|
40
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-UMFEBYCN.mjs";
|
|
41
52
|
import {
|
|
42
53
|
FailureConditionEvaluator,
|
|
43
54
|
init_failure_condition_evaluator
|
|
44
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-YTAGJZHN.mjs";
|
|
45
56
|
import {
|
|
46
57
|
addEvent,
|
|
47
58
|
emitNdjsonFallback,
|
|
@@ -52,7 +63,7 @@ import {
|
|
|
52
63
|
setSpanAttributes,
|
|
53
64
|
trace_helpers_exports,
|
|
54
65
|
withActiveSpan
|
|
55
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-ALB3N4ZQ.mjs";
|
|
56
67
|
import {
|
|
57
68
|
addDiagramBlock,
|
|
58
69
|
init_metrics
|
|
@@ -60,7 +71,7 @@ import {
|
|
|
60
71
|
import {
|
|
61
72
|
createExtendedLiquid,
|
|
62
73
|
init_liquid_extensions
|
|
63
|
-
} from "./chunk-
|
|
74
|
+
} from "./chunk-ZUEQNCKB.mjs";
|
|
64
75
|
import {
|
|
65
76
|
createPermissionHelpers,
|
|
66
77
|
detectLocalMode,
|
|
@@ -69,9 +80,10 @@ import {
|
|
|
69
80
|
} from "./chunk-25IC7KXZ.mjs";
|
|
70
81
|
import {
|
|
71
82
|
compileAndRun,
|
|
83
|
+
compileAndRunAsync,
|
|
72
84
|
createSecureSandbox,
|
|
73
85
|
init_sandbox
|
|
74
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-LW3INISN.mjs";
|
|
75
87
|
import {
|
|
76
88
|
MemoryStore,
|
|
77
89
|
init_memory_store,
|
|
@@ -116,7 +128,7 @@ async function processDiffWithOutline(diffContent) {
|
|
|
116
128
|
}
|
|
117
129
|
try {
|
|
118
130
|
const originalProbePath = process.env.PROBE_PATH;
|
|
119
|
-
const
|
|
131
|
+
const fs10 = __require("fs");
|
|
120
132
|
const possiblePaths = [
|
|
121
133
|
// Relative to current working directory (most common in production)
|
|
122
134
|
path.join(process.cwd(), "node_modules/@probelabs/probe/bin/probe-binary"),
|
|
@@ -127,7 +139,7 @@ async function processDiffWithOutline(diffContent) {
|
|
|
127
139
|
];
|
|
128
140
|
let probeBinaryPath;
|
|
129
141
|
for (const candidatePath of possiblePaths) {
|
|
130
|
-
if (
|
|
142
|
+
if (fs10.existsSync(candidatePath)) {
|
|
131
143
|
probeBinaryPath = candidatePath;
|
|
132
144
|
break;
|
|
133
145
|
}
|
|
@@ -1210,8 +1222,8 @@ ${schemaString}`);
|
|
|
1210
1222
|
}
|
|
1211
1223
|
if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
|
|
1212
1224
|
try {
|
|
1213
|
-
const
|
|
1214
|
-
const
|
|
1225
|
+
const fs10 = __require("fs");
|
|
1226
|
+
const path13 = __require("path");
|
|
1215
1227
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1216
1228
|
const provider = this.config.provider || "auto";
|
|
1217
1229
|
const model = this.config.model || "default";
|
|
@@ -1325,20 +1337,20 @@ ${"=".repeat(60)}
|
|
|
1325
1337
|
`;
|
|
1326
1338
|
readableVersion += `${"=".repeat(60)}
|
|
1327
1339
|
`;
|
|
1328
|
-
const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS ||
|
|
1329
|
-
if (!
|
|
1330
|
-
|
|
1340
|
+
const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path13.join(process.cwd(), "debug-artifacts");
|
|
1341
|
+
if (!fs10.existsSync(debugArtifactsDir)) {
|
|
1342
|
+
fs10.mkdirSync(debugArtifactsDir, { recursive: true });
|
|
1331
1343
|
}
|
|
1332
|
-
const debugFile =
|
|
1344
|
+
const debugFile = path13.join(
|
|
1333
1345
|
debugArtifactsDir,
|
|
1334
1346
|
`prompt-${_checkName || "unknown"}-${timestamp}.json`
|
|
1335
1347
|
);
|
|
1336
|
-
|
|
1337
|
-
const readableFile =
|
|
1348
|
+
fs10.writeFileSync(debugFile, debugJson, "utf-8");
|
|
1349
|
+
const readableFile = path13.join(
|
|
1338
1350
|
debugArtifactsDir,
|
|
1339
1351
|
`prompt-${_checkName || "unknown"}-${timestamp}.txt`
|
|
1340
1352
|
);
|
|
1341
|
-
|
|
1353
|
+
fs10.writeFileSync(readableFile, readableVersion, "utf-8");
|
|
1342
1354
|
log(`
|
|
1343
1355
|
\u{1F4BE} Full debug info saved to:`);
|
|
1344
1356
|
log(` JSON: ${debugFile}`);
|
|
@@ -1371,8 +1383,8 @@ ${"=".repeat(60)}
|
|
|
1371
1383
|
log(`\u{1F4E4} Response length: ${response.length} characters`);
|
|
1372
1384
|
if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
|
|
1373
1385
|
try {
|
|
1374
|
-
const
|
|
1375
|
-
const
|
|
1386
|
+
const fs10 = __require("fs");
|
|
1387
|
+
const path13 = __require("path");
|
|
1376
1388
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1377
1389
|
const agentAny2 = agent;
|
|
1378
1390
|
let fullHistory = [];
|
|
@@ -1383,8 +1395,8 @@ ${"=".repeat(60)}
|
|
|
1383
1395
|
} else if (agentAny2._messages) {
|
|
1384
1396
|
fullHistory = agentAny2._messages;
|
|
1385
1397
|
}
|
|
1386
|
-
const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS ||
|
|
1387
|
-
const sessionBase =
|
|
1398
|
+
const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path13.join(process.cwd(), "debug-artifacts");
|
|
1399
|
+
const sessionBase = path13.join(
|
|
1388
1400
|
debugArtifactsDir,
|
|
1389
1401
|
`session-${_checkName || "unknown"}-${timestamp}`
|
|
1390
1402
|
);
|
|
@@ -1396,7 +1408,7 @@ ${"=".repeat(60)}
|
|
|
1396
1408
|
schema: effectiveSchema,
|
|
1397
1409
|
totalMessages: fullHistory.length
|
|
1398
1410
|
};
|
|
1399
|
-
|
|
1411
|
+
fs10.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
|
|
1400
1412
|
let readable = `=============================================================
|
|
1401
1413
|
`;
|
|
1402
1414
|
readable += `COMPLETE AI SESSION HISTORY (AFTER RESPONSE)
|
|
@@ -1423,7 +1435,7 @@ ${"=".repeat(60)}
|
|
|
1423
1435
|
`;
|
|
1424
1436
|
readable += content + "\n";
|
|
1425
1437
|
});
|
|
1426
|
-
|
|
1438
|
+
fs10.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
|
|
1427
1439
|
log(`\u{1F4BE} Complete session history saved:`);
|
|
1428
1440
|
log(` - Contains ALL ${fullHistory.length} messages (prompts + responses)`);
|
|
1429
1441
|
} catch (error) {
|
|
@@ -1432,11 +1444,11 @@ ${"=".repeat(60)}
|
|
|
1432
1444
|
}
|
|
1433
1445
|
if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
|
|
1434
1446
|
try {
|
|
1435
|
-
const
|
|
1436
|
-
const
|
|
1447
|
+
const fs10 = __require("fs");
|
|
1448
|
+
const path13 = __require("path");
|
|
1437
1449
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1438
|
-
const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS ||
|
|
1439
|
-
const responseFile =
|
|
1450
|
+
const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path13.join(process.cwd(), "debug-artifacts");
|
|
1451
|
+
const responseFile = path13.join(
|
|
1440
1452
|
debugArtifactsDir,
|
|
1441
1453
|
`response-${_checkName || "unknown"}-${timestamp}.txt`
|
|
1442
1454
|
);
|
|
@@ -1469,7 +1481,7 @@ ${"=".repeat(60)}
|
|
|
1469
1481
|
`;
|
|
1470
1482
|
responseContent += `${"=".repeat(60)}
|
|
1471
1483
|
`;
|
|
1472
|
-
|
|
1484
|
+
fs10.writeFileSync(responseFile, responseContent, "utf-8");
|
|
1473
1485
|
log(`\u{1F4BE} Response saved to: ${responseFile}`);
|
|
1474
1486
|
} catch (error) {
|
|
1475
1487
|
log(`\u26A0\uFE0F Could not save response file: ${error}`);
|
|
@@ -1485,9 +1497,9 @@ ${"=".repeat(60)}
|
|
|
1485
1497
|
await agentAny._telemetryConfig.shutdown();
|
|
1486
1498
|
log(`\u{1F4CA} OpenTelemetry trace saved to: ${agentAny._traceFilePath}`);
|
|
1487
1499
|
if (process.env.GITHUB_ACTIONS) {
|
|
1488
|
-
const
|
|
1489
|
-
if (
|
|
1490
|
-
const stats =
|
|
1500
|
+
const fs10 = __require("fs");
|
|
1501
|
+
if (fs10.existsSync(agentAny._traceFilePath)) {
|
|
1502
|
+
const stats = fs10.statSync(agentAny._traceFilePath);
|
|
1491
1503
|
console.log(
|
|
1492
1504
|
`::notice title=AI Trace Saved::${agentAny._traceFilePath} (${stats.size} bytes)`
|
|
1493
1505
|
);
|
|
@@ -1688,8 +1700,8 @@ ${schemaString}`);
|
|
|
1688
1700
|
const model = this.config.model || "default";
|
|
1689
1701
|
if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
|
|
1690
1702
|
try {
|
|
1691
|
-
const
|
|
1692
|
-
const
|
|
1703
|
+
const fs10 = __require("fs");
|
|
1704
|
+
const path13 = __require("path");
|
|
1693
1705
|
const os = __require("os");
|
|
1694
1706
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1695
1707
|
const debugData = {
|
|
@@ -1763,18 +1775,18 @@ ${"=".repeat(60)}
|
|
|
1763
1775
|
readableVersion += `${"=".repeat(60)}
|
|
1764
1776
|
`;
|
|
1765
1777
|
const tempDir = os.tmpdir();
|
|
1766
|
-
const promptFile =
|
|
1767
|
-
|
|
1778
|
+
const promptFile = path13.join(tempDir, `visor-prompt-${timestamp}.txt`);
|
|
1779
|
+
fs10.writeFileSync(promptFile, prompt, "utf-8");
|
|
1768
1780
|
log(`
|
|
1769
1781
|
\u{1F4BE} Prompt saved to: ${promptFile}`);
|
|
1770
|
-
const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS ||
|
|
1782
|
+
const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path13.join(process.cwd(), "debug-artifacts");
|
|
1771
1783
|
try {
|
|
1772
|
-
const base =
|
|
1784
|
+
const base = path13.join(
|
|
1773
1785
|
debugArtifactsDir,
|
|
1774
1786
|
`prompt-${_checkName || "unknown"}-${timestamp}`
|
|
1775
1787
|
);
|
|
1776
|
-
|
|
1777
|
-
|
|
1788
|
+
fs10.writeFileSync(base + ".json", debugJson, "utf-8");
|
|
1789
|
+
fs10.writeFileSync(base + ".summary.txt", readableVersion, "utf-8");
|
|
1778
1790
|
log(`
|
|
1779
1791
|
\u{1F4BE} Full debug info saved to directory: ${debugArtifactsDir}`);
|
|
1780
1792
|
} catch {
|
|
@@ -1819,8 +1831,8 @@ $ ${cliCommand}
|
|
|
1819
1831
|
log(`\u{1F4E4} Response length: ${response.length} characters`);
|
|
1820
1832
|
if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
|
|
1821
1833
|
try {
|
|
1822
|
-
const
|
|
1823
|
-
const
|
|
1834
|
+
const fs10 = __require("fs");
|
|
1835
|
+
const path13 = __require("path");
|
|
1824
1836
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1825
1837
|
const agentAny = agent;
|
|
1826
1838
|
let fullHistory = [];
|
|
@@ -1831,8 +1843,8 @@ $ ${cliCommand}
|
|
|
1831
1843
|
} else if (agentAny._messages) {
|
|
1832
1844
|
fullHistory = agentAny._messages;
|
|
1833
1845
|
}
|
|
1834
|
-
const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS ||
|
|
1835
|
-
const sessionBase =
|
|
1846
|
+
const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path13.join(process.cwd(), "debug-artifacts");
|
|
1847
|
+
const sessionBase = path13.join(
|
|
1836
1848
|
debugArtifactsDir,
|
|
1837
1849
|
`session-${_checkName || "unknown"}-${timestamp}`
|
|
1838
1850
|
);
|
|
@@ -1844,7 +1856,7 @@ $ ${cliCommand}
|
|
|
1844
1856
|
schema: effectiveSchema,
|
|
1845
1857
|
totalMessages: fullHistory.length
|
|
1846
1858
|
};
|
|
1847
|
-
|
|
1859
|
+
fs10.writeFileSync(sessionBase + ".json", JSON.stringify(sessionData, null, 2), "utf-8");
|
|
1848
1860
|
let readable = `=============================================================
|
|
1849
1861
|
`;
|
|
1850
1862
|
readable += `COMPLETE AI SESSION HISTORY (AFTER RESPONSE)
|
|
@@ -1871,7 +1883,7 @@ ${"=".repeat(60)}
|
|
|
1871
1883
|
`;
|
|
1872
1884
|
readable += content + "\n";
|
|
1873
1885
|
});
|
|
1874
|
-
|
|
1886
|
+
fs10.writeFileSync(sessionBase + ".summary.txt", readable, "utf-8");
|
|
1875
1887
|
log(`\u{1F4BE} Complete session history saved:`);
|
|
1876
1888
|
log(` - Contains ALL ${fullHistory.length} messages (prompts + responses)`);
|
|
1877
1889
|
} catch (error) {
|
|
@@ -1880,11 +1892,11 @@ ${"=".repeat(60)}
|
|
|
1880
1892
|
}
|
|
1881
1893
|
if (process.env.VISOR_DEBUG_AI_SESSIONS === "true") {
|
|
1882
1894
|
try {
|
|
1883
|
-
const
|
|
1884
|
-
const
|
|
1895
|
+
const fs10 = __require("fs");
|
|
1896
|
+
const path13 = __require("path");
|
|
1885
1897
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1886
|
-
const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS ||
|
|
1887
|
-
const responseFile =
|
|
1898
|
+
const debugArtifactsDir = process.env.VISOR_DEBUG_ARTIFACTS || path13.join(process.cwd(), "debug-artifacts");
|
|
1899
|
+
const responseFile = path13.join(
|
|
1888
1900
|
debugArtifactsDir,
|
|
1889
1901
|
`response-${_checkName || "unknown"}-${timestamp}.txt`
|
|
1890
1902
|
);
|
|
@@ -1917,7 +1929,7 @@ ${"=".repeat(60)}
|
|
|
1917
1929
|
`;
|
|
1918
1930
|
responseContent += `${"=".repeat(60)}
|
|
1919
1931
|
`;
|
|
1920
|
-
|
|
1932
|
+
fs10.writeFileSync(responseFile, responseContent, "utf-8");
|
|
1921
1933
|
log(`\u{1F4BE} Response saved to: ${responseFile}`);
|
|
1922
1934
|
} catch (error) {
|
|
1923
1935
|
log(`\u26A0\uFE0F Could not save response file: ${error}`);
|
|
@@ -1935,9 +1947,9 @@ ${"=".repeat(60)}
|
|
|
1935
1947
|
await telemetry.shutdown();
|
|
1936
1948
|
log(`\u{1F4CA} OpenTelemetry trace saved to: ${traceFilePath}`);
|
|
1937
1949
|
if (process.env.GITHUB_ACTIONS) {
|
|
1938
|
-
const
|
|
1939
|
-
if (
|
|
1940
|
-
const stats =
|
|
1950
|
+
const fs10 = __require("fs");
|
|
1951
|
+
if (fs10.existsSync(traceFilePath)) {
|
|
1952
|
+
const stats = fs10.statSync(traceFilePath);
|
|
1941
1953
|
console.log(
|
|
1942
1954
|
`::notice title=AI Trace Saved::OpenTelemetry trace file size: ${stats.size} bytes`
|
|
1943
1955
|
);
|
|
@@ -1975,8 +1987,8 @@ ${"=".repeat(60)}
|
|
|
1975
1987
|
* Load schema content from schema files or inline definitions
|
|
1976
1988
|
*/
|
|
1977
1989
|
async loadSchemaContent(schema) {
|
|
1978
|
-
const
|
|
1979
|
-
const
|
|
1990
|
+
const fs10 = __require("fs").promises;
|
|
1991
|
+
const path13 = __require("path");
|
|
1980
1992
|
if (typeof schema === "object" && schema !== null) {
|
|
1981
1993
|
log("\u{1F4CB} Using inline schema object from configuration");
|
|
1982
1994
|
return JSON.stringify(schema);
|
|
@@ -1989,14 +2001,14 @@ ${"=".repeat(60)}
|
|
|
1989
2001
|
}
|
|
1990
2002
|
} catch {
|
|
1991
2003
|
}
|
|
1992
|
-
if ((schema.startsWith("./") || schema.includes(".json")) && !
|
|
2004
|
+
if ((schema.startsWith("./") || schema.includes(".json")) && !path13.isAbsolute(schema)) {
|
|
1993
2005
|
if (schema.includes("..") || schema.includes("\0")) {
|
|
1994
2006
|
throw new Error("Invalid schema path: path traversal not allowed");
|
|
1995
2007
|
}
|
|
1996
2008
|
try {
|
|
1997
|
-
const schemaPath =
|
|
2009
|
+
const schemaPath = path13.resolve(process.cwd(), schema);
|
|
1998
2010
|
log(`\u{1F4CB} Loading custom schema from file: ${schemaPath}`);
|
|
1999
|
-
const schemaContent = await
|
|
2011
|
+
const schemaContent = await fs10.readFile(schemaPath, "utf-8");
|
|
2000
2012
|
return schemaContent.trim();
|
|
2001
2013
|
} catch (error) {
|
|
2002
2014
|
throw new Error(
|
|
@@ -2010,22 +2022,22 @@ ${"=".repeat(60)}
|
|
|
2010
2022
|
}
|
|
2011
2023
|
const candidatePaths = [
|
|
2012
2024
|
// GitHub Action bundle location
|
|
2013
|
-
|
|
2025
|
+
path13.join(__dirname, "output", sanitizedSchemaName, "schema.json"),
|
|
2014
2026
|
// Historical fallback when src/output was inadvertently bundled as output1/
|
|
2015
|
-
|
|
2027
|
+
path13.join(__dirname, "output1", sanitizedSchemaName, "schema.json"),
|
|
2016
2028
|
// Local dev (repo root)
|
|
2017
|
-
|
|
2029
|
+
path13.join(process.cwd(), "output", sanitizedSchemaName, "schema.json")
|
|
2018
2030
|
];
|
|
2019
2031
|
for (const schemaPath of candidatePaths) {
|
|
2020
2032
|
try {
|
|
2021
|
-
const schemaContent = await
|
|
2033
|
+
const schemaContent = await fs10.readFile(schemaPath, "utf-8");
|
|
2022
2034
|
return schemaContent.trim();
|
|
2023
2035
|
} catch {
|
|
2024
2036
|
}
|
|
2025
2037
|
}
|
|
2026
|
-
const distPath =
|
|
2027
|
-
const distAltPath =
|
|
2028
|
-
const cwdPath =
|
|
2038
|
+
const distPath = path13.join(__dirname, "output", sanitizedSchemaName, "schema.json");
|
|
2039
|
+
const distAltPath = path13.join(__dirname, "output1", sanitizedSchemaName, "schema.json");
|
|
2040
|
+
const cwdPath = path13.join(process.cwd(), "output", sanitizedSchemaName, "schema.json");
|
|
2029
2041
|
throw new Error(
|
|
2030
2042
|
`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.`
|
|
2031
2043
|
);
|
|
@@ -2787,6 +2799,758 @@ var init_state_capture = __esm({
|
|
|
2787
2799
|
}
|
|
2788
2800
|
});
|
|
2789
2801
|
|
|
2802
|
+
// src/providers/api-tool-executor.ts
|
|
2803
|
+
import fs2 from "fs/promises";
|
|
2804
|
+
import path3 from "path";
|
|
2805
|
+
import YAML from "js-yaml";
|
|
2806
|
+
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
2807
|
+
import deepmerge from "deepmerge";
|
|
2808
|
+
import { JSONPath } from "jsonpath-plus";
|
|
2809
|
+
import { minimatch } from "minimatch";
|
|
2810
|
+
function isHttpUrl(value) {
|
|
2811
|
+
return value.startsWith("http://") || value.startsWith("https://");
|
|
2812
|
+
}
|
|
2813
|
+
function toStringArray(value) {
|
|
2814
|
+
if (!value) return [];
|
|
2815
|
+
if (Array.isArray(value)) {
|
|
2816
|
+
return value.map((item) => String(item).trim()).filter(Boolean);
|
|
2817
|
+
}
|
|
2818
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
2819
|
+
}
|
|
2820
|
+
function isPlainObject(value) {
|
|
2821
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2822
|
+
}
|
|
2823
|
+
function toOverlaySourceArray(value) {
|
|
2824
|
+
if (!value) return [];
|
|
2825
|
+
if (typeof value === "string" || isPlainObject(value)) {
|
|
2826
|
+
return [value];
|
|
2827
|
+
}
|
|
2828
|
+
if (Array.isArray(value)) {
|
|
2829
|
+
return value.filter((item) => typeof item === "string" || isPlainObject(item));
|
|
2830
|
+
}
|
|
2831
|
+
return [];
|
|
2832
|
+
}
|
|
2833
|
+
function resolvePathOrUrl(candidate, baseDir) {
|
|
2834
|
+
if (isHttpUrl(candidate)) return candidate;
|
|
2835
|
+
if (path3.isAbsolute(candidate)) return candidate;
|
|
2836
|
+
if (isHttpUrl(baseDir)) {
|
|
2837
|
+
return new URL(candidate, baseDir).toString();
|
|
2838
|
+
}
|
|
2839
|
+
return path3.resolve(baseDir, candidate);
|
|
2840
|
+
}
|
|
2841
|
+
async function readTextFromPathOrUrl(location) {
|
|
2842
|
+
if (isHttpUrl(location)) {
|
|
2843
|
+
const res = await fetch(location);
|
|
2844
|
+
if (!res.ok) {
|
|
2845
|
+
throw new Error(`Failed to fetch ${location}: ${res.status} ${res.statusText}`);
|
|
2846
|
+
}
|
|
2847
|
+
return await res.text();
|
|
2848
|
+
}
|
|
2849
|
+
return await fs2.readFile(location, "utf8");
|
|
2850
|
+
}
|
|
2851
|
+
function parseJsonOrYaml(raw, location) {
|
|
2852
|
+
const ext = path3.extname(location).toLowerCase();
|
|
2853
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
2854
|
+
return YAML.load(raw);
|
|
2855
|
+
}
|
|
2856
|
+
try {
|
|
2857
|
+
return JSON.parse(raw);
|
|
2858
|
+
} catch {
|
|
2859
|
+
return YAML.load(raw);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
function parseOverlayTargetPath(pathValue) {
|
|
2863
|
+
if (!Array.isArray(pathValue) || pathValue.length === 0) return "$";
|
|
2864
|
+
return pathValue.map((segment, index) => {
|
|
2865
|
+
if (index === 0) return "$";
|
|
2866
|
+
if (typeof segment === "number") return `[${segment}]`;
|
|
2867
|
+
if (/^[A-Za-z_][\w$]*$/.test(segment)) return `.${segment}`;
|
|
2868
|
+
return `['${segment.replace(/'/g, "\\'")}']`;
|
|
2869
|
+
}).join("");
|
|
2870
|
+
}
|
|
2871
|
+
function applyOverlayActions(target, overlay) {
|
|
2872
|
+
const cloned = JSON.parse(JSON.stringify(target));
|
|
2873
|
+
if (!overlay || typeof overlay !== "object") return cloned;
|
|
2874
|
+
const actions = Array.isArray(overlay.actions) ? overlay.actions : [];
|
|
2875
|
+
if (actions.length === 0) {
|
|
2876
|
+
return deepmerge(cloned, overlay, {
|
|
2877
|
+
arrayMerge: (dst, src) => dst.concat(src)
|
|
2878
|
+
});
|
|
2879
|
+
}
|
|
2880
|
+
for (const action of actions) {
|
|
2881
|
+
const targetExpr = action?.target;
|
|
2882
|
+
if (!targetExpr || typeof targetExpr !== "string") continue;
|
|
2883
|
+
let matches = [];
|
|
2884
|
+
try {
|
|
2885
|
+
matches = JSONPath({
|
|
2886
|
+
path: targetExpr,
|
|
2887
|
+
json: cloned,
|
|
2888
|
+
resultType: "all"
|
|
2889
|
+
});
|
|
2890
|
+
} catch (error) {
|
|
2891
|
+
logger.warn(`[ApiToolExecutor] Invalid overlay target "${targetExpr}": ${error}`);
|
|
2892
|
+
continue;
|
|
2893
|
+
}
|
|
2894
|
+
if (matches.length === 0) {
|
|
2895
|
+
continue;
|
|
2896
|
+
}
|
|
2897
|
+
for (const match of matches) {
|
|
2898
|
+
if (!match || typeof match !== "object") continue;
|
|
2899
|
+
const parent = match.parent;
|
|
2900
|
+
const key = match.parentProperty;
|
|
2901
|
+
if (parent === void 0 || key === void 0) {
|
|
2902
|
+
const jsonPath = parseOverlayTargetPath(
|
|
2903
|
+
match.path || []
|
|
2904
|
+
);
|
|
2905
|
+
logger.debug(`[ApiToolExecutor] Overlay target has no writable parent: ${jsonPath}`);
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
if (action.remove === true) {
|
|
2909
|
+
if (Array.isArray(parent)) {
|
|
2910
|
+
parent.splice(Number(key), 1);
|
|
2911
|
+
} else if (parent && typeof parent === "object") {
|
|
2912
|
+
delete parent[key];
|
|
2913
|
+
}
|
|
2914
|
+
continue;
|
|
2915
|
+
}
|
|
2916
|
+
if (action.update === void 0) {
|
|
2917
|
+
continue;
|
|
2918
|
+
}
|
|
2919
|
+
const current = match.value;
|
|
2920
|
+
if (Array.isArray(current)) {
|
|
2921
|
+
current.push(action.update);
|
|
2922
|
+
} else if (current && typeof current === "object") {
|
|
2923
|
+
const merged = deepmerge(current, action.update, {
|
|
2924
|
+
arrayMerge: (dst, src) => dst.concat(src)
|
|
2925
|
+
});
|
|
2926
|
+
if (Array.isArray(parent)) {
|
|
2927
|
+
parent[Number(key)] = merged;
|
|
2928
|
+
} else {
|
|
2929
|
+
parent[key] = merged;
|
|
2930
|
+
}
|
|
2931
|
+
} else if (Array.isArray(parent)) {
|
|
2932
|
+
parent[Number(key)] = action.update;
|
|
2933
|
+
} else {
|
|
2934
|
+
parent[key] = action.update;
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
return cloned;
|
|
2939
|
+
}
|
|
2940
|
+
function isRefObject(value) {
|
|
2941
|
+
return Boolean(value && typeof value === "object" && "$ref" in value);
|
|
2942
|
+
}
|
|
2943
|
+
function isSchemaObject(value) {
|
|
2944
|
+
return Boolean(
|
|
2945
|
+
value && typeof value === "object" && !Array.isArray(value) && !isRefObject(value)
|
|
2946
|
+
);
|
|
2947
|
+
}
|
|
2948
|
+
function getSchemaFromContent(content) {
|
|
2949
|
+
if (!content || typeof content !== "object") return void 0;
|
|
2950
|
+
const entries = Object.values(content);
|
|
2951
|
+
const withSchema = entries.find((entry) => entry && typeof entry === "object" && entry.schema);
|
|
2952
|
+
return withSchema?.schema;
|
|
2953
|
+
}
|
|
2954
|
+
function mapOpenApiTypeToJsonType(schema) {
|
|
2955
|
+
if (!schema || !schema.type) return { type: "string" };
|
|
2956
|
+
const openApiType = String(schema.type);
|
|
2957
|
+
const nullable = schema.nullable === true;
|
|
2958
|
+
switch (openApiType) {
|
|
2959
|
+
case "integer":
|
|
2960
|
+
case "number":
|
|
2961
|
+
case "boolean":
|
|
2962
|
+
case "string":
|
|
2963
|
+
case "array":
|
|
2964
|
+
case "object":
|
|
2965
|
+
return { type: openApiType, format: schema.format, nullable };
|
|
2966
|
+
default:
|
|
2967
|
+
return { type: "string", format: schema.format, nullable };
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
function openApiSchemaToJsonSchema(schema) {
|
|
2971
|
+
if (!schema) {
|
|
2972
|
+
return { type: "string" };
|
|
2973
|
+
}
|
|
2974
|
+
const mapped = mapOpenApiTypeToJsonType(schema);
|
|
2975
|
+
const result = {
|
|
2976
|
+
type: mapped.nullable ? [mapped.type, "null"] : mapped.type
|
|
2977
|
+
};
|
|
2978
|
+
if (mapped.format) result.format = mapped.format;
|
|
2979
|
+
if (schema.description !== void 0) result.description = schema.description;
|
|
2980
|
+
if (schema.default !== void 0) result.default = schema.default;
|
|
2981
|
+
if (schema.enum !== void 0) result.enum = schema.enum;
|
|
2982
|
+
if (schema.example !== void 0) result.example = schema.example;
|
|
2983
|
+
if (schema.minimum !== void 0) result.minimum = schema.minimum;
|
|
2984
|
+
if (schema.maximum !== void 0) result.maximum = schema.maximum;
|
|
2985
|
+
if (schema.minLength !== void 0) result.minLength = schema.minLength;
|
|
2986
|
+
if (schema.maxLength !== void 0) result.maxLength = schema.maxLength;
|
|
2987
|
+
if (schema.pattern !== void 0) result.pattern = schema.pattern;
|
|
2988
|
+
if (schema.multipleOf !== void 0) result.multipleOf = schema.multipleOf;
|
|
2989
|
+
if (schema.minItems !== void 0) result.minItems = schema.minItems;
|
|
2990
|
+
if (schema.maxItems !== void 0) result.maxItems = schema.maxItems;
|
|
2991
|
+
if (schema.uniqueItems !== void 0) result.uniqueItems = schema.uniqueItems;
|
|
2992
|
+
if (schema.type === "object" && schema.properties && typeof schema.properties === "object") {
|
|
2993
|
+
const props = {};
|
|
2994
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
2995
|
+
if (isSchemaObject(propSchema)) {
|
|
2996
|
+
props[propName] = openApiSchemaToJsonSchema(propSchema);
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
result.properties = props;
|
|
3000
|
+
if (Array.isArray(schema.required) && schema.required.length > 0) {
|
|
3001
|
+
result.required = schema.required;
|
|
3002
|
+
}
|
|
3003
|
+
if (schema.additionalProperties === true || schema.additionalProperties === false) {
|
|
3004
|
+
result.additionalProperties = schema.additionalProperties;
|
|
3005
|
+
} else if (isSchemaObject(schema.additionalProperties)) {
|
|
3006
|
+
result.additionalProperties = openApiSchemaToJsonSchema(schema.additionalProperties);
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
if (schema.type === "array" && isSchemaObject(schema.items)) {
|
|
3010
|
+
result.items = openApiSchemaToJsonSchema(schema.items);
|
|
3011
|
+
}
|
|
3012
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
3013
|
+
if (key.startsWith("x-")) {
|
|
3014
|
+
result[key] = value;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
return result;
|
|
3018
|
+
}
|
|
3019
|
+
function shouldIncludeOperation(operationId, pathValue, method, whitelist, blacklist) {
|
|
3020
|
+
const methodPath = `${method.toUpperCase()}:${pathValue}`;
|
|
3021
|
+
const opKey = operationId || methodPath;
|
|
3022
|
+
if (whitelist.length > 0) {
|
|
3023
|
+
return whitelist.some((pattern) => minimatch(opKey, pattern) || minimatch(methodPath, pattern));
|
|
3024
|
+
}
|
|
3025
|
+
if (blacklist.length > 0) {
|
|
3026
|
+
return !blacklist.some((pattern) => minimatch(opKey, pattern) || minimatch(methodPath, pattern));
|
|
3027
|
+
}
|
|
3028
|
+
return true;
|
|
3029
|
+
}
|
|
3030
|
+
function getApiToolConfig(tool) {
|
|
3031
|
+
return {
|
|
3032
|
+
customHeaders: tool.headers || {},
|
|
3033
|
+
disableXMcp: Boolean(tool.disableXMcp ?? tool.disable_x_mcp ?? false),
|
|
3034
|
+
apiKey: tool.apiKey ?? tool.api_key,
|
|
3035
|
+
securitySchemeName: tool.securitySchemeName ?? tool.security_scheme_name,
|
|
3036
|
+
securityCredentials: tool.securityCredentials || tool.security_credentials || {},
|
|
3037
|
+
requestTimeoutMs: tool.requestTimeoutMs ?? tool.request_timeout_ms ?? tool.timeout ?? 3e4
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
function buildOutputSchema(operation) {
|
|
3041
|
+
const responses = operation.responses;
|
|
3042
|
+
if (!responses || typeof responses !== "object") return void 0;
|
|
3043
|
+
const successCode = Object.keys(responses).find((code) => code.startsWith("2"));
|
|
3044
|
+
if (!successCode) return void 0;
|
|
3045
|
+
const response = responses[successCode];
|
|
3046
|
+
if (!response || typeof response !== "object" || isRefObject(response)) return void 0;
|
|
3047
|
+
const jsonSchema = response.content?.["application/json"]?.schema || getSchemaFromContent(response.content);
|
|
3048
|
+
if (!isSchemaObject(jsonSchema)) return void 0;
|
|
3049
|
+
const mapped = openApiSchemaToJsonSchema(jsonSchema);
|
|
3050
|
+
if (response.description && typeof response.description === "string") {
|
|
3051
|
+
mapped.description = response.description;
|
|
3052
|
+
}
|
|
3053
|
+
return mapped;
|
|
3054
|
+
}
|
|
3055
|
+
function getToolName(operationId, operation, pathItem, tool) {
|
|
3056
|
+
let toolName = operationId;
|
|
3057
|
+
const opExtension = operation["x-mcp"];
|
|
3058
|
+
const pathExtension = pathItem["x-mcp"];
|
|
3059
|
+
if (opExtension && typeof opExtension === "object" && typeof opExtension.name === "string") {
|
|
3060
|
+
toolName = opExtension.name;
|
|
3061
|
+
} else if (pathExtension && typeof pathExtension === "object" && typeof pathExtension.name === "string") {
|
|
3062
|
+
toolName = pathExtension.name;
|
|
3063
|
+
}
|
|
3064
|
+
const prefix = tool.namePrefix || tool.name_prefix;
|
|
3065
|
+
if (prefix) {
|
|
3066
|
+
return `${prefix}${toolName}`;
|
|
3067
|
+
}
|
|
3068
|
+
return toolName;
|
|
3069
|
+
}
|
|
3070
|
+
function getToolDescription(operation, pathItem) {
|
|
3071
|
+
const opExtension = operation["x-mcp"];
|
|
3072
|
+
const pathExtension = pathItem["x-mcp"];
|
|
3073
|
+
if (opExtension && typeof opExtension === "object" && typeof opExtension.description === "string") {
|
|
3074
|
+
return opExtension.description;
|
|
3075
|
+
}
|
|
3076
|
+
if (pathExtension && typeof pathExtension === "object" && typeof pathExtension.description === "string") {
|
|
3077
|
+
return pathExtension.description;
|
|
3078
|
+
}
|
|
3079
|
+
return typeof operation.description === "string" && operation.description || typeof operation.summary === "string" && operation.summary || typeof pathItem.summary === "string" && pathItem.summary || "No description available.";
|
|
3080
|
+
}
|
|
3081
|
+
function isApiToolDefinition(tool) {
|
|
3082
|
+
return Boolean(tool && tool.type === "api");
|
|
3083
|
+
}
|
|
3084
|
+
async function loadOpenApiDocument(tool) {
|
|
3085
|
+
if (!tool.spec) {
|
|
3086
|
+
throw new Error(`API tool '${tool.name}' is missing required field: spec`);
|
|
3087
|
+
}
|
|
3088
|
+
const configuredBaseDir = tool.__baseDir;
|
|
3089
|
+
const baseDir = (() => {
|
|
3090
|
+
if (tool.cwd) {
|
|
3091
|
+
if (path3.isAbsolute(tool.cwd) || isHttpUrl(tool.cwd)) {
|
|
3092
|
+
return tool.cwd;
|
|
3093
|
+
}
|
|
3094
|
+
if (configuredBaseDir) {
|
|
3095
|
+
return resolvePathOrUrl(tool.cwd, configuredBaseDir);
|
|
3096
|
+
}
|
|
3097
|
+
return path3.resolve(tool.cwd);
|
|
3098
|
+
}
|
|
3099
|
+
return configuredBaseDir || process.cwd();
|
|
3100
|
+
})();
|
|
3101
|
+
const dereferenceWithContext = async (source, spec) => {
|
|
3102
|
+
try {
|
|
3103
|
+
return await SwaggerParser.dereference(spec);
|
|
3104
|
+
} catch (error) {
|
|
3105
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3106
|
+
throw new Error(
|
|
3107
|
+
`Failed to dereference OpenAPI spec for API tool '${tool.name}' from ${source}: ${errorMessage}`
|
|
3108
|
+
);
|
|
3109
|
+
}
|
|
3110
|
+
};
|
|
3111
|
+
let openapi;
|
|
3112
|
+
if (typeof tool.spec === "string") {
|
|
3113
|
+
const specLocation = resolvePathOrUrl(tool.spec, baseDir);
|
|
3114
|
+
if (isHttpUrl(specLocation)) {
|
|
3115
|
+
const raw = await readTextFromPathOrUrl(specLocation);
|
|
3116
|
+
const parsed = parseJsonOrYaml(raw, specLocation);
|
|
3117
|
+
openapi = await dereferenceWithContext(specLocation, parsed);
|
|
3118
|
+
} else {
|
|
3119
|
+
openapi = await dereferenceWithContext(specLocation, specLocation);
|
|
3120
|
+
}
|
|
3121
|
+
} else if (isPlainObject(tool.spec)) {
|
|
3122
|
+
openapi = await dereferenceWithContext("inline spec", JSON.parse(JSON.stringify(tool.spec)));
|
|
3123
|
+
} else {
|
|
3124
|
+
throw new Error(
|
|
3125
|
+
`API tool '${tool.name}' has invalid spec field (expected string path/URL or object)`
|
|
3126
|
+
);
|
|
3127
|
+
}
|
|
3128
|
+
const overlays = toOverlaySourceArray(tool.overlays);
|
|
3129
|
+
let working = openapi;
|
|
3130
|
+
for (const overlaySource of overlays) {
|
|
3131
|
+
let overlay = overlaySource;
|
|
3132
|
+
if (typeof overlaySource === "string") {
|
|
3133
|
+
const resolved = resolvePathOrUrl(overlaySource, baseDir);
|
|
3134
|
+
const raw = await readTextFromPathOrUrl(resolved);
|
|
3135
|
+
overlay = parseJsonOrYaml(raw, resolved);
|
|
3136
|
+
}
|
|
3137
|
+
working = applyOverlayActions(working, overlay);
|
|
3138
|
+
}
|
|
3139
|
+
return working;
|
|
3140
|
+
}
|
|
3141
|
+
function mapOpenApiToTools(openapi, tool) {
|
|
3142
|
+
const paths = openapi?.paths;
|
|
3143
|
+
if (!paths || typeof paths !== "object") {
|
|
3144
|
+
return [];
|
|
3145
|
+
}
|
|
3146
|
+
const targetUrl = tool.targetUrl || tool.target_url;
|
|
3147
|
+
const baseServerUrl = String(targetUrl || openapi?.servers?.[0]?.url || "").replace(/\/$/, "");
|
|
3148
|
+
if (!baseServerUrl) {
|
|
3149
|
+
throw new Error(
|
|
3150
|
+
`API tool '${tool.name}' cannot determine target API URL. Set targetUrl/target_url or provide OpenAPI servers[].`
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
const whitelist = toStringArray(tool.whitelist);
|
|
3154
|
+
const blacklist = toStringArray(tool.blacklist);
|
|
3155
|
+
const globalSecurity = Array.isArray(openapi?.security) ? openapi.security : null;
|
|
3156
|
+
const securitySchemes = openapi?.components?.securitySchemes;
|
|
3157
|
+
const mapped = [];
|
|
3158
|
+
const apiToolConfig = getApiToolConfig(tool);
|
|
3159
|
+
for (const [pathValue, pathItemRaw] of Object.entries(paths)) {
|
|
3160
|
+
const pathItem = pathItemRaw;
|
|
3161
|
+
if (!pathItem || typeof pathItem !== "object") continue;
|
|
3162
|
+
for (const [method, operationRaw] of Object.entries(pathItem)) {
|
|
3163
|
+
if (!HTTP_METHODS.has(method.toLowerCase())) continue;
|
|
3164
|
+
const operation = operationRaw;
|
|
3165
|
+
if (!operation || typeof operation !== "object") continue;
|
|
3166
|
+
const operationId = typeof operation.operationId === "string" ? String(operation.operationId) : void 0;
|
|
3167
|
+
if (!operationId) {
|
|
3168
|
+
logger.debug(
|
|
3169
|
+
`[ApiToolExecutor] Skipping ${method.toUpperCase()} ${pathValue} (missing operationId)`
|
|
3170
|
+
);
|
|
3171
|
+
continue;
|
|
3172
|
+
}
|
|
3173
|
+
if (!shouldIncludeOperation(operationId, pathValue, method, whitelist, blacklist)) {
|
|
3174
|
+
continue;
|
|
3175
|
+
}
|
|
3176
|
+
const toolName = getToolName(operationId, operation, pathItem, tool);
|
|
3177
|
+
const toolDescription = getToolDescription(operation, pathItem);
|
|
3178
|
+
const inputSchema = {
|
|
3179
|
+
type: "object",
|
|
3180
|
+
properties: {}
|
|
3181
|
+
};
|
|
3182
|
+
const requiredNames = /* @__PURE__ */ new Set();
|
|
3183
|
+
const allParameters = [
|
|
3184
|
+
...Array.isArray(pathItem.parameters) ? pathItem.parameters : [],
|
|
3185
|
+
...Array.isArray(operation.parameters) ? operation.parameters : []
|
|
3186
|
+
].filter((param) => param && typeof param === "object" && !isRefObject(param));
|
|
3187
|
+
for (const param of allParameters) {
|
|
3188
|
+
const paramObj = param;
|
|
3189
|
+
const paramName = typeof paramObj.name === "string" ? paramObj.name : "";
|
|
3190
|
+
if (!paramName) continue;
|
|
3191
|
+
if (!isSchemaObject(paramObj.schema)) continue;
|
|
3192
|
+
const schema = openApiSchemaToJsonSchema(paramObj.schema);
|
|
3193
|
+
if (typeof paramObj.description === "string") {
|
|
3194
|
+
schema.description = paramObj.description;
|
|
3195
|
+
}
|
|
3196
|
+
schema["x-parameter-location"] = paramObj.in || "query";
|
|
3197
|
+
if (paramObj.example !== void 0) schema.example = paramObj.example;
|
|
3198
|
+
if (paramObj.deprecated === true) schema.deprecated = true;
|
|
3199
|
+
inputSchema.properties[paramName] = schema;
|
|
3200
|
+
if (paramObj.required === true) requiredNames.add(paramName);
|
|
3201
|
+
}
|
|
3202
|
+
const requestBody = !isRefObject(operation.requestBody) ? operation.requestBody : void 0;
|
|
3203
|
+
if (requestBody && typeof requestBody === "object") {
|
|
3204
|
+
const reqSchema = requestBody.content?.["application/json"]?.schema || getSchemaFromContent(requestBody.content);
|
|
3205
|
+
if (isSchemaObject(reqSchema)) {
|
|
3206
|
+
const bodySchema = openApiSchemaToJsonSchema(reqSchema);
|
|
3207
|
+
if (typeof requestBody.description === "string") {
|
|
3208
|
+
bodySchema.description = requestBody.description;
|
|
3209
|
+
}
|
|
3210
|
+
const contentTypes = Object.keys(requestBody.content || {});
|
|
3211
|
+
if (contentTypes.length > 0) {
|
|
3212
|
+
bodySchema["x-content-types"] = contentTypes;
|
|
3213
|
+
}
|
|
3214
|
+
inputSchema.properties.requestBody = bodySchema;
|
|
3215
|
+
if (requestBody.required === true) {
|
|
3216
|
+
requiredNames.add("requestBody");
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
if (requiredNames.size > 0) {
|
|
3221
|
+
inputSchema.required = Array.from(requiredNames);
|
|
3222
|
+
}
|
|
3223
|
+
const securityRequirements = Array.isArray(operation.security) ? operation.security : globalSecurity;
|
|
3224
|
+
mapped.push({
|
|
3225
|
+
sourceToolName: tool.name,
|
|
3226
|
+
mcpToolDefinition: {
|
|
3227
|
+
name: toolName,
|
|
3228
|
+
description: toolDescription,
|
|
3229
|
+
inputSchema,
|
|
3230
|
+
outputSchema: buildOutputSchema(operation)
|
|
3231
|
+
},
|
|
3232
|
+
apiCallDetails: {
|
|
3233
|
+
method: method.toUpperCase(),
|
|
3234
|
+
pathTemplate: pathValue,
|
|
3235
|
+
serverUrl: baseServerUrl,
|
|
3236
|
+
parameters: allParameters,
|
|
3237
|
+
requestBody,
|
|
3238
|
+
securityRequirements,
|
|
3239
|
+
securitySchemes,
|
|
3240
|
+
apiToolConfig
|
|
3241
|
+
}
|
|
3242
|
+
});
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
return mapped;
|
|
3246
|
+
}
|
|
3247
|
+
function validateParameterValue(value, paramDef) {
|
|
3248
|
+
const schema = paramDef.schema;
|
|
3249
|
+
if (!schema || typeof schema !== "object") return null;
|
|
3250
|
+
const schemaType = schema.type;
|
|
3251
|
+
if (schemaType === "integer") {
|
|
3252
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
3253
|
+
return "must be an integer";
|
|
3254
|
+
}
|
|
3255
|
+
} else if (schemaType === "number") {
|
|
3256
|
+
if (typeof value !== "number") {
|
|
3257
|
+
return `expected number, got ${typeof value}`;
|
|
3258
|
+
}
|
|
3259
|
+
} else if (schemaType === "boolean") {
|
|
3260
|
+
if (typeof value !== "boolean") {
|
|
3261
|
+
return `expected boolean, got ${typeof value}`;
|
|
3262
|
+
}
|
|
3263
|
+
} else if (schemaType === "string") {
|
|
3264
|
+
if (typeof value !== "string") {
|
|
3265
|
+
return `expected string, got ${typeof value}`;
|
|
3266
|
+
}
|
|
3267
|
+
} else if (schemaType === "array") {
|
|
3268
|
+
if (!Array.isArray(value)) {
|
|
3269
|
+
return `expected array, got ${typeof value}`;
|
|
3270
|
+
}
|
|
3271
|
+
} else if (schemaType === "object") {
|
|
3272
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3273
|
+
return `expected object, got ${Array.isArray(value) ? "array" : typeof value}`;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
if (schema.minimum !== void 0 && typeof value === "number" && value < schema.minimum) {
|
|
3277
|
+
return `must be >= ${schema.minimum}`;
|
|
3278
|
+
}
|
|
3279
|
+
if (schema.maximum !== void 0 && typeof value === "number" && value > schema.maximum) {
|
|
3280
|
+
return `must be <= ${schema.maximum}`;
|
|
3281
|
+
}
|
|
3282
|
+
if (schema.minLength !== void 0 && typeof value === "string" && value.length < schema.minLength) {
|
|
3283
|
+
return `length must be >= ${schema.minLength}`;
|
|
3284
|
+
}
|
|
3285
|
+
if (schema.maxLength !== void 0 && typeof value === "string" && value.length > schema.maxLength) {
|
|
3286
|
+
return `length must be <= ${schema.maxLength}`;
|
|
3287
|
+
}
|
|
3288
|
+
if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
|
|
3289
|
+
return `must be one of: ${schema.enum.join(", ")}`;
|
|
3290
|
+
}
|
|
3291
|
+
return null;
|
|
3292
|
+
}
|
|
3293
|
+
function applySecurityToRequest(details, headers, queryParams) {
|
|
3294
|
+
const requirements = details.securityRequirements;
|
|
3295
|
+
if (!requirements || requirements.length === 0) {
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
const schemes = details.securitySchemes || {};
|
|
3299
|
+
const cfg = details.apiToolConfig;
|
|
3300
|
+
const tryResolveCredential = (schemeName) => {
|
|
3301
|
+
if (cfg.securityCredentials[schemeName]) {
|
|
3302
|
+
return cfg.securityCredentials[schemeName];
|
|
3303
|
+
}
|
|
3304
|
+
if (cfg.securitySchemeName && cfg.securitySchemeName !== schemeName) {
|
|
3305
|
+
return void 0;
|
|
3306
|
+
}
|
|
3307
|
+
return cfg.apiKey;
|
|
3308
|
+
};
|
|
3309
|
+
for (const requirement of requirements) {
|
|
3310
|
+
const schemeNames = Object.keys(requirement);
|
|
3311
|
+
if (schemeNames.length === 0) return;
|
|
3312
|
+
const tempHeaders = { ...headers };
|
|
3313
|
+
const tempQuery = new URLSearchParams(queryParams);
|
|
3314
|
+
let satisfied = true;
|
|
3315
|
+
for (const schemeName of schemeNames) {
|
|
3316
|
+
const scheme = schemes[schemeName];
|
|
3317
|
+
if (!scheme || typeof scheme !== "object") {
|
|
3318
|
+
satisfied = false;
|
|
3319
|
+
break;
|
|
3320
|
+
}
|
|
3321
|
+
const credential = tryResolveCredential(schemeName);
|
|
3322
|
+
if (!credential) {
|
|
3323
|
+
satisfied = false;
|
|
3324
|
+
break;
|
|
3325
|
+
}
|
|
3326
|
+
switch (scheme.type) {
|
|
3327
|
+
case "apiKey":
|
|
3328
|
+
if (scheme.in === "header") {
|
|
3329
|
+
tempHeaders[String(scheme.name)] = credential;
|
|
3330
|
+
} else if (scheme.in === "query") {
|
|
3331
|
+
tempQuery.set(String(scheme.name), credential);
|
|
3332
|
+
} else if (scheme.in === "cookie") {
|
|
3333
|
+
const cookieName = String(scheme.name);
|
|
3334
|
+
const existing = tempHeaders["Cookie"];
|
|
3335
|
+
tempHeaders["Cookie"] = existing ? `${existing}; ${cookieName}=${credential}` : `${cookieName}=${credential}`;
|
|
3336
|
+
} else {
|
|
3337
|
+
satisfied = false;
|
|
3338
|
+
}
|
|
3339
|
+
break;
|
|
3340
|
+
case "http":
|
|
3341
|
+
if (typeof scheme.scheme !== "string") {
|
|
3342
|
+
satisfied = false;
|
|
3343
|
+
break;
|
|
3344
|
+
}
|
|
3345
|
+
if (scheme.scheme.toLowerCase() === "bearer") {
|
|
3346
|
+
tempHeaders["Authorization"] = `Bearer ${credential}`;
|
|
3347
|
+
} else if (scheme.scheme.toLowerCase() === "basic") {
|
|
3348
|
+
tempHeaders["Authorization"] = `Basic ${Buffer.from(credential).toString("base64")}`;
|
|
3349
|
+
} else {
|
|
3350
|
+
satisfied = false;
|
|
3351
|
+
}
|
|
3352
|
+
break;
|
|
3353
|
+
case "oauth2":
|
|
3354
|
+
case "openIdConnect":
|
|
3355
|
+
tempHeaders["Authorization"] = `Bearer ${credential}`;
|
|
3356
|
+
break;
|
|
3357
|
+
default:
|
|
3358
|
+
satisfied = false;
|
|
3359
|
+
}
|
|
3360
|
+
if (!satisfied) {
|
|
3361
|
+
break;
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
if (satisfied) {
|
|
3365
|
+
Object.assign(headers, tempHeaders);
|
|
3366
|
+
queryParams.forEach((_value, key) => queryParams.delete(key));
|
|
3367
|
+
tempQuery.forEach((value, key) => queryParams.append(key, value));
|
|
3368
|
+
return;
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
function responseBodyToString(body) {
|
|
3373
|
+
if (typeof body === "string") return body;
|
|
3374
|
+
try {
|
|
3375
|
+
return JSON.stringify(body);
|
|
3376
|
+
} catch {
|
|
3377
|
+
return String(body);
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
async function executeMappedApiTool(mappedTool, args) {
|
|
3381
|
+
const { apiCallDetails } = mappedTool;
|
|
3382
|
+
const { method, pathTemplate, serverUrl, parameters, requestBody, apiToolConfig } = apiCallDetails;
|
|
3383
|
+
const urlPath = pathTemplate.replace(/{([^}]+)}/g, (_token, rawName) => {
|
|
3384
|
+
const value = args[rawName];
|
|
3385
|
+
if (value === void 0 || value === null) {
|
|
3386
|
+
return `{${rawName}}`;
|
|
3387
|
+
}
|
|
3388
|
+
return encodeURIComponent(String(value));
|
|
3389
|
+
});
|
|
3390
|
+
if (urlPath.includes("{") || urlPath.includes("}")) {
|
|
3391
|
+
throw new Error(`Missing required path parameters for ${method} ${pathTemplate}`);
|
|
3392
|
+
}
|
|
3393
|
+
let endpoint;
|
|
3394
|
+
try {
|
|
3395
|
+
endpoint = new URL(`${serverUrl}${urlPath}`);
|
|
3396
|
+
} catch (error) {
|
|
3397
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3398
|
+
throw new Error(
|
|
3399
|
+
`Failed to construct endpoint URL for API tool '${mappedTool.sourceToolName}' operation '${mappedTool.mcpToolDefinition.name}' (${method} ${pathTemplate}) with serverUrl '${serverUrl}': ${errorMessage}`
|
|
3400
|
+
);
|
|
3401
|
+
}
|
|
3402
|
+
const queryParams = new URLSearchParams(endpoint.search);
|
|
3403
|
+
const headers = { ...apiToolConfig.customHeaders };
|
|
3404
|
+
let requestBodyValue;
|
|
3405
|
+
for (const param of parameters) {
|
|
3406
|
+
const name = String(param.name || "");
|
|
3407
|
+
if (!name) continue;
|
|
3408
|
+
const value = args[name];
|
|
3409
|
+
if (value === void 0 || value === null) {
|
|
3410
|
+
if (param.required) {
|
|
3411
|
+
throw new Error(`Missing required parameter: ${name}`);
|
|
3412
|
+
}
|
|
3413
|
+
continue;
|
|
3414
|
+
}
|
|
3415
|
+
const validationError = validateParameterValue(value, param);
|
|
3416
|
+
if (validationError) {
|
|
3417
|
+
throw new Error(`Parameter '${name}' ${validationError}`);
|
|
3418
|
+
}
|
|
3419
|
+
switch (param.in) {
|
|
3420
|
+
case "query":
|
|
3421
|
+
if (Array.isArray(value)) {
|
|
3422
|
+
for (const item of value) {
|
|
3423
|
+
queryParams.append(name, String(item));
|
|
3424
|
+
}
|
|
3425
|
+
} else {
|
|
3426
|
+
queryParams.set(name, String(value));
|
|
3427
|
+
}
|
|
3428
|
+
break;
|
|
3429
|
+
case "header":
|
|
3430
|
+
headers[name] = String(value);
|
|
3431
|
+
break;
|
|
3432
|
+
case "path":
|
|
3433
|
+
break;
|
|
3434
|
+
case "cookie": {
|
|
3435
|
+
const existing = headers["Cookie"];
|
|
3436
|
+
headers["Cookie"] = existing ? `${existing}; ${name}=${String(value)}` : `${name}=${String(value)}`;
|
|
3437
|
+
break;
|
|
3438
|
+
}
|
|
3439
|
+
default:
|
|
3440
|
+
break;
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
if (requestBody && requestBody.required && args.requestBody === void 0) {
|
|
3444
|
+
throw new Error("Missing required requestBody parameter");
|
|
3445
|
+
}
|
|
3446
|
+
if (args.requestBody !== void 0) {
|
|
3447
|
+
requestBodyValue = args.requestBody;
|
|
3448
|
+
if (!headers["Content-Type"]) {
|
|
3449
|
+
headers["Content-Type"] = "application/json";
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
if (!apiToolConfig.disableXMcp) {
|
|
3453
|
+
headers["X-MCP"] = "1";
|
|
3454
|
+
}
|
|
3455
|
+
applySecurityToRequest(apiCallDetails, headers, queryParams);
|
|
3456
|
+
endpoint.search = queryParams.toString();
|
|
3457
|
+
const controller = new AbortController();
|
|
3458
|
+
const timeout = setTimeout(() => controller.abort(), apiToolConfig.requestTimeoutMs);
|
|
3459
|
+
try {
|
|
3460
|
+
const response = await fetch(endpoint.toString(), {
|
|
3461
|
+
method,
|
|
3462
|
+
headers,
|
|
3463
|
+
body: requestBodyValue === void 0 ? void 0 : headers["Content-Type"]?.includes("application/json") ? JSON.stringify(requestBodyValue) : String(requestBodyValue),
|
|
3464
|
+
signal: controller.signal
|
|
3465
|
+
});
|
|
3466
|
+
const raw = await response.text();
|
|
3467
|
+
let body = raw;
|
|
3468
|
+
const contentType = response.headers.get("content-type") || "";
|
|
3469
|
+
if (contentType.includes("json") && raw.trim().length > 0) {
|
|
3470
|
+
try {
|
|
3471
|
+
body = JSON.parse(raw);
|
|
3472
|
+
} catch {
|
|
3473
|
+
body = raw;
|
|
3474
|
+
}
|
|
3475
|
+
} else if (raw.trim().length > 0) {
|
|
3476
|
+
try {
|
|
3477
|
+
body = JSON.parse(raw);
|
|
3478
|
+
} catch {
|
|
3479
|
+
body = raw;
|
|
3480
|
+
}
|
|
3481
|
+
} else {
|
|
3482
|
+
body = null;
|
|
3483
|
+
}
|
|
3484
|
+
if (response.ok) {
|
|
3485
|
+
return body;
|
|
3486
|
+
}
|
|
3487
|
+
throw new Error(`API Error ${response.status}: ${responseBodyToString(body)}`);
|
|
3488
|
+
} catch (error) {
|
|
3489
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
3490
|
+
throw new Error(`API request timed out after ${apiToolConfig.requestTimeoutMs}ms`);
|
|
3491
|
+
}
|
|
3492
|
+
throw error;
|
|
3493
|
+
} finally {
|
|
3494
|
+
clearTimeout(timeout);
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
var HTTP_METHODS, ApiToolRegistry;
|
|
3498
|
+
var init_api_tool_executor = __esm({
|
|
3499
|
+
"src/providers/api-tool-executor.ts"() {
|
|
3500
|
+
"use strict";
|
|
3501
|
+
init_logger();
|
|
3502
|
+
HTTP_METHODS = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
|
|
3503
|
+
ApiToolRegistry = class {
|
|
3504
|
+
bundleCache = /* @__PURE__ */ new Map();
|
|
3505
|
+
operationCache = /* @__PURE__ */ new Map();
|
|
3506
|
+
registerMappedTools(sourceToolName, mappedTools) {
|
|
3507
|
+
this.bundleCache.set(sourceToolName, mappedTools);
|
|
3508
|
+
for (const mapped of mappedTools) {
|
|
3509
|
+
const existing = this.operationCache.get(mapped.mcpToolDefinition.name);
|
|
3510
|
+
if (existing && existing.sourceToolName !== sourceToolName) {
|
|
3511
|
+
logger.warn(
|
|
3512
|
+
`[ApiToolExecutor] Tool name collision: '${mapped.mcpToolDefinition.name}' from '${sourceToolName}' overrides '${existing.sourceToolName}'. Use namePrefix to avoid collisions.`
|
|
3513
|
+
);
|
|
3514
|
+
}
|
|
3515
|
+
this.operationCache.set(mapped.mcpToolDefinition.name, mapped);
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
async ensureBundle(sourceToolName, tool) {
|
|
3519
|
+
const cached = this.bundleCache.get(sourceToolName);
|
|
3520
|
+
if (cached) return cached;
|
|
3521
|
+
if (!isApiToolDefinition(tool)) {
|
|
3522
|
+
return [];
|
|
3523
|
+
}
|
|
3524
|
+
const openapi = await loadOpenApiDocument(tool);
|
|
3525
|
+
const mapped = mapOpenApiToTools(openapi, tool);
|
|
3526
|
+
this.registerMappedTools(sourceToolName, mapped);
|
|
3527
|
+
return mapped;
|
|
3528
|
+
}
|
|
3529
|
+
async ensureAll(toolMap) {
|
|
3530
|
+
for (const [sourceToolName, tool] of toolMap.entries()) {
|
|
3531
|
+
if (!isApiToolDefinition(tool)) continue;
|
|
3532
|
+
await this.ensureBundle(sourceToolName, tool);
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
async listMappedTools(toolMap) {
|
|
3536
|
+
await this.ensureAll(toolMap);
|
|
3537
|
+
return Array.from(this.operationCache.values());
|
|
3538
|
+
}
|
|
3539
|
+
async getMappedTool(toolName, toolMap) {
|
|
3540
|
+
const cached = this.operationCache.get(toolName);
|
|
3541
|
+
if (cached) return cached;
|
|
3542
|
+
for (const [sourceToolName, tool] of toolMap.entries()) {
|
|
3543
|
+
if (!isApiToolDefinition(tool)) continue;
|
|
3544
|
+
await this.ensureBundle(sourceToolName, tool);
|
|
3545
|
+
const resolved = this.operationCache.get(toolName);
|
|
3546
|
+
if (resolved) return resolved;
|
|
3547
|
+
}
|
|
3548
|
+
return void 0;
|
|
3549
|
+
}
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3552
|
+
});
|
|
3553
|
+
|
|
2790
3554
|
// src/providers/custom-tool-executor.ts
|
|
2791
3555
|
import Ajv from "ajv";
|
|
2792
3556
|
var CustomToolExecutor;
|
|
@@ -2797,11 +3561,13 @@ var init_custom_tool_executor = __esm({
|
|
|
2797
3561
|
init_sandbox();
|
|
2798
3562
|
init_logger();
|
|
2799
3563
|
init_command_executor();
|
|
3564
|
+
init_api_tool_executor();
|
|
2800
3565
|
CustomToolExecutor = class {
|
|
2801
3566
|
liquid;
|
|
2802
3567
|
sandbox;
|
|
2803
3568
|
tools;
|
|
2804
3569
|
ajv;
|
|
3570
|
+
apiToolRegistry;
|
|
2805
3571
|
constructor(tools) {
|
|
2806
3572
|
this.liquid = createExtendedLiquid({
|
|
2807
3573
|
cache: false,
|
|
@@ -2809,7 +3575,8 @@ var init_custom_tool_executor = __esm({
|
|
|
2809
3575
|
strictVariables: false
|
|
2810
3576
|
});
|
|
2811
3577
|
this.tools = new Map(Object.entries(tools || {}));
|
|
2812
|
-
this.ajv = new Ajv({ allErrors: true, verbose: true });
|
|
3578
|
+
this.ajv = new Ajv({ allErrors: true, verbose: true, strict: false });
|
|
3579
|
+
this.apiToolRegistry = new ApiToolRegistry();
|
|
2813
3580
|
}
|
|
2814
3581
|
/**
|
|
2815
3582
|
* Register a custom tool
|
|
@@ -2818,6 +3585,13 @@ var init_custom_tool_executor = __esm({
|
|
|
2818
3585
|
if (!tool.name) {
|
|
2819
3586
|
throw new Error("Tool must have a name");
|
|
2820
3587
|
}
|
|
3588
|
+
if (isApiToolDefinition(tool)) {
|
|
3589
|
+
if (!tool.spec) {
|
|
3590
|
+
throw new Error(`API tool '${tool.name}' must define 'spec'`);
|
|
3591
|
+
}
|
|
3592
|
+
} else if (!tool.exec) {
|
|
3593
|
+
throw new Error(`Tool '${tool.name}' must define 'exec' (or set type: 'api')`);
|
|
3594
|
+
}
|
|
2821
3595
|
this.tools.set(tool.name, tool);
|
|
2822
3596
|
}
|
|
2823
3597
|
/**
|
|
@@ -2860,12 +3634,45 @@ var init_custom_tool_executor = __esm({
|
|
|
2860
3634
|
throw new Error(`Input validation failed for tool '${tool.name}': ${errors}`);
|
|
2861
3635
|
}
|
|
2862
3636
|
}
|
|
3637
|
+
/**
|
|
3638
|
+
* Validate input against a JSON schema object
|
|
3639
|
+
*/
|
|
3640
|
+
validateInputSchema(toolName, schema, input) {
|
|
3641
|
+
if (!schema) {
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3644
|
+
const validate = this.ajv.compile(schema);
|
|
3645
|
+
const valid = validate(input);
|
|
3646
|
+
if (!valid) {
|
|
3647
|
+
const errors = validate.errors?.map((err) => {
|
|
3648
|
+
if (err.instancePath) {
|
|
3649
|
+
return `${err.instancePath}: ${err.message}`;
|
|
3650
|
+
}
|
|
3651
|
+
return err.message;
|
|
3652
|
+
}).join(", ");
|
|
3653
|
+
throw new Error(`Input validation failed for tool '${toolName}': ${errors}`);
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
2863
3656
|
/**
|
|
2864
3657
|
* Execute a custom tool
|
|
2865
3658
|
*/
|
|
2866
3659
|
async execute(toolName, args, context2) {
|
|
2867
3660
|
const tool = this.tools.get(toolName);
|
|
3661
|
+
if (tool && isApiToolDefinition(tool)) {
|
|
3662
|
+
throw new Error(
|
|
3663
|
+
`Tool '${toolName}' is an API bundle. Call one of its generated operations instead.`
|
|
3664
|
+
);
|
|
3665
|
+
}
|
|
2868
3666
|
if (!tool) {
|
|
3667
|
+
const apiMappedTool = await this.apiToolRegistry.getMappedTool(toolName, this.tools);
|
|
3668
|
+
if (apiMappedTool) {
|
|
3669
|
+
this.validateInputSchema(
|
|
3670
|
+
toolName,
|
|
3671
|
+
apiMappedTool.mcpToolDefinition.inputSchema,
|
|
3672
|
+
args
|
|
3673
|
+
);
|
|
3674
|
+
return await executeMappedApiTool(apiMappedTool, args);
|
|
3675
|
+
}
|
|
2869
3676
|
throw new Error(`Tool not found: ${toolName}`);
|
|
2870
3677
|
}
|
|
2871
3678
|
this.validateInput(tool, args);
|
|
@@ -2874,6 +3681,9 @@ var init_custom_tool_executor = __esm({
|
|
|
2874
3681
|
args,
|
|
2875
3682
|
input: args
|
|
2876
3683
|
};
|
|
3684
|
+
if (!tool.exec) {
|
|
3685
|
+
throw new Error(`Tool '${toolName}' is missing exec command`);
|
|
3686
|
+
}
|
|
2877
3687
|
const command = await this.liquid.parseAndRender(tool.exec, templateContext);
|
|
2878
3688
|
let stdin;
|
|
2879
3689
|
if (tool.stdin) {
|
|
@@ -2933,6 +3743,50 @@ var init_custom_tool_executor = __esm({
|
|
|
2933
3743
|
}
|
|
2934
3744
|
return output;
|
|
2935
3745
|
}
|
|
3746
|
+
/**
|
|
3747
|
+
* Check if a tool exists (direct or API-generated)
|
|
3748
|
+
*/
|
|
3749
|
+
async hasTool(toolName) {
|
|
3750
|
+
if (this.tools.has(toolName)) {
|
|
3751
|
+
const tool = this.tools.get(toolName);
|
|
3752
|
+
return !isApiToolDefinition(tool);
|
|
3753
|
+
}
|
|
3754
|
+
const apiMappedTool = await this.apiToolRegistry.getMappedTool(toolName, this.tools);
|
|
3755
|
+
return Boolean(apiMappedTool);
|
|
3756
|
+
}
|
|
3757
|
+
/**
|
|
3758
|
+
* Get all available tool names, including API-generated operations
|
|
3759
|
+
*/
|
|
3760
|
+
async getToolNames() {
|
|
3761
|
+
const names = [];
|
|
3762
|
+
for (const tool of this.tools.values()) {
|
|
3763
|
+
if (!isApiToolDefinition(tool)) {
|
|
3764
|
+
names.push(tool.name);
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
const apiTools = await this.apiToolRegistry.listMappedTools(this.tools);
|
|
3768
|
+
for (const mapped of apiTools) {
|
|
3769
|
+
names.push(mapped.mcpToolDefinition.name);
|
|
3770
|
+
}
|
|
3771
|
+
return names;
|
|
3772
|
+
}
|
|
3773
|
+
/**
|
|
3774
|
+
* List MCP-compatible tool definitions including API-generated operations
|
|
3775
|
+
*/
|
|
3776
|
+
async listMcpTools() {
|
|
3777
|
+
const directTools = this.getTools().filter((tool) => !isApiToolDefinition(tool)).map((tool) => ({
|
|
3778
|
+
name: tool.name,
|
|
3779
|
+
description: tool.description,
|
|
3780
|
+
inputSchema: tool.inputSchema
|
|
3781
|
+
}));
|
|
3782
|
+
const apiTools = await this.apiToolRegistry.listMappedTools(this.tools);
|
|
3783
|
+
const mappedApiTools = apiTools.map((tool) => ({
|
|
3784
|
+
name: tool.mcpToolDefinition.name,
|
|
3785
|
+
description: tool.mcpToolDefinition.description,
|
|
3786
|
+
inputSchema: tool.mcpToolDefinition.inputSchema
|
|
3787
|
+
}));
|
|
3788
|
+
return [...directTools, ...mappedApiTools];
|
|
3789
|
+
}
|
|
2936
3790
|
/**
|
|
2937
3791
|
* Apply JavaScript transform to output
|
|
2938
3792
|
*/
|
|
@@ -2960,7 +3814,7 @@ var init_custom_tool_executor = __esm({
|
|
|
2960
3814
|
* Convert custom tools to MCP tool format
|
|
2961
3815
|
*/
|
|
2962
3816
|
toMcpTools() {
|
|
2963
|
-
return Array.from(this.tools.values()).map((tool) => ({
|
|
3817
|
+
return Array.from(this.tools.values()).filter((tool) => !isApiToolDefinition(tool)).map((tool) => ({
|
|
2964
3818
|
name: tool.name,
|
|
2965
3819
|
description: tool.description,
|
|
2966
3820
|
inputSchema: tool.inputSchema,
|
|
@@ -3068,7 +3922,7 @@ async function executeWorkflowAsTool(workflowId, args, context2, argsOverrides)
|
|
|
3068
3922
|
...args,
|
|
3069
3923
|
...argsOverrides
|
|
3070
3924
|
};
|
|
3071
|
-
const { WorkflowCheckProvider: WorkflowCheckProvider2 } = await import("./workflow-check-provider-
|
|
3925
|
+
const { WorkflowCheckProvider: WorkflowCheckProvider2 } = await import("./workflow-check-provider-7SR7ZWSV.mjs");
|
|
3072
3926
|
const provider = new WorkflowCheckProvider2();
|
|
3073
3927
|
const checkConfig = {
|
|
3074
3928
|
type: "workflow",
|
|
@@ -3136,1476 +3990,6 @@ var init_workflow_tool_executor = __esm({
|
|
|
3136
3990
|
}
|
|
3137
3991
|
});
|
|
3138
3992
|
|
|
3139
|
-
// src/scheduler/store/sqlite-store.ts
|
|
3140
|
-
import path3 from "path";
|
|
3141
|
-
import fs2 from "fs";
|
|
3142
|
-
import { v4 as uuidv4 } from "uuid";
|
|
3143
|
-
function toDbRow(schedule) {
|
|
3144
|
-
return {
|
|
3145
|
-
id: schedule.id,
|
|
3146
|
-
creator_id: schedule.creatorId,
|
|
3147
|
-
creator_context: schedule.creatorContext ?? null,
|
|
3148
|
-
creator_name: schedule.creatorName ?? null,
|
|
3149
|
-
timezone: schedule.timezone,
|
|
3150
|
-
schedule_expr: schedule.schedule,
|
|
3151
|
-
run_at: schedule.runAt ?? null,
|
|
3152
|
-
is_recurring: schedule.isRecurring ? 1 : 0,
|
|
3153
|
-
original_expression: schedule.originalExpression,
|
|
3154
|
-
workflow: schedule.workflow ?? null,
|
|
3155
|
-
workflow_inputs: schedule.workflowInputs ? JSON.stringify(schedule.workflowInputs) : null,
|
|
3156
|
-
output_context: schedule.outputContext ? JSON.stringify(schedule.outputContext) : null,
|
|
3157
|
-
status: schedule.status,
|
|
3158
|
-
created_at: schedule.createdAt,
|
|
3159
|
-
last_run_at: schedule.lastRunAt ?? null,
|
|
3160
|
-
next_run_at: schedule.nextRunAt ?? null,
|
|
3161
|
-
run_count: schedule.runCount,
|
|
3162
|
-
failure_count: schedule.failureCount,
|
|
3163
|
-
last_error: schedule.lastError ?? null,
|
|
3164
|
-
previous_response: schedule.previousResponse ?? null
|
|
3165
|
-
};
|
|
3166
|
-
}
|
|
3167
|
-
function safeJsonParse(value) {
|
|
3168
|
-
if (!value) return void 0;
|
|
3169
|
-
try {
|
|
3170
|
-
return JSON.parse(value);
|
|
3171
|
-
} catch {
|
|
3172
|
-
return void 0;
|
|
3173
|
-
}
|
|
3174
|
-
}
|
|
3175
|
-
function fromDbRow(row) {
|
|
3176
|
-
return {
|
|
3177
|
-
id: row.id,
|
|
3178
|
-
creatorId: row.creator_id,
|
|
3179
|
-
creatorContext: row.creator_context ?? void 0,
|
|
3180
|
-
creatorName: row.creator_name ?? void 0,
|
|
3181
|
-
timezone: row.timezone,
|
|
3182
|
-
schedule: row.schedule_expr,
|
|
3183
|
-
runAt: row.run_at ?? void 0,
|
|
3184
|
-
isRecurring: row.is_recurring === 1,
|
|
3185
|
-
originalExpression: row.original_expression,
|
|
3186
|
-
workflow: row.workflow ?? void 0,
|
|
3187
|
-
workflowInputs: safeJsonParse(row.workflow_inputs),
|
|
3188
|
-
outputContext: safeJsonParse(row.output_context),
|
|
3189
|
-
status: row.status,
|
|
3190
|
-
createdAt: row.created_at,
|
|
3191
|
-
lastRunAt: row.last_run_at ?? void 0,
|
|
3192
|
-
nextRunAt: row.next_run_at ?? void 0,
|
|
3193
|
-
runCount: row.run_count,
|
|
3194
|
-
failureCount: row.failure_count,
|
|
3195
|
-
lastError: row.last_error ?? void 0,
|
|
3196
|
-
previousResponse: row.previous_response ?? void 0
|
|
3197
|
-
};
|
|
3198
|
-
}
|
|
3199
|
-
var SqliteStoreBackend;
|
|
3200
|
-
var init_sqlite_store = __esm({
|
|
3201
|
-
"src/scheduler/store/sqlite-store.ts"() {
|
|
3202
|
-
"use strict";
|
|
3203
|
-
init_logger();
|
|
3204
|
-
SqliteStoreBackend = class {
|
|
3205
|
-
db = null;
|
|
3206
|
-
dbPath;
|
|
3207
|
-
// In-memory locks (single-node only; SQLite doesn't support distributed locking)
|
|
3208
|
-
locks = /* @__PURE__ */ new Map();
|
|
3209
|
-
constructor(filename) {
|
|
3210
|
-
this.dbPath = filename || ".visor/schedules.db";
|
|
3211
|
-
}
|
|
3212
|
-
async initialize() {
|
|
3213
|
-
const resolvedPath = path3.resolve(process.cwd(), this.dbPath);
|
|
3214
|
-
const dir = path3.dirname(resolvedPath);
|
|
3215
|
-
fs2.mkdirSync(dir, { recursive: true });
|
|
3216
|
-
const { createRequire } = __require("module");
|
|
3217
|
-
const runtimeRequire = createRequire(__filename);
|
|
3218
|
-
let Database;
|
|
3219
|
-
try {
|
|
3220
|
-
Database = runtimeRequire("better-sqlite3");
|
|
3221
|
-
} catch (err) {
|
|
3222
|
-
const code = err?.code;
|
|
3223
|
-
if (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND") {
|
|
3224
|
-
throw new Error(
|
|
3225
|
-
"better-sqlite3 is required for SQLite schedule storage. Install it with: npm install better-sqlite3"
|
|
3226
|
-
);
|
|
3227
|
-
}
|
|
3228
|
-
throw err;
|
|
3229
|
-
}
|
|
3230
|
-
this.db = new Database(resolvedPath);
|
|
3231
|
-
this.db.pragma("journal_mode = WAL");
|
|
3232
|
-
this.migrateSchema();
|
|
3233
|
-
logger.info(`[SqliteStore] Initialized at ${this.dbPath}`);
|
|
3234
|
-
}
|
|
3235
|
-
async shutdown() {
|
|
3236
|
-
if (this.db) {
|
|
3237
|
-
this.db.close();
|
|
3238
|
-
this.db = null;
|
|
3239
|
-
}
|
|
3240
|
-
this.locks.clear();
|
|
3241
|
-
}
|
|
3242
|
-
// --- Schema Migration ---
|
|
3243
|
-
migrateSchema() {
|
|
3244
|
-
const db = this.getDb();
|
|
3245
|
-
db.exec(`
|
|
3246
|
-
CREATE TABLE IF NOT EXISTS schedules (
|
|
3247
|
-
id VARCHAR(36) PRIMARY KEY,
|
|
3248
|
-
creator_id VARCHAR(255) NOT NULL,
|
|
3249
|
-
creator_context VARCHAR(255),
|
|
3250
|
-
creator_name VARCHAR(255),
|
|
3251
|
-
timezone VARCHAR(64) NOT NULL DEFAULT 'UTC',
|
|
3252
|
-
schedule_expr VARCHAR(255),
|
|
3253
|
-
run_at BIGINT,
|
|
3254
|
-
is_recurring BOOLEAN NOT NULL,
|
|
3255
|
-
original_expression TEXT,
|
|
3256
|
-
workflow VARCHAR(255),
|
|
3257
|
-
workflow_inputs TEXT,
|
|
3258
|
-
output_context TEXT,
|
|
3259
|
-
status VARCHAR(20) NOT NULL,
|
|
3260
|
-
created_at BIGINT NOT NULL,
|
|
3261
|
-
last_run_at BIGINT,
|
|
3262
|
-
next_run_at BIGINT,
|
|
3263
|
-
run_count INTEGER NOT NULL DEFAULT 0,
|
|
3264
|
-
failure_count INTEGER NOT NULL DEFAULT 0,
|
|
3265
|
-
last_error TEXT,
|
|
3266
|
-
previous_response TEXT,
|
|
3267
|
-
claimed_by VARCHAR(255),
|
|
3268
|
-
claimed_at BIGINT,
|
|
3269
|
-
lock_token VARCHAR(36)
|
|
3270
|
-
);
|
|
3271
|
-
|
|
3272
|
-
CREATE INDEX IF NOT EXISTS idx_schedules_creator_id
|
|
3273
|
-
ON schedules(creator_id);
|
|
3274
|
-
|
|
3275
|
-
CREATE INDEX IF NOT EXISTS idx_schedules_status
|
|
3276
|
-
ON schedules(status);
|
|
3277
|
-
|
|
3278
|
-
CREATE INDEX IF NOT EXISTS idx_schedules_status_next_run
|
|
3279
|
-
ON schedules(status, next_run_at);
|
|
3280
|
-
|
|
3281
|
-
CREATE TABLE IF NOT EXISTS scheduler_locks (
|
|
3282
|
-
lock_id VARCHAR(255) PRIMARY KEY,
|
|
3283
|
-
node_id VARCHAR(255) NOT NULL,
|
|
3284
|
-
lock_token VARCHAR(36) NOT NULL,
|
|
3285
|
-
acquired_at BIGINT NOT NULL,
|
|
3286
|
-
expires_at BIGINT NOT NULL
|
|
3287
|
-
);
|
|
3288
|
-
`);
|
|
3289
|
-
}
|
|
3290
|
-
// --- Helpers ---
|
|
3291
|
-
getDb() {
|
|
3292
|
-
if (!this.db) {
|
|
3293
|
-
throw new Error("[SqliteStore] Database not initialized. Call initialize() first.");
|
|
3294
|
-
}
|
|
3295
|
-
return this.db;
|
|
3296
|
-
}
|
|
3297
|
-
// --- CRUD ---
|
|
3298
|
-
async create(schedule) {
|
|
3299
|
-
const db = this.getDb();
|
|
3300
|
-
const newSchedule = {
|
|
3301
|
-
...schedule,
|
|
3302
|
-
id: uuidv4(),
|
|
3303
|
-
createdAt: Date.now(),
|
|
3304
|
-
runCount: 0,
|
|
3305
|
-
failureCount: 0,
|
|
3306
|
-
status: "active"
|
|
3307
|
-
};
|
|
3308
|
-
const row = toDbRow(newSchedule);
|
|
3309
|
-
db.prepare(
|
|
3310
|
-
`
|
|
3311
|
-
INSERT INTO schedules (
|
|
3312
|
-
id, creator_id, creator_context, creator_name, timezone,
|
|
3313
|
-
schedule_expr, run_at, is_recurring, original_expression,
|
|
3314
|
-
workflow, workflow_inputs, output_context,
|
|
3315
|
-
status, created_at, last_run_at, next_run_at,
|
|
3316
|
-
run_count, failure_count, last_error, previous_response
|
|
3317
|
-
) VALUES (
|
|
3318
|
-
?, ?, ?, ?, ?,
|
|
3319
|
-
?, ?, ?, ?,
|
|
3320
|
-
?, ?, ?,
|
|
3321
|
-
?, ?, ?, ?,
|
|
3322
|
-
?, ?, ?, ?
|
|
3323
|
-
)
|
|
3324
|
-
`
|
|
3325
|
-
).run(
|
|
3326
|
-
row.id,
|
|
3327
|
-
row.creator_id,
|
|
3328
|
-
row.creator_context,
|
|
3329
|
-
row.creator_name,
|
|
3330
|
-
row.timezone,
|
|
3331
|
-
row.schedule_expr,
|
|
3332
|
-
row.run_at,
|
|
3333
|
-
row.is_recurring,
|
|
3334
|
-
row.original_expression,
|
|
3335
|
-
row.workflow,
|
|
3336
|
-
row.workflow_inputs,
|
|
3337
|
-
row.output_context,
|
|
3338
|
-
row.status,
|
|
3339
|
-
row.created_at,
|
|
3340
|
-
row.last_run_at,
|
|
3341
|
-
row.next_run_at,
|
|
3342
|
-
row.run_count,
|
|
3343
|
-
row.failure_count,
|
|
3344
|
-
row.last_error,
|
|
3345
|
-
row.previous_response
|
|
3346
|
-
);
|
|
3347
|
-
logger.info(
|
|
3348
|
-
`[SqliteStore] Created schedule ${newSchedule.id} for user ${newSchedule.creatorId}`
|
|
3349
|
-
);
|
|
3350
|
-
return newSchedule;
|
|
3351
|
-
}
|
|
3352
|
-
async importSchedule(schedule) {
|
|
3353
|
-
const db = this.getDb();
|
|
3354
|
-
const row = toDbRow(schedule);
|
|
3355
|
-
db.prepare(
|
|
3356
|
-
`
|
|
3357
|
-
INSERT OR IGNORE INTO schedules (
|
|
3358
|
-
id, creator_id, creator_context, creator_name, timezone,
|
|
3359
|
-
schedule_expr, run_at, is_recurring, original_expression,
|
|
3360
|
-
workflow, workflow_inputs, output_context,
|
|
3361
|
-
status, created_at, last_run_at, next_run_at,
|
|
3362
|
-
run_count, failure_count, last_error, previous_response
|
|
3363
|
-
) VALUES (
|
|
3364
|
-
?, ?, ?, ?, ?,
|
|
3365
|
-
?, ?, ?, ?,
|
|
3366
|
-
?, ?, ?,
|
|
3367
|
-
?, ?, ?, ?,
|
|
3368
|
-
?, ?, ?, ?
|
|
3369
|
-
)
|
|
3370
|
-
`
|
|
3371
|
-
).run(
|
|
3372
|
-
row.id,
|
|
3373
|
-
row.creator_id,
|
|
3374
|
-
row.creator_context,
|
|
3375
|
-
row.creator_name,
|
|
3376
|
-
row.timezone,
|
|
3377
|
-
row.schedule_expr,
|
|
3378
|
-
row.run_at,
|
|
3379
|
-
row.is_recurring,
|
|
3380
|
-
row.original_expression,
|
|
3381
|
-
row.workflow,
|
|
3382
|
-
row.workflow_inputs,
|
|
3383
|
-
row.output_context,
|
|
3384
|
-
row.status,
|
|
3385
|
-
row.created_at,
|
|
3386
|
-
row.last_run_at,
|
|
3387
|
-
row.next_run_at,
|
|
3388
|
-
row.run_count,
|
|
3389
|
-
row.failure_count,
|
|
3390
|
-
row.last_error,
|
|
3391
|
-
row.previous_response
|
|
3392
|
-
);
|
|
3393
|
-
}
|
|
3394
|
-
async get(id) {
|
|
3395
|
-
const db = this.getDb();
|
|
3396
|
-
const row = db.prepare("SELECT * FROM schedules WHERE id = ?").get(id);
|
|
3397
|
-
return row ? fromDbRow(row) : void 0;
|
|
3398
|
-
}
|
|
3399
|
-
async update(id, patch) {
|
|
3400
|
-
const db = this.getDb();
|
|
3401
|
-
const existing = db.prepare("SELECT * FROM schedules WHERE id = ?").get(id);
|
|
3402
|
-
if (!existing) return void 0;
|
|
3403
|
-
const current = fromDbRow(existing);
|
|
3404
|
-
const updated = { ...current, ...patch, id: current.id };
|
|
3405
|
-
const row = toDbRow(updated);
|
|
3406
|
-
db.prepare(
|
|
3407
|
-
`
|
|
3408
|
-
UPDATE schedules SET
|
|
3409
|
-
creator_id = ?, creator_context = ?, creator_name = ?, timezone = ?,
|
|
3410
|
-
schedule_expr = ?, run_at = ?, is_recurring = ?, original_expression = ?,
|
|
3411
|
-
workflow = ?, workflow_inputs = ?, output_context = ?,
|
|
3412
|
-
status = ?, last_run_at = ?, next_run_at = ?,
|
|
3413
|
-
run_count = ?, failure_count = ?, last_error = ?, previous_response = ?
|
|
3414
|
-
WHERE id = ?
|
|
3415
|
-
`
|
|
3416
|
-
).run(
|
|
3417
|
-
row.creator_id,
|
|
3418
|
-
row.creator_context,
|
|
3419
|
-
row.creator_name,
|
|
3420
|
-
row.timezone,
|
|
3421
|
-
row.schedule_expr,
|
|
3422
|
-
row.run_at,
|
|
3423
|
-
row.is_recurring,
|
|
3424
|
-
row.original_expression,
|
|
3425
|
-
row.workflow,
|
|
3426
|
-
row.workflow_inputs,
|
|
3427
|
-
row.output_context,
|
|
3428
|
-
row.status,
|
|
3429
|
-
row.last_run_at,
|
|
3430
|
-
row.next_run_at,
|
|
3431
|
-
row.run_count,
|
|
3432
|
-
row.failure_count,
|
|
3433
|
-
row.last_error,
|
|
3434
|
-
row.previous_response,
|
|
3435
|
-
row.id
|
|
3436
|
-
);
|
|
3437
|
-
return updated;
|
|
3438
|
-
}
|
|
3439
|
-
async delete(id) {
|
|
3440
|
-
const db = this.getDb();
|
|
3441
|
-
const result = db.prepare("DELETE FROM schedules WHERE id = ?").run(id);
|
|
3442
|
-
if (result.changes > 0) {
|
|
3443
|
-
logger.info(`[SqliteStore] Deleted schedule ${id}`);
|
|
3444
|
-
return true;
|
|
3445
|
-
}
|
|
3446
|
-
return false;
|
|
3447
|
-
}
|
|
3448
|
-
// --- Queries ---
|
|
3449
|
-
async getByCreator(creatorId) {
|
|
3450
|
-
const db = this.getDb();
|
|
3451
|
-
const rows = db.prepare("SELECT * FROM schedules WHERE creator_id = ?").all(creatorId);
|
|
3452
|
-
return rows.map(fromDbRow);
|
|
3453
|
-
}
|
|
3454
|
-
async getActiveSchedules() {
|
|
3455
|
-
const db = this.getDb();
|
|
3456
|
-
const rows = db.prepare("SELECT * FROM schedules WHERE status = 'active'").all();
|
|
3457
|
-
return rows.map(fromDbRow);
|
|
3458
|
-
}
|
|
3459
|
-
async getDueSchedules(now) {
|
|
3460
|
-
const ts = now ?? Date.now();
|
|
3461
|
-
const db = this.getDb();
|
|
3462
|
-
const rows = db.prepare(
|
|
3463
|
-
`SELECT * FROM schedules
|
|
3464
|
-
WHERE status = 'active'
|
|
3465
|
-
AND (
|
|
3466
|
-
(is_recurring = 0 AND run_at IS NOT NULL AND run_at <= ?)
|
|
3467
|
-
OR
|
|
3468
|
-
(is_recurring = 1 AND next_run_at IS NOT NULL AND next_run_at <= ?)
|
|
3469
|
-
)`
|
|
3470
|
-
).all(ts, ts);
|
|
3471
|
-
return rows.map(fromDbRow);
|
|
3472
|
-
}
|
|
3473
|
-
async findByWorkflow(creatorId, workflowName) {
|
|
3474
|
-
const db = this.getDb();
|
|
3475
|
-
const escaped = workflowName.toLowerCase().replace(/[%_\\]/g, "\\$&");
|
|
3476
|
-
const pattern = `%${escaped}%`;
|
|
3477
|
-
const rows = db.prepare(
|
|
3478
|
-
`SELECT * FROM schedules
|
|
3479
|
-
WHERE creator_id = ? AND status = 'active'
|
|
3480
|
-
AND LOWER(workflow) LIKE ? ESCAPE '\\'`
|
|
3481
|
-
).all(creatorId, pattern);
|
|
3482
|
-
return rows.map(fromDbRow);
|
|
3483
|
-
}
|
|
3484
|
-
async getAll() {
|
|
3485
|
-
const db = this.getDb();
|
|
3486
|
-
const rows = db.prepare("SELECT * FROM schedules").all();
|
|
3487
|
-
return rows.map(fromDbRow);
|
|
3488
|
-
}
|
|
3489
|
-
async getStats() {
|
|
3490
|
-
const db = this.getDb();
|
|
3491
|
-
const row = db.prepare(
|
|
3492
|
-
`SELECT
|
|
3493
|
-
COUNT(*) as total,
|
|
3494
|
-
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active,
|
|
3495
|
-
SUM(CASE WHEN status = 'paused' THEN 1 ELSE 0 END) as paused,
|
|
3496
|
-
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,
|
|
3497
|
-
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed,
|
|
3498
|
-
SUM(CASE WHEN is_recurring = 1 THEN 1 ELSE 0 END) as recurring,
|
|
3499
|
-
SUM(CASE WHEN is_recurring = 0 THEN 1 ELSE 0 END) as one_time
|
|
3500
|
-
FROM schedules`
|
|
3501
|
-
).get();
|
|
3502
|
-
return {
|
|
3503
|
-
total: row.total,
|
|
3504
|
-
active: row.active,
|
|
3505
|
-
paused: row.paused,
|
|
3506
|
-
completed: row.completed,
|
|
3507
|
-
failed: row.failed,
|
|
3508
|
-
recurring: row.recurring,
|
|
3509
|
-
oneTime: row.one_time
|
|
3510
|
-
};
|
|
3511
|
-
}
|
|
3512
|
-
async validateLimits(creatorId, isRecurring, limits) {
|
|
3513
|
-
const db = this.getDb();
|
|
3514
|
-
if (limits.maxGlobal) {
|
|
3515
|
-
const row = db.prepare("SELECT COUNT(*) as cnt FROM schedules").get();
|
|
3516
|
-
if (row.cnt >= limits.maxGlobal) {
|
|
3517
|
-
throw new Error(`Global schedule limit reached (${limits.maxGlobal})`);
|
|
3518
|
-
}
|
|
3519
|
-
}
|
|
3520
|
-
if (limits.maxPerUser) {
|
|
3521
|
-
const row = db.prepare("SELECT COUNT(*) as cnt FROM schedules WHERE creator_id = ?").get(creatorId);
|
|
3522
|
-
if (row.cnt >= limits.maxPerUser) {
|
|
3523
|
-
throw new Error(`You have reached the maximum number of schedules (${limits.maxPerUser})`);
|
|
3524
|
-
}
|
|
3525
|
-
}
|
|
3526
|
-
if (isRecurring && limits.maxRecurringPerUser) {
|
|
3527
|
-
const row = db.prepare("SELECT COUNT(*) as cnt FROM schedules WHERE creator_id = ? AND is_recurring = 1").get(creatorId);
|
|
3528
|
-
if (row.cnt >= limits.maxRecurringPerUser) {
|
|
3529
|
-
throw new Error(
|
|
3530
|
-
`You have reached the maximum number of recurring schedules (${limits.maxRecurringPerUser})`
|
|
3531
|
-
);
|
|
3532
|
-
}
|
|
3533
|
-
}
|
|
3534
|
-
}
|
|
3535
|
-
// --- HA Locking (in-memory for SQLite — single-node only) ---
|
|
3536
|
-
async tryAcquireLock(scheduleId, nodeId, ttlSeconds) {
|
|
3537
|
-
const now = Date.now();
|
|
3538
|
-
const existing = this.locks.get(scheduleId);
|
|
3539
|
-
if (existing && existing.expiresAt > now) {
|
|
3540
|
-
if (existing.nodeId === nodeId) {
|
|
3541
|
-
return existing.token;
|
|
3542
|
-
}
|
|
3543
|
-
return null;
|
|
3544
|
-
}
|
|
3545
|
-
const token = uuidv4();
|
|
3546
|
-
this.locks.set(scheduleId, {
|
|
3547
|
-
nodeId,
|
|
3548
|
-
token,
|
|
3549
|
-
expiresAt: now + ttlSeconds * 1e3
|
|
3550
|
-
});
|
|
3551
|
-
return token;
|
|
3552
|
-
}
|
|
3553
|
-
async releaseLock(scheduleId, lockToken) {
|
|
3554
|
-
const existing = this.locks.get(scheduleId);
|
|
3555
|
-
if (existing && existing.token === lockToken) {
|
|
3556
|
-
this.locks.delete(scheduleId);
|
|
3557
|
-
}
|
|
3558
|
-
}
|
|
3559
|
-
async renewLock(scheduleId, lockToken, ttlSeconds) {
|
|
3560
|
-
const existing = this.locks.get(scheduleId);
|
|
3561
|
-
if (!existing || existing.token !== lockToken) {
|
|
3562
|
-
return false;
|
|
3563
|
-
}
|
|
3564
|
-
existing.expiresAt = Date.now() + ttlSeconds * 1e3;
|
|
3565
|
-
return true;
|
|
3566
|
-
}
|
|
3567
|
-
async flush() {
|
|
3568
|
-
}
|
|
3569
|
-
};
|
|
3570
|
-
}
|
|
3571
|
-
});
|
|
3572
|
-
|
|
3573
|
-
// src/scheduler/store/index.ts
|
|
3574
|
-
async function createStoreBackend(storageConfig, haConfig) {
|
|
3575
|
-
const driver = storageConfig?.driver || "sqlite";
|
|
3576
|
-
switch (driver) {
|
|
3577
|
-
case "sqlite": {
|
|
3578
|
-
const conn = storageConfig?.connection;
|
|
3579
|
-
return new SqliteStoreBackend(conn?.filename);
|
|
3580
|
-
}
|
|
3581
|
-
case "postgresql":
|
|
3582
|
-
case "mysql":
|
|
3583
|
-
case "mssql": {
|
|
3584
|
-
try {
|
|
3585
|
-
const loaderPath = "../../enterprise/loader";
|
|
3586
|
-
const { loadEnterpriseStoreBackend } = await import(loaderPath);
|
|
3587
|
-
return await loadEnterpriseStoreBackend(driver, storageConfig, haConfig);
|
|
3588
|
-
} catch (err) {
|
|
3589
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3590
|
-
logger.error(`[StoreFactory] Failed to load enterprise ${driver} backend: ${msg}`);
|
|
3591
|
-
throw new Error(
|
|
3592
|
-
`The ${driver} schedule storage driver requires a Visor Enterprise license. Install the enterprise package or use driver: 'sqlite' (default). Original error: ${msg}`
|
|
3593
|
-
);
|
|
3594
|
-
}
|
|
3595
|
-
}
|
|
3596
|
-
default:
|
|
3597
|
-
throw new Error(`Unknown schedule storage driver: ${driver}`);
|
|
3598
|
-
}
|
|
3599
|
-
}
|
|
3600
|
-
var init_store = __esm({
|
|
3601
|
-
"src/scheduler/store/index.ts"() {
|
|
3602
|
-
"use strict";
|
|
3603
|
-
init_logger();
|
|
3604
|
-
init_sqlite_store();
|
|
3605
|
-
}
|
|
3606
|
-
});
|
|
3607
|
-
|
|
3608
|
-
// src/scheduler/store/json-migrator.ts
|
|
3609
|
-
import fs3 from "fs/promises";
|
|
3610
|
-
import path4 from "path";
|
|
3611
|
-
async function migrateJsonToBackend(jsonPath, backend) {
|
|
3612
|
-
const resolvedPath = path4.resolve(process.cwd(), jsonPath);
|
|
3613
|
-
let content;
|
|
3614
|
-
try {
|
|
3615
|
-
content = await fs3.readFile(resolvedPath, "utf-8");
|
|
3616
|
-
} catch (err) {
|
|
3617
|
-
if (err.code === "ENOENT") {
|
|
3618
|
-
return 0;
|
|
3619
|
-
}
|
|
3620
|
-
throw err;
|
|
3621
|
-
}
|
|
3622
|
-
let data;
|
|
3623
|
-
try {
|
|
3624
|
-
data = JSON.parse(content);
|
|
3625
|
-
} catch {
|
|
3626
|
-
logger.warn(`[JsonMigrator] Failed to parse ${jsonPath}, skipping migration`);
|
|
3627
|
-
return 0;
|
|
3628
|
-
}
|
|
3629
|
-
const schedules = data.schedules;
|
|
3630
|
-
if (!Array.isArray(schedules) || schedules.length === 0) {
|
|
3631
|
-
logger.debug("[JsonMigrator] No schedules to migrate");
|
|
3632
|
-
await renameToMigrated(resolvedPath);
|
|
3633
|
-
return 0;
|
|
3634
|
-
}
|
|
3635
|
-
let migrated = 0;
|
|
3636
|
-
for (const schedule of schedules) {
|
|
3637
|
-
if (!schedule.id) {
|
|
3638
|
-
logger.warn("[JsonMigrator] Skipping schedule without ID");
|
|
3639
|
-
continue;
|
|
3640
|
-
}
|
|
3641
|
-
const existing = await backend.get(schedule.id);
|
|
3642
|
-
if (existing) {
|
|
3643
|
-
logger.debug(`[JsonMigrator] Schedule ${schedule.id} already exists, skipping`);
|
|
3644
|
-
continue;
|
|
3645
|
-
}
|
|
3646
|
-
try {
|
|
3647
|
-
await backend.importSchedule(schedule);
|
|
3648
|
-
migrated++;
|
|
3649
|
-
} catch (err) {
|
|
3650
|
-
logger.warn(
|
|
3651
|
-
`[JsonMigrator] Failed to migrate schedule ${schedule.id}: ${err instanceof Error ? err.message : err}`
|
|
3652
|
-
);
|
|
3653
|
-
}
|
|
3654
|
-
}
|
|
3655
|
-
await renameToMigrated(resolvedPath);
|
|
3656
|
-
logger.info(`[JsonMigrator] Migrated ${migrated}/${schedules.length} schedules from ${jsonPath}`);
|
|
3657
|
-
return migrated;
|
|
3658
|
-
}
|
|
3659
|
-
async function renameToMigrated(resolvedPath) {
|
|
3660
|
-
const migratedPath = `${resolvedPath}.migrated`;
|
|
3661
|
-
try {
|
|
3662
|
-
await fs3.rename(resolvedPath, migratedPath);
|
|
3663
|
-
logger.info(`[JsonMigrator] Backed up ${resolvedPath} \u2192 ${migratedPath}`);
|
|
3664
|
-
} catch (err) {
|
|
3665
|
-
logger.warn(
|
|
3666
|
-
`[JsonMigrator] Failed to rename ${resolvedPath}: ${err instanceof Error ? err.message : err}`
|
|
3667
|
-
);
|
|
3668
|
-
}
|
|
3669
|
-
}
|
|
3670
|
-
var init_json_migrator = __esm({
|
|
3671
|
-
"src/scheduler/store/json-migrator.ts"() {
|
|
3672
|
-
"use strict";
|
|
3673
|
-
init_logger();
|
|
3674
|
-
}
|
|
3675
|
-
});
|
|
3676
|
-
|
|
3677
|
-
// src/scheduler/schedule-store.ts
|
|
3678
|
-
var ScheduleStore;
|
|
3679
|
-
var init_schedule_store = __esm({
|
|
3680
|
-
"src/scheduler/schedule-store.ts"() {
|
|
3681
|
-
"use strict";
|
|
3682
|
-
init_logger();
|
|
3683
|
-
init_store();
|
|
3684
|
-
init_json_migrator();
|
|
3685
|
-
ScheduleStore = class _ScheduleStore {
|
|
3686
|
-
static instance;
|
|
3687
|
-
backend = null;
|
|
3688
|
-
initialized = false;
|
|
3689
|
-
limits;
|
|
3690
|
-
config;
|
|
3691
|
-
externalBackend = null;
|
|
3692
|
-
constructor(config, limits, backend) {
|
|
3693
|
-
this.config = config || {};
|
|
3694
|
-
this.limits = {
|
|
3695
|
-
maxPerUser: limits?.maxPerUser ?? 25,
|
|
3696
|
-
maxRecurringPerUser: limits?.maxRecurringPerUser ?? 10,
|
|
3697
|
-
maxGlobal: limits?.maxGlobal ?? 1e3
|
|
3698
|
-
};
|
|
3699
|
-
if (backend) {
|
|
3700
|
-
this.externalBackend = backend;
|
|
3701
|
-
}
|
|
3702
|
-
}
|
|
3703
|
-
/**
|
|
3704
|
-
* Get singleton instance
|
|
3705
|
-
*
|
|
3706
|
-
* Note: Config and limits are only applied on first call. Subsequent calls
|
|
3707
|
-
* with different parameters will log a warning and return the existing instance.
|
|
3708
|
-
* Use createIsolated() for testing with different configurations.
|
|
3709
|
-
*/
|
|
3710
|
-
static getInstance(config, limits) {
|
|
3711
|
-
if (!_ScheduleStore.instance) {
|
|
3712
|
-
_ScheduleStore.instance = new _ScheduleStore(config, limits);
|
|
3713
|
-
} else if (config || limits) {
|
|
3714
|
-
logger.warn(
|
|
3715
|
-
"[ScheduleStore] getInstance() called with config/limits but instance already exists. Parameters ignored. Use createIsolated() for testing or resetInstance() first."
|
|
3716
|
-
);
|
|
3717
|
-
}
|
|
3718
|
-
return _ScheduleStore.instance;
|
|
3719
|
-
}
|
|
3720
|
-
/**
|
|
3721
|
-
* Create a new isolated instance (for testing)
|
|
3722
|
-
*/
|
|
3723
|
-
static createIsolated(config, limits, backend) {
|
|
3724
|
-
return new _ScheduleStore(config, limits, backend);
|
|
3725
|
-
}
|
|
3726
|
-
/**
|
|
3727
|
-
* Reset singleton instance (for testing)
|
|
3728
|
-
*/
|
|
3729
|
-
static resetInstance() {
|
|
3730
|
-
if (_ScheduleStore.instance) {
|
|
3731
|
-
if (_ScheduleStore.instance.backend) {
|
|
3732
|
-
_ScheduleStore.instance.backend.shutdown().catch(() => {
|
|
3733
|
-
});
|
|
3734
|
-
}
|
|
3735
|
-
}
|
|
3736
|
-
_ScheduleStore.instance = void 0;
|
|
3737
|
-
}
|
|
3738
|
-
/**
|
|
3739
|
-
* Initialize the store - creates backend and runs migrations
|
|
3740
|
-
*/
|
|
3741
|
-
async initialize() {
|
|
3742
|
-
if (this.initialized) {
|
|
3743
|
-
return;
|
|
3744
|
-
}
|
|
3745
|
-
if (this.externalBackend) {
|
|
3746
|
-
this.backend = this.externalBackend;
|
|
3747
|
-
} else {
|
|
3748
|
-
this.backend = await createStoreBackend(this.config.storage, this.config.ha);
|
|
3749
|
-
}
|
|
3750
|
-
await this.backend.initialize();
|
|
3751
|
-
const jsonPath = this.config.path || ".visor/schedules.json";
|
|
3752
|
-
try {
|
|
3753
|
-
await migrateJsonToBackend(jsonPath, this.backend);
|
|
3754
|
-
} catch (err) {
|
|
3755
|
-
logger.warn(
|
|
3756
|
-
`[ScheduleStore] JSON migration failed (non-fatal): ${err instanceof Error ? err.message : err}`
|
|
3757
|
-
);
|
|
3758
|
-
}
|
|
3759
|
-
this.initialized = true;
|
|
3760
|
-
}
|
|
3761
|
-
/**
|
|
3762
|
-
* Create a new schedule (async, persists immediately)
|
|
3763
|
-
*/
|
|
3764
|
-
async createAsync(schedule) {
|
|
3765
|
-
const backend = this.getBackend();
|
|
3766
|
-
await backend.validateLimits(schedule.creatorId, schedule.isRecurring, this.limits);
|
|
3767
|
-
return backend.create(schedule);
|
|
3768
|
-
}
|
|
3769
|
-
/**
|
|
3770
|
-
* Get a schedule by ID
|
|
3771
|
-
*/
|
|
3772
|
-
async getAsync(id) {
|
|
3773
|
-
return this.getBackend().get(id);
|
|
3774
|
-
}
|
|
3775
|
-
/**
|
|
3776
|
-
* Update a schedule
|
|
3777
|
-
*/
|
|
3778
|
-
async updateAsync(id, patch) {
|
|
3779
|
-
return this.getBackend().update(id, patch);
|
|
3780
|
-
}
|
|
3781
|
-
/**
|
|
3782
|
-
* Delete a schedule
|
|
3783
|
-
*/
|
|
3784
|
-
async deleteAsync(id) {
|
|
3785
|
-
return this.getBackend().delete(id);
|
|
3786
|
-
}
|
|
3787
|
-
/**
|
|
3788
|
-
* Get all schedules for a specific creator
|
|
3789
|
-
*/
|
|
3790
|
-
async getByCreatorAsync(creatorId) {
|
|
3791
|
-
return this.getBackend().getByCreator(creatorId);
|
|
3792
|
-
}
|
|
3793
|
-
/**
|
|
3794
|
-
* Get all active schedules
|
|
3795
|
-
*/
|
|
3796
|
-
async getActiveSchedulesAsync() {
|
|
3797
|
-
return this.getBackend().getActiveSchedules();
|
|
3798
|
-
}
|
|
3799
|
-
/**
|
|
3800
|
-
* Get all schedules due for execution
|
|
3801
|
-
* @param now Current timestamp in milliseconds
|
|
3802
|
-
*/
|
|
3803
|
-
async getDueSchedulesAsync(now = Date.now()) {
|
|
3804
|
-
return this.getBackend().getDueSchedules(now);
|
|
3805
|
-
}
|
|
3806
|
-
/**
|
|
3807
|
-
* Find schedules by workflow name
|
|
3808
|
-
*/
|
|
3809
|
-
async findByWorkflowAsync(creatorId, workflowName) {
|
|
3810
|
-
return this.getBackend().findByWorkflow(creatorId, workflowName);
|
|
3811
|
-
}
|
|
3812
|
-
/**
|
|
3813
|
-
* Get schedule count statistics
|
|
3814
|
-
*/
|
|
3815
|
-
async getStatsAsync() {
|
|
3816
|
-
return this.getBackend().getStats();
|
|
3817
|
-
}
|
|
3818
|
-
/**
|
|
3819
|
-
* Force immediate save (useful for shutdown)
|
|
3820
|
-
*/
|
|
3821
|
-
async flush() {
|
|
3822
|
-
if (this.backend) {
|
|
3823
|
-
await this.backend.flush();
|
|
3824
|
-
}
|
|
3825
|
-
}
|
|
3826
|
-
/**
|
|
3827
|
-
* Check if initialized
|
|
3828
|
-
*/
|
|
3829
|
-
isInitialized() {
|
|
3830
|
-
return this.initialized;
|
|
3831
|
-
}
|
|
3832
|
-
/**
|
|
3833
|
-
* Check if there are unsaved changes
|
|
3834
|
-
*/
|
|
3835
|
-
hasPendingChanges() {
|
|
3836
|
-
return false;
|
|
3837
|
-
}
|
|
3838
|
-
/**
|
|
3839
|
-
* Get all schedules
|
|
3840
|
-
*/
|
|
3841
|
-
async getAllAsync() {
|
|
3842
|
-
return this.getBackend().getAll();
|
|
3843
|
-
}
|
|
3844
|
-
/**
|
|
3845
|
-
* Get the underlying backend (for HA lock operations)
|
|
3846
|
-
*/
|
|
3847
|
-
getBackend() {
|
|
3848
|
-
if (!this.backend) {
|
|
3849
|
-
throw new Error("[ScheduleStore] Not initialized. Call initialize() first.");
|
|
3850
|
-
}
|
|
3851
|
-
return this.backend;
|
|
3852
|
-
}
|
|
3853
|
-
/**
|
|
3854
|
-
* Shut down the backend cleanly
|
|
3855
|
-
*/
|
|
3856
|
-
async shutdown() {
|
|
3857
|
-
if (this.backend) {
|
|
3858
|
-
await this.backend.shutdown();
|
|
3859
|
-
this.backend = null;
|
|
3860
|
-
}
|
|
3861
|
-
this.initialized = false;
|
|
3862
|
-
}
|
|
3863
|
-
};
|
|
3864
|
-
}
|
|
3865
|
-
});
|
|
3866
|
-
|
|
3867
|
-
// src/scheduler/schedule-parser.ts
|
|
3868
|
-
function getNextRunTime(cronExpression, _timezone = "UTC") {
|
|
3869
|
-
const parts = cronExpression.split(" ");
|
|
3870
|
-
if (parts.length !== 5) {
|
|
3871
|
-
throw new Error(`Invalid cron expression: ${cronExpression}`);
|
|
3872
|
-
}
|
|
3873
|
-
const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
|
|
3874
|
-
const now = /* @__PURE__ */ new Date();
|
|
3875
|
-
const next = new Date(now);
|
|
3876
|
-
next.setSeconds(0, 0);
|
|
3877
|
-
next.setMinutes(next.getMinutes() + 1);
|
|
3878
|
-
const maxAttempts = 365 * 24 * 60;
|
|
3879
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
3880
|
-
if (matchesCronPart(next.getMinutes(), minute) && matchesCronPart(next.getHours(), hour) && matchesCronPart(next.getDate(), dayOfMonth) && matchesCronPart(next.getMonth() + 1, month) && matchesCronPart(next.getDay(), dayOfWeek)) {
|
|
3881
|
-
return next;
|
|
3882
|
-
}
|
|
3883
|
-
next.setMinutes(next.getMinutes() + 1);
|
|
3884
|
-
}
|
|
3885
|
-
const fallback = new Date(now);
|
|
3886
|
-
fallback.setDate(fallback.getDate() + 1);
|
|
3887
|
-
fallback.setHours(parseInt(hour, 10) || 9);
|
|
3888
|
-
fallback.setMinutes(parseInt(minute, 10) || 0);
|
|
3889
|
-
fallback.setSeconds(0, 0);
|
|
3890
|
-
return fallback;
|
|
3891
|
-
}
|
|
3892
|
-
function matchesCronPart(value, cronPart) {
|
|
3893
|
-
if (cronPart === "*") return true;
|
|
3894
|
-
if (cronPart.startsWith("*/")) {
|
|
3895
|
-
const step = parseInt(cronPart.slice(2), 10);
|
|
3896
|
-
return value % step === 0;
|
|
3897
|
-
}
|
|
3898
|
-
if (cronPart.includes("-")) {
|
|
3899
|
-
const [start, end] = cronPart.split("-").map((n) => parseInt(n, 10));
|
|
3900
|
-
return value >= start && value <= end;
|
|
3901
|
-
}
|
|
3902
|
-
if (cronPart.includes(",")) {
|
|
3903
|
-
return cronPart.split(",").map((n) => parseInt(n, 10)).includes(value);
|
|
3904
|
-
}
|
|
3905
|
-
return parseInt(cronPart, 10) === value;
|
|
3906
|
-
}
|
|
3907
|
-
function isValidCronExpression(expr) {
|
|
3908
|
-
if (!expr || typeof expr !== "string") return false;
|
|
3909
|
-
const parts = expr.trim().split(/\s+/);
|
|
3910
|
-
if (parts.length !== 5) return false;
|
|
3911
|
-
const ranges = [
|
|
3912
|
-
[0, 59],
|
|
3913
|
-
// minute
|
|
3914
|
-
[0, 23],
|
|
3915
|
-
// hour
|
|
3916
|
-
[1, 31],
|
|
3917
|
-
// day of month
|
|
3918
|
-
[1, 12],
|
|
3919
|
-
// month
|
|
3920
|
-
[0, 7]
|
|
3921
|
-
// day of week (0 and 7 are Sunday)
|
|
3922
|
-
];
|
|
3923
|
-
return parts.every((part, i) => {
|
|
3924
|
-
if (part === "*") return true;
|
|
3925
|
-
if (part.startsWith("*/")) {
|
|
3926
|
-
const step = parseInt(part.slice(2), 10);
|
|
3927
|
-
return !isNaN(step) && step > 0;
|
|
3928
|
-
}
|
|
3929
|
-
if (part.includes("-")) {
|
|
3930
|
-
const [start, end] = part.split("-").map((n) => parseInt(n, 10));
|
|
3931
|
-
return !isNaN(start) && !isNaN(end) && start >= ranges[i][0] && end <= ranges[i][1];
|
|
3932
|
-
}
|
|
3933
|
-
if (part.includes(",")) {
|
|
3934
|
-
return part.split(",").every((n) => {
|
|
3935
|
-
const val2 = parseInt(n, 10);
|
|
3936
|
-
return !isNaN(val2) && val2 >= ranges[i][0] && val2 <= ranges[i][1];
|
|
3937
|
-
});
|
|
3938
|
-
}
|
|
3939
|
-
const val = parseInt(part, 10);
|
|
3940
|
-
return !isNaN(val) && val >= ranges[i][0] && val <= ranges[i][1];
|
|
3941
|
-
});
|
|
3942
|
-
}
|
|
3943
|
-
var init_schedule_parser = __esm({
|
|
3944
|
-
"src/scheduler/schedule-parser.ts"() {
|
|
3945
|
-
"use strict";
|
|
3946
|
-
}
|
|
3947
|
-
});
|
|
3948
|
-
|
|
3949
|
-
// src/scheduler/schedule-tool.ts
|
|
3950
|
-
function matchGlobPattern(pattern, value) {
|
|
3951
|
-
const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
3952
|
-
return new RegExp(`^${regexPattern}$`).test(value);
|
|
3953
|
-
}
|
|
3954
|
-
function isWorkflowAllowedByPatterns(workflow, allowedPatterns, deniedPatterns) {
|
|
3955
|
-
if (deniedPatterns && deniedPatterns.length > 0) {
|
|
3956
|
-
for (const pattern of deniedPatterns) {
|
|
3957
|
-
if (matchGlobPattern(pattern, workflow)) {
|
|
3958
|
-
return {
|
|
3959
|
-
allowed: false,
|
|
3960
|
-
reason: `Workflow "${workflow}" matches denied pattern "${pattern}"`
|
|
3961
|
-
};
|
|
3962
|
-
}
|
|
3963
|
-
}
|
|
3964
|
-
}
|
|
3965
|
-
if (allowedPatterns && allowedPatterns.length > 0) {
|
|
3966
|
-
for (const pattern of allowedPatterns) {
|
|
3967
|
-
if (matchGlobPattern(pattern, workflow)) {
|
|
3968
|
-
return { allowed: true };
|
|
3969
|
-
}
|
|
3970
|
-
}
|
|
3971
|
-
return {
|
|
3972
|
-
allowed: false,
|
|
3973
|
-
reason: `Workflow "${workflow}" does not match any allowed patterns: ${allowedPatterns.join(", ")}`
|
|
3974
|
-
};
|
|
3975
|
-
}
|
|
3976
|
-
return { allowed: true };
|
|
3977
|
-
}
|
|
3978
|
-
function checkSchedulePermissions(context2, workflow, requestedScheduleType) {
|
|
3979
|
-
const permissions = context2.permissions;
|
|
3980
|
-
const scheduleType = requestedScheduleType || context2.scheduleType || "personal";
|
|
3981
|
-
if (context2.allowedScheduleType && scheduleType !== context2.allowedScheduleType) {
|
|
3982
|
-
const contextNames = {
|
|
3983
|
-
personal: "a direct message (DM)",
|
|
3984
|
-
channel: "a channel",
|
|
3985
|
-
dm: "a group DM"
|
|
3986
|
-
};
|
|
3987
|
-
const targetNames = {
|
|
3988
|
-
personal: "personal",
|
|
3989
|
-
channel: "channel",
|
|
3990
|
-
dm: "group"
|
|
3991
|
-
};
|
|
3992
|
-
return {
|
|
3993
|
-
allowed: false,
|
|
3994
|
-
reason: `From ${contextNames[context2.allowedScheduleType]}, you can only create ${targetNames[context2.allowedScheduleType]} schedules. To create a ${targetNames[scheduleType]} schedule, please use the appropriate context.`
|
|
3995
|
-
};
|
|
3996
|
-
}
|
|
3997
|
-
if (!permissions) {
|
|
3998
|
-
return { allowed: true };
|
|
3999
|
-
}
|
|
4000
|
-
switch (scheduleType) {
|
|
4001
|
-
case "personal":
|
|
4002
|
-
if (permissions.allowPersonal === false) {
|
|
4003
|
-
return {
|
|
4004
|
-
allowed: false,
|
|
4005
|
-
reason: "Personal schedules are not allowed in this configuration"
|
|
4006
|
-
};
|
|
4007
|
-
}
|
|
4008
|
-
break;
|
|
4009
|
-
case "channel":
|
|
4010
|
-
if (permissions.allowChannel === false) {
|
|
4011
|
-
return {
|
|
4012
|
-
allowed: false,
|
|
4013
|
-
reason: "Channel schedules are not allowed in this configuration"
|
|
4014
|
-
};
|
|
4015
|
-
}
|
|
4016
|
-
break;
|
|
4017
|
-
case "dm":
|
|
4018
|
-
if (permissions.allowDm === false) {
|
|
4019
|
-
return {
|
|
4020
|
-
allowed: false,
|
|
4021
|
-
reason: "DM schedules are not allowed in this configuration"
|
|
4022
|
-
};
|
|
4023
|
-
}
|
|
4024
|
-
break;
|
|
4025
|
-
}
|
|
4026
|
-
return isWorkflowAllowedByPatterns(
|
|
4027
|
-
workflow,
|
|
4028
|
-
permissions.allowedWorkflows,
|
|
4029
|
-
permissions.deniedWorkflows
|
|
4030
|
-
);
|
|
4031
|
-
}
|
|
4032
|
-
function formatSchedule(schedule) {
|
|
4033
|
-
const time = schedule.isRecurring ? schedule.originalExpression : new Date(schedule.runAt).toLocaleString();
|
|
4034
|
-
const status = schedule.status !== "active" ? ` (${schedule.status})` : "";
|
|
4035
|
-
const displayName = schedule.workflow || schedule.workflowInputs?.text || "scheduled message";
|
|
4036
|
-
const truncatedName = displayName.length > 30 ? displayName.substring(0, 27) + "..." : displayName;
|
|
4037
|
-
const output = schedule.outputContext?.type || "none";
|
|
4038
|
-
return `\`${schedule.id.substring(0, 8)}\` - "${truncatedName}" - ${time} (\u2192 ${output})${status}`;
|
|
4039
|
-
}
|
|
4040
|
-
function formatCreateConfirmation(schedule) {
|
|
4041
|
-
const outputDesc = schedule.outputContext?.type ? `${schedule.outputContext.type}${schedule.outputContext.target ? `:${schedule.outputContext.target}` : ""}` : "none";
|
|
4042
|
-
const displayName = schedule.workflow || schedule.workflowInputs?.text || "scheduled message";
|
|
4043
|
-
if (schedule.isRecurring) {
|
|
4044
|
-
const nextRun = schedule.nextRunAt ? new Date(schedule.nextRunAt).toLocaleString("en-US", {
|
|
4045
|
-
weekday: "long",
|
|
4046
|
-
month: "short",
|
|
4047
|
-
day: "numeric",
|
|
4048
|
-
hour: "numeric",
|
|
4049
|
-
minute: "2-digit"
|
|
4050
|
-
}) : "calculating...";
|
|
4051
|
-
return `**Schedule created!**
|
|
4052
|
-
|
|
4053
|
-
**${schedule.workflow ? "Workflow" : "Reminder"}**: ${displayName}
|
|
4054
|
-
**When**: ${schedule.originalExpression}
|
|
4055
|
-
**Output**: ${outputDesc}
|
|
4056
|
-
**Next run**: ${nextRun}
|
|
4057
|
-
|
|
4058
|
-
ID: \`${schedule.id.substring(0, 8)}\``;
|
|
4059
|
-
} else {
|
|
4060
|
-
const when = new Date(schedule.runAt).toLocaleString("en-US", {
|
|
4061
|
-
weekday: "long",
|
|
4062
|
-
month: "short",
|
|
4063
|
-
day: "numeric",
|
|
4064
|
-
hour: "numeric",
|
|
4065
|
-
minute: "2-digit"
|
|
4066
|
-
});
|
|
4067
|
-
return `**Schedule created!**
|
|
4068
|
-
|
|
4069
|
-
**${schedule.workflow ? "Workflow" : "Reminder"}**: ${displayName}
|
|
4070
|
-
**When**: ${when}
|
|
4071
|
-
**Output**: ${outputDesc}
|
|
4072
|
-
|
|
4073
|
-
ID: \`${schedule.id.substring(0, 8)}\``;
|
|
4074
|
-
}
|
|
4075
|
-
}
|
|
4076
|
-
function formatScheduleList(schedules) {
|
|
4077
|
-
if (schedules.length === 0) {
|
|
4078
|
-
return `You don't have any active schedules.
|
|
4079
|
-
|
|
4080
|
-
To create one: "remind me every Monday at 9am to check PRs" or "schedule %daily-report every Monday at 9am"`;
|
|
4081
|
-
}
|
|
4082
|
-
const lines = schedules.map((s, i) => `${i + 1}. ${formatSchedule(s)}`);
|
|
4083
|
-
return `**Your active schedules:**
|
|
4084
|
-
|
|
4085
|
-
${lines.join("\n")}
|
|
4086
|
-
|
|
4087
|
-
To cancel: "cancel schedule <id>"
|
|
4088
|
-
To pause: "pause schedule <id>"`;
|
|
4089
|
-
}
|
|
4090
|
-
async function handleScheduleAction(args, context2) {
|
|
4091
|
-
const store = ScheduleStore.getInstance();
|
|
4092
|
-
if (!store.isInitialized()) {
|
|
4093
|
-
await store.initialize();
|
|
4094
|
-
}
|
|
4095
|
-
switch (args.action) {
|
|
4096
|
-
case "create":
|
|
4097
|
-
return handleCreate(args, context2, store);
|
|
4098
|
-
case "list":
|
|
4099
|
-
return handleList(context2, store);
|
|
4100
|
-
case "cancel":
|
|
4101
|
-
return handleCancel(args, context2, store);
|
|
4102
|
-
case "pause":
|
|
4103
|
-
return handlePauseResume(args, context2, store, "paused");
|
|
4104
|
-
case "resume":
|
|
4105
|
-
return handlePauseResume(args, context2, store, "active");
|
|
4106
|
-
default:
|
|
4107
|
-
return {
|
|
4108
|
-
success: false,
|
|
4109
|
-
message: `Unknown action: ${args.action}`,
|
|
4110
|
-
error: `Supported actions: create, list, cancel, pause, resume`
|
|
4111
|
-
};
|
|
4112
|
-
}
|
|
4113
|
-
}
|
|
4114
|
-
async function handleCreate(args, context2, store) {
|
|
4115
|
-
if (!args.reminder_text && !args.workflow) {
|
|
4116
|
-
return {
|
|
4117
|
-
success: false,
|
|
4118
|
-
message: "Missing reminder content",
|
|
4119
|
-
error: "Please specify either reminder_text (what to say) or workflow (what to run)"
|
|
4120
|
-
};
|
|
4121
|
-
}
|
|
4122
|
-
if (!args.cron && !args.run_at) {
|
|
4123
|
-
return {
|
|
4124
|
-
success: false,
|
|
4125
|
-
message: "Missing schedule timing",
|
|
4126
|
-
error: 'Please specify either cron (for recurring, e.g., "* * * * *") or run_at (ISO timestamp for one-time)'
|
|
4127
|
-
};
|
|
4128
|
-
}
|
|
4129
|
-
if (args.cron && !isValidCronExpression(args.cron)) {
|
|
4130
|
-
return {
|
|
4131
|
-
success: false,
|
|
4132
|
-
message: "Invalid cron expression",
|
|
4133
|
-
error: `"${args.cron}" is not a valid cron expression. Format: "minute hour day-of-month month day-of-week"`
|
|
4134
|
-
};
|
|
4135
|
-
}
|
|
4136
|
-
let runAtTimestamp;
|
|
4137
|
-
if (args.run_at) {
|
|
4138
|
-
const parsed = new Date(args.run_at);
|
|
4139
|
-
if (isNaN(parsed.getTime())) {
|
|
4140
|
-
return {
|
|
4141
|
-
success: false,
|
|
4142
|
-
message: "Invalid run_at timestamp",
|
|
4143
|
-
error: `"${args.run_at}" is not a valid ISO 8601 timestamp`
|
|
4144
|
-
};
|
|
4145
|
-
}
|
|
4146
|
-
if (parsed.getTime() <= Date.now()) {
|
|
4147
|
-
return {
|
|
4148
|
-
success: false,
|
|
4149
|
-
message: "run_at must be in the future",
|
|
4150
|
-
error: "Cannot schedule a reminder in the past"
|
|
4151
|
-
};
|
|
4152
|
-
}
|
|
4153
|
-
runAtTimestamp = parsed.getTime();
|
|
4154
|
-
}
|
|
4155
|
-
if (args.target_type && !args.target_id) {
|
|
4156
|
-
return {
|
|
4157
|
-
success: false,
|
|
4158
|
-
message: "Missing target_id",
|
|
4159
|
-
error: `target_type "${args.target_type}" requires a target_id (channel ID, user ID, or thread_ts)`
|
|
4160
|
-
};
|
|
4161
|
-
}
|
|
4162
|
-
let scheduleType = "personal";
|
|
4163
|
-
if (args.target_type === "channel") {
|
|
4164
|
-
scheduleType = "channel";
|
|
4165
|
-
} else if (args.target_type === "user") {
|
|
4166
|
-
scheduleType = "dm";
|
|
4167
|
-
}
|
|
4168
|
-
const workflowName = args.workflow || "reminder";
|
|
4169
|
-
const permissionCheck = checkSchedulePermissions(context2, workflowName, scheduleType);
|
|
4170
|
-
if (!permissionCheck.allowed) {
|
|
4171
|
-
logger.warn(
|
|
4172
|
-
`[ScheduleTool] Permission denied for user ${context2.userId}: ${permissionCheck.reason}`
|
|
4173
|
-
);
|
|
4174
|
-
return {
|
|
4175
|
-
success: false,
|
|
4176
|
-
message: "Permission denied",
|
|
4177
|
-
error: permissionCheck.reason || "You do not have permission to create this schedule"
|
|
4178
|
-
};
|
|
4179
|
-
}
|
|
4180
|
-
if (args.workflow && context2.availableWorkflows && !context2.availableWorkflows.includes(args.workflow)) {
|
|
4181
|
-
return {
|
|
4182
|
-
success: false,
|
|
4183
|
-
message: `Workflow "${args.workflow}" not found`,
|
|
4184
|
-
error: `Available workflows: ${context2.availableWorkflows.slice(0, 5).join(", ")}${context2.availableWorkflows.length > 5 ? "..." : ""}`
|
|
4185
|
-
};
|
|
4186
|
-
}
|
|
4187
|
-
try {
|
|
4188
|
-
const timezone = context2.timezone || "UTC";
|
|
4189
|
-
const isRecurring = args.is_recurring === true || !!args.cron;
|
|
4190
|
-
let outputContext;
|
|
4191
|
-
if (args.target_type && args.target_id) {
|
|
4192
|
-
outputContext = {
|
|
4193
|
-
type: "slack",
|
|
4194
|
-
// Currently only Slack supported
|
|
4195
|
-
target: args.target_id,
|
|
4196
|
-
// Channel ID (C... or D...)
|
|
4197
|
-
threadId: args.thread_ts,
|
|
4198
|
-
// Thread timestamp for replies
|
|
4199
|
-
metadata: {
|
|
4200
|
-
targetType: args.target_type,
|
|
4201
|
-
reminderText: args.reminder_text
|
|
4202
|
-
}
|
|
4203
|
-
};
|
|
4204
|
-
}
|
|
4205
|
-
let nextRunAt;
|
|
4206
|
-
if (isRecurring && args.cron) {
|
|
4207
|
-
nextRunAt = getNextRunTime(args.cron, timezone).getTime();
|
|
4208
|
-
} else if (runAtTimestamp) {
|
|
4209
|
-
nextRunAt = runAtTimestamp;
|
|
4210
|
-
}
|
|
4211
|
-
const schedule = await store.createAsync({
|
|
4212
|
-
creatorId: context2.userId,
|
|
4213
|
-
creatorContext: context2.contextType,
|
|
4214
|
-
creatorName: context2.userName,
|
|
4215
|
-
timezone,
|
|
4216
|
-
schedule: args.cron || "",
|
|
4217
|
-
runAt: runAtTimestamp,
|
|
4218
|
-
isRecurring,
|
|
4219
|
-
originalExpression: args.original_expression || args.cron || args.run_at || "",
|
|
4220
|
-
workflow: args.workflow,
|
|
4221
|
-
// Only set if explicitly provided
|
|
4222
|
-
workflowInputs: args.workflow_inputs || (args.reminder_text ? { text: args.reminder_text } : void 0),
|
|
4223
|
-
outputContext,
|
|
4224
|
-
nextRunAt
|
|
4225
|
-
});
|
|
4226
|
-
const displayText = args.reminder_text || args.workflow || "scheduled task";
|
|
4227
|
-
logger.info(
|
|
4228
|
-
`[ScheduleTool] Created schedule ${schedule.id} for user ${context2.userId}: "${displayText}"`
|
|
4229
|
-
);
|
|
4230
|
-
return {
|
|
4231
|
-
success: true,
|
|
4232
|
-
message: formatCreateConfirmation(schedule),
|
|
4233
|
-
schedule
|
|
4234
|
-
};
|
|
4235
|
-
} catch (error) {
|
|
4236
|
-
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
4237
|
-
logger.warn(`[ScheduleTool] Failed to create schedule: ${errorMsg}`);
|
|
4238
|
-
return {
|
|
4239
|
-
success: false,
|
|
4240
|
-
message: `Failed to create schedule: ${errorMsg}`,
|
|
4241
|
-
error: errorMsg
|
|
4242
|
-
};
|
|
4243
|
-
}
|
|
4244
|
-
}
|
|
4245
|
-
async function handleList(context2, store) {
|
|
4246
|
-
const allUserSchedules = await store.getByCreatorAsync(context2.userId);
|
|
4247
|
-
const schedules = allUserSchedules.filter((s) => s.status !== "completed");
|
|
4248
|
-
let filteredSchedules = schedules;
|
|
4249
|
-
if (context2.allowedScheduleType) {
|
|
4250
|
-
filteredSchedules = schedules.filter((s) => {
|
|
4251
|
-
const scheduleOutputType = s.outputContext?.type;
|
|
4252
|
-
if (!scheduleOutputType || scheduleOutputType === "none") {
|
|
4253
|
-
return context2.allowedScheduleType === "personal";
|
|
4254
|
-
}
|
|
4255
|
-
if (scheduleOutputType === "slack") {
|
|
4256
|
-
const target = s.outputContext?.target || "";
|
|
4257
|
-
if (target.startsWith("#") || target.match(/^C[A-Z0-9]+$/)) {
|
|
4258
|
-
return context2.allowedScheduleType === "channel";
|
|
4259
|
-
}
|
|
4260
|
-
if (target.startsWith("@") || target.match(/^U[A-Z0-9]+$/)) {
|
|
4261
|
-
return context2.allowedScheduleType === "dm";
|
|
4262
|
-
}
|
|
4263
|
-
}
|
|
4264
|
-
return context2.allowedScheduleType === "personal";
|
|
4265
|
-
});
|
|
4266
|
-
}
|
|
4267
|
-
return {
|
|
4268
|
-
success: true,
|
|
4269
|
-
message: formatScheduleList(filteredSchedules),
|
|
4270
|
-
schedules: filteredSchedules
|
|
4271
|
-
};
|
|
4272
|
-
}
|
|
4273
|
-
async function handleCancel(args, context2, store) {
|
|
4274
|
-
let schedule;
|
|
4275
|
-
if (args.schedule_id) {
|
|
4276
|
-
const userSchedules = await store.getByCreatorAsync(context2.userId);
|
|
4277
|
-
schedule = userSchedules.find((s) => s.id === args.schedule_id);
|
|
4278
|
-
if (!schedule) {
|
|
4279
|
-
schedule = userSchedules.find((s) => s.id.startsWith(args.schedule_id));
|
|
4280
|
-
}
|
|
4281
|
-
}
|
|
4282
|
-
if (!schedule) {
|
|
4283
|
-
return {
|
|
4284
|
-
success: false,
|
|
4285
|
-
message: "Schedule not found",
|
|
4286
|
-
error: `Could not find schedule with ID "${args.schedule_id}" in your schedules. Use "list my schedules" to see your schedules.`
|
|
4287
|
-
};
|
|
4288
|
-
}
|
|
4289
|
-
if (schedule.creatorId !== context2.userId) {
|
|
4290
|
-
logger.warn(
|
|
4291
|
-
`[ScheduleTool] Attempted cross-user schedule cancellation: ${context2.userId} tried to cancel ${schedule.id} owned by ${schedule.creatorId}`
|
|
4292
|
-
);
|
|
4293
|
-
return {
|
|
4294
|
-
success: false,
|
|
4295
|
-
message: "Not your schedule",
|
|
4296
|
-
error: "You can only cancel your own schedules."
|
|
4297
|
-
};
|
|
4298
|
-
}
|
|
4299
|
-
await store.deleteAsync(schedule.id);
|
|
4300
|
-
logger.info(`[ScheduleTool] Cancelled schedule ${schedule.id} for user ${context2.userId}`);
|
|
4301
|
-
return {
|
|
4302
|
-
success: true,
|
|
4303
|
-
message: `**Schedule cancelled!**
|
|
4304
|
-
|
|
4305
|
-
Was: "${schedule.workflow}" scheduled for ${schedule.originalExpression}`
|
|
4306
|
-
};
|
|
4307
|
-
}
|
|
4308
|
-
async function handlePauseResume(args, context2, store, newStatus) {
|
|
4309
|
-
if (!args.schedule_id) {
|
|
4310
|
-
return {
|
|
4311
|
-
success: false,
|
|
4312
|
-
message: "Missing schedule ID",
|
|
4313
|
-
error: "Please specify which schedule to pause/resume."
|
|
4314
|
-
};
|
|
4315
|
-
}
|
|
4316
|
-
const userSchedules = await store.getByCreatorAsync(context2.userId);
|
|
4317
|
-
let schedule = userSchedules.find((s) => s.id === args.schedule_id);
|
|
4318
|
-
if (!schedule) {
|
|
4319
|
-
schedule = userSchedules.find((s) => s.id.startsWith(args.schedule_id));
|
|
4320
|
-
}
|
|
4321
|
-
if (!schedule) {
|
|
4322
|
-
return {
|
|
4323
|
-
success: false,
|
|
4324
|
-
message: "Schedule not found",
|
|
4325
|
-
error: `Could not find schedule with ID "${args.schedule_id}" in your schedules.`
|
|
4326
|
-
};
|
|
4327
|
-
}
|
|
4328
|
-
if (schedule.creatorId !== context2.userId) {
|
|
4329
|
-
logger.warn(
|
|
4330
|
-
`[ScheduleTool] Attempted cross-user schedule modification: ${context2.userId} tried to modify ${schedule.id} owned by ${schedule.creatorId}`
|
|
4331
|
-
);
|
|
4332
|
-
return {
|
|
4333
|
-
success: false,
|
|
4334
|
-
message: "Not your schedule",
|
|
4335
|
-
error: "You can only modify your own schedules."
|
|
4336
|
-
};
|
|
4337
|
-
}
|
|
4338
|
-
const updated = await store.updateAsync(schedule.id, { status: newStatus });
|
|
4339
|
-
const action = newStatus === "paused" ? "paused" : "resumed";
|
|
4340
|
-
logger.info(`[ScheduleTool] ${action} schedule ${schedule.id} for user ${context2.userId}`);
|
|
4341
|
-
return {
|
|
4342
|
-
success: true,
|
|
4343
|
-
message: `**Schedule ${action}!**
|
|
4344
|
-
|
|
4345
|
-
"${schedule.workflow}" - ${schedule.originalExpression}`,
|
|
4346
|
-
schedule: updated
|
|
4347
|
-
};
|
|
4348
|
-
}
|
|
4349
|
-
function getScheduleToolDefinition() {
|
|
4350
|
-
return {
|
|
4351
|
-
name: "schedule",
|
|
4352
|
-
description: `Schedule, list, and manage reminders or workflow executions.
|
|
4353
|
-
|
|
4354
|
-
YOU (the AI) must extract and structure all scheduling parameters. Do NOT pass natural language time expressions - convert them to cron or ISO timestamps.
|
|
4355
|
-
|
|
4356
|
-
CRITICAL WORKFLOW RULE:
|
|
4357
|
-
- To schedule a WORKFLOW, the user MUST use a '%' prefix (e.g., "schedule %my-workflow daily").
|
|
4358
|
-
- If the '%' prefix is present, extract the word following it as the 'workflow' parameter (without the '%').
|
|
4359
|
-
- If the '%' prefix is NOT present, the request is a simple text reminder. The ENTIRE user request (excluding the schedule expression) MUST be placed in the 'reminder_text' parameter.
|
|
4360
|
-
- DO NOT guess or infer a workflow name from a user's request without the '%' prefix.
|
|
4361
|
-
|
|
4362
|
-
ACTIONS:
|
|
4363
|
-
- create: Schedule a new reminder or workflow
|
|
4364
|
-
- list: Show user's active schedules
|
|
4365
|
-
- cancel: Remove a schedule by ID
|
|
4366
|
-
- pause/resume: Temporarily disable/enable a schedule
|
|
4367
|
-
|
|
4368
|
-
FOR CREATE ACTION - Extract these from user's request:
|
|
4369
|
-
1. WHAT:
|
|
4370
|
-
- If user says "schedule %some-workflow ...", populate 'workflow' with "some-workflow".
|
|
4371
|
-
- Otherwise, populate 'reminder_text' with the user's full request text.
|
|
4372
|
-
2. WHERE: Use the CURRENT channel from context
|
|
4373
|
-
- target_id: The channel ID from context (C... for channels, D... for DMs)
|
|
4374
|
-
- target_type: "channel" for public/private channels, "dm" for direct messages
|
|
4375
|
-
- ONLY use target_type="thread" with thread_ts if user is INSIDE a thread
|
|
4376
|
-
- When NOT in a thread, reminders post as NEW messages (not thread replies)
|
|
4377
|
-
3. WHEN: Either cron (for recurring) OR run_at (ISO 8601 for one-time)
|
|
4378
|
-
- Recurring: Generate cron expression (minute hour day-of-month month day-of-week)
|
|
4379
|
-
- One-time: Generate ISO 8601 timestamp
|
|
4380
|
-
|
|
4381
|
-
CRON EXAMPLES:
|
|
4382
|
-
- "every minute" \u2192 cron: "* * * * *"
|
|
4383
|
-
- "every hour" \u2192 cron: "0 * * * *"
|
|
4384
|
-
- "every day at 9am" \u2192 cron: "0 9 * * *"
|
|
4385
|
-
- "every Monday at 9am" \u2192 cron: "0 9 * * 1"
|
|
4386
|
-
- "weekdays at 8:30am" \u2192 cron: "30 8 * * 1-5"
|
|
4387
|
-
- "every 5 minutes" \u2192 cron: "*/5 * * * *"
|
|
4388
|
-
|
|
4389
|
-
ONE-TIME EXAMPLES:
|
|
4390
|
-
- "in 2 hours" \u2192 run_at: "<ISO timestamp 2 hours from now>"
|
|
4391
|
-
- "tomorrow at 3pm" \u2192 run_at: "2026-02-08T15:00:00Z"
|
|
4392
|
-
|
|
4393
|
-
USAGE EXAMPLES:
|
|
4394
|
-
|
|
4395
|
-
User in DM: "remind me to check builds every day at 9am"
|
|
4396
|
-
\u2192 {
|
|
4397
|
-
"action": "create",
|
|
4398
|
-
"reminder_text": "check builds",
|
|
4399
|
-
"is_recurring": true,
|
|
4400
|
-
"cron": "0 9 * * *",
|
|
4401
|
-
"target_type": "dm",
|
|
4402
|
-
"target_id": "<DM channel ID from context, e.g., D09SZABNLG3>",
|
|
4403
|
-
"original_expression": "every day at 9am"
|
|
4404
|
-
}
|
|
4405
|
-
|
|
4406
|
-
User in #security channel: "schedule %security-scan every Monday at 10am"
|
|
4407
|
-
\u2192 {
|
|
4408
|
-
"action": "create",
|
|
4409
|
-
"workflow": "security-scan",
|
|
4410
|
-
"is_recurring": true,
|
|
4411
|
-
"cron": "0 10 * * 1",
|
|
4412
|
-
"target_type": "channel",
|
|
4413
|
-
"target_id": "<channel ID from context, e.g., C05ABC123>",
|
|
4414
|
-
"original_expression": "every Monday at 10am"
|
|
4415
|
-
}
|
|
4416
|
-
|
|
4417
|
-
User in #security channel: "run security-scan every Monday at 10am" (NO % prefix!)
|
|
4418
|
-
\u2192 {
|
|
4419
|
-
"action": "create",
|
|
4420
|
-
"reminder_text": "run security-scan every Monday at 10am",
|
|
4421
|
-
"is_recurring": true,
|
|
4422
|
-
"cron": "0 10 * * 1",
|
|
4423
|
-
"target_type": "channel",
|
|
4424
|
-
"target_id": "<channel ID from context, e.g., C05ABC123>",
|
|
4425
|
-
"original_expression": "every Monday at 10am"
|
|
4426
|
-
}
|
|
4427
|
-
|
|
4428
|
-
User in DM: "remind me in 2 hours to review the PR"
|
|
4429
|
-
\u2192 {
|
|
4430
|
-
"action": "create",
|
|
4431
|
-
"reminder_text": "review the PR",
|
|
4432
|
-
"is_recurring": false,
|
|
4433
|
-
"run_at": "2026-02-07T18:00:00Z",
|
|
4434
|
-
"target_type": "dm",
|
|
4435
|
-
"target_id": "<DM channel ID from context>",
|
|
4436
|
-
"original_expression": "in 2 hours"
|
|
4437
|
-
}
|
|
4438
|
-
|
|
4439
|
-
User inside a thread: "remind me about this tomorrow"
|
|
4440
|
-
\u2192 {
|
|
4441
|
-
"action": "create",
|
|
4442
|
-
"reminder_text": "Check this thread",
|
|
4443
|
-
"is_recurring": false,
|
|
4444
|
-
"run_at": "2026-02-08T09:00:00Z",
|
|
4445
|
-
"target_type": "thread",
|
|
4446
|
-
"target_id": "<channel ID>",
|
|
4447
|
-
"thread_ts": "<thread_ts from context>",
|
|
4448
|
-
"original_expression": "tomorrow"
|
|
4449
|
-
}
|
|
4450
|
-
|
|
4451
|
-
User: "list my schedules"
|
|
4452
|
-
\u2192 { "action": "list" }
|
|
4453
|
-
|
|
4454
|
-
User: "cancel schedule abc123"
|
|
4455
|
-
\u2192 { "action": "cancel", "schedule_id": "abc123" }`,
|
|
4456
|
-
inputSchema: {
|
|
4457
|
-
type: "object",
|
|
4458
|
-
properties: {
|
|
4459
|
-
action: {
|
|
4460
|
-
type: "string",
|
|
4461
|
-
enum: ["create", "list", "cancel", "pause", "resume"],
|
|
4462
|
-
description: "What to do: create new, list existing, cancel/pause/resume by ID"
|
|
4463
|
-
},
|
|
4464
|
-
// WHAT to do
|
|
4465
|
-
reminder_text: {
|
|
4466
|
-
type: "string",
|
|
4467
|
-
description: "For create: the message/reminder text to send when triggered"
|
|
4468
|
-
},
|
|
4469
|
-
workflow: {
|
|
4470
|
-
type: "string",
|
|
4471
|
-
description: 'For create: workflow ID to run. ONLY populate this if the user used the % prefix (e.g., "%my-workflow"). Extract the name without the % symbol. If no % prefix, use reminder_text instead.'
|
|
4472
|
-
},
|
|
4473
|
-
workflow_inputs: {
|
|
4474
|
-
type: "object",
|
|
4475
|
-
description: "For create: optional inputs to pass to the workflow"
|
|
4476
|
-
},
|
|
4477
|
-
// WHERE to send
|
|
4478
|
-
target_type: {
|
|
4479
|
-
type: "string",
|
|
4480
|
-
enum: ["channel", "dm", "thread", "user"],
|
|
4481
|
-
description: "For create: where to send output. channel=public/private channel, dm=DM to self (current DM channel), user=DM to specific user, thread=reply in current thread"
|
|
4482
|
-
},
|
|
4483
|
-
target_id: {
|
|
4484
|
-
type: "string",
|
|
4485
|
-
description: "For create: Slack channel ID. Channels start with C, DMs start with D. Always use the channel ID from the current context."
|
|
4486
|
-
},
|
|
4487
|
-
thread_ts: {
|
|
4488
|
-
type: "string",
|
|
4489
|
-
description: "For create with target_type=thread: the thread timestamp to reply to. Get this from the current thread context."
|
|
4490
|
-
},
|
|
4491
|
-
// WHEN to run
|
|
4492
|
-
is_recurring: {
|
|
4493
|
-
type: "boolean",
|
|
4494
|
-
description: "For create: true for recurring schedules (cron), false for one-time (run_at)"
|
|
4495
|
-
},
|
|
4496
|
-
cron: {
|
|
4497
|
-
type: "string",
|
|
4498
|
-
description: 'For create recurring: cron expression (minute hour day-of-month month day-of-week). Examples: "0 9 * * *" (daily 9am), "* * * * *" (every minute), "0 9 * * 1" (Mondays 9am)'
|
|
4499
|
-
},
|
|
4500
|
-
run_at: {
|
|
4501
|
-
type: "string",
|
|
4502
|
-
description: 'For create one-time: ISO 8601 timestamp when to run (e.g., "2026-02-07T15:00:00Z")'
|
|
4503
|
-
},
|
|
4504
|
-
original_expression: {
|
|
4505
|
-
type: "string",
|
|
4506
|
-
description: "For create: the original natural language expression from user (for display only)"
|
|
4507
|
-
},
|
|
4508
|
-
// For cancel/pause/resume
|
|
4509
|
-
schedule_id: {
|
|
4510
|
-
type: "string",
|
|
4511
|
-
description: "For cancel/pause/resume: the schedule ID to act on (first 8 chars is enough)"
|
|
4512
|
-
}
|
|
4513
|
-
},
|
|
4514
|
-
required: ["action"]
|
|
4515
|
-
},
|
|
4516
|
-
exec: ""
|
|
4517
|
-
// Not used - this tool has a custom handler
|
|
4518
|
-
};
|
|
4519
|
-
}
|
|
4520
|
-
function isScheduleTool(toolName) {
|
|
4521
|
-
return toolName === "schedule";
|
|
4522
|
-
}
|
|
4523
|
-
function determineScheduleType(contextType, outputType, outputTarget) {
|
|
4524
|
-
if (outputType === "slack" && outputTarget) {
|
|
4525
|
-
if (outputTarget.startsWith("#") || outputTarget.match(/^C[A-Z0-9]+$/)) {
|
|
4526
|
-
return "channel";
|
|
4527
|
-
}
|
|
4528
|
-
if (outputTarget.startsWith("@") || outputTarget.match(/^U[A-Z0-9]+$/)) {
|
|
4529
|
-
return "dm";
|
|
4530
|
-
}
|
|
4531
|
-
}
|
|
4532
|
-
if (contextType === "cli" || contextType.startsWith("github:")) {
|
|
4533
|
-
return "personal";
|
|
4534
|
-
}
|
|
4535
|
-
return "personal";
|
|
4536
|
-
}
|
|
4537
|
-
function slackChannelTypeToScheduleType(channelType) {
|
|
4538
|
-
switch (channelType) {
|
|
4539
|
-
case "channel":
|
|
4540
|
-
return "channel";
|
|
4541
|
-
case "group":
|
|
4542
|
-
return "dm";
|
|
4543
|
-
// Group DMs map to 'dm' schedule type
|
|
4544
|
-
case "dm":
|
|
4545
|
-
default:
|
|
4546
|
-
return "personal";
|
|
4547
|
-
}
|
|
4548
|
-
}
|
|
4549
|
-
function buildScheduleToolContext(sources, availableWorkflows, permissions, outputInfo) {
|
|
4550
|
-
if (sources.slackContext) {
|
|
4551
|
-
const contextType = `slack:${sources.slackContext.userId}`;
|
|
4552
|
-
const scheduleType = determineScheduleType(
|
|
4553
|
-
contextType,
|
|
4554
|
-
outputInfo?.outputType,
|
|
4555
|
-
outputInfo?.outputTarget
|
|
4556
|
-
);
|
|
4557
|
-
let allowedScheduleType;
|
|
4558
|
-
if (sources.slackContext.channelType) {
|
|
4559
|
-
allowedScheduleType = slackChannelTypeToScheduleType(sources.slackContext.channelType);
|
|
4560
|
-
}
|
|
4561
|
-
let finalScheduleType = scheduleType;
|
|
4562
|
-
if (!outputInfo?.outputType && sources.slackContext.channelType) {
|
|
4563
|
-
finalScheduleType = slackChannelTypeToScheduleType(sources.slackContext.channelType);
|
|
4564
|
-
}
|
|
4565
|
-
return {
|
|
4566
|
-
userId: sources.slackContext.userId,
|
|
4567
|
-
userName: sources.slackContext.userName,
|
|
4568
|
-
contextType,
|
|
4569
|
-
timezone: sources.slackContext.timezone,
|
|
4570
|
-
availableWorkflows,
|
|
4571
|
-
scheduleType: finalScheduleType,
|
|
4572
|
-
permissions,
|
|
4573
|
-
allowedScheduleType
|
|
4574
|
-
};
|
|
4575
|
-
}
|
|
4576
|
-
if (sources.githubContext) {
|
|
4577
|
-
return {
|
|
4578
|
-
userId: sources.githubContext.login,
|
|
4579
|
-
contextType: `github:${sources.githubContext.login}`,
|
|
4580
|
-
timezone: "UTC",
|
|
4581
|
-
// GitHub doesn't provide timezone
|
|
4582
|
-
availableWorkflows,
|
|
4583
|
-
scheduleType: "personal",
|
|
4584
|
-
permissions,
|
|
4585
|
-
allowedScheduleType: "personal"
|
|
4586
|
-
// GitHub context only allows personal schedules
|
|
4587
|
-
};
|
|
4588
|
-
}
|
|
4589
|
-
return {
|
|
4590
|
-
userId: sources.cliContext?.userId || process.env.USER || "cli-user",
|
|
4591
|
-
contextType: "cli",
|
|
4592
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
|
|
4593
|
-
availableWorkflows,
|
|
4594
|
-
scheduleType: "personal",
|
|
4595
|
-
permissions,
|
|
4596
|
-
allowedScheduleType: "personal"
|
|
4597
|
-
// CLI context only allows personal schedules
|
|
4598
|
-
};
|
|
4599
|
-
}
|
|
4600
|
-
var init_schedule_tool = __esm({
|
|
4601
|
-
"src/scheduler/schedule-tool.ts"() {
|
|
4602
|
-
"use strict";
|
|
4603
|
-
init_schedule_store();
|
|
4604
|
-
init_schedule_parser();
|
|
4605
|
-
init_logger();
|
|
4606
|
-
}
|
|
4607
|
-
});
|
|
4608
|
-
|
|
4609
3993
|
// src/state-machine/states/init.ts
|
|
4610
3994
|
async function handleInit(context2, state, transition) {
|
|
4611
3995
|
if (context2.debug) {
|
|
@@ -5389,8 +4773,8 @@ var init_wave_planning = __esm({
|
|
|
5389
4773
|
});
|
|
5390
4774
|
|
|
5391
4775
|
// src/utils/mermaid-telemetry.ts
|
|
5392
|
-
import * as
|
|
5393
|
-
import * as
|
|
4776
|
+
import * as fs3 from "fs";
|
|
4777
|
+
import * as path4 from "path";
|
|
5394
4778
|
function emitMermaidFromMarkdown(checkName, markdown, origin) {
|
|
5395
4779
|
if (!markdown || typeof markdown !== "string") return 0;
|
|
5396
4780
|
let m;
|
|
@@ -5403,16 +4787,16 @@ function emitMermaidFromMarkdown(checkName, markdown, origin) {
|
|
|
5403
4787
|
addEvent("diagram.block", { check: checkName, origin, code });
|
|
5404
4788
|
addDiagramBlock(origin);
|
|
5405
4789
|
if (process.env.VISOR_TRACE_REPORT === "true") {
|
|
5406
|
-
const outDir = process.env.VISOR_TRACE_DIR ||
|
|
4790
|
+
const outDir = process.env.VISOR_TRACE_DIR || path4.join(process.cwd(), "output", "traces");
|
|
5407
4791
|
try {
|
|
5408
|
-
if (!
|
|
4792
|
+
if (!fs3.existsSync(outDir)) fs3.mkdirSync(outDir, { recursive: true });
|
|
5409
4793
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5410
|
-
const jsonPath =
|
|
5411
|
-
const htmlPath =
|
|
4794
|
+
const jsonPath = path4.join(outDir, `${ts}.trace.json`);
|
|
4795
|
+
const htmlPath = path4.join(outDir, `${ts}.report.html`);
|
|
5412
4796
|
let data = { spans: [] };
|
|
5413
|
-
if (
|
|
4797
|
+
if (fs3.existsSync(jsonPath)) {
|
|
5414
4798
|
try {
|
|
5415
|
-
data = JSON.parse(
|
|
4799
|
+
data = JSON.parse(fs3.readFileSync(jsonPath, "utf8"));
|
|
5416
4800
|
} catch {
|
|
5417
4801
|
data = { spans: [] };
|
|
5418
4802
|
}
|
|
@@ -5420,9 +4804,9 @@ function emitMermaidFromMarkdown(checkName, markdown, origin) {
|
|
|
5420
4804
|
data.spans.push({
|
|
5421
4805
|
events: [{ name: "diagram.block", attrs: { check: checkName, origin, code } }]
|
|
5422
4806
|
});
|
|
5423
|
-
|
|
5424
|
-
if (!
|
|
5425
|
-
|
|
4807
|
+
fs3.writeFileSync(jsonPath, JSON.stringify(data, null, 2), "utf8");
|
|
4808
|
+
if (!fs3.existsSync(htmlPath)) {
|
|
4809
|
+
fs3.writeFileSync(
|
|
5426
4810
|
htmlPath,
|
|
5427
4811
|
'<!doctype html><html><head><meta charset="utf-8"/><title>Visor Trace Report</title></head><body><h2>Visor Trace Report</h2></body></html>',
|
|
5428
4812
|
"utf8"
|
|
@@ -6078,9 +5462,9 @@ var init_dependency_gating = __esm({
|
|
|
6078
5462
|
// src/state-machine/dispatch/template-renderer.ts
|
|
6079
5463
|
async function renderTemplateContent(checkId, checkConfig, reviewSummary) {
|
|
6080
5464
|
try {
|
|
6081
|
-
const { createExtendedLiquid: createExtendedLiquid2 } = await import("./liquid-extensions-
|
|
6082
|
-
const
|
|
6083
|
-
const
|
|
5465
|
+
const { createExtendedLiquid: createExtendedLiquid2 } = await import("./liquid-extensions-PLBOMRLI.mjs");
|
|
5466
|
+
const fs10 = await import("fs/promises");
|
|
5467
|
+
const path13 = await import("path");
|
|
6084
5468
|
const schemaRaw = checkConfig.schema || "plain";
|
|
6085
5469
|
const schema = typeof schemaRaw === "string" ? schemaRaw : "code-review";
|
|
6086
5470
|
let templateContent;
|
|
@@ -6088,24 +5472,24 @@ async function renderTemplateContent(checkId, checkConfig, reviewSummary) {
|
|
|
6088
5472
|
templateContent = String(checkConfig.template.content);
|
|
6089
5473
|
} else if (checkConfig.template && checkConfig.template.file) {
|
|
6090
5474
|
const file = String(checkConfig.template.file);
|
|
6091
|
-
const resolved =
|
|
6092
|
-
templateContent = await
|
|
5475
|
+
const resolved = path13.resolve(process.cwd(), file);
|
|
5476
|
+
templateContent = await fs10.readFile(resolved, "utf-8");
|
|
6093
5477
|
} else if (schema && schema !== "plain") {
|
|
6094
5478
|
const sanitized = String(schema).replace(/[^a-zA-Z0-9-]/g, "");
|
|
6095
5479
|
if (sanitized) {
|
|
6096
5480
|
const candidatePaths = [
|
|
6097
|
-
|
|
5481
|
+
path13.join(__dirname, "output", sanitized, "template.liquid"),
|
|
6098
5482
|
// bundled: dist/output/
|
|
6099
|
-
|
|
5483
|
+
path13.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
|
|
6100
5484
|
// source: output/
|
|
6101
|
-
|
|
5485
|
+
path13.join(process.cwd(), "output", sanitized, "template.liquid"),
|
|
6102
5486
|
// fallback: cwd/output/
|
|
6103
|
-
|
|
5487
|
+
path13.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
|
|
6104
5488
|
// fallback: cwd/dist/output/
|
|
6105
5489
|
];
|
|
6106
5490
|
for (const p of candidatePaths) {
|
|
6107
5491
|
try {
|
|
6108
|
-
templateContent = await
|
|
5492
|
+
templateContent = await fs10.readFile(p, "utf-8");
|
|
6109
5493
|
if (templateContent) break;
|
|
6110
5494
|
} catch {
|
|
6111
5495
|
}
|
|
@@ -7394,6 +6778,7 @@ async function executeSingleCheck(checkId, context2, state, emitEvent, transitio
|
|
|
7394
6778
|
...checkConfig,
|
|
7395
6779
|
eventContext: context2.prInfo?.eventContext || {},
|
|
7396
6780
|
__outputHistory: outputHistory,
|
|
6781
|
+
__globalTools: context2.config.tools || {},
|
|
7397
6782
|
// Propagate workflow inputs for template access via {{ inputs.* }}
|
|
7398
6783
|
workflowInputs,
|
|
7399
6784
|
ai: {
|
|
@@ -8790,7 +8175,7 @@ async function executeCheckWithForEachItems2(checkId, forEachParent, forEachItem
|
|
|
8790
8175
|
}
|
|
8791
8176
|
}
|
|
8792
8177
|
try {
|
|
8793
|
-
const { evaluateTransitions } = await import("./routing-
|
|
8178
|
+
const { evaluateTransitions } = await import("./routing-HR6N43RQ.mjs");
|
|
8794
8179
|
const transTarget = await evaluateTransitions(
|
|
8795
8180
|
onFinish.transitions,
|
|
8796
8181
|
forEachParent,
|
|
@@ -8850,7 +8235,7 @@ async function executeCheckWithForEachItems2(checkId, forEachParent, forEachItem
|
|
|
8850
8235
|
`[LevelDispatch] Error evaluating on_finish transitions for ${forEachParent}: ${e instanceof Error ? e.message : String(e)}`
|
|
8851
8236
|
);
|
|
8852
8237
|
}
|
|
8853
|
-
const { evaluateGoto: evaluateGoto2 } = await import("./routing-
|
|
8238
|
+
const { evaluateGoto: evaluateGoto2 } = await import("./routing-HR6N43RQ.mjs");
|
|
8854
8239
|
if (context2.debug) {
|
|
8855
8240
|
logger.info(
|
|
8856
8241
|
`[LevelDispatch] Evaluating on_finish.goto_js for forEach parent: ${forEachParent}`
|
|
@@ -10102,9 +9487,9 @@ function updateStats2(results, state, isForEachIteration = false) {
|
|
|
10102
9487
|
}
|
|
10103
9488
|
async function renderTemplateContent2(checkId, checkConfig, reviewSummary) {
|
|
10104
9489
|
try {
|
|
10105
|
-
const { createExtendedLiquid: createExtendedLiquid2 } = await import("./liquid-extensions-
|
|
10106
|
-
const
|
|
10107
|
-
const
|
|
9490
|
+
const { createExtendedLiquid: createExtendedLiquid2 } = await import("./liquid-extensions-PLBOMRLI.mjs");
|
|
9491
|
+
const fs10 = await import("fs/promises");
|
|
9492
|
+
const path13 = await import("path");
|
|
10108
9493
|
const schemaRaw = checkConfig.schema || "plain";
|
|
10109
9494
|
const schema = typeof schemaRaw === "string" && !schemaRaw.includes("{{") && !schemaRaw.includes("{%") ? schemaRaw : typeof schemaRaw === "object" ? "code-review" : "plain";
|
|
10110
9495
|
let templateContent;
|
|
@@ -10113,27 +9498,27 @@ async function renderTemplateContent2(checkId, checkConfig, reviewSummary) {
|
|
|
10113
9498
|
logger.debug(`[LevelDispatch] Using inline template for ${checkId}`);
|
|
10114
9499
|
} else if (checkConfig.template && checkConfig.template.file) {
|
|
10115
9500
|
const file = String(checkConfig.template.file);
|
|
10116
|
-
const resolved =
|
|
10117
|
-
templateContent = await
|
|
9501
|
+
const resolved = path13.resolve(process.cwd(), file);
|
|
9502
|
+
templateContent = await fs10.readFile(resolved, "utf-8");
|
|
10118
9503
|
logger.debug(`[LevelDispatch] Using template file for ${checkId}: ${resolved}`);
|
|
10119
9504
|
} else if (schema && schema !== "plain") {
|
|
10120
9505
|
const sanitized = String(schema).replace(/[^a-zA-Z0-9-]/g, "");
|
|
10121
9506
|
if (sanitized) {
|
|
10122
9507
|
const candidatePaths = [
|
|
10123
|
-
|
|
9508
|
+
path13.join(__dirname, "output", sanitized, "template.liquid"),
|
|
10124
9509
|
// bundled: dist/output/
|
|
10125
|
-
|
|
9510
|
+
path13.join(__dirname, "..", "..", "output", sanitized, "template.liquid"),
|
|
10126
9511
|
// source (from state-machine/states)
|
|
10127
|
-
|
|
9512
|
+
path13.join(__dirname, "..", "..", "..", "output", sanitized, "template.liquid"),
|
|
10128
9513
|
// source (alternate)
|
|
10129
|
-
|
|
9514
|
+
path13.join(process.cwd(), "output", sanitized, "template.liquid"),
|
|
10130
9515
|
// fallback: cwd/output/
|
|
10131
|
-
|
|
9516
|
+
path13.join(process.cwd(), "dist", "output", sanitized, "template.liquid")
|
|
10132
9517
|
// fallback: cwd/dist/output/
|
|
10133
9518
|
];
|
|
10134
9519
|
for (const p of candidatePaths) {
|
|
10135
9520
|
try {
|
|
10136
|
-
templateContent = await
|
|
9521
|
+
templateContent = await fs10.readFile(p, "utf-8");
|
|
10137
9522
|
if (templateContent) {
|
|
10138
9523
|
logger.debug(`[LevelDispatch] Using schema template for ${checkId}: ${p}`);
|
|
10139
9524
|
break;
|
|
@@ -11375,7 +10760,7 @@ var init_sandbox_manager = __esm({
|
|
|
11375
10760
|
|
|
11376
10761
|
// src/utils/workspace-manager.ts
|
|
11377
10762
|
import * as fsp from "fs/promises";
|
|
11378
|
-
import * as
|
|
10763
|
+
import * as path5 from "path";
|
|
11379
10764
|
function shellEscape(str) {
|
|
11380
10765
|
return "'" + str.replace(/'/g, "'\\''") + "'";
|
|
11381
10766
|
}
|
|
@@ -11419,7 +10804,7 @@ var init_workspace_manager = __esm({
|
|
|
11419
10804
|
};
|
|
11420
10805
|
this.basePath = this.config.basePath;
|
|
11421
10806
|
const workspaceDirName = sanitizePathComponent(this.config.name || this.sessionId);
|
|
11422
|
-
this.workspacePath =
|
|
10807
|
+
this.workspacePath = path5.join(this.basePath, workspaceDirName);
|
|
11423
10808
|
}
|
|
11424
10809
|
/**
|
|
11425
10810
|
* Get or create a WorkspaceManager instance for a session
|
|
@@ -11514,7 +10899,7 @@ var init_workspace_manager = __esm({
|
|
|
11514
10899
|
configuredMainProjectName || this.extractProjectName(this.originalPath)
|
|
11515
10900
|
);
|
|
11516
10901
|
this.usedNames.add(mainProjectName);
|
|
11517
|
-
const mainProjectPath =
|
|
10902
|
+
const mainProjectPath = path5.join(this.workspacePath, mainProjectName);
|
|
11518
10903
|
const isGitRepo = await this.isGitRepository(this.originalPath);
|
|
11519
10904
|
if (isGitRepo) {
|
|
11520
10905
|
await this.createMainProjectWorktree(mainProjectPath);
|
|
@@ -11555,7 +10940,7 @@ var init_workspace_manager = __esm({
|
|
|
11555
10940
|
let projectName = sanitizePathComponent(description || this.extractRepoName(repository));
|
|
11556
10941
|
projectName = this.getUniqueName(projectName);
|
|
11557
10942
|
this.usedNames.add(projectName);
|
|
11558
|
-
const workspacePath =
|
|
10943
|
+
const workspacePath = path5.join(this.workspacePath, projectName);
|
|
11559
10944
|
await fsp.rm(workspacePath, { recursive: true, force: true });
|
|
11560
10945
|
try {
|
|
11561
10946
|
await fsp.symlink(worktreePath, workspacePath);
|
|
@@ -11694,7 +11079,7 @@ var init_workspace_manager = __esm({
|
|
|
11694
11079
|
* Extract project name from path
|
|
11695
11080
|
*/
|
|
11696
11081
|
extractProjectName(dirPath) {
|
|
11697
|
-
return
|
|
11082
|
+
return path5.basename(dirPath);
|
|
11698
11083
|
}
|
|
11699
11084
|
/**
|
|
11700
11085
|
* Extract repository name from owner/repo format
|
|
@@ -11925,8 +11310,8 @@ var init_summary = __esm({
|
|
|
11925
11310
|
});
|
|
11926
11311
|
|
|
11927
11312
|
// src/state-machine-execution-engine.ts
|
|
11928
|
-
import * as
|
|
11929
|
-
import * as
|
|
11313
|
+
import * as path6 from "path";
|
|
11314
|
+
import * as fs4 from "fs";
|
|
11930
11315
|
function serializeRunState(state) {
|
|
11931
11316
|
return {
|
|
11932
11317
|
...state,
|
|
@@ -12023,7 +11408,7 @@ var init_state_machine_execution_engine = __esm({
|
|
|
12023
11408
|
try {
|
|
12024
11409
|
const map = options?.webhookContext?.webhookData;
|
|
12025
11410
|
if (map) {
|
|
12026
|
-
const { CheckProviderRegistry: CheckProviderRegistry2 } = await import("./check-provider-registry-
|
|
11411
|
+
const { CheckProviderRegistry: CheckProviderRegistry2 } = await import("./check-provider-registry-SA2WHPLO.mjs");
|
|
12027
11412
|
const reg = CheckProviderRegistry2.getInstance();
|
|
12028
11413
|
const p = reg.getProvider("http_input");
|
|
12029
11414
|
if (p && typeof p.setWebhookContext === "function") p.setWebhookContext(map);
|
|
@@ -12136,7 +11521,7 @@ var init_state_machine_execution_engine = __esm({
|
|
|
12136
11521
|
logger.info("[StateMachine] Using state machine engine");
|
|
12137
11522
|
}
|
|
12138
11523
|
if (!config) {
|
|
12139
|
-
const { ConfigManager } = await import("./config-
|
|
11524
|
+
const { ConfigManager } = await import("./config-3UIU4TMP.mjs");
|
|
12140
11525
|
const configManager = new ConfigManager();
|
|
12141
11526
|
config = await configManager.getDefaultConfig();
|
|
12142
11527
|
logger.debug("[StateMachine] Using default configuration (no config provided)");
|
|
@@ -12145,6 +11530,15 @@ var init_state_machine_execution_engine = __esm({
|
|
|
12145
11530
|
...config,
|
|
12146
11531
|
tag_filter: tagFilter
|
|
12147
11532
|
} : config;
|
|
11533
|
+
try {
|
|
11534
|
+
const { CheckProviderRegistry: CheckProviderRegistry2 } = await import("./check-provider-registry-SA2WHPLO.mjs");
|
|
11535
|
+
const registry = CheckProviderRegistry2.getInstance();
|
|
11536
|
+
registry.setCustomTools(configWithTagFilter.tools || {});
|
|
11537
|
+
} catch (error) {
|
|
11538
|
+
logger.warn(
|
|
11539
|
+
`[StateMachine] Failed to register custom tools: ${error instanceof Error ? error.message : String(error)}`
|
|
11540
|
+
);
|
|
11541
|
+
}
|
|
12148
11542
|
const context2 = this.buildEngineContext(
|
|
12149
11543
|
configWithTagFilter,
|
|
12150
11544
|
prInfo,
|
|
@@ -12201,7 +11595,7 @@ var init_state_machine_execution_engine = __esm({
|
|
|
12201
11595
|
try {
|
|
12202
11596
|
const webhookData = this.executionContext?.webhookContext?.webhookData;
|
|
12203
11597
|
if (webhookData instanceof Map) {
|
|
12204
|
-
const { extractSlackContext: extractSlackContext2 } = await import("./schedule-tool-handler-
|
|
11598
|
+
const { extractSlackContext: extractSlackContext2 } = await import("./schedule-tool-handler-Y2UABBXN.mjs");
|
|
12205
11599
|
const slackCtx = extractSlackContext2(webhookData);
|
|
12206
11600
|
if (slackCtx) {
|
|
12207
11601
|
const payload = Array.from(webhookData.values())[0];
|
|
@@ -12230,7 +11624,7 @@ var init_state_machine_execution_engine = __esm({
|
|
|
12230
11624
|
if (Array.isArray(configWithTagFilter.frontends) && configWithTagFilter.frontends.length > 0) {
|
|
12231
11625
|
try {
|
|
12232
11626
|
const { EventBus } = await import("./event-bus-5K3Y2FCS.mjs");
|
|
12233
|
-
const { FrontendsHost } = await import("./host-
|
|
11627
|
+
const { FrontendsHost } = await import("./host-LOQWBHWT.mjs");
|
|
12234
11628
|
const bus = new EventBus();
|
|
12235
11629
|
context2.eventBus = bus;
|
|
12236
11630
|
frontendsHost = new FrontendsHost(bus, logger);
|
|
@@ -12331,9 +11725,9 @@ var init_state_machine_execution_engine = __esm({
|
|
|
12331
11725
|
}
|
|
12332
11726
|
const checkId = String(ev?.checkId || "unknown");
|
|
12333
11727
|
const threadKey = ev?.threadKey || (channel && threadTs ? `${channel}:${threadTs}` : "session");
|
|
12334
|
-
const baseDir = process.env.VISOR_SNAPSHOT_DIR ||
|
|
12335
|
-
|
|
12336
|
-
const filePath =
|
|
11728
|
+
const baseDir = process.env.VISOR_SNAPSHOT_DIR || path6.resolve(process.cwd(), ".visor", "snapshots");
|
|
11729
|
+
fs4.mkdirSync(baseDir, { recursive: true });
|
|
11730
|
+
const filePath = path6.join(baseDir, `${threadKey}-${checkId}.json`);
|
|
12337
11731
|
await this.saveSnapshotToFile(filePath);
|
|
12338
11732
|
logger.info(`[Snapshot] Saved run snapshot: ${filePath}`);
|
|
12339
11733
|
try {
|
|
@@ -12474,7 +11868,7 @@ var init_state_machine_execution_engine = __esm({
|
|
|
12474
11868
|
* Does not include secrets. Intended for debugging and future resume support.
|
|
12475
11869
|
*/
|
|
12476
11870
|
async saveSnapshotToFile(filePath) {
|
|
12477
|
-
const
|
|
11871
|
+
const fs10 = await import("fs/promises");
|
|
12478
11872
|
const ctx = this._lastContext;
|
|
12479
11873
|
const runner = this._lastRunner;
|
|
12480
11874
|
if (!ctx || !runner) {
|
|
@@ -12494,14 +11888,14 @@ var init_state_machine_execution_engine = __esm({
|
|
|
12494
11888
|
journal: entries,
|
|
12495
11889
|
requestedChecks: ctx.requestedChecks || []
|
|
12496
11890
|
};
|
|
12497
|
-
await
|
|
11891
|
+
await fs10.writeFile(filePath, JSON.stringify(payload, null, 2), "utf8");
|
|
12498
11892
|
}
|
|
12499
11893
|
/**
|
|
12500
11894
|
* Load a snapshot JSON from file and return it. Resume support can build on this.
|
|
12501
11895
|
*/
|
|
12502
11896
|
async loadSnapshotFromFile(filePath) {
|
|
12503
|
-
const
|
|
12504
|
-
const raw = await
|
|
11897
|
+
const fs10 = await import("fs/promises");
|
|
11898
|
+
const raw = await fs10.readFile(filePath, "utf8");
|
|
12505
11899
|
return JSON.parse(raw);
|
|
12506
11900
|
}
|
|
12507
11901
|
/**
|
|
@@ -12580,9 +11974,9 @@ var init_state_machine_execution_engine = __esm({
|
|
|
12580
11974
|
* @returns Array of failure condition evaluation results
|
|
12581
11975
|
*/
|
|
12582
11976
|
async evaluateFailureConditions(checkName, reviewSummary, config, previousOutputs, authorAssociation) {
|
|
12583
|
-
const { FailureConditionEvaluator: FailureConditionEvaluator2 } = await import("./failure-condition-evaluator-
|
|
11977
|
+
const { FailureConditionEvaluator: FailureConditionEvaluator2 } = await import("./failure-condition-evaluator-3B3G5NYW.mjs");
|
|
12584
11978
|
const evaluator = new FailureConditionEvaluator2();
|
|
12585
|
-
const { addEvent: addEvent3 } = await import("./trace-helpers-
|
|
11979
|
+
const { addEvent: addEvent3 } = await import("./trace-helpers-IGMH7ZPP.mjs");
|
|
12586
11980
|
const { addFailIfTriggered } = await import("./metrics-I6A7IHG4.mjs");
|
|
12587
11981
|
const checkConfig = config.checks?.[checkName];
|
|
12588
11982
|
if (!checkConfig) {
|
|
@@ -13466,7 +12860,23 @@ var init_mcp_custom_sse_server = __esm({
|
|
|
13466
12860
|
* Handle tools/list MCP request
|
|
13467
12861
|
*/
|
|
13468
12862
|
async handleToolsList(id) {
|
|
13469
|
-
const
|
|
12863
|
+
const normalizeInputSchema = (schema) => {
|
|
12864
|
+
if (schema && schema.type === "object") {
|
|
12865
|
+
return schema;
|
|
12866
|
+
}
|
|
12867
|
+
return {
|
|
12868
|
+
type: "object",
|
|
12869
|
+
properties: {},
|
|
12870
|
+
required: []
|
|
12871
|
+
};
|
|
12872
|
+
};
|
|
12873
|
+
const regularTools = await this.toolExecutor.listMcpTools();
|
|
12874
|
+
const workflowTools = Array.from(this.tools.values()).filter(isWorkflowTool).map((tool) => ({
|
|
12875
|
+
name: tool.name,
|
|
12876
|
+
description: tool.description || `Execute ${tool.name}`,
|
|
12877
|
+
inputSchema: normalizeInputSchema(tool.inputSchema)
|
|
12878
|
+
}));
|
|
12879
|
+
const allTools = [...regularTools, ...workflowTools];
|
|
13470
12880
|
if (this.debug) {
|
|
13471
12881
|
logger.debug(
|
|
13472
12882
|
`[CustomToolsSSEServer:${this.sessionId}] Listing ${allTools.length} tools: ${allTools.map((t) => t.name).join(", ")}`
|
|
@@ -13479,11 +12889,7 @@ var init_mcp_custom_sse_server = __esm({
|
|
|
13479
12889
|
tools: allTools.map((tool) => ({
|
|
13480
12890
|
name: tool.name,
|
|
13481
12891
|
description: tool.description || `Execute ${tool.name}`,
|
|
13482
|
-
inputSchema: tool.inputSchema
|
|
13483
|
-
type: "object",
|
|
13484
|
-
properties: {},
|
|
13485
|
-
required: []
|
|
13486
|
-
}
|
|
12892
|
+
inputSchema: normalizeInputSchema(tool.inputSchema)
|
|
13487
12893
|
}))
|
|
13488
12894
|
}
|
|
13489
12895
|
};
|
|
@@ -13673,9 +13079,46 @@ var init_mcp_custom_sse_server = __esm({
|
|
|
13673
13079
|
}
|
|
13674
13080
|
});
|
|
13675
13081
|
|
|
13082
|
+
// src/utils/tool-resolver.ts
|
|
13083
|
+
function resolveTools(toolItems, globalTools, logPrefix = "[ToolResolver]") {
|
|
13084
|
+
const tools = /* @__PURE__ */ new Map();
|
|
13085
|
+
for (const item of toolItems) {
|
|
13086
|
+
const workflowTool = resolveWorkflowToolFromItem(item);
|
|
13087
|
+
if (workflowTool) {
|
|
13088
|
+
logger.debug(`${logPrefix} Loaded workflow '${workflowTool.name}' as tool`);
|
|
13089
|
+
tools.set(workflowTool.name, workflowTool);
|
|
13090
|
+
continue;
|
|
13091
|
+
}
|
|
13092
|
+
if (typeof item === "string") {
|
|
13093
|
+
if (globalTools && globalTools[item]) {
|
|
13094
|
+
const tool = globalTools[item];
|
|
13095
|
+
tool.name = tool.name || item;
|
|
13096
|
+
tools.set(item, tool);
|
|
13097
|
+
continue;
|
|
13098
|
+
}
|
|
13099
|
+
logger.warn(`${logPrefix} Tool '${item}' not found in global tools or workflow registry`);
|
|
13100
|
+
} else if (isWorkflowToolReference(item)) {
|
|
13101
|
+
logger.warn(`${logPrefix} Workflow '${item.workflow}' referenced but not found in registry`);
|
|
13102
|
+
}
|
|
13103
|
+
}
|
|
13104
|
+
if (tools.size === 0 && toolItems.length > 0 && !globalTools) {
|
|
13105
|
+
logger.warn(
|
|
13106
|
+
`${logPrefix} Tools specified but no global tools found in configuration and no workflows matched`
|
|
13107
|
+
);
|
|
13108
|
+
}
|
|
13109
|
+
return tools;
|
|
13110
|
+
}
|
|
13111
|
+
var init_tool_resolver = __esm({
|
|
13112
|
+
"src/utils/tool-resolver.ts"() {
|
|
13113
|
+
"use strict";
|
|
13114
|
+
init_workflow_tool_executor();
|
|
13115
|
+
init_logger();
|
|
13116
|
+
}
|
|
13117
|
+
});
|
|
13118
|
+
|
|
13676
13119
|
// src/providers/ai-check-provider.ts
|
|
13677
|
-
import
|
|
13678
|
-
import
|
|
13120
|
+
import fs5 from "fs/promises";
|
|
13121
|
+
import path7 from "path";
|
|
13679
13122
|
var AICheckProvider;
|
|
13680
13123
|
var init_ai_check_provider = __esm({
|
|
13681
13124
|
"src/providers/ai-check-provider.ts"() {
|
|
@@ -13690,6 +13133,7 @@ var init_ai_check_provider = __esm({
|
|
|
13690
13133
|
init_mcp_custom_sse_server();
|
|
13691
13134
|
init_logger();
|
|
13692
13135
|
init_workflow_tool_executor();
|
|
13136
|
+
init_tool_resolver();
|
|
13693
13137
|
init_sandbox();
|
|
13694
13138
|
init_schedule_tool();
|
|
13695
13139
|
init_schedule_tool_handler();
|
|
@@ -13848,7 +13292,7 @@ var init_ai_check_provider = __esm({
|
|
|
13848
13292
|
const hasFileExtension = /\.[a-zA-Z0-9]{1,10}$/i.test(str);
|
|
13849
13293
|
const hasPathSeparators = /[\/\\]/.test(str);
|
|
13850
13294
|
const isRelativePath = /^\.{1,2}\//.test(str);
|
|
13851
|
-
const isAbsolutePath =
|
|
13295
|
+
const isAbsolutePath = path7.isAbsolute(str);
|
|
13852
13296
|
const hasTypicalFileChars = /^[a-zA-Z0-9._\-\/\\:~]+$/.test(str);
|
|
13853
13297
|
if (!(hasFileExtension || isRelativePath || isAbsolutePath || hasPathSeparators)) {
|
|
13854
13298
|
return false;
|
|
@@ -13858,14 +13302,14 @@ var init_ai_check_provider = __esm({
|
|
|
13858
13302
|
}
|
|
13859
13303
|
try {
|
|
13860
13304
|
let resolvedPath;
|
|
13861
|
-
if (
|
|
13862
|
-
resolvedPath =
|
|
13305
|
+
if (path7.isAbsolute(str)) {
|
|
13306
|
+
resolvedPath = path7.normalize(str);
|
|
13863
13307
|
} else {
|
|
13864
|
-
resolvedPath =
|
|
13308
|
+
resolvedPath = path7.resolve(process.cwd(), str);
|
|
13865
13309
|
}
|
|
13866
|
-
const
|
|
13310
|
+
const fs10 = __require("fs").promises;
|
|
13867
13311
|
try {
|
|
13868
|
-
const stat = await
|
|
13312
|
+
const stat = await fs10.stat(resolvedPath);
|
|
13869
13313
|
return stat.isFile();
|
|
13870
13314
|
} catch {
|
|
13871
13315
|
return hasFileExtension && (isRelativePath || isAbsolutePath || hasPathSeparators);
|
|
@@ -13882,14 +13326,14 @@ var init_ai_check_provider = __esm({
|
|
|
13882
13326
|
throw new Error("Prompt file must have .liquid extension");
|
|
13883
13327
|
}
|
|
13884
13328
|
let resolvedPath;
|
|
13885
|
-
if (
|
|
13329
|
+
if (path7.isAbsolute(promptPath)) {
|
|
13886
13330
|
resolvedPath = promptPath;
|
|
13887
13331
|
} else {
|
|
13888
|
-
resolvedPath =
|
|
13332
|
+
resolvedPath = path7.resolve(process.cwd(), promptPath);
|
|
13889
13333
|
}
|
|
13890
|
-
if (!
|
|
13891
|
-
const normalizedPath =
|
|
13892
|
-
const currentDir =
|
|
13334
|
+
if (!path7.isAbsolute(promptPath)) {
|
|
13335
|
+
const normalizedPath = path7.normalize(resolvedPath);
|
|
13336
|
+
const currentDir = path7.resolve(process.cwd());
|
|
13893
13337
|
if (!normalizedPath.startsWith(currentDir)) {
|
|
13894
13338
|
throw new Error("Invalid prompt file path: path traversal detected");
|
|
13895
13339
|
}
|
|
@@ -13898,7 +13342,7 @@ var init_ai_check_provider = __esm({
|
|
|
13898
13342
|
throw new Error("Invalid prompt file path: path traversal detected");
|
|
13899
13343
|
}
|
|
13900
13344
|
try {
|
|
13901
|
-
const promptContent = await
|
|
13345
|
+
const promptContent = await fs5.readFile(resolvedPath, "utf-8");
|
|
13902
13346
|
return promptContent;
|
|
13903
13347
|
} catch (error) {
|
|
13904
13348
|
throw new Error(
|
|
@@ -15301,37 +14745,8 @@ ${processedPrompt}` : processedPrompt;
|
|
|
15301
14745
|
* Supports both traditional custom tools and workflow-as-tool references
|
|
15302
14746
|
*/
|
|
15303
14747
|
loadCustomTools(toolItems, config) {
|
|
15304
|
-
const tools = /* @__PURE__ */ new Map();
|
|
15305
14748
|
const globalTools = config.__globalTools;
|
|
15306
|
-
|
|
15307
|
-
const workflowTool = resolveWorkflowToolFromItem(item);
|
|
15308
|
-
if (workflowTool) {
|
|
15309
|
-
logger.debug(`[AICheckProvider] Loaded workflow '${workflowTool.name}' as custom tool`);
|
|
15310
|
-
tools.set(workflowTool.name, workflowTool);
|
|
15311
|
-
continue;
|
|
15312
|
-
}
|
|
15313
|
-
if (typeof item === "string") {
|
|
15314
|
-
if (globalTools && globalTools[item]) {
|
|
15315
|
-
const tool = globalTools[item];
|
|
15316
|
-
tool.name = tool.name || item;
|
|
15317
|
-
tools.set(item, tool);
|
|
15318
|
-
continue;
|
|
15319
|
-
}
|
|
15320
|
-
logger.warn(
|
|
15321
|
-
`[AICheckProvider] Custom tool '${item}' not found in global tools or workflow registry`
|
|
15322
|
-
);
|
|
15323
|
-
} else if (isWorkflowToolReference(item)) {
|
|
15324
|
-
logger.warn(
|
|
15325
|
-
`[AICheckProvider] Workflow '${item.workflow}' referenced but not found in registry`
|
|
15326
|
-
);
|
|
15327
|
-
}
|
|
15328
|
-
}
|
|
15329
|
-
if (tools.size === 0 && toolItems.length > 0 && !globalTools) {
|
|
15330
|
-
logger.warn(
|
|
15331
|
-
`[AICheckProvider] ai_custom_tools specified but no global tools found in configuration and no workflows matched`
|
|
15332
|
-
);
|
|
15333
|
-
}
|
|
15334
|
-
return tools;
|
|
14749
|
+
return resolveTools(toolItems, globalTools, "[AICheckProvider]");
|
|
15335
14750
|
}
|
|
15336
14751
|
/**
|
|
15337
14752
|
* Intersect config-level allowedTools with policy-level allowedTools.
|
|
@@ -15881,8 +15296,8 @@ var init_template_context = __esm({
|
|
|
15881
15296
|
});
|
|
15882
15297
|
|
|
15883
15298
|
// src/providers/http-client-provider.ts
|
|
15884
|
-
import * as
|
|
15885
|
-
import * as
|
|
15299
|
+
import * as fs6 from "fs";
|
|
15300
|
+
import * as path8 from "path";
|
|
15886
15301
|
var HttpClientProvider;
|
|
15887
15302
|
var init_http_client_provider = __esm({
|
|
15888
15303
|
"src/providers/http-client-provider.ts"() {
|
|
@@ -15987,14 +15402,14 @@ var init_http_client_provider = __esm({
|
|
|
15987
15402
|
const parentContext = context2?._parentContext;
|
|
15988
15403
|
const workingDirectory = parentContext?.workingDirectory;
|
|
15989
15404
|
const workspaceEnabled = parentContext?.workspace?.isEnabled?.();
|
|
15990
|
-
if (workspaceEnabled && workingDirectory && !
|
|
15991
|
-
resolvedOutputFile =
|
|
15405
|
+
if (workspaceEnabled && workingDirectory && !path8.isAbsolute(resolvedOutputFile)) {
|
|
15406
|
+
resolvedOutputFile = path8.join(workingDirectory, resolvedOutputFile);
|
|
15992
15407
|
logger.debug(
|
|
15993
15408
|
`[http_client] Resolved relative output_file to workspace: ${resolvedOutputFile}`
|
|
15994
15409
|
);
|
|
15995
15410
|
}
|
|
15996
|
-
if (skipIfExists &&
|
|
15997
|
-
const stats =
|
|
15411
|
+
if (skipIfExists && fs6.existsSync(resolvedOutputFile)) {
|
|
15412
|
+
const stats = fs6.statSync(resolvedOutputFile);
|
|
15998
15413
|
logger.verbose(`[http_client] File cached: ${resolvedOutputFile} (${stats.size} bytes)`);
|
|
15999
15414
|
return {
|
|
16000
15415
|
issues: [],
|
|
@@ -16205,13 +15620,13 @@ var init_http_client_provider = __esm({
|
|
|
16205
15620
|
]
|
|
16206
15621
|
};
|
|
16207
15622
|
}
|
|
16208
|
-
const parentDir =
|
|
16209
|
-
if (parentDir && !
|
|
16210
|
-
|
|
15623
|
+
const parentDir = path8.dirname(outputFile);
|
|
15624
|
+
if (parentDir && !fs6.existsSync(parentDir)) {
|
|
15625
|
+
fs6.mkdirSync(parentDir, { recursive: true });
|
|
16211
15626
|
}
|
|
16212
15627
|
const arrayBuffer = await response.arrayBuffer();
|
|
16213
15628
|
const buffer = Buffer.from(arrayBuffer);
|
|
16214
|
-
|
|
15629
|
+
fs6.writeFileSync(outputFile, buffer);
|
|
16215
15630
|
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
16216
15631
|
logger.verbose(`[http_client] Downloaded: ${outputFile} (${buffer.length} bytes)`);
|
|
16217
15632
|
return {
|
|
@@ -16967,8 +16382,8 @@ var init_claude_code_types = __esm({
|
|
|
16967
16382
|
});
|
|
16968
16383
|
|
|
16969
16384
|
// src/providers/claude-code-check-provider.ts
|
|
16970
|
-
import
|
|
16971
|
-
import
|
|
16385
|
+
import fs7 from "fs/promises";
|
|
16386
|
+
import path9 from "path";
|
|
16972
16387
|
function isClaudeCodeConstructor(value) {
|
|
16973
16388
|
return typeof value === "function";
|
|
16974
16389
|
}
|
|
@@ -17127,7 +16542,7 @@ var init_claude_code_check_provider = __esm({
|
|
|
17127
16542
|
const hasFileExtension = /\.[a-zA-Z0-9]{1,10}$/i.test(str);
|
|
17128
16543
|
const hasPathSeparators = /[\/\\]/.test(str);
|
|
17129
16544
|
const isRelativePath = /^\.{1,2}\//.test(str);
|
|
17130
|
-
const isAbsolutePath =
|
|
16545
|
+
const isAbsolutePath = path9.isAbsolute(str);
|
|
17131
16546
|
const hasTypicalFileChars = /^[a-zA-Z0-9._\-\/\\:~]+$/.test(str);
|
|
17132
16547
|
if (!(hasFileExtension || isRelativePath || isAbsolutePath || hasPathSeparators)) {
|
|
17133
16548
|
return false;
|
|
@@ -17137,13 +16552,13 @@ var init_claude_code_check_provider = __esm({
|
|
|
17137
16552
|
}
|
|
17138
16553
|
try {
|
|
17139
16554
|
let resolvedPath;
|
|
17140
|
-
if (
|
|
17141
|
-
resolvedPath =
|
|
16555
|
+
if (path9.isAbsolute(str)) {
|
|
16556
|
+
resolvedPath = path9.normalize(str);
|
|
17142
16557
|
} else {
|
|
17143
|
-
resolvedPath =
|
|
16558
|
+
resolvedPath = path9.resolve(process.cwd(), str);
|
|
17144
16559
|
}
|
|
17145
16560
|
try {
|
|
17146
|
-
const stat = await
|
|
16561
|
+
const stat = await fs7.stat(resolvedPath);
|
|
17147
16562
|
return stat.isFile();
|
|
17148
16563
|
} catch {
|
|
17149
16564
|
return hasFileExtension && (isRelativePath || isAbsolutePath || hasPathSeparators);
|
|
@@ -17160,14 +16575,14 @@ var init_claude_code_check_provider = __esm({
|
|
|
17160
16575
|
throw new Error("Prompt file must have .liquid extension");
|
|
17161
16576
|
}
|
|
17162
16577
|
let resolvedPath;
|
|
17163
|
-
if (
|
|
16578
|
+
if (path9.isAbsolute(promptPath)) {
|
|
17164
16579
|
resolvedPath = promptPath;
|
|
17165
16580
|
} else {
|
|
17166
|
-
resolvedPath =
|
|
16581
|
+
resolvedPath = path9.resolve(process.cwd(), promptPath);
|
|
17167
16582
|
}
|
|
17168
|
-
if (!
|
|
17169
|
-
const normalizedPath =
|
|
17170
|
-
const currentDir =
|
|
16583
|
+
if (!path9.isAbsolute(promptPath)) {
|
|
16584
|
+
const normalizedPath = path9.normalize(resolvedPath);
|
|
16585
|
+
const currentDir = path9.resolve(process.cwd());
|
|
17171
16586
|
if (!normalizedPath.startsWith(currentDir)) {
|
|
17172
16587
|
throw new Error("Invalid prompt file path: path traversal detected");
|
|
17173
16588
|
}
|
|
@@ -17176,7 +16591,7 @@ var init_claude_code_check_provider = __esm({
|
|
|
17176
16591
|
throw new Error("Invalid prompt file path: path traversal detected");
|
|
17177
16592
|
}
|
|
17178
16593
|
try {
|
|
17179
|
-
const promptContent = await
|
|
16594
|
+
const promptContent = await fs7.readFile(resolvedPath, "utf-8");
|
|
17180
16595
|
return promptContent;
|
|
17181
16596
|
} catch (error) {
|
|
17182
16597
|
throw new Error(
|
|
@@ -17528,6 +16943,7 @@ var init_command_check_provider = __esm({
|
|
|
17528
16943
|
init_sandbox();
|
|
17529
16944
|
init_liquid_extensions();
|
|
17530
16945
|
init_logger();
|
|
16946
|
+
init_env_resolver();
|
|
17531
16947
|
init_command_executor();
|
|
17532
16948
|
init_author_permissions();
|
|
17533
16949
|
init_lazy_otel();
|
|
@@ -17729,7 +17145,7 @@ var init_command_check_provider = __esm({
|
|
|
17729
17145
|
if (config.env) {
|
|
17730
17146
|
for (const [key, value] of Object.entries(config.env)) {
|
|
17731
17147
|
if (value !== void 0 && value !== null) {
|
|
17732
|
-
scriptEnv[key] = String(value);
|
|
17148
|
+
scriptEnv[key] = String(EnvironmentResolver.resolveValue(value));
|
|
17733
17149
|
}
|
|
17734
17150
|
}
|
|
17735
17151
|
}
|
|
@@ -19745,14 +19161,14 @@ var require_util = __commonJS({
|
|
|
19745
19161
|
}
|
|
19746
19162
|
const port = url.port != null ? url.port : url.protocol === "https:" ? 443 : 80;
|
|
19747
19163
|
let origin = url.origin != null ? url.origin : `${url.protocol}//${url.hostname}:${port}`;
|
|
19748
|
-
let
|
|
19164
|
+
let path13 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`;
|
|
19749
19165
|
if (origin.endsWith("/")) {
|
|
19750
19166
|
origin = origin.substring(0, origin.length - 1);
|
|
19751
19167
|
}
|
|
19752
|
-
if (
|
|
19753
|
-
|
|
19168
|
+
if (path13 && !path13.startsWith("/")) {
|
|
19169
|
+
path13 = `/${path13}`;
|
|
19754
19170
|
}
|
|
19755
|
-
url = new URL(origin +
|
|
19171
|
+
url = new URL(origin + path13);
|
|
19756
19172
|
}
|
|
19757
19173
|
return url;
|
|
19758
19174
|
}
|
|
@@ -21366,20 +20782,20 @@ var require_parseParams = __commonJS({
|
|
|
21366
20782
|
var require_basename = __commonJS({
|
|
21367
20783
|
"node_modules/@fastify/busboy/lib/utils/basename.js"(exports, module) {
|
|
21368
20784
|
"use strict";
|
|
21369
|
-
module.exports = function basename2(
|
|
21370
|
-
if (typeof
|
|
20785
|
+
module.exports = function basename2(path13) {
|
|
20786
|
+
if (typeof path13 !== "string") {
|
|
21371
20787
|
return "";
|
|
21372
20788
|
}
|
|
21373
|
-
for (var i =
|
|
21374
|
-
switch (
|
|
20789
|
+
for (var i = path13.length - 1; i >= 0; --i) {
|
|
20790
|
+
switch (path13.charCodeAt(i)) {
|
|
21375
20791
|
case 47:
|
|
21376
20792
|
// '/'
|
|
21377
20793
|
case 92:
|
|
21378
|
-
|
|
21379
|
-
return
|
|
20794
|
+
path13 = path13.slice(i + 1);
|
|
20795
|
+
return path13 === ".." || path13 === "." ? "" : path13;
|
|
21380
20796
|
}
|
|
21381
20797
|
}
|
|
21382
|
-
return
|
|
20798
|
+
return path13 === ".." || path13 === "." ? "" : path13;
|
|
21383
20799
|
};
|
|
21384
20800
|
}
|
|
21385
20801
|
});
|
|
@@ -24410,7 +23826,7 @@ var require_request = __commonJS({
|
|
|
24410
23826
|
}
|
|
24411
23827
|
var Request = class _Request {
|
|
24412
23828
|
constructor(origin, {
|
|
24413
|
-
path:
|
|
23829
|
+
path: path13,
|
|
24414
23830
|
method,
|
|
24415
23831
|
body,
|
|
24416
23832
|
headers,
|
|
@@ -24424,11 +23840,11 @@ var require_request = __commonJS({
|
|
|
24424
23840
|
throwOnError,
|
|
24425
23841
|
expectContinue
|
|
24426
23842
|
}, handler) {
|
|
24427
|
-
if (typeof
|
|
23843
|
+
if (typeof path13 !== "string") {
|
|
24428
23844
|
throw new InvalidArgumentError("path must be a string");
|
|
24429
|
-
} else if (
|
|
23845
|
+
} else if (path13[0] !== "/" && !(path13.startsWith("http://") || path13.startsWith("https://")) && method !== "CONNECT") {
|
|
24430
23846
|
throw new InvalidArgumentError("path must be an absolute URL or start with a slash");
|
|
24431
|
-
} else if (invalidPathRegex.exec(
|
|
23847
|
+
} else if (invalidPathRegex.exec(path13) !== null) {
|
|
24432
23848
|
throw new InvalidArgumentError("invalid request path");
|
|
24433
23849
|
}
|
|
24434
23850
|
if (typeof method !== "string") {
|
|
@@ -24491,7 +23907,7 @@ var require_request = __commonJS({
|
|
|
24491
23907
|
this.completed = false;
|
|
24492
23908
|
this.aborted = false;
|
|
24493
23909
|
this.upgrade = upgrade || null;
|
|
24494
|
-
this.path = query ? util.buildURL(
|
|
23910
|
+
this.path = query ? util.buildURL(path13, query) : path13;
|
|
24495
23911
|
this.origin = origin;
|
|
24496
23912
|
this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent;
|
|
24497
23913
|
this.blocking = blocking == null ? false : blocking;
|
|
@@ -25499,9 +24915,9 @@ var require_RedirectHandler = __commonJS({
|
|
|
25499
24915
|
return this.handler.onHeaders(statusCode, headers, resume, statusText);
|
|
25500
24916
|
}
|
|
25501
24917
|
const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)));
|
|
25502
|
-
const
|
|
24918
|
+
const path13 = search ? `${pathname}${search}` : pathname;
|
|
25503
24919
|
this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin);
|
|
25504
|
-
this.opts.path =
|
|
24920
|
+
this.opts.path = path13;
|
|
25505
24921
|
this.opts.origin = origin;
|
|
25506
24922
|
this.opts.maxRedirections = 0;
|
|
25507
24923
|
this.opts.query = null;
|
|
@@ -26743,7 +26159,7 @@ var require_client = __commonJS({
|
|
|
26743
26159
|
writeH2(client, client[kHTTP2Session], request);
|
|
26744
26160
|
return;
|
|
26745
26161
|
}
|
|
26746
|
-
const { body, method, path:
|
|
26162
|
+
const { body, method, path: path13, host, upgrade, headers, blocking, reset } = request;
|
|
26747
26163
|
const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
|
|
26748
26164
|
if (body && typeof body.read === "function") {
|
|
26749
26165
|
body.read(0);
|
|
@@ -26793,7 +26209,7 @@ var require_client = __commonJS({
|
|
|
26793
26209
|
if (blocking) {
|
|
26794
26210
|
socket[kBlocking] = true;
|
|
26795
26211
|
}
|
|
26796
|
-
let header = `${method} ${
|
|
26212
|
+
let header = `${method} ${path13} HTTP/1.1\r
|
|
26797
26213
|
`;
|
|
26798
26214
|
if (typeof host === "string") {
|
|
26799
26215
|
header += `host: ${host}\r
|
|
@@ -26856,7 +26272,7 @@ upgrade: ${upgrade}\r
|
|
|
26856
26272
|
return true;
|
|
26857
26273
|
}
|
|
26858
26274
|
function writeH2(client, session, request) {
|
|
26859
|
-
const { body, method, path:
|
|
26275
|
+
const { body, method, path: path13, host, upgrade, expectContinue, signal, headers: reqHeaders } = request;
|
|
26860
26276
|
let headers;
|
|
26861
26277
|
if (typeof reqHeaders === "string") headers = Request[kHTTP2CopyHeaders](reqHeaders.trim());
|
|
26862
26278
|
else headers = reqHeaders;
|
|
@@ -26899,7 +26315,7 @@ upgrade: ${upgrade}\r
|
|
|
26899
26315
|
});
|
|
26900
26316
|
return true;
|
|
26901
26317
|
}
|
|
26902
|
-
headers[HTTP2_HEADER_PATH] =
|
|
26318
|
+
headers[HTTP2_HEADER_PATH] = path13;
|
|
26903
26319
|
headers[HTTP2_HEADER_SCHEME] = "https";
|
|
26904
26320
|
const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH";
|
|
26905
26321
|
if (body && typeof body.read === "function") {
|
|
@@ -29142,20 +28558,20 @@ var require_mock_utils = __commonJS({
|
|
|
29142
28558
|
}
|
|
29143
28559
|
return true;
|
|
29144
28560
|
}
|
|
29145
|
-
function safeUrl(
|
|
29146
|
-
if (typeof
|
|
29147
|
-
return
|
|
28561
|
+
function safeUrl(path13) {
|
|
28562
|
+
if (typeof path13 !== "string") {
|
|
28563
|
+
return path13;
|
|
29148
28564
|
}
|
|
29149
|
-
const pathSegments =
|
|
28565
|
+
const pathSegments = path13.split("?");
|
|
29150
28566
|
if (pathSegments.length !== 2) {
|
|
29151
|
-
return
|
|
28567
|
+
return path13;
|
|
29152
28568
|
}
|
|
29153
28569
|
const qp = new URLSearchParams(pathSegments.pop());
|
|
29154
28570
|
qp.sort();
|
|
29155
28571
|
return [...pathSegments, qp.toString()].join("?");
|
|
29156
28572
|
}
|
|
29157
|
-
function matchKey(mockDispatch2, { path:
|
|
29158
|
-
const pathMatch = matchValue(mockDispatch2.path,
|
|
28573
|
+
function matchKey(mockDispatch2, { path: path13, method, body, headers }) {
|
|
28574
|
+
const pathMatch = matchValue(mockDispatch2.path, path13);
|
|
29159
28575
|
const methodMatch = matchValue(mockDispatch2.method, method);
|
|
29160
28576
|
const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true;
|
|
29161
28577
|
const headersMatch = matchHeaders(mockDispatch2, headers);
|
|
@@ -29173,7 +28589,7 @@ var require_mock_utils = __commonJS({
|
|
|
29173
28589
|
function getMockDispatch(mockDispatches, key) {
|
|
29174
28590
|
const basePath = key.query ? buildURL(key.path, key.query) : key.path;
|
|
29175
28591
|
const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath;
|
|
29176
|
-
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path:
|
|
28592
|
+
let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path13 }) => matchValue(safeUrl(path13), resolvedPath));
|
|
29177
28593
|
if (matchedMockDispatches.length === 0) {
|
|
29178
28594
|
throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`);
|
|
29179
28595
|
}
|
|
@@ -29210,9 +28626,9 @@ var require_mock_utils = __commonJS({
|
|
|
29210
28626
|
}
|
|
29211
28627
|
}
|
|
29212
28628
|
function buildKey(opts) {
|
|
29213
|
-
const { path:
|
|
28629
|
+
const { path: path13, method, body, headers, query } = opts;
|
|
29214
28630
|
return {
|
|
29215
|
-
path:
|
|
28631
|
+
path: path13,
|
|
29216
28632
|
method,
|
|
29217
28633
|
body,
|
|
29218
28634
|
headers,
|
|
@@ -29661,10 +29077,10 @@ var require_pending_interceptors_formatter = __commonJS({
|
|
|
29661
29077
|
}
|
|
29662
29078
|
format(pendingInterceptors) {
|
|
29663
29079
|
const withPrettyHeaders = pendingInterceptors.map(
|
|
29664
|
-
({ method, path:
|
|
29080
|
+
({ method, path: path13, data: { statusCode }, persist, times, timesInvoked, origin }) => ({
|
|
29665
29081
|
Method: method,
|
|
29666
29082
|
Origin: origin,
|
|
29667
|
-
Path:
|
|
29083
|
+
Path: path13,
|
|
29668
29084
|
"Status code": statusCode,
|
|
29669
29085
|
Persistent: persist ? "\u2705" : "\u274C",
|
|
29670
29086
|
Invocations: timesInvoked,
|
|
@@ -34285,8 +33701,8 @@ var require_util6 = __commonJS({
|
|
|
34285
33701
|
}
|
|
34286
33702
|
}
|
|
34287
33703
|
}
|
|
34288
|
-
function validateCookiePath(
|
|
34289
|
-
for (const char of
|
|
33704
|
+
function validateCookiePath(path13) {
|
|
33705
|
+
for (const char of path13) {
|
|
34290
33706
|
const code = char.charCodeAt(0);
|
|
34291
33707
|
if (code < 33 || char === ";") {
|
|
34292
33708
|
throw new Error("Invalid cookie path");
|
|
@@ -35966,11 +35382,11 @@ var require_undici = __commonJS({
|
|
|
35966
35382
|
if (typeof opts.path !== "string") {
|
|
35967
35383
|
throw new InvalidArgumentError("invalid opts.path");
|
|
35968
35384
|
}
|
|
35969
|
-
let
|
|
35385
|
+
let path13 = opts.path;
|
|
35970
35386
|
if (!opts.path.startsWith("/")) {
|
|
35971
|
-
|
|
35387
|
+
path13 = `/${path13}`;
|
|
35972
35388
|
}
|
|
35973
|
-
url = new URL(util.parseOrigin(url).origin +
|
|
35389
|
+
url = new URL(util.parseOrigin(url).origin + path13);
|
|
35974
35390
|
} else {
|
|
35975
35391
|
if (!opts) {
|
|
35976
35392
|
opts = typeof url === "object" ? url : {};
|
|
@@ -36398,10 +35814,11 @@ var init_mcp_check_provider = __esm({
|
|
|
36398
35814
|
'No custom tools available. Define tools in the "tools" section of your configuration.'
|
|
36399
35815
|
);
|
|
36400
35816
|
}
|
|
36401
|
-
const
|
|
36402
|
-
if (!
|
|
35817
|
+
const hasTool = await this.customToolExecutor.hasTool(config.method);
|
|
35818
|
+
if (!hasTool) {
|
|
35819
|
+
const availableToolNames = await this.customToolExecutor.getToolNames();
|
|
36403
35820
|
throw new Error(
|
|
36404
|
-
`Custom tool not found: ${config.method}. Available tools: ${
|
|
35821
|
+
`Custom tool not found: ${config.method}. Available tools: ${availableToolNames.join(", ")}`
|
|
36405
35822
|
);
|
|
36406
35823
|
}
|
|
36407
35824
|
const context2 = {
|
|
@@ -36512,11 +35929,24 @@ var init_mcp_check_provider = __esm({
|
|
|
36512
35929
|
* Execute MCP method using stdio transport
|
|
36513
35930
|
*/
|
|
36514
35931
|
async executeStdioMethod(config, methodArgs, timeout) {
|
|
35932
|
+
const env = {};
|
|
35933
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
35934
|
+
if (value !== void 0) {
|
|
35935
|
+
env[key] = value;
|
|
35936
|
+
}
|
|
35937
|
+
}
|
|
35938
|
+
if (config.env) {
|
|
35939
|
+
for (const [key, value] of Object.entries(config.env)) {
|
|
35940
|
+
if (value !== void 0 && value !== null) {
|
|
35941
|
+
env[key] = String(EnvironmentResolver.resolveValue(value));
|
|
35942
|
+
}
|
|
35943
|
+
}
|
|
35944
|
+
}
|
|
36515
35945
|
return this.executeWithTransport(
|
|
36516
35946
|
() => new StdioClientTransport({
|
|
36517
35947
|
command: config.command,
|
|
36518
35948
|
args: config.command_args,
|
|
36519
|
-
env
|
|
35949
|
+
env,
|
|
36520
35950
|
cwd: config.workingDirectory,
|
|
36521
35951
|
stderr: "pipe"
|
|
36522
35952
|
// Prevent child stderr from corrupting TUI
|
|
@@ -37092,8 +36522,8 @@ var init_stdin_reader = __esm({
|
|
|
37092
36522
|
});
|
|
37093
36523
|
|
|
37094
36524
|
// src/providers/human-input-check-provider.ts
|
|
37095
|
-
import * as
|
|
37096
|
-
import * as
|
|
36525
|
+
import * as fs8 from "fs";
|
|
36526
|
+
import * as path10 from "path";
|
|
37097
36527
|
var HumanInputCheckProvider;
|
|
37098
36528
|
var init_human_input_check_provider = __esm({
|
|
37099
36529
|
"src/providers/human-input-check-provider.ts"() {
|
|
@@ -37276,19 +36706,19 @@ var init_human_input_check_provider = __esm({
|
|
|
37276
36706
|
*/
|
|
37277
36707
|
async tryReadFile(filePath) {
|
|
37278
36708
|
try {
|
|
37279
|
-
const absolutePath =
|
|
37280
|
-
const normalizedPath =
|
|
36709
|
+
const absolutePath = path10.isAbsolute(filePath) ? filePath : path10.resolve(process.cwd(), filePath);
|
|
36710
|
+
const normalizedPath = path10.normalize(absolutePath);
|
|
37281
36711
|
const cwd = process.cwd();
|
|
37282
|
-
if (!normalizedPath.startsWith(cwd +
|
|
36712
|
+
if (!normalizedPath.startsWith(cwd + path10.sep) && normalizedPath !== cwd) {
|
|
37283
36713
|
return null;
|
|
37284
36714
|
}
|
|
37285
36715
|
try {
|
|
37286
|
-
await
|
|
37287
|
-
const stats = await
|
|
36716
|
+
await fs8.promises.access(normalizedPath, fs8.constants.R_OK);
|
|
36717
|
+
const stats = await fs8.promises.stat(normalizedPath);
|
|
37288
36718
|
if (!stats.isFile()) {
|
|
37289
36719
|
return null;
|
|
37290
36720
|
}
|
|
37291
|
-
const content = await
|
|
36721
|
+
const content = await fs8.promises.readFile(normalizedPath, "utf-8");
|
|
37292
36722
|
return content.trim();
|
|
37293
36723
|
} catch {
|
|
37294
36724
|
return null;
|
|
@@ -37573,6 +37003,494 @@ ${snippet}`
|
|
|
37573
37003
|
}
|
|
37574
37004
|
});
|
|
37575
37005
|
|
|
37006
|
+
// src/utils/script-tool-environment.ts
|
|
37007
|
+
import * as acorn from "acorn";
|
|
37008
|
+
import * as walk from "acorn-walk";
|
|
37009
|
+
function formatSyntaxError(code, err) {
|
|
37010
|
+
const line = err.loc?.line ?? 0;
|
|
37011
|
+
const col = err.loc?.column ?? 0;
|
|
37012
|
+
const baseMsg = err.message?.replace(/\s*\(\d+:\d+\)$/, "") || "Syntax error";
|
|
37013
|
+
if (!line) return `Syntax error: ${baseMsg}`;
|
|
37014
|
+
const lines = code.split("\n");
|
|
37015
|
+
const snippetLines = [];
|
|
37016
|
+
const start = Math.max(0, line - 2);
|
|
37017
|
+
const end = Math.min(lines.length, line + 1);
|
|
37018
|
+
for (let i = start; i < end; i++) {
|
|
37019
|
+
const lineNum = String(i + 1).padStart(3, " ");
|
|
37020
|
+
if (i === line - 1) {
|
|
37021
|
+
snippetLines.push(` > ${lineNum} | ${lines[i]}`);
|
|
37022
|
+
snippetLines.push(` ${" ".repeat(lineNum.length)} | ${" ".repeat(col)}^`);
|
|
37023
|
+
} else {
|
|
37024
|
+
snippetLines.push(` ${lineNum} | ${lines[i]}`);
|
|
37025
|
+
}
|
|
37026
|
+
}
|
|
37027
|
+
return `Syntax error at line ${line}, column ${col}: ${baseMsg}
|
|
37028
|
+
|
|
37029
|
+
${snippetLines.join("\n")}`;
|
|
37030
|
+
}
|
|
37031
|
+
function levenshtein(a, b) {
|
|
37032
|
+
const m = a.length;
|
|
37033
|
+
const n = b.length;
|
|
37034
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
37035
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
37036
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
37037
|
+
for (let i = 1; i <= m; i++) {
|
|
37038
|
+
for (let j = 1; j <= n; j++) {
|
|
37039
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
37040
|
+
}
|
|
37041
|
+
}
|
|
37042
|
+
return dp[m][n];
|
|
37043
|
+
}
|
|
37044
|
+
function transformScriptForAsync(code, asyncFunctionNames, opts) {
|
|
37045
|
+
if (asyncFunctionNames.size === 0) {
|
|
37046
|
+
return `return (() => {
|
|
37047
|
+
${code}
|
|
37048
|
+
})()`;
|
|
37049
|
+
}
|
|
37050
|
+
let ast;
|
|
37051
|
+
try {
|
|
37052
|
+
ast = acorn.parse(code, {
|
|
37053
|
+
ecmaVersion: 2022,
|
|
37054
|
+
sourceType: "script",
|
|
37055
|
+
allowReturnOutsideFunction: true,
|
|
37056
|
+
locations: true
|
|
37057
|
+
});
|
|
37058
|
+
} catch (e) {
|
|
37059
|
+
throw new Error(formatSyntaxError(code, e));
|
|
37060
|
+
}
|
|
37061
|
+
if (opts?.knownGlobals) {
|
|
37062
|
+
lintUnknownCalls(code, ast, opts.knownGlobals, opts.disabledBuiltins);
|
|
37063
|
+
}
|
|
37064
|
+
const insertions = [];
|
|
37065
|
+
const functionsNeedingAsync = /* @__PURE__ */ new Set();
|
|
37066
|
+
const functionScopes = [];
|
|
37067
|
+
walk.full(ast, (node) => {
|
|
37068
|
+
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") {
|
|
37069
|
+
functionScopes.push(node);
|
|
37070
|
+
}
|
|
37071
|
+
});
|
|
37072
|
+
walk.full(ast, (node) => {
|
|
37073
|
+
if (node.type !== "CallExpression") return;
|
|
37074
|
+
const calleeName = getCalleeName(node);
|
|
37075
|
+
if (!calleeName || !asyncFunctionNames.has(calleeName)) return;
|
|
37076
|
+
insertions.push({ offset: node.start, text: "await " });
|
|
37077
|
+
for (const fn of functionScopes) {
|
|
37078
|
+
const body2 = fn.body;
|
|
37079
|
+
if (body2 && body2.start <= node.start && body2.end >= node.end) {
|
|
37080
|
+
functionsNeedingAsync.add(fn);
|
|
37081
|
+
}
|
|
37082
|
+
}
|
|
37083
|
+
});
|
|
37084
|
+
walk.full(ast, (node) => {
|
|
37085
|
+
if (node.type !== "CallExpression") return;
|
|
37086
|
+
const callNode = node;
|
|
37087
|
+
const calleeName = getCalleeName(callNode);
|
|
37088
|
+
if (calleeName !== "map" || !callNode.arguments || callNode.arguments.length < 2) return;
|
|
37089
|
+
const callback = callNode.arguments[1];
|
|
37090
|
+
if (callback.type === "ArrowFunctionExpression" || callback.type === "FunctionExpression") {
|
|
37091
|
+
let hasAsyncCall = false;
|
|
37092
|
+
walk.full(callback, (inner) => {
|
|
37093
|
+
if (inner.type === "CallExpression") {
|
|
37094
|
+
const innerName = getCalleeName(inner);
|
|
37095
|
+
if (innerName && asyncFunctionNames.has(innerName)) {
|
|
37096
|
+
hasAsyncCall = true;
|
|
37097
|
+
}
|
|
37098
|
+
}
|
|
37099
|
+
});
|
|
37100
|
+
if (hasAsyncCall) {
|
|
37101
|
+
functionsNeedingAsync.add(callback);
|
|
37102
|
+
}
|
|
37103
|
+
}
|
|
37104
|
+
});
|
|
37105
|
+
walk.full(ast, (node) => {
|
|
37106
|
+
if (node.type === "WhileStatement" || node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement") {
|
|
37107
|
+
const body2 = node.body;
|
|
37108
|
+
if (body2 && body2.type === "BlockStatement" && body2.body && body2.body.length > 0) {
|
|
37109
|
+
insertions.push({ offset: body2.start + 1, text: " __checkLoop();" });
|
|
37110
|
+
}
|
|
37111
|
+
}
|
|
37112
|
+
});
|
|
37113
|
+
for (const fn of functionsNeedingAsync) {
|
|
37114
|
+
insertions.push({ offset: fn.start, text: "async " });
|
|
37115
|
+
}
|
|
37116
|
+
const body = ast.body;
|
|
37117
|
+
if (body && body.length > 0) {
|
|
37118
|
+
const lastStmt = body[body.length - 1];
|
|
37119
|
+
if (lastStmt.type === "ExpressionStatement") {
|
|
37120
|
+
insertions.push({ offset: lastStmt.start, text: "return " });
|
|
37121
|
+
}
|
|
37122
|
+
}
|
|
37123
|
+
insertions.sort((a, b) => b.offset - a.offset);
|
|
37124
|
+
let transformed = code;
|
|
37125
|
+
for (const ins of insertions) {
|
|
37126
|
+
transformed = transformed.slice(0, ins.offset) + ins.text + transformed.slice(ins.offset);
|
|
37127
|
+
}
|
|
37128
|
+
return `return (async () => {
|
|
37129
|
+
${transformed}
|
|
37130
|
+
})()`;
|
|
37131
|
+
}
|
|
37132
|
+
function getCalleeName(callExpr) {
|
|
37133
|
+
const callee = callExpr.callee;
|
|
37134
|
+
if (callee.type === "Identifier" && callee.name) {
|
|
37135
|
+
return callee.name;
|
|
37136
|
+
}
|
|
37137
|
+
return null;
|
|
37138
|
+
}
|
|
37139
|
+
function lintUnknownCalls(_code, ast, knownGlobals, disabledBuiltins) {
|
|
37140
|
+
const declaredFunctions = /* @__PURE__ */ new Set();
|
|
37141
|
+
walk.full(ast, (node) => {
|
|
37142
|
+
if (node.type === "FunctionDeclaration" && node.id?.name) {
|
|
37143
|
+
declaredFunctions.add(node.id.name);
|
|
37144
|
+
}
|
|
37145
|
+
if (node.type === "VariableDeclarator" && node.id?.name) {
|
|
37146
|
+
const init = node.init;
|
|
37147
|
+
if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) {
|
|
37148
|
+
declaredFunctions.add(node.id.name);
|
|
37149
|
+
}
|
|
37150
|
+
}
|
|
37151
|
+
});
|
|
37152
|
+
const warnings = [];
|
|
37153
|
+
walk.full(ast, (node) => {
|
|
37154
|
+
if (node.type !== "CallExpression") return;
|
|
37155
|
+
const name = getCalleeName(node);
|
|
37156
|
+
if (!name) return;
|
|
37157
|
+
if (knownGlobals.has(name) || JS_BUILTINS.has(name) || declaredFunctions.has(name)) return;
|
|
37158
|
+
const loc = node.loc?.start;
|
|
37159
|
+
const lineInfo = loc ? ` (line ${loc.line}, column ${loc.column})` : "";
|
|
37160
|
+
if (disabledBuiltins?.has(name)) {
|
|
37161
|
+
const hint = disabledBuiltins.get(name);
|
|
37162
|
+
warnings.push(`'${name}()' is not enabled${lineInfo}. ${hint}`);
|
|
37163
|
+
return;
|
|
37164
|
+
}
|
|
37165
|
+
const allNames = [...knownGlobals];
|
|
37166
|
+
let bestMatch = "";
|
|
37167
|
+
let bestDist = Infinity;
|
|
37168
|
+
for (const candidate of allNames) {
|
|
37169
|
+
const dist = levenshtein(name.toLowerCase(), candidate.toLowerCase());
|
|
37170
|
+
if (dist < bestDist) {
|
|
37171
|
+
bestDist = dist;
|
|
37172
|
+
bestMatch = candidate;
|
|
37173
|
+
}
|
|
37174
|
+
}
|
|
37175
|
+
const maxLen = Math.max(name.length, bestMatch.length);
|
|
37176
|
+
const suggestion = bestDist <= Math.ceil(maxLen * 0.4) ? ` Did you mean '${bestMatch}'?` : "";
|
|
37177
|
+
warnings.push(`Unknown function '${name}()'${lineInfo}.${suggestion}`);
|
|
37178
|
+
});
|
|
37179
|
+
if (warnings.length > 0) {
|
|
37180
|
+
throw new Error(`Script lint errors:
|
|
37181
|
+
${warnings.map((w) => ` - ${w}`).join("\n")}`);
|
|
37182
|
+
}
|
|
37183
|
+
}
|
|
37184
|
+
function tryParseJSON(text) {
|
|
37185
|
+
if (typeof text !== "string") return text;
|
|
37186
|
+
const firstChar = text.trimStart()[0];
|
|
37187
|
+
if (firstChar === "{" || firstChar === "[") {
|
|
37188
|
+
try {
|
|
37189
|
+
return JSON.parse(text);
|
|
37190
|
+
} catch {
|
|
37191
|
+
}
|
|
37192
|
+
}
|
|
37193
|
+
return text;
|
|
37194
|
+
}
|
|
37195
|
+
function buildToolGlobals(opts) {
|
|
37196
|
+
const { resolvedTools, mcpClients, toolContext, workflowContext } = opts;
|
|
37197
|
+
const globals = {};
|
|
37198
|
+
const asyncFunctionNames = /* @__PURE__ */ new Set();
|
|
37199
|
+
const commandTools = {};
|
|
37200
|
+
for (const [name, tool] of resolvedTools) {
|
|
37201
|
+
if (!isWorkflowTool(tool)) {
|
|
37202
|
+
commandTools[name] = tool;
|
|
37203
|
+
}
|
|
37204
|
+
}
|
|
37205
|
+
const toolExecutor = new CustomToolExecutor(commandTools);
|
|
37206
|
+
const allToolInfo = [];
|
|
37207
|
+
for (const [name, tool] of resolvedTools) {
|
|
37208
|
+
const toolFn = async (args = {}) => {
|
|
37209
|
+
try {
|
|
37210
|
+
if (isWorkflowTool(tool)) {
|
|
37211
|
+
if (!workflowContext) {
|
|
37212
|
+
return `ERROR: Workflow context not available for tool '${name}'`;
|
|
37213
|
+
}
|
|
37214
|
+
return await executeWorkflowAsTool(
|
|
37215
|
+
tool.__workflowId,
|
|
37216
|
+
args,
|
|
37217
|
+
workflowContext,
|
|
37218
|
+
tool.__argsOverrides
|
|
37219
|
+
);
|
|
37220
|
+
}
|
|
37221
|
+
return await toolExecutor.execute(name, args, toolContext);
|
|
37222
|
+
} catch (e) {
|
|
37223
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
37224
|
+
logger.warn(`[script:${name}] Tool error: ${msg}`);
|
|
37225
|
+
return `ERROR: ${msg}`;
|
|
37226
|
+
}
|
|
37227
|
+
};
|
|
37228
|
+
globals[name] = toolFn;
|
|
37229
|
+
asyncFunctionNames.add(name);
|
|
37230
|
+
allToolInfo.push({ name, description: tool.description });
|
|
37231
|
+
}
|
|
37232
|
+
if (mcpClients) {
|
|
37233
|
+
for (const entry of mcpClients) {
|
|
37234
|
+
for (const mcpTool of entry.tools) {
|
|
37235
|
+
const globalName = `${entry.serverName}_${mcpTool.name}`;
|
|
37236
|
+
const mcpToolFn = async (args = {}) => {
|
|
37237
|
+
try {
|
|
37238
|
+
const result = await entry.client.callTool({
|
|
37239
|
+
name: mcpTool.name,
|
|
37240
|
+
arguments: args
|
|
37241
|
+
});
|
|
37242
|
+
const content = result?.content;
|
|
37243
|
+
if (Array.isArray(content) && content.length > 0) {
|
|
37244
|
+
const text = content[0]?.text;
|
|
37245
|
+
if (text !== void 0) {
|
|
37246
|
+
return tryParseJSON(text);
|
|
37247
|
+
}
|
|
37248
|
+
}
|
|
37249
|
+
return result;
|
|
37250
|
+
} catch (e) {
|
|
37251
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
37252
|
+
logger.warn(`[script:${globalName}] MCP tool error: ${msg}`);
|
|
37253
|
+
return `ERROR: ${msg}`;
|
|
37254
|
+
}
|
|
37255
|
+
};
|
|
37256
|
+
globals[globalName] = mcpToolFn;
|
|
37257
|
+
asyncFunctionNames.add(globalName);
|
|
37258
|
+
allToolInfo.push({ name: globalName, description: mcpTool.description });
|
|
37259
|
+
}
|
|
37260
|
+
}
|
|
37261
|
+
}
|
|
37262
|
+
const callToolFn = async (name, args = {}) => {
|
|
37263
|
+
const fn = globals[name];
|
|
37264
|
+
if (!fn || typeof fn !== "function") {
|
|
37265
|
+
const available = Array.from(asyncFunctionNames).join(", ");
|
|
37266
|
+
return `ERROR: Tool '${name}' not found. Available: ${available}`;
|
|
37267
|
+
}
|
|
37268
|
+
return fn(args);
|
|
37269
|
+
};
|
|
37270
|
+
globals.callTool = callToolFn;
|
|
37271
|
+
asyncFunctionNames.add("callTool");
|
|
37272
|
+
globals.listTools = () => {
|
|
37273
|
+
return [...allToolInfo];
|
|
37274
|
+
};
|
|
37275
|
+
return { globals, asyncFunctionNames };
|
|
37276
|
+
}
|
|
37277
|
+
function buildBuiltinGlobals(opts) {
|
|
37278
|
+
const globals = {};
|
|
37279
|
+
const asyncFunctionNames = /* @__PURE__ */ new Set();
|
|
37280
|
+
const scheduleFn = async (args = {}) => {
|
|
37281
|
+
try {
|
|
37282
|
+
const { handleScheduleAction: handleScheduleAction2, buildScheduleToolContext: buildScheduleToolContext2 } = await import("./schedule-tool-2COUUTF7.mjs");
|
|
37283
|
+
const { extractSlackContext: extractSlackContext2 } = await import("./schedule-tool-handler-Y2UABBXN.mjs");
|
|
37284
|
+
const parentCtx = opts.sessionInfo?._parentContext;
|
|
37285
|
+
const webhookData = parentCtx?.prInfo?.eventContext?.webhookData;
|
|
37286
|
+
const visorCfg = parentCtx?.config;
|
|
37287
|
+
const slackContext = webhookData ? extractSlackContext2(webhookData) : null;
|
|
37288
|
+
const availableWorkflows = visorCfg?.checks ? Object.keys(visorCfg.checks) : void 0;
|
|
37289
|
+
const permissions = visorCfg?.scheduler?.permissions;
|
|
37290
|
+
const context2 = buildScheduleToolContext2(
|
|
37291
|
+
{
|
|
37292
|
+
slackContext: slackContext || void 0,
|
|
37293
|
+
cliContext: slackContext ? void 0 : { userId: "script" }
|
|
37294
|
+
},
|
|
37295
|
+
availableWorkflows,
|
|
37296
|
+
permissions
|
|
37297
|
+
);
|
|
37298
|
+
return await handleScheduleAction2(args, context2);
|
|
37299
|
+
} catch (e) {
|
|
37300
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
37301
|
+
logger.warn(`[script:schedule] Error: ${msg}`);
|
|
37302
|
+
return `ERROR: ${msg}`;
|
|
37303
|
+
}
|
|
37304
|
+
};
|
|
37305
|
+
globals.schedule = scheduleFn;
|
|
37306
|
+
asyncFunctionNames.add("schedule");
|
|
37307
|
+
if (opts.config.enable_fetch === true) {
|
|
37308
|
+
const fetchFn = async (args = {}) => {
|
|
37309
|
+
try {
|
|
37310
|
+
const url = String(args.url || "");
|
|
37311
|
+
if (!url) return "ERROR: url is required";
|
|
37312
|
+
const method = String(args.method || "GET");
|
|
37313
|
+
const headers = args.headers || {};
|
|
37314
|
+
const body = args.body != null ? String(args.body) : void 0;
|
|
37315
|
+
const timeout = Number(args.timeout) || 3e4;
|
|
37316
|
+
const controller = new AbortController();
|
|
37317
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
37318
|
+
try {
|
|
37319
|
+
const resp = await globalThis.fetch(url, {
|
|
37320
|
+
method,
|
|
37321
|
+
headers,
|
|
37322
|
+
body: method !== "GET" ? body : void 0,
|
|
37323
|
+
signal: controller.signal
|
|
37324
|
+
});
|
|
37325
|
+
clearTimeout(timeoutId);
|
|
37326
|
+
const contentType = resp.headers.get("content-type") || "";
|
|
37327
|
+
if (contentType.includes("json")) return await resp.json();
|
|
37328
|
+
const text = await resp.text();
|
|
37329
|
+
try {
|
|
37330
|
+
return JSON.parse(text);
|
|
37331
|
+
} catch {
|
|
37332
|
+
return text;
|
|
37333
|
+
}
|
|
37334
|
+
} finally {
|
|
37335
|
+
clearTimeout(timeoutId);
|
|
37336
|
+
}
|
|
37337
|
+
} catch (e) {
|
|
37338
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
37339
|
+
logger.warn(`[script:fetch] Error: ${msg}`);
|
|
37340
|
+
return `ERROR: ${msg}`;
|
|
37341
|
+
}
|
|
37342
|
+
};
|
|
37343
|
+
globals.fetch = fetchFn;
|
|
37344
|
+
asyncFunctionNames.add("fetch");
|
|
37345
|
+
}
|
|
37346
|
+
const octokit = opts.config.eventContext?.octokit;
|
|
37347
|
+
if (octokit) {
|
|
37348
|
+
const githubFn = async (args = {}) => {
|
|
37349
|
+
try {
|
|
37350
|
+
const op = String(args.op || "");
|
|
37351
|
+
const repoEnv = process.env.GITHUB_REPOSITORY || "";
|
|
37352
|
+
let owner = "";
|
|
37353
|
+
let repo = "";
|
|
37354
|
+
if (repoEnv.includes("/")) {
|
|
37355
|
+
[owner, repo] = repoEnv.split("/");
|
|
37356
|
+
}
|
|
37357
|
+
if (!owner || !repo) {
|
|
37358
|
+
const ec = opts.config.eventContext || {};
|
|
37359
|
+
owner = owner || ec.repository?.owner?.login || "";
|
|
37360
|
+
repo = repo || ec.repository?.name || "";
|
|
37361
|
+
}
|
|
37362
|
+
const prNumber = opts.prInfo?.number;
|
|
37363
|
+
if (!owner || !repo || !prNumber) {
|
|
37364
|
+
return "ERROR: Missing GitHub repo/PR context";
|
|
37365
|
+
}
|
|
37366
|
+
const values = Array.isArray(args.values) ? args.values.map(String) : typeof args.value === "string" ? [args.value] : typeof args.values === "string" ? [args.values] : [];
|
|
37367
|
+
switch (op) {
|
|
37368
|
+
case "labels.add":
|
|
37369
|
+
await octokit.rest.issues.addLabels({
|
|
37370
|
+
owner,
|
|
37371
|
+
repo,
|
|
37372
|
+
issue_number: prNumber,
|
|
37373
|
+
labels: values
|
|
37374
|
+
});
|
|
37375
|
+
return { success: true, op };
|
|
37376
|
+
case "labels.remove":
|
|
37377
|
+
for (const name of values) {
|
|
37378
|
+
try {
|
|
37379
|
+
await octokit.rest.issues.removeLabel({
|
|
37380
|
+
owner,
|
|
37381
|
+
repo,
|
|
37382
|
+
issue_number: prNumber,
|
|
37383
|
+
name
|
|
37384
|
+
});
|
|
37385
|
+
} catch {
|
|
37386
|
+
}
|
|
37387
|
+
}
|
|
37388
|
+
return { success: true, op };
|
|
37389
|
+
case "comment.create":
|
|
37390
|
+
await octokit.rest.issues.createComment({
|
|
37391
|
+
owner,
|
|
37392
|
+
repo,
|
|
37393
|
+
issue_number: prNumber,
|
|
37394
|
+
body: values[0] || ""
|
|
37395
|
+
});
|
|
37396
|
+
return { success: true, op };
|
|
37397
|
+
default:
|
|
37398
|
+
return `ERROR: Unknown github op '${op}'. Supported: labels.add, labels.remove, comment.create`;
|
|
37399
|
+
}
|
|
37400
|
+
} catch (e) {
|
|
37401
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
37402
|
+
logger.warn(`[script:github] Error: ${msg}`);
|
|
37403
|
+
return `ERROR: ${msg}`;
|
|
37404
|
+
}
|
|
37405
|
+
};
|
|
37406
|
+
globals.github = githubFn;
|
|
37407
|
+
asyncFunctionNames.add("github");
|
|
37408
|
+
}
|
|
37409
|
+
if (opts.config.enable_bash === true) {
|
|
37410
|
+
const bashFn = async (args = {}) => {
|
|
37411
|
+
try {
|
|
37412
|
+
const { CommandExecutor } = await import("./command-executor-TOYBBE7S.mjs");
|
|
37413
|
+
const executor = CommandExecutor.getInstance();
|
|
37414
|
+
const command = String(args.command || "");
|
|
37415
|
+
if (!command) return "ERROR: command is required";
|
|
37416
|
+
return await executor.execute(command, {
|
|
37417
|
+
cwd: args.cwd ? String(args.cwd) : void 0,
|
|
37418
|
+
env: args.env,
|
|
37419
|
+
timeout: Number(args.timeout) || 3e4
|
|
37420
|
+
});
|
|
37421
|
+
} catch (e) {
|
|
37422
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
37423
|
+
logger.warn(`[script:bash] Error: ${msg}`);
|
|
37424
|
+
return `ERROR: ${msg}`;
|
|
37425
|
+
}
|
|
37426
|
+
};
|
|
37427
|
+
globals.bash = bashFn;
|
|
37428
|
+
asyncFunctionNames.add("bash");
|
|
37429
|
+
}
|
|
37430
|
+
return { globals, asyncFunctionNames };
|
|
37431
|
+
}
|
|
37432
|
+
var JS_BUILTINS;
|
|
37433
|
+
var init_script_tool_environment = __esm({
|
|
37434
|
+
"src/utils/script-tool-environment.ts"() {
|
|
37435
|
+
"use strict";
|
|
37436
|
+
init_custom_tool_executor();
|
|
37437
|
+
init_workflow_tool_executor();
|
|
37438
|
+
init_logger();
|
|
37439
|
+
JS_BUILTINS = /* @__PURE__ */ new Set([
|
|
37440
|
+
// Constructors & types
|
|
37441
|
+
"Array",
|
|
37442
|
+
"Object",
|
|
37443
|
+
"String",
|
|
37444
|
+
"Number",
|
|
37445
|
+
"Boolean",
|
|
37446
|
+
"Date",
|
|
37447
|
+
"RegExp",
|
|
37448
|
+
"Error",
|
|
37449
|
+
"TypeError",
|
|
37450
|
+
"RangeError",
|
|
37451
|
+
"Map",
|
|
37452
|
+
"Set",
|
|
37453
|
+
"WeakMap",
|
|
37454
|
+
"WeakSet",
|
|
37455
|
+
"Promise",
|
|
37456
|
+
"Symbol",
|
|
37457
|
+
"BigInt",
|
|
37458
|
+
"Proxy",
|
|
37459
|
+
"Reflect",
|
|
37460
|
+
// Static methods commonly called as functions
|
|
37461
|
+
"parseInt",
|
|
37462
|
+
"parseFloat",
|
|
37463
|
+
"isNaN",
|
|
37464
|
+
"isFinite",
|
|
37465
|
+
"encodeURIComponent",
|
|
37466
|
+
"decodeURIComponent",
|
|
37467
|
+
"encodeURI",
|
|
37468
|
+
"decodeURI",
|
|
37469
|
+
// Timers
|
|
37470
|
+
"setTimeout",
|
|
37471
|
+
"clearTimeout",
|
|
37472
|
+
"setInterval",
|
|
37473
|
+
"clearInterval",
|
|
37474
|
+
// JSON/Math accessed via method calls are on objects, but just in case
|
|
37475
|
+
"JSON",
|
|
37476
|
+
"Math",
|
|
37477
|
+
"console",
|
|
37478
|
+
// Node.js globals — sandbox blocks these at runtime with a better error
|
|
37479
|
+
"require",
|
|
37480
|
+
"process",
|
|
37481
|
+
"Buffer",
|
|
37482
|
+
"global",
|
|
37483
|
+
"globalThis",
|
|
37484
|
+
// Common patterns
|
|
37485
|
+
"eval",
|
|
37486
|
+
"Function",
|
|
37487
|
+
"alert",
|
|
37488
|
+
"confirm",
|
|
37489
|
+
"prompt"
|
|
37490
|
+
]);
|
|
37491
|
+
}
|
|
37492
|
+
});
|
|
37493
|
+
|
|
37576
37494
|
// src/providers/script-check-provider.ts
|
|
37577
37495
|
var ScriptCheckProvider;
|
|
37578
37496
|
var init_script_check_provider = __esm({
|
|
@@ -37585,6 +37503,10 @@ var init_script_check_provider = __esm({
|
|
|
37585
37503
|
init_sandbox();
|
|
37586
37504
|
init_template_context();
|
|
37587
37505
|
init_script_memory_ops();
|
|
37506
|
+
init_tool_resolver();
|
|
37507
|
+
init_script_tool_environment();
|
|
37508
|
+
init_workflow_tool_executor();
|
|
37509
|
+
init_env_resolver();
|
|
37588
37510
|
ScriptCheckProvider = class extends CheckProvider {
|
|
37589
37511
|
liquid;
|
|
37590
37512
|
constructor() {
|
|
@@ -37601,7 +37523,7 @@ var init_script_check_provider = __esm({
|
|
|
37601
37523
|
return "script";
|
|
37602
37524
|
}
|
|
37603
37525
|
getDescription() {
|
|
37604
|
-
return "Execute JavaScript with access to PR context, dependency outputs, and
|
|
37526
|
+
return "Execute JavaScript with access to PR context, dependency outputs, memory, and tools.";
|
|
37605
37527
|
}
|
|
37606
37528
|
async validateConfig(config) {
|
|
37607
37529
|
if (!config || typeof config !== "object") return false;
|
|
@@ -37651,16 +37573,77 @@ var init_script_check_provider = __esm({
|
|
|
37651
37573
|
ctx.atob = (str) => {
|
|
37652
37574
|
return Buffer.from(String(str), "base64").toString("binary");
|
|
37653
37575
|
};
|
|
37576
|
+
const { globals: builtinGlobals, asyncFunctionNames: builtinAsyncNames } = buildBuiltinGlobals({
|
|
37577
|
+
config,
|
|
37578
|
+
prInfo,
|
|
37579
|
+
sessionInfo: _sessionInfo
|
|
37580
|
+
});
|
|
37581
|
+
Object.assign(ctx, builtinGlobals);
|
|
37582
|
+
const hasTools = Array.isArray(config.tools) || config.tools_js || config.mcp_servers;
|
|
37654
37583
|
const sandbox = this.createSecureSandbox();
|
|
37655
37584
|
let result;
|
|
37585
|
+
let mcpClients = [];
|
|
37656
37586
|
try {
|
|
37657
|
-
|
|
37587
|
+
const asyncFunctionNames = new Set(builtinAsyncNames);
|
|
37588
|
+
if (hasTools) {
|
|
37589
|
+
const toolItems = this.resolveToolItems(config, prInfo, dependencyResults, ctx);
|
|
37590
|
+
const globalTools = config.__globalTools;
|
|
37591
|
+
const resolvedTools = resolveTools(toolItems, globalTools, "[script]");
|
|
37592
|
+
mcpClients = await this.connectMcpServers(config.mcp_servers);
|
|
37593
|
+
const toolContext = {
|
|
37594
|
+
pr: ctx.pr,
|
|
37595
|
+
files: ctx.files || prInfo.files,
|
|
37596
|
+
outputs: ctx.outputs,
|
|
37597
|
+
env: process.env
|
|
37598
|
+
};
|
|
37599
|
+
const parentCtx = _sessionInfo?._parentContext;
|
|
37600
|
+
const workflowContext = {
|
|
37601
|
+
prInfo,
|
|
37602
|
+
outputs: dependencyResults,
|
|
37603
|
+
executionContext: _sessionInfo,
|
|
37604
|
+
workspace: parentCtx?.workspace
|
|
37605
|
+
};
|
|
37606
|
+
const { globals: toolGlobals, asyncFunctionNames: toolAsyncNames } = buildToolGlobals({
|
|
37607
|
+
resolvedTools,
|
|
37608
|
+
mcpClients,
|
|
37609
|
+
toolContext,
|
|
37610
|
+
workflowContext
|
|
37611
|
+
});
|
|
37612
|
+
Object.assign(ctx, toolGlobals);
|
|
37613
|
+
for (const name of toolAsyncNames) asyncFunctionNames.add(name);
|
|
37614
|
+
}
|
|
37615
|
+
let loopIterations = 0;
|
|
37616
|
+
const maxLoopIterations = 1e4;
|
|
37617
|
+
ctx.__checkLoop = () => {
|
|
37618
|
+
loopIterations++;
|
|
37619
|
+
if (loopIterations > maxLoopIterations) {
|
|
37620
|
+
throw new Error(`Loop exceeded maximum of ${maxLoopIterations} iterations`);
|
|
37621
|
+
}
|
|
37622
|
+
};
|
|
37623
|
+
const knownGlobals = new Set(Object.keys(ctx));
|
|
37624
|
+
for (const name of asyncFunctionNames) knownGlobals.add(name);
|
|
37625
|
+
knownGlobals.add("__checkLoop");
|
|
37626
|
+
knownGlobals.add("log");
|
|
37627
|
+
const disabledBuiltins = /* @__PURE__ */ new Map();
|
|
37628
|
+
if (!config.enable_bash) {
|
|
37629
|
+
disabledBuiltins.set("bash", "Add 'enable_bash: true' to your check config to enable it.");
|
|
37630
|
+
}
|
|
37631
|
+
if (!config.enable_fetch) {
|
|
37632
|
+
disabledBuiltins.set(
|
|
37633
|
+
"fetch",
|
|
37634
|
+
"Add 'enable_fetch: true' to your check config to enable it."
|
|
37635
|
+
);
|
|
37636
|
+
}
|
|
37637
|
+
const transformed = transformScriptForAsync(script, asyncFunctionNames, {
|
|
37638
|
+
knownGlobals,
|
|
37639
|
+
disabledBuiltins
|
|
37640
|
+
});
|
|
37641
|
+
result = await compileAndRunAsync(
|
|
37658
37642
|
sandbox,
|
|
37659
|
-
|
|
37643
|
+
transformed,
|
|
37660
37644
|
{ ...ctx },
|
|
37661
37645
|
{
|
|
37662
37646
|
injectLog: true,
|
|
37663
|
-
wrapFunction: true,
|
|
37664
37647
|
logPrefix: "[script]"
|
|
37665
37648
|
}
|
|
37666
37649
|
);
|
|
@@ -37680,6 +37663,8 @@ var init_script_check_provider = __esm({
|
|
|
37680
37663
|
],
|
|
37681
37664
|
output: null
|
|
37682
37665
|
};
|
|
37666
|
+
} finally {
|
|
37667
|
+
await this.disconnectMcpClients(mcpClients);
|
|
37683
37668
|
}
|
|
37684
37669
|
try {
|
|
37685
37670
|
if (needsSave() && memoryStore.getConfig().storage === "file" && memoryStore.getConfig().auto_save) {
|
|
@@ -37705,17 +37690,167 @@ var init_script_check_provider = __esm({
|
|
|
37705
37690
|
}
|
|
37706
37691
|
return out;
|
|
37707
37692
|
}
|
|
37693
|
+
/**
|
|
37694
|
+
* Resolve tool items from static config and optional JS expression.
|
|
37695
|
+
*/
|
|
37696
|
+
resolveToolItems(config, prInfo, dependencyResults, ctx) {
|
|
37697
|
+
let items = [];
|
|
37698
|
+
const staticTools = config.tools;
|
|
37699
|
+
if (Array.isArray(staticTools)) {
|
|
37700
|
+
items = staticTools.filter(
|
|
37701
|
+
(item) => typeof item === "string" || isWorkflowToolReference(item)
|
|
37702
|
+
);
|
|
37703
|
+
}
|
|
37704
|
+
const toolsJsExpr = config.tools_js;
|
|
37705
|
+
if (toolsJsExpr && dependencyResults) {
|
|
37706
|
+
try {
|
|
37707
|
+
const jsSandbox = this.createSecureSandbox();
|
|
37708
|
+
const jsCtx = ctx || buildProviderTemplateContext(prInfo, dependencyResults);
|
|
37709
|
+
jsCtx.env = process.env;
|
|
37710
|
+
jsCtx.inputs = config.workflowInputs || {};
|
|
37711
|
+
const evalResult = compileAndRun(jsSandbox, toolsJsExpr, jsCtx, {
|
|
37712
|
+
injectLog: true,
|
|
37713
|
+
wrapFunction: true,
|
|
37714
|
+
logPrefix: "[tools_js]"
|
|
37715
|
+
});
|
|
37716
|
+
if (Array.isArray(evalResult)) {
|
|
37717
|
+
const dynamic = evalResult.filter(
|
|
37718
|
+
(item) => typeof item === "string" || isWorkflowToolReference(item)
|
|
37719
|
+
);
|
|
37720
|
+
const existingNames = new Set(items.map((i) => typeof i === "string" ? i : i.workflow));
|
|
37721
|
+
for (const tool of dynamic) {
|
|
37722
|
+
const name = typeof tool === "string" ? tool : tool.workflow;
|
|
37723
|
+
if (!existingNames.has(name)) {
|
|
37724
|
+
items.push(tool);
|
|
37725
|
+
}
|
|
37726
|
+
}
|
|
37727
|
+
}
|
|
37728
|
+
} catch (error) {
|
|
37729
|
+
logger.error(
|
|
37730
|
+
`[script] Failed to evaluate tools_js: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
37731
|
+
);
|
|
37732
|
+
}
|
|
37733
|
+
}
|
|
37734
|
+
return items;
|
|
37735
|
+
}
|
|
37736
|
+
/**
|
|
37737
|
+
* Connect to MCP servers and discover their tools.
|
|
37738
|
+
*/
|
|
37739
|
+
async connectMcpServers(mcpServersConfig) {
|
|
37740
|
+
if (!mcpServersConfig || Object.keys(mcpServersConfig).length === 0) {
|
|
37741
|
+
return [];
|
|
37742
|
+
}
|
|
37743
|
+
const entries = [];
|
|
37744
|
+
for (const [serverName, serverConfig] of Object.entries(mcpServersConfig)) {
|
|
37745
|
+
try {
|
|
37746
|
+
const { Client: Client2 } = await import("@modelcontextprotocol/sdk/client/index.js");
|
|
37747
|
+
const client = new Client2(
|
|
37748
|
+
{ name: "visor-script-client", version: "1.0.0" },
|
|
37749
|
+
{ capabilities: {} }
|
|
37750
|
+
);
|
|
37751
|
+
const env = {};
|
|
37752
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
37753
|
+
if (value !== void 0) env[key] = value;
|
|
37754
|
+
}
|
|
37755
|
+
if (serverConfig.env) {
|
|
37756
|
+
for (const [key, value] of Object.entries(serverConfig.env)) {
|
|
37757
|
+
env[key] = String(EnvironmentResolver.resolveValue(String(value)));
|
|
37758
|
+
}
|
|
37759
|
+
}
|
|
37760
|
+
const timeout = (serverConfig.timeout || 60) * 1e3;
|
|
37761
|
+
if (serverConfig.command) {
|
|
37762
|
+
const { StdioClientTransport: StdioClientTransport2 } = await import("@modelcontextprotocol/sdk/client/stdio.js");
|
|
37763
|
+
const transport = new StdioClientTransport2({
|
|
37764
|
+
command: serverConfig.command,
|
|
37765
|
+
args: serverConfig.args,
|
|
37766
|
+
env,
|
|
37767
|
+
stderr: "pipe"
|
|
37768
|
+
});
|
|
37769
|
+
await Promise.race([
|
|
37770
|
+
client.connect(transport),
|
|
37771
|
+
new Promise(
|
|
37772
|
+
(_, reject) => setTimeout(() => reject(new Error("MCP connection timeout")), timeout)
|
|
37773
|
+
)
|
|
37774
|
+
]);
|
|
37775
|
+
} else if (serverConfig.url) {
|
|
37776
|
+
const transportType = serverConfig.transport || "sse";
|
|
37777
|
+
if (transportType === "sse") {
|
|
37778
|
+
const { SSEClientTransport: SSEClientTransport2 } = await import("@modelcontextprotocol/sdk/client/sse.js");
|
|
37779
|
+
await Promise.race([
|
|
37780
|
+
client.connect(new SSEClientTransport2(new URL(serverConfig.url))),
|
|
37781
|
+
new Promise(
|
|
37782
|
+
(_, reject) => setTimeout(() => reject(new Error("MCP connection timeout")), timeout)
|
|
37783
|
+
)
|
|
37784
|
+
]);
|
|
37785
|
+
} else {
|
|
37786
|
+
const { StreamableHTTPClientTransport: StreamableHTTPClientTransport2 } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
37787
|
+
await Promise.race([
|
|
37788
|
+
client.connect(new StreamableHTTPClientTransport2(new URL(serverConfig.url))),
|
|
37789
|
+
new Promise(
|
|
37790
|
+
(_, reject) => setTimeout(() => reject(new Error("MCP connection timeout")), timeout)
|
|
37791
|
+
)
|
|
37792
|
+
]);
|
|
37793
|
+
}
|
|
37794
|
+
} else {
|
|
37795
|
+
logger.warn(`[script] MCP server '${serverName}' has no command or url, skipping`);
|
|
37796
|
+
continue;
|
|
37797
|
+
}
|
|
37798
|
+
let tools = [];
|
|
37799
|
+
try {
|
|
37800
|
+
const listResult = await client.listTools();
|
|
37801
|
+
tools = (listResult?.tools || []).map((t) => ({
|
|
37802
|
+
name: t.name,
|
|
37803
|
+
description: t.description,
|
|
37804
|
+
inputSchema: t.inputSchema
|
|
37805
|
+
}));
|
|
37806
|
+
logger.debug(
|
|
37807
|
+
`[script] MCP '${serverName}': ${tools.length} tools [${tools.map((t) => t.name).join(", ")}]`
|
|
37808
|
+
);
|
|
37809
|
+
} catch (err) {
|
|
37810
|
+
logger.warn(
|
|
37811
|
+
`[script] Could not list tools from MCP '${serverName}': ${err instanceof Error ? err.message : String(err)}`
|
|
37812
|
+
);
|
|
37813
|
+
}
|
|
37814
|
+
entries.push({ client, serverName, tools });
|
|
37815
|
+
} catch (err) {
|
|
37816
|
+
logger.error(
|
|
37817
|
+
`[script] Failed to connect MCP '${serverName}': ${err instanceof Error ? err.message : String(err)}`
|
|
37818
|
+
);
|
|
37819
|
+
}
|
|
37820
|
+
}
|
|
37821
|
+
return entries;
|
|
37822
|
+
}
|
|
37823
|
+
/**
|
|
37824
|
+
* Disconnect all MCP clients.
|
|
37825
|
+
*/
|
|
37826
|
+
async disconnectMcpClients(clients) {
|
|
37827
|
+
for (const entry of clients) {
|
|
37828
|
+
try {
|
|
37829
|
+
await entry.client.close();
|
|
37830
|
+
} catch (err) {
|
|
37831
|
+
logger.debug(
|
|
37832
|
+
`[script] Error closing MCP '${entry.serverName}': ${err instanceof Error ? err.message : String(err)}`
|
|
37833
|
+
);
|
|
37834
|
+
}
|
|
37835
|
+
}
|
|
37836
|
+
}
|
|
37708
37837
|
getSupportedConfigKeys() {
|
|
37709
37838
|
return [
|
|
37710
37839
|
"type",
|
|
37711
37840
|
"content",
|
|
37841
|
+
"tools",
|
|
37842
|
+
"tools_js",
|
|
37843
|
+
"mcp_servers",
|
|
37844
|
+
"enable_fetch",
|
|
37845
|
+
"enable_bash",
|
|
37712
37846
|
"depends_on",
|
|
37713
37847
|
"group",
|
|
37714
37848
|
"on",
|
|
37715
37849
|
"if",
|
|
37716
37850
|
"fail_if",
|
|
37717
37851
|
"on_fail",
|
|
37718
|
-
"on_success"
|
|
37852
|
+
"on_success",
|
|
37853
|
+
"timeout"
|
|
37719
37854
|
];
|
|
37720
37855
|
}
|
|
37721
37856
|
async isAvailable() {
|
|
@@ -37724,15 +37859,14 @@ var init_script_check_provider = __esm({
|
|
|
37724
37859
|
getRequirements() {
|
|
37725
37860
|
return ["No external dependencies required"];
|
|
37726
37861
|
}
|
|
37727
|
-
// No local buildTemplateContext; uses shared builder above
|
|
37728
37862
|
};
|
|
37729
37863
|
}
|
|
37730
37864
|
});
|
|
37731
37865
|
|
|
37732
37866
|
// src/utils/worktree-manager.ts
|
|
37733
|
-
import * as
|
|
37867
|
+
import * as fs9 from "fs";
|
|
37734
37868
|
import * as fsp2 from "fs/promises";
|
|
37735
|
-
import * as
|
|
37869
|
+
import * as path11 from "path";
|
|
37736
37870
|
import * as crypto from "crypto";
|
|
37737
37871
|
var WorktreeManager, worktreeManager;
|
|
37738
37872
|
var init_worktree_manager = __esm({
|
|
@@ -37752,7 +37886,7 @@ var init_worktree_manager = __esm({
|
|
|
37752
37886
|
} catch {
|
|
37753
37887
|
cwd = "/tmp";
|
|
37754
37888
|
}
|
|
37755
|
-
const defaultBasePath = process.env.VISOR_WORKTREE_PATH ||
|
|
37889
|
+
const defaultBasePath = process.env.VISOR_WORKTREE_PATH || path11.join(cwd, ".visor", "worktrees");
|
|
37756
37890
|
this.config = {
|
|
37757
37891
|
enabled: true,
|
|
37758
37892
|
base_path: defaultBasePath,
|
|
@@ -37789,20 +37923,20 @@ var init_worktree_manager = __esm({
|
|
|
37789
37923
|
}
|
|
37790
37924
|
const reposDir = this.getReposDir();
|
|
37791
37925
|
const worktreesDir = this.getWorktreesDir();
|
|
37792
|
-
if (!
|
|
37793
|
-
|
|
37926
|
+
if (!fs9.existsSync(reposDir)) {
|
|
37927
|
+
fs9.mkdirSync(reposDir, { recursive: true });
|
|
37794
37928
|
logger.debug(`Created repos directory: ${reposDir}`);
|
|
37795
37929
|
}
|
|
37796
|
-
if (!
|
|
37797
|
-
|
|
37930
|
+
if (!fs9.existsSync(worktreesDir)) {
|
|
37931
|
+
fs9.mkdirSync(worktreesDir, { recursive: true });
|
|
37798
37932
|
logger.debug(`Created worktrees directory: ${worktreesDir}`);
|
|
37799
37933
|
}
|
|
37800
37934
|
}
|
|
37801
37935
|
getReposDir() {
|
|
37802
|
-
return
|
|
37936
|
+
return path11.join(this.config.base_path, "repos");
|
|
37803
37937
|
}
|
|
37804
37938
|
getWorktreesDir() {
|
|
37805
|
-
return
|
|
37939
|
+
return path11.join(this.config.base_path, "worktrees");
|
|
37806
37940
|
}
|
|
37807
37941
|
/**
|
|
37808
37942
|
* Generate a deterministic worktree ID based on repository and ref.
|
|
@@ -37820,8 +37954,8 @@ var init_worktree_manager = __esm({
|
|
|
37820
37954
|
async getOrCreateBareRepo(repository, repoUrl, token, fetchDepth, cloneTimeoutMs) {
|
|
37821
37955
|
const reposDir = this.getReposDir();
|
|
37822
37956
|
const repoName = repository.replace(/\//g, "-");
|
|
37823
|
-
const bareRepoPath =
|
|
37824
|
-
if (
|
|
37957
|
+
const bareRepoPath = path11.join(reposDir, `${repoName}.git`);
|
|
37958
|
+
if (fs9.existsSync(bareRepoPath)) {
|
|
37825
37959
|
logger.debug(`Bare repository already exists: ${bareRepoPath}`);
|
|
37826
37960
|
const verifyResult = await this.verifyBareRepoRemote(bareRepoPath, repoUrl);
|
|
37827
37961
|
if (verifyResult === "timeout") {
|
|
@@ -37940,11 +38074,11 @@ var init_worktree_manager = __esm({
|
|
|
37940
38074
|
options.cloneTimeoutMs
|
|
37941
38075
|
);
|
|
37942
38076
|
const worktreeId = this.generateWorktreeId(repository, ref);
|
|
37943
|
-
let worktreePath = options.workingDirectory ||
|
|
38077
|
+
let worktreePath = options.workingDirectory || path11.join(this.getWorktreesDir(), worktreeId);
|
|
37944
38078
|
if (options.workingDirectory) {
|
|
37945
38079
|
worktreePath = this.validatePath(options.workingDirectory);
|
|
37946
38080
|
}
|
|
37947
|
-
if (
|
|
38081
|
+
if (fs9.existsSync(worktreePath)) {
|
|
37948
38082
|
logger.debug(`Worktree already exists: ${worktreePath}`);
|
|
37949
38083
|
const metadata2 = await this.loadMetadata(worktreePath);
|
|
37950
38084
|
if (metadata2) {
|
|
@@ -38185,9 +38319,9 @@ var init_worktree_manager = __esm({
|
|
|
38185
38319
|
const result = await this.executeGitCommand(removeCmd, { timeout: 3e4 });
|
|
38186
38320
|
if (result.exitCode !== 0) {
|
|
38187
38321
|
logger.warn(`Failed to remove worktree via git: ${result.stderr}`);
|
|
38188
|
-
if (
|
|
38322
|
+
if (fs9.existsSync(worktree_path)) {
|
|
38189
38323
|
logger.debug(`Manually removing worktree directory`);
|
|
38190
|
-
|
|
38324
|
+
fs9.rmSync(worktree_path, { recursive: true, force: true });
|
|
38191
38325
|
}
|
|
38192
38326
|
}
|
|
38193
38327
|
this.activeWorktrees.delete(worktreeId);
|
|
@@ -38197,19 +38331,19 @@ var init_worktree_manager = __esm({
|
|
|
38197
38331
|
* Save worktree metadata
|
|
38198
38332
|
*/
|
|
38199
38333
|
async saveMetadata(worktreePath, metadata) {
|
|
38200
|
-
const metadataPath =
|
|
38201
|
-
|
|
38334
|
+
const metadataPath = path11.join(worktreePath, ".visor-metadata.json");
|
|
38335
|
+
fs9.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf8");
|
|
38202
38336
|
}
|
|
38203
38337
|
/**
|
|
38204
38338
|
* Load worktree metadata
|
|
38205
38339
|
*/
|
|
38206
38340
|
async loadMetadata(worktreePath) {
|
|
38207
|
-
const metadataPath =
|
|
38208
|
-
if (!
|
|
38341
|
+
const metadataPath = path11.join(worktreePath, ".visor-metadata.json");
|
|
38342
|
+
if (!fs9.existsSync(metadataPath)) {
|
|
38209
38343
|
return null;
|
|
38210
38344
|
}
|
|
38211
38345
|
try {
|
|
38212
|
-
const content =
|
|
38346
|
+
const content = fs9.readFileSync(metadataPath, "utf8");
|
|
38213
38347
|
return JSON.parse(content);
|
|
38214
38348
|
} catch (error) {
|
|
38215
38349
|
logger.warn(`Failed to load metadata: ${error}`);
|
|
@@ -38221,14 +38355,14 @@ var init_worktree_manager = __esm({
|
|
|
38221
38355
|
*/
|
|
38222
38356
|
async listWorktrees() {
|
|
38223
38357
|
const worktreesDir = this.getWorktreesDir();
|
|
38224
|
-
if (!
|
|
38358
|
+
if (!fs9.existsSync(worktreesDir)) {
|
|
38225
38359
|
return [];
|
|
38226
38360
|
}
|
|
38227
|
-
const entries =
|
|
38361
|
+
const entries = fs9.readdirSync(worktreesDir, { withFileTypes: true });
|
|
38228
38362
|
const worktrees = [];
|
|
38229
38363
|
for (const entry of entries) {
|
|
38230
38364
|
if (!entry.isDirectory()) continue;
|
|
38231
|
-
const worktreePath =
|
|
38365
|
+
const worktreePath = path11.join(worktreesDir, entry.name);
|
|
38232
38366
|
const metadata = await this.loadMetadata(worktreePath);
|
|
38233
38367
|
if (metadata) {
|
|
38234
38368
|
worktrees.push({
|
|
@@ -38360,8 +38494,8 @@ var init_worktree_manager = __esm({
|
|
|
38360
38494
|
* Validate path to prevent directory traversal
|
|
38361
38495
|
*/
|
|
38362
38496
|
validatePath(userPath) {
|
|
38363
|
-
const resolvedPath =
|
|
38364
|
-
if (!
|
|
38497
|
+
const resolvedPath = path11.resolve(userPath);
|
|
38498
|
+
if (!path11.isAbsolute(resolvedPath)) {
|
|
38365
38499
|
throw new Error("Path must be absolute");
|
|
38366
38500
|
}
|
|
38367
38501
|
const sensitivePatterns = [
|
|
@@ -39339,7 +39473,7 @@ var init_workflow_projection = __esm({
|
|
|
39339
39473
|
});
|
|
39340
39474
|
|
|
39341
39475
|
// src/providers/workflow-check-provider.ts
|
|
39342
|
-
import * as
|
|
39476
|
+
import * as path12 from "path";
|
|
39343
39477
|
import * as yaml from "js-yaml";
|
|
39344
39478
|
var WorkflowCheckProvider;
|
|
39345
39479
|
var init_workflow_check_provider = __esm({
|
|
@@ -39558,13 +39692,13 @@ var init_workflow_check_provider = __esm({
|
|
|
39558
39692
|
const loadConfigLiquid = createExtendedLiquid();
|
|
39559
39693
|
const loadConfig = (filePath) => {
|
|
39560
39694
|
try {
|
|
39561
|
-
const normalizedBasePath =
|
|
39562
|
-
const resolvedPath =
|
|
39563
|
-
const basePathWithSep = normalizedBasePath.endsWith(
|
|
39695
|
+
const normalizedBasePath = path12.normalize(basePath);
|
|
39696
|
+
const resolvedPath = path12.isAbsolute(filePath) ? path12.normalize(filePath) : path12.normalize(path12.resolve(basePath, filePath));
|
|
39697
|
+
const basePathWithSep = normalizedBasePath.endsWith(path12.sep) ? normalizedBasePath : normalizedBasePath + path12.sep;
|
|
39564
39698
|
if (!resolvedPath.startsWith(basePathWithSep) && resolvedPath !== normalizedBasePath) {
|
|
39565
39699
|
throw new Error(`Path '${filePath}' escapes base directory`);
|
|
39566
39700
|
}
|
|
39567
|
-
const configDir =
|
|
39701
|
+
const configDir = path12.dirname(resolvedPath);
|
|
39568
39702
|
const rawContent = __require("fs").readFileSync(resolvedPath, "utf-8");
|
|
39569
39703
|
const renderedContent = loadConfigLiquid.parseAndRenderSync(rawContent, {
|
|
39570
39704
|
basePath: configDir
|
|
@@ -40015,17 +40149,17 @@ var init_workflow_check_provider = __esm({
|
|
|
40015
40149
|
* so it can be executed by the state machine as a nested workflow.
|
|
40016
40150
|
*/
|
|
40017
40151
|
async loadWorkflowFromConfigPath(sourcePath, baseDir) {
|
|
40018
|
-
const
|
|
40019
|
-
const
|
|
40152
|
+
const path13 = __require("path");
|
|
40153
|
+
const fs10 = __require("fs");
|
|
40020
40154
|
const yaml2 = __require("js-yaml");
|
|
40021
|
-
const resolved =
|
|
40022
|
-
if (!
|
|
40155
|
+
const resolved = path13.isAbsolute(sourcePath) ? sourcePath : path13.resolve(baseDir, sourcePath);
|
|
40156
|
+
if (!fs10.existsSync(resolved)) {
|
|
40023
40157
|
throw new Error(`Workflow config not found at: ${resolved}`);
|
|
40024
40158
|
}
|
|
40025
|
-
const rawContent =
|
|
40159
|
+
const rawContent = fs10.readFileSync(resolved, "utf8");
|
|
40026
40160
|
const rawData = yaml2.load(rawContent);
|
|
40027
40161
|
if (rawData.imports && Array.isArray(rawData.imports)) {
|
|
40028
|
-
const configDir =
|
|
40162
|
+
const configDir = path13.dirname(resolved);
|
|
40029
40163
|
for (const source of rawData.imports) {
|
|
40030
40164
|
const results = await this.registry.import(source, {
|
|
40031
40165
|
basePath: configDir,
|
|
@@ -40055,8 +40189,8 @@ ${errors}`);
|
|
|
40055
40189
|
if (!steps || Object.keys(steps).length === 0) {
|
|
40056
40190
|
throw new Error(`Config '${resolved}' does not contain any steps to execute as a workflow`);
|
|
40057
40191
|
}
|
|
40058
|
-
const id =
|
|
40059
|
-
const name = loaded.name || `Workflow from ${
|
|
40192
|
+
const id = path13.basename(resolved).replace(/\.(ya?ml)$/i, "");
|
|
40193
|
+
const name = loaded.name || `Workflow from ${path13.basename(resolved)}`;
|
|
40060
40194
|
const workflowDef = {
|
|
40061
40195
|
id,
|
|
40062
40196
|
name,
|
|
@@ -40098,4 +40232,4 @@ undici/lib/fetch/body.js:
|
|
|
40098
40232
|
undici/lib/websocket/frame.js:
|
|
40099
40233
|
(*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> *)
|
|
40100
40234
|
*/
|
|
40101
|
-
//# sourceMappingURL=chunk-
|
|
40235
|
+
//# sourceMappingURL=chunk-J6F5K5EG.mjs.map
|