@probelabs/visor 0.1.170 → 0.1.171-ee
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/defaults/code-talk.yaml +39 -238
- package/defaults/intent-router.yaml +1 -0
- package/dist/agent-protocol/task-store.d.ts +5 -0
- package/dist/agent-protocol/task-store.d.ts.map +1 -1
- package/dist/agent-protocol/tasks-cli-handler.d.ts.map +1 -1
- package/dist/agent-protocol/track-execution.d.ts +34 -0
- package/dist/agent-protocol/track-execution.d.ts.map +1 -0
- package/dist/cli-main.d.ts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/defaults/code-talk.yaml +39 -238
- package/dist/defaults/intent-router.yaml +1 -0
- package/dist/frontends/host.d.ts +2 -0
- package/dist/frontends/host.d.ts.map +1 -1
- package/dist/generated/config-schema.d.ts +10 -6
- package/dist/generated/config-schema.d.ts.map +1 -1
- package/dist/generated/config-schema.json +10 -6
- package/dist/index.js +2534 -116
- package/dist/scheduler/scheduler.d.ts +4 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/sdk/{a2a-frontend-YTXQGUDH.mjs → a2a-frontend-GUEGI5SX.mjs} +20 -3
- package/dist/sdk/a2a-frontend-GUEGI5SX.mjs.map +1 -0
- package/dist/sdk/{check-provider-registry-5CMLUEFG.mjs → check-provider-registry-7P2QIKJR.mjs} +8 -8
- package/dist/sdk/{check-provider-registry-STRAOYRJ.mjs → check-provider-registry-ZUU7KSKR.mjs} +7 -7
- package/dist/sdk/{chunk-WNLCRRQO.mjs → chunk-5SBX4KLG.mjs} +2 -2
- package/dist/sdk/{chunk-2CNT2EB3.mjs → chunk-6FXVWL6M.mjs} +3 -3
- package/dist/sdk/{chunk-KFKHU6CM.mjs → chunk-6VVXKXTI.mjs} +19 -2
- package/dist/sdk/chunk-6VVXKXTI.mjs.map +1 -0
- package/dist/sdk/{chunk-SVBF7Y2R.mjs → chunk-A2YVTICA.mjs} +11 -7
- package/dist/sdk/chunk-A2YVTICA.mjs.map +1 -0
- package/dist/sdk/{chunk-DLO46M5M.mjs → chunk-CXA3WUOB.mjs} +62 -23
- package/dist/sdk/chunk-CXA3WUOB.mjs.map +1 -0
- package/dist/sdk/{chunk-62PXPI6Q.mjs → chunk-GGNR347O.mjs} +8 -2
- package/dist/sdk/chunk-GGNR347O.mjs.map +1 -0
- package/dist/sdk/{chunk-NYQTQYGU.mjs → chunk-YCPJBOJB.mjs} +68 -29
- package/dist/sdk/chunk-YCPJBOJB.mjs.map +1 -0
- package/dist/sdk/{config-IHECYTNT.mjs → config-6GWD673K.mjs} +2 -2
- package/dist/sdk/{failure-condition-evaluator-NJO6DSL4.mjs → failure-condition-evaluator-5HRNHZCC.mjs} +4 -3
- package/dist/sdk/{github-frontend-BAPXDLBB.mjs → github-frontend-ZZRU6P43.mjs} +7 -7
- package/dist/sdk/{host-6HV5FMD7.mjs → host-A7UNRBQU.mjs} +3 -3
- package/dist/sdk/host-A7UNRBQU.mjs.map +1 -0
- package/dist/sdk/{host-K6IZWJG3.mjs → host-ECXTIDWG.mjs} +3 -3
- package/dist/sdk/host-ECXTIDWG.mjs.map +1 -0
- package/dist/sdk/knex-store-CRORFJE6.mjs +527 -0
- package/dist/sdk/knex-store-CRORFJE6.mjs.map +1 -0
- package/dist/sdk/loader-QMJFFST6.mjs +89 -0
- package/dist/sdk/loader-QMJFFST6.mjs.map +1 -0
- package/dist/sdk/opa-policy-engine-S2S2ULEI.mjs +655 -0
- package/dist/sdk/opa-policy-engine-S2S2ULEI.mjs.map +1 -0
- package/dist/sdk/{routing-RWZEXSRZ.mjs → routing-AMRQYI7J.mjs} +5 -4
- package/dist/sdk/{schedule-tool-JCKV47FU.mjs → schedule-tool-DGVJDHJM.mjs} +7 -7
- package/dist/sdk/{schedule-tool-TGWPINHO.mjs → schedule-tool-I6VG3ZVA.mjs} +8 -8
- package/dist/sdk/{schedule-tool-handler-OEBLE5AB.mjs → schedule-tool-handler-DFUC5S55.mjs} +8 -8
- package/dist/sdk/{schedule-tool-handler-UQWDPFP6.mjs → schedule-tool-handler-XLCSBU3E.mjs} +7 -7
- package/dist/sdk/sdk.d.mts +4 -0
- package/dist/sdk/sdk.d.ts +4 -0
- package/dist/sdk/sdk.js +1926 -408
- package/dist/sdk/sdk.js.map +1 -1
- package/dist/sdk/sdk.mjs +7 -7
- package/dist/sdk/{trace-helpers-ZYN23GBG.mjs → trace-helpers-4ZBZWH5W.mjs} +3 -2
- package/dist/sdk/track-execution-VWLQIGY7.mjs +82 -0
- package/dist/sdk/track-execution-VWLQIGY7.mjs.map +1 -0
- package/dist/sdk/validator-XTZJZZJH.mjs +134 -0
- package/dist/sdk/validator-XTZJZZJH.mjs.map +1 -0
- package/dist/sdk/{workflow-check-provider-OAOD3A5U.mjs → workflow-check-provider-AKXDIL2Y.mjs} +8 -8
- package/dist/sdk/{workflow-check-provider-VDSZR7Y5.mjs → workflow-check-provider-KQNLEQEY.mjs} +7 -7
- package/dist/slack/socket-runner.d.ts +4 -0
- package/dist/slack/socket-runner.d.ts.map +1 -1
- package/dist/telemetry/trace-helpers.d.ts.map +1 -1
- package/dist/tui/chat-runner.d.ts +4 -0
- package/dist/tui/chat-runner.d.ts.map +1 -1
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/config.d.ts +4 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/utils/instance-id.d.ts +9 -0
- package/dist/utils/instance-id.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/output/traces/run-2026-03-07T15-43-18-430Z.ndjson +0 -138
- package/dist/output/traces/run-2026-03-07T15-43-56-196Z.ndjson +0 -2266
- package/dist/sdk/a2a-frontend-IPLHACI6.mjs +0 -1605
- package/dist/sdk/a2a-frontend-IPLHACI6.mjs.map +0 -1
- package/dist/sdk/a2a-frontend-YTXQGUDH.mjs.map +0 -1
- package/dist/sdk/check-provider-registry-T5FWS4SW.mjs +0 -30
- package/dist/sdk/chunk-47WAHGHK.mjs +0 -1502
- package/dist/sdk/chunk-47WAHGHK.mjs.map +0 -1
- package/dist/sdk/chunk-62PXPI6Q.mjs.map +0 -1
- package/dist/sdk/chunk-DLO46M5M.mjs.map +0 -1
- package/dist/sdk/chunk-FTUGQP5L.mjs +0 -739
- package/dist/sdk/chunk-KFKHU6CM.mjs.map +0 -1
- package/dist/sdk/chunk-LB77GR4Q.mjs +0 -44771
- package/dist/sdk/chunk-LB77GR4Q.mjs.map +0 -1
- package/dist/sdk/chunk-NYQTQYGU.mjs.map +0 -1
- package/dist/sdk/chunk-SVBF7Y2R.mjs.map +0 -1
- package/dist/sdk/chunk-WNLCRRQO.mjs.map +0 -1
- package/dist/sdk/chunk-ZM7ALGTE.mjs +0 -443
- package/dist/sdk/chunk-ZM7ALGTE.mjs.map +0 -1
- package/dist/sdk/failure-condition-evaluator-T67YFO2Z.mjs +0 -17
- package/dist/sdk/github-frontend-WPTKI4AY.mjs +0 -1386
- package/dist/sdk/github-frontend-WPTKI4AY.mjs.map +0 -1
- package/dist/sdk/host-6HV5FMD7.mjs.map +0 -1
- package/dist/sdk/host-K6IZWJG3.mjs.map +0 -1
- package/dist/sdk/routing-SAGHEUOA.mjs +0 -25
- package/dist/sdk/schedule-tool-H4G5ITNL.mjs +0 -36
- package/dist/sdk/schedule-tool-handler-UQWDPFP6.mjs.map +0 -1
- package/dist/sdk/schedule-tool-handler-ZDAD6SWM.mjs +0 -40
- package/dist/sdk/schedule-tool-handler-ZDAD6SWM.mjs.map +0 -1
- package/dist/sdk/trace-helpers-M7RVAZQ2.mjs +0 -25
- package/dist/sdk/trace-helpers-M7RVAZQ2.mjs.map +0 -1
- package/dist/sdk/trace-helpers-ZYN23GBG.mjs.map +0 -1
- package/dist/sdk/workflow-check-provider-FAO4AUGB.mjs +0 -30
- package/dist/sdk/workflow-check-provider-FAO4AUGB.mjs.map +0 -1
- package/dist/sdk/workflow-check-provider-OAOD3A5U.mjs.map +0 -1
- package/dist/sdk/workflow-check-provider-VDSZR7Y5.mjs.map +0 -1
- package/dist/traces/run-2026-03-07T15-43-18-430Z.ndjson +0 -138
- package/dist/traces/run-2026-03-07T15-43-56-196Z.ndjson +0 -2266
- /package/dist/sdk/{check-provider-registry-5CMLUEFG.mjs.map → check-provider-registry-7P2QIKJR.mjs.map} +0 -0
- /package/dist/sdk/{check-provider-registry-STRAOYRJ.mjs.map → check-provider-registry-ZUU7KSKR.mjs.map} +0 -0
- /package/dist/sdk/{chunk-FTUGQP5L.mjs.map → chunk-5SBX4KLG.mjs.map} +0 -0
- /package/dist/sdk/{chunk-2CNT2EB3.mjs.map → chunk-6FXVWL6M.mjs.map} +0 -0
- /package/dist/sdk/{check-provider-registry-T5FWS4SW.mjs.map → config-6GWD673K.mjs.map} +0 -0
- /package/dist/sdk/{config-IHECYTNT.mjs.map → failure-condition-evaluator-5HRNHZCC.mjs.map} +0 -0
- /package/dist/sdk/{github-frontend-BAPXDLBB.mjs.map → github-frontend-ZZRU6P43.mjs.map} +0 -0
- /package/dist/sdk/{failure-condition-evaluator-NJO6DSL4.mjs.map → routing-AMRQYI7J.mjs.map} +0 -0
- /package/dist/sdk/{failure-condition-evaluator-T67YFO2Z.mjs.map → schedule-tool-DGVJDHJM.mjs.map} +0 -0
- /package/dist/sdk/{routing-RWZEXSRZ.mjs.map → schedule-tool-I6VG3ZVA.mjs.map} +0 -0
- /package/dist/sdk/{routing-SAGHEUOA.mjs.map → schedule-tool-handler-DFUC5S55.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-H4G5ITNL.mjs.map → schedule-tool-handler-XLCSBU3E.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-JCKV47FU.mjs.map → trace-helpers-4ZBZWH5W.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-TGWPINHO.mjs.map → workflow-check-provider-AKXDIL2Y.mjs.map} +0 -0
- /package/dist/sdk/{schedule-tool-handler-OEBLE5AB.mjs.map → workflow-check-provider-KQNLEQEY.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
process.env.VISOR_VERSION = '0.1.
|
|
3
|
-
process.env.PROBE_VERSION = '0.6.0-
|
|
4
|
-
process.env.VISOR_COMMIT_SHA = '
|
|
5
|
-
process.env.VISOR_COMMIT_SHORT = '
|
|
2
|
+
process.env.VISOR_VERSION = '0.1.171';
|
|
3
|
+
process.env.PROBE_VERSION = '0.6.0-rc290';
|
|
4
|
+
process.env.VISOR_COMMIT_SHA = '12d9273ce614376592cf397b6c874dcbfa220b8b';
|
|
5
|
+
process.env.VISOR_COMMIT_SHORT = '12d9273';
|
|
6
6
|
/******/ (() => { // webpackBootstrap
|
|
7
7
|
/******/ var __webpack_modules__ = ({
|
|
8
8
|
|
|
@@ -168110,6 +168110,13 @@ class SqliteTaskStore {
|
|
|
168110
168110
|
messageText = textPart?.text ?? '';
|
|
168111
168111
|
}
|
|
168112
168112
|
catch { }
|
|
168113
|
+
let source = '-';
|
|
168114
|
+
let metadata = {};
|
|
168115
|
+
try {
|
|
168116
|
+
metadata = JSON.parse(r.request_metadata || '{}');
|
|
168117
|
+
source = metadata.source || '-';
|
|
168118
|
+
}
|
|
168119
|
+
catch { }
|
|
168113
168120
|
return {
|
|
168114
168121
|
id: r.id,
|
|
168115
168122
|
context_id: r.context_id,
|
|
@@ -168121,6 +168128,8 @@ class SqliteTaskStore {
|
|
|
168121
168128
|
workflow_id: r.workflow_id,
|
|
168122
168129
|
run_id: r.run_id,
|
|
168123
168130
|
request_message: messageText,
|
|
168131
|
+
source,
|
|
168132
|
+
metadata,
|
|
168124
168133
|
};
|
|
168125
168134
|
});
|
|
168126
168135
|
return { rows, total };
|
|
@@ -168139,6 +168148,11 @@ class SqliteTaskStore {
|
|
|
168139
168148
|
SET state = ?, updated_at = ?, status_message = ?
|
|
168140
168149
|
WHERE id = ?`).run(newState, now, statusMessage ? JSON.stringify(statusMessage) : null, taskId);
|
|
168141
168150
|
}
|
|
168151
|
+
claimTask(taskId, workerId) {
|
|
168152
|
+
const db = this.getDb();
|
|
168153
|
+
const now = nowISO();
|
|
168154
|
+
db.prepare(`UPDATE agent_tasks SET claimed_by = ?, claimed_at = ?, updated_at = ? WHERE id = ?`).run(workerId, now, now, taskId);
|
|
168155
|
+
}
|
|
168142
168156
|
addArtifact(taskId, artifact) {
|
|
168143
168157
|
const db = this.getDb();
|
|
168144
168158
|
const row = db.prepare('SELECT artifacts FROM agent_tasks WHERE id = ?').get(taskId);
|
|
@@ -168396,6 +168410,7 @@ exports.handleTasksCommand = handleTasksCommand;
|
|
|
168396
168410
|
* visor tasks help - Show usage
|
|
168397
168411
|
*/
|
|
168398
168412
|
const logger_1 = __nccwpck_require__(86999);
|
|
168413
|
+
const instance_id_1 = __nccwpck_require__(89942);
|
|
168399
168414
|
const task_store_1 = __nccwpck_require__(24711);
|
|
168400
168415
|
const state_transitions_1 = __nccwpck_require__(68612);
|
|
168401
168416
|
// ---------------------------------------------------------------------------
|
|
@@ -168477,22 +168492,34 @@ function buildFilter(flags) {
|
|
|
168477
168492
|
// ---------------------------------------------------------------------------
|
|
168478
168493
|
// Table formatting
|
|
168479
168494
|
// ---------------------------------------------------------------------------
|
|
168495
|
+
function formatMeta(meta) {
|
|
168496
|
+
const parts = [];
|
|
168497
|
+
if (meta.slack_user)
|
|
168498
|
+
parts.push(`user:${meta.slack_user}`);
|
|
168499
|
+
if (meta.slack_channel)
|
|
168500
|
+
parts.push(`ch:${meta.slack_channel}`);
|
|
168501
|
+
if (meta.trace_id)
|
|
168502
|
+
parts.push(`trace:${String(meta.trace_id).slice(0, 8)}`);
|
|
168503
|
+
if (meta.schedule_id)
|
|
168504
|
+
parts.push(`sched:${meta.schedule_id}`);
|
|
168505
|
+
return parts.join(' ') || '-';
|
|
168506
|
+
}
|
|
168480
168507
|
function formatTable(rows, total) {
|
|
168481
168508
|
if (rows.length === 0)
|
|
168482
168509
|
return 'No tasks found.';
|
|
168483
|
-
const header = ['ID', 'State', '
|
|
168510
|
+
const header = ['ID', 'Source', 'State', 'Workflow', 'Duration', 'Instance', 'Meta', 'Input'];
|
|
168484
168511
|
const data = rows.map(r => {
|
|
168485
|
-
const duration = r.state
|
|
168486
|
-
? formatDuration(r.
|
|
168487
|
-
: r.
|
|
168488
|
-
|
|
168489
|
-
|
|
168490
|
-
const
|
|
168491
|
-
const
|
|
168512
|
+
const duration = (0, state_transitions_1.isTerminalState)(r.state)
|
|
168513
|
+
? formatDuration(r.created_at, r.updated_at)
|
|
168514
|
+
: formatDuration(r.claimed_at || r.created_at);
|
|
168515
|
+
const instance = r.claimed_by || '-';
|
|
168516
|
+
const workflow = r.workflow_id || '-';
|
|
168517
|
+
const source = r.source || '-';
|
|
168518
|
+
const meta = formatMeta(r.metadata);
|
|
168492
168519
|
const input = r.request_message.length > 60
|
|
168493
168520
|
? r.request_message.slice(0, 57) + '...'
|
|
168494
168521
|
: r.request_message || '-';
|
|
168495
|
-
return [r.id.slice(0, 8), r.state,
|
|
168522
|
+
return [r.id.slice(0, 8), source, r.state, workflow, duration, instance, meta, input];
|
|
168496
168523
|
});
|
|
168497
168524
|
const widths = header.map((h, i) => Math.max(h.length, ...data.map(row => row[i].length)));
|
|
168498
168525
|
const sep = widths.map(w => '-'.repeat(w)).join(' ');
|
|
@@ -168505,19 +168532,19 @@ function formatTable(rows, total) {
|
|
|
168505
168532
|
function formatMarkdown(rows, total) {
|
|
168506
168533
|
if (rows.length === 0)
|
|
168507
168534
|
return 'No tasks found.';
|
|
168508
|
-
const header = ['ID', 'State', '
|
|
168535
|
+
const header = ['ID', 'Source', 'State', 'Workflow', 'Duration', 'Instance', 'Meta', 'Input'];
|
|
168509
168536
|
const data = rows.map(r => {
|
|
168510
|
-
const duration = r.state
|
|
168511
|
-
? formatDuration(r.
|
|
168512
|
-
: r.
|
|
168513
|
-
|
|
168514
|
-
|
|
168515
|
-
const
|
|
168516
|
-
const
|
|
168537
|
+
const duration = (0, state_transitions_1.isTerminalState)(r.state)
|
|
168538
|
+
? formatDuration(r.created_at, r.updated_at)
|
|
168539
|
+
: formatDuration(r.claimed_at || r.created_at);
|
|
168540
|
+
const instance = r.claimed_by || '-';
|
|
168541
|
+
const workflow = r.workflow_id || '-';
|
|
168542
|
+
const source = r.source || '-';
|
|
168543
|
+
const meta = formatMeta(r.metadata);
|
|
168517
168544
|
const input = r.request_message.length > 60
|
|
168518
168545
|
? r.request_message.slice(0, 57) + '...'
|
|
168519
168546
|
: r.request_message || '-';
|
|
168520
|
-
return [r.id.slice(0, 8), r.state,
|
|
168547
|
+
return [r.id.slice(0, 8), source, r.state, workflow, duration, instance, meta, input];
|
|
168521
168548
|
});
|
|
168522
168549
|
const lines = [
|
|
168523
168550
|
'| ' + header.join(' | ') + ' |',
|
|
@@ -168534,16 +168561,19 @@ function formatMarkdown(rows, total) {
|
|
|
168534
168561
|
async function handleList(flags) {
|
|
168535
168562
|
const filter = buildFilter(flags);
|
|
168536
168563
|
const output = typeof flags.output === 'string' ? flags.output : 'table';
|
|
168564
|
+
const instanceId = (0, instance_id_1.getInstanceId)();
|
|
168537
168565
|
const render = async () => {
|
|
168538
168566
|
await withTaskStore(async (store) => {
|
|
168539
168567
|
const { rows, total } = store.listTasksRaw(filter);
|
|
168540
168568
|
if (output === 'json') {
|
|
168541
|
-
console.log(JSON.stringify({ tasks: rows, total }, null, 2));
|
|
168569
|
+
console.log(JSON.stringify({ instance_id: instanceId, tasks: rows, total }, null, 2));
|
|
168542
168570
|
}
|
|
168543
168571
|
else if (output === 'markdown') {
|
|
168572
|
+
console.log(`Instance: ${instanceId}\n`);
|
|
168544
168573
|
console.log(formatMarkdown(rows, total));
|
|
168545
168574
|
}
|
|
168546
168575
|
else {
|
|
168576
|
+
console.log(`Instance: ${instanceId}\n`);
|
|
168547
168577
|
console.log(formatTable(rows, total));
|
|
168548
168578
|
}
|
|
168549
168579
|
});
|
|
@@ -168551,7 +168581,7 @@ async function handleList(flags) {
|
|
|
168551
168581
|
if (flags.watch) {
|
|
168552
168582
|
const watchRender = async () => {
|
|
168553
168583
|
process.stdout.write('\x1Bc'); // clear terminal
|
|
168554
|
-
console.log(`visor tasks list (watching, Ctrl+C to exit)\n`);
|
|
168584
|
+
console.log(`visor tasks list (instance: ${instanceId}, watching, Ctrl+C to exit)\n`);
|
|
168555
168585
|
try {
|
|
168556
168586
|
await render();
|
|
168557
168587
|
}
|
|
@@ -168657,17 +168687,67 @@ async function handleCancel(positional, _flags) {
|
|
|
168657
168687
|
});
|
|
168658
168688
|
}
|
|
168659
168689
|
// ---------------------------------------------------------------------------
|
|
168690
|
+
// Subcommand: show
|
|
168691
|
+
// ---------------------------------------------------------------------------
|
|
168692
|
+
async function handleShow(positional, flags) {
|
|
168693
|
+
const taskId = positional[0];
|
|
168694
|
+
if (!taskId) {
|
|
168695
|
+
console.error('Usage: visor tasks show <task-id>');
|
|
168696
|
+
process.exitCode = 1;
|
|
168697
|
+
return;
|
|
168698
|
+
}
|
|
168699
|
+
const output = typeof flags.output === 'string' ? flags.output : 'table';
|
|
168700
|
+
await withTaskStore(async (store) => {
|
|
168701
|
+
// Try to find by prefix match
|
|
168702
|
+
const { rows } = store.listTasksRaw({ limit: 200 });
|
|
168703
|
+
const match = rows.find(r => r.id.startsWith(taskId));
|
|
168704
|
+
if (!match) {
|
|
168705
|
+
console.error(`Task not found: ${taskId}`);
|
|
168706
|
+
process.exitCode = 1;
|
|
168707
|
+
return;
|
|
168708
|
+
}
|
|
168709
|
+
if (output === 'json') {
|
|
168710
|
+
console.log(JSON.stringify(match, null, 2));
|
|
168711
|
+
return;
|
|
168712
|
+
}
|
|
168713
|
+
const duration = (0, state_transitions_1.isTerminalState)(match.state)
|
|
168714
|
+
? formatDuration(match.created_at, match.updated_at)
|
|
168715
|
+
: formatDuration(match.claimed_at || match.created_at);
|
|
168716
|
+
console.log(`Task: ${match.id}`);
|
|
168717
|
+
console.log(`State: ${match.state}`);
|
|
168718
|
+
console.log(`Source: ${match.source}`);
|
|
168719
|
+
console.log(`Workflow: ${match.workflow_id || '-'}`);
|
|
168720
|
+
console.log(`Instance: ${match.claimed_by || '-'}`);
|
|
168721
|
+
console.log(`Duration: ${duration}`);
|
|
168722
|
+
console.log(`Created: ${match.created_at}`);
|
|
168723
|
+
console.log(`Updated: ${match.updated_at}`);
|
|
168724
|
+
if (match.run_id)
|
|
168725
|
+
console.log(`Run ID: ${match.run_id}`);
|
|
168726
|
+
console.log(`Input: ${match.request_message}`);
|
|
168727
|
+
// Show metadata
|
|
168728
|
+
const meta = match.metadata;
|
|
168729
|
+
const metaKeys = Object.keys(meta).filter(k => k !== 'source');
|
|
168730
|
+
if (metaKeys.length > 0) {
|
|
168731
|
+
console.log(`\nMetadata:`);
|
|
168732
|
+
for (const key of metaKeys) {
|
|
168733
|
+
console.log(` ${key}: ${JSON.stringify(meta[key])}`);
|
|
168734
|
+
}
|
|
168735
|
+
}
|
|
168736
|
+
});
|
|
168737
|
+
}
|
|
168738
|
+
// ---------------------------------------------------------------------------
|
|
168660
168739
|
// Help
|
|
168661
168740
|
// ---------------------------------------------------------------------------
|
|
168662
168741
|
function printHelp() {
|
|
168663
168742
|
console.log(`
|
|
168664
|
-
Visor Tasks - Monitor and manage
|
|
168743
|
+
Visor Tasks - Monitor and manage agent tasks
|
|
168665
168744
|
|
|
168666
168745
|
USAGE:
|
|
168667
168746
|
visor tasks [command] [options]
|
|
168668
168747
|
|
|
168669
168748
|
COMMANDS:
|
|
168670
168749
|
list List tasks (default)
|
|
168750
|
+
show <task-id> Show task details (supports prefix match)
|
|
168671
168751
|
stats Queue summary statistics
|
|
168672
168752
|
cancel <task-id> Cancel a task
|
|
168673
168753
|
help Show this help
|
|
@@ -168681,6 +168761,7 @@ OPTIONS:
|
|
|
168681
168761
|
|
|
168682
168762
|
EXAMPLES:
|
|
168683
168763
|
visor tasks List all tasks
|
|
168764
|
+
visor tasks show abc123 Show full task details
|
|
168684
168765
|
visor tasks list --state working Show only working tasks
|
|
168685
168766
|
visor tasks list --agent security-review Show tasks for a specific agent
|
|
168686
168767
|
visor tasks list --output json JSON output
|
|
@@ -168704,6 +168785,9 @@ async function handleTasksCommand(argv) {
|
|
|
168704
168785
|
case 'list':
|
|
168705
168786
|
await handleList(flags);
|
|
168706
168787
|
break;
|
|
168788
|
+
case 'show':
|
|
168789
|
+
await handleShow(positional, flags);
|
|
168790
|
+
break;
|
|
168707
168791
|
case 'stats':
|
|
168708
168792
|
await handleStats(flags);
|
|
168709
168793
|
break;
|
|
@@ -168730,6 +168814,93 @@ async function handleTasksCommand(argv) {
|
|
|
168730
168814
|
}
|
|
168731
168815
|
|
|
168732
168816
|
|
|
168817
|
+
/***/ }),
|
|
168818
|
+
|
|
168819
|
+
/***/ 7628:
|
|
168820
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
168821
|
+
|
|
168822
|
+
"use strict";
|
|
168823
|
+
|
|
168824
|
+
/**
|
|
168825
|
+
* Shared execution tracking helper.
|
|
168826
|
+
*
|
|
168827
|
+
* Wraps any engine execution call with task lifecycle management:
|
|
168828
|
+
* creates a task in 'submitted' state, transitions to 'working',
|
|
168829
|
+
* runs the executor, then sets terminal state (completed/failed).
|
|
168830
|
+
*
|
|
168831
|
+
* Used by CLI, Slack, TUI, and Scheduler frontends when task_tracking is enabled.
|
|
168832
|
+
*/
|
|
168833
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
168834
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
168835
|
+
};
|
|
168836
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
168837
|
+
exports.trackExecution = trackExecution;
|
|
168838
|
+
const crypto_1 = __importDefault(__nccwpck_require__(76982));
|
|
168839
|
+
const path_1 = __importDefault(__nccwpck_require__(16928));
|
|
168840
|
+
const logger_1 = __nccwpck_require__(86999);
|
|
168841
|
+
const lazy_otel_1 = __nccwpck_require__(21084);
|
|
168842
|
+
const instance_id_1 = __nccwpck_require__(89942);
|
|
168843
|
+
/**
|
|
168844
|
+
* Wrap an engine execution call with task lifecycle tracking.
|
|
168845
|
+
*
|
|
168846
|
+
* Creates a task, transitions to 'working', runs the executor,
|
|
168847
|
+
* then sets terminal state. Returns both the task and the original result.
|
|
168848
|
+
* Re-throws any executor error after marking the task as failed.
|
|
168849
|
+
*/
|
|
168850
|
+
async function trackExecution(opts, executor) {
|
|
168851
|
+
const { taskStore, source, configPath, metadata, messageText } = opts;
|
|
168852
|
+
const configName = configPath ? path_1.default.basename(configPath, path_1.default.extname(configPath)) : undefined;
|
|
168853
|
+
const workflowId = configName && opts.workflowId ? `${configName}#${opts.workflowId}` : opts.workflowId;
|
|
168854
|
+
const requestMessage = {
|
|
168855
|
+
message_id: crypto_1.default.randomUUID(),
|
|
168856
|
+
role: 'user',
|
|
168857
|
+
parts: [{ text: messageText }],
|
|
168858
|
+
};
|
|
168859
|
+
const task = taskStore.createTask({
|
|
168860
|
+
contextId: crypto_1.default.randomUUID(),
|
|
168861
|
+
requestMessage,
|
|
168862
|
+
workflowId,
|
|
168863
|
+
requestMetadata: {
|
|
168864
|
+
source,
|
|
168865
|
+
instance_id: (0, instance_id_1.getInstanceId)(),
|
|
168866
|
+
trace_id: lazy_otel_1.trace.getActiveSpan()?.spanContext().traceId || undefined,
|
|
168867
|
+
...metadata,
|
|
168868
|
+
},
|
|
168869
|
+
});
|
|
168870
|
+
const instanceId = (0, instance_id_1.getInstanceId)();
|
|
168871
|
+
taskStore.updateTaskState(task.id, 'working');
|
|
168872
|
+
taskStore.claimTask(task.id, instanceId);
|
|
168873
|
+
logger_1.logger.info(`[TaskTracking] Task ${task.id} started (source=${source}, workflow=${workflowId || '-'}, instance=${instanceId})`);
|
|
168874
|
+
try {
|
|
168875
|
+
const result = await executor();
|
|
168876
|
+
const completedMsg = {
|
|
168877
|
+
message_id: crypto_1.default.randomUUID(),
|
|
168878
|
+
role: 'agent',
|
|
168879
|
+
parts: [{ text: 'Execution completed' }],
|
|
168880
|
+
};
|
|
168881
|
+
taskStore.updateTaskState(task.id, 'completed', completedMsg);
|
|
168882
|
+
logger_1.logger.info(`[TaskTracking] Task ${task.id} completed`);
|
|
168883
|
+
return { task, result };
|
|
168884
|
+
}
|
|
168885
|
+
catch (err) {
|
|
168886
|
+
const errorText = err instanceof Error ? err.message : String(err);
|
|
168887
|
+
const failMessage = {
|
|
168888
|
+
message_id: crypto_1.default.randomUUID(),
|
|
168889
|
+
role: 'agent',
|
|
168890
|
+
parts: [{ text: errorText }],
|
|
168891
|
+
};
|
|
168892
|
+
try {
|
|
168893
|
+
taskStore.updateTaskState(task.id, 'failed', failMessage);
|
|
168894
|
+
logger_1.logger.info(`[TaskTracking] Task ${task.id} failed: ${errorText}`);
|
|
168895
|
+
}
|
|
168896
|
+
catch {
|
|
168897
|
+
// ignore double-failure
|
|
168898
|
+
}
|
|
168899
|
+
throw err; // re-throw to preserve original behavior
|
|
168900
|
+
}
|
|
168901
|
+
}
|
|
168902
|
+
|
|
168903
|
+
|
|
168733
168904
|
/***/ }),
|
|
168734
168905
|
|
|
168735
168906
|
/***/ 43137:
|
|
@@ -171591,7 +171762,7 @@ async function handleDumpPolicyInput(checkId, argv) {
|
|
|
171591
171762
|
let PolicyInputBuilder;
|
|
171592
171763
|
try {
|
|
171593
171764
|
// @ts-ignore — enterprise/ may not exist in OSS builds (caught at runtime)
|
|
171594
|
-
const mod = await Promise.resolve().then(() => __importStar(__nccwpck_require__(
|
|
171765
|
+
const mod = await Promise.resolve().then(() => __importStar(__nccwpck_require__(17117)));
|
|
171595
171766
|
PolicyInputBuilder = mod.PolicyInputBuilder;
|
|
171596
171767
|
}
|
|
171597
171768
|
catch {
|
|
@@ -172627,6 +172798,20 @@ async function main() {
|
|
|
172627
172798
|
catch (err) {
|
|
172628
172799
|
logger_1.logger.debug(`Startup snapshot failed: ${err}`);
|
|
172629
172800
|
}
|
|
172801
|
+
// ---- Shared Task Store (cross-frontend tracking) ----
|
|
172802
|
+
let sharedTaskStore = null;
|
|
172803
|
+
const taskTrackingEnabled = options.taskTracking || config.task_tracking === true;
|
|
172804
|
+
if (taskTrackingEnabled) {
|
|
172805
|
+
try {
|
|
172806
|
+
const { SqliteTaskStore } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(24711)));
|
|
172807
|
+
sharedTaskStore = new SqliteTaskStore();
|
|
172808
|
+
await sharedTaskStore.initialize();
|
|
172809
|
+
logger_1.logger.info('[TaskTracking] Shared task store initialized');
|
|
172810
|
+
}
|
|
172811
|
+
catch (err) {
|
|
172812
|
+
logger_1.logger.warn(`[TaskTracking] Failed to initialize task store: ${err instanceof Error ? err.message : err}`);
|
|
172813
|
+
}
|
|
172814
|
+
}
|
|
172630
172815
|
// ---- A2A Agent Protocol Server ----
|
|
172631
172816
|
let a2aFrontendInstance = null;
|
|
172632
172817
|
let sharedEngine = null;
|
|
@@ -172640,7 +172825,7 @@ async function main() {
|
|
|
172640
172825
|
process.exit(1);
|
|
172641
172826
|
}
|
|
172642
172827
|
const engine = new SMEngine();
|
|
172643
|
-
const frontend = new A2AFrontend(agentConfig);
|
|
172828
|
+
const frontend = new A2AFrontend(agentConfig, sharedTaskStore ?? undefined);
|
|
172644
172829
|
frontend.setEngine(engine);
|
|
172645
172830
|
frontend.setVisorConfig(config);
|
|
172646
172831
|
const eventBus = new EventBus();
|
|
@@ -172717,6 +172902,8 @@ async function main() {
|
|
|
172717
172902
|
threads,
|
|
172718
172903
|
channel_allowlist: allow,
|
|
172719
172904
|
});
|
|
172905
|
+
if (sharedTaskStore)
|
|
172906
|
+
runner.setTaskStore(sharedTaskStore, options.configPath);
|
|
172720
172907
|
await runner.start();
|
|
172721
172908
|
console.log('✅ Slack Socket Mode is running. Press Ctrl+C to exit.');
|
|
172722
172909
|
// Start config file watcher if --watch is enabled
|
|
@@ -172773,6 +172960,12 @@ async function main() {
|
|
|
172773
172960
|
}
|
|
172774
172961
|
}
|
|
172775
172962
|
await runner.stop();
|
|
172963
|
+
if (sharedTaskStore) {
|
|
172964
|
+
try {
|
|
172965
|
+
await sharedTaskStore.shutdown();
|
|
172966
|
+
}
|
|
172967
|
+
catch { }
|
|
172968
|
+
}
|
|
172776
172969
|
process.exit(0);
|
|
172777
172970
|
};
|
|
172778
172971
|
process.on('SIGINT', sig => {
|
|
@@ -173144,12 +173337,27 @@ async function main() {
|
|
|
173144
173337
|
conversation: tuiConversation,
|
|
173145
173338
|
});
|
|
173146
173339
|
// Execute workflow
|
|
173147
|
-
const
|
|
173340
|
+
const tuiExecFn = () => (0, trace_helpers_1.withActiveSpan)('visor.run', {
|
|
173148
173341
|
...(0, trace_helpers_1.getVisorRunAttributes)(),
|
|
173149
173342
|
'visor.run.checks_configured': checksToRun.length,
|
|
173150
173343
|
'visor.run.source': 'tui-rerun',
|
|
173151
173344
|
}, async () => rerunEngine.executeGroupedChecks(prInfo, checksToRun, options.timeout, config, options.output, options.debug || false, options.maxParallelism, options.failFast, tagFilter, async () => { } // No pause gate for TUI re-runs
|
|
173152
173345
|
));
|
|
173346
|
+
let rerunResult;
|
|
173347
|
+
if (sharedTaskStore) {
|
|
173348
|
+
const { trackExecution } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(7628)));
|
|
173349
|
+
const tracked = await trackExecution({
|
|
173350
|
+
taskStore: sharedTaskStore,
|
|
173351
|
+
source: 'tui',
|
|
173352
|
+
workflowId: checksToRun.join(','),
|
|
173353
|
+
configPath: options.configPath,
|
|
173354
|
+
messageText: message,
|
|
173355
|
+
}, tuiExecFn);
|
|
173356
|
+
rerunResult = tracked.result;
|
|
173357
|
+
}
|
|
173358
|
+
else {
|
|
173359
|
+
rerunResult = await tuiExecFn();
|
|
173360
|
+
}
|
|
173153
173361
|
// Show any errors and the last result in the chat
|
|
173154
173362
|
if (rerunResult?.results) {
|
|
173155
173363
|
const allRerunResults = Object.values(rerunResult.results).flat();
|
|
@@ -173251,7 +173459,21 @@ async function main() {
|
|
|
173251
173459
|
process.exit(0);
|
|
173252
173460
|
}
|
|
173253
173461
|
else {
|
|
173254
|
-
|
|
173462
|
+
const cliExecFn = () => (0, trace_helpers_1.withActiveSpan)('visor.run', { ...(0, trace_helpers_1.getVisorRunAttributes)(), 'visor.run.checks_configured': checksToRun.length }, async () => engine.executeGroupedChecks(prInfo, checksToRun, options.timeout, config, options.output, options.debug || false, options.maxParallelism, options.failFast, tagFilter, pauseGate));
|
|
173463
|
+
if (sharedTaskStore) {
|
|
173464
|
+
const { trackExecution } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(7628)));
|
|
173465
|
+
const tracked = await trackExecution({
|
|
173466
|
+
taskStore: sharedTaskStore,
|
|
173467
|
+
source: 'cli',
|
|
173468
|
+
workflowId: checksToRun.join(','),
|
|
173469
|
+
configPath: options.configPath,
|
|
173470
|
+
messageText: options.message || `CLI run: ${checksToRun.join(', ')}`,
|
|
173471
|
+
}, cliExecFn);
|
|
173472
|
+
executionResult = tracked.result;
|
|
173473
|
+
}
|
|
173474
|
+
else {
|
|
173475
|
+
executionResult = await cliExecFn();
|
|
173476
|
+
}
|
|
173255
173477
|
}
|
|
173256
173478
|
// Extract results and statistics from the execution result
|
|
173257
173479
|
const { results: groupedResults, statistics: executionStatistics } = executionResult;
|
|
@@ -173443,6 +173665,12 @@ async function main() {
|
|
|
173443
173665
|
return;
|
|
173444
173666
|
}
|
|
173445
173667
|
// Normal exit path (no debug server)
|
|
173668
|
+
if (sharedTaskStore) {
|
|
173669
|
+
try {
|
|
173670
|
+
await sharedTaskStore.shutdown();
|
|
173671
|
+
}
|
|
173672
|
+
catch { }
|
|
173673
|
+
}
|
|
173446
173674
|
try {
|
|
173447
173675
|
await (0, fallback_ndjson_1.flushNdjson)();
|
|
173448
173676
|
}
|
|
@@ -173652,6 +173880,7 @@ class CLI {
|
|
|
173652
173880
|
.option('--workspace-name <name>', 'Workspace directory name (overrides VISOR_WORKSPACE_NAME)')
|
|
173653
173881
|
.option('--workspace-project-name <name>', 'Main project folder name inside workspace (overrides VISOR_WORKSPACE_PROJECT)')
|
|
173654
173882
|
.option('--watch', 'Watch config file for changes and reload automatically (requires --config)')
|
|
173883
|
+
.option('--task-tracking', 'Enable cross-frontend task tracking (all executions recorded in visor tasks)')
|
|
173655
173884
|
.option('--github-token <token>', 'GitHub token for API operations (env: GITHUB_TOKEN)')
|
|
173656
173885
|
.option('--github-app-id <id>', 'GitHub App ID (env: GITHUB_APP_ID)')
|
|
173657
173886
|
.option('--github-private-key <key>', 'GitHub App private key, PEM content or file path (env: GITHUB_APP_PRIVATE_KEY)')
|
|
@@ -173713,6 +173942,7 @@ class CLI {
|
|
|
173713
173942
|
.option('--workspace-name <name>', 'Workspace directory name (overrides VISOR_WORKSPACE_NAME)')
|
|
173714
173943
|
.option('--workspace-project-name <name>', 'Main project folder name inside workspace (overrides VISOR_WORKSPACE_PROJECT)')
|
|
173715
173944
|
.option('--watch', 'Watch config file for changes and reload automatically (requires --config)')
|
|
173945
|
+
.option('--task-tracking', 'Enable cross-frontend task tracking (all executions recorded in visor tasks)')
|
|
173716
173946
|
.option('--github-token <token>', 'GitHub token for API operations (env: GITHUB_TOKEN)')
|
|
173717
173947
|
.option('--github-app-id <id>', 'GitHub App ID (env: GITHUB_APP_ID)')
|
|
173718
173948
|
.option('--github-private-key <key>', 'GitHub App private key, PEM content or file path (env: GITHUB_APP_PRIVATE_KEY)')
|
|
@@ -173787,6 +174017,7 @@ class CLI {
|
|
|
173787
174017
|
workspaceName: options.workspaceName,
|
|
173788
174018
|
workspaceProjectName: options.workspaceProjectName,
|
|
173789
174019
|
watch: Boolean(options.watch),
|
|
174020
|
+
taskTracking: Boolean(options.taskTracking),
|
|
173790
174021
|
githubToken: options.githubToken,
|
|
173791
174022
|
githubAppId: options.githubAppId,
|
|
173792
174023
|
githubPrivateKey: options.githubPrivateKey,
|
|
@@ -177560,6 +177791,1810 @@ class DependencyResolver {
|
|
|
177560
177791
|
exports.DependencyResolver = DependencyResolver;
|
|
177561
177792
|
|
|
177562
177793
|
|
|
177794
|
+
/***/ }),
|
|
177795
|
+
|
|
177796
|
+
/***/ 50069:
|
|
177797
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
177798
|
+
|
|
177799
|
+
"use strict";
|
|
177800
|
+
|
|
177801
|
+
/**
|
|
177802
|
+
* Copyright (c) ProbeLabs. All rights reserved.
|
|
177803
|
+
* Licensed under the Elastic License 2.0; you may not use this file except
|
|
177804
|
+
* in compliance with the Elastic License 2.0.
|
|
177805
|
+
*/
|
|
177806
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
177807
|
+
if (k2 === undefined) k2 = k;
|
|
177808
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
177809
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
177810
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
177811
|
+
}
|
|
177812
|
+
Object.defineProperty(o, k2, desc);
|
|
177813
|
+
}) : (function(o, m, k, k2) {
|
|
177814
|
+
if (k2 === undefined) k2 = k;
|
|
177815
|
+
o[k2] = m[k];
|
|
177816
|
+
}));
|
|
177817
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
177818
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
177819
|
+
}) : function(o, v) {
|
|
177820
|
+
o["default"] = v;
|
|
177821
|
+
});
|
|
177822
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
177823
|
+
var ownKeys = function(o) {
|
|
177824
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
177825
|
+
var ar = [];
|
|
177826
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
177827
|
+
return ar;
|
|
177828
|
+
};
|
|
177829
|
+
return ownKeys(o);
|
|
177830
|
+
};
|
|
177831
|
+
return function (mod) {
|
|
177832
|
+
if (mod && mod.__esModule) return mod;
|
|
177833
|
+
var result = {};
|
|
177834
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
177835
|
+
__setModuleDefault(result, mod);
|
|
177836
|
+
return result;
|
|
177837
|
+
};
|
|
177838
|
+
})();
|
|
177839
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
177840
|
+
exports.LicenseValidator = void 0;
|
|
177841
|
+
const crypto = __importStar(__nccwpck_require__(76982));
|
|
177842
|
+
const fs = __importStar(__nccwpck_require__(79896));
|
|
177843
|
+
const path = __importStar(__nccwpck_require__(16928));
|
|
177844
|
+
class LicenseValidator {
|
|
177845
|
+
/** Ed25519 public key for license verification (PEM format). */
|
|
177846
|
+
static PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----\n' +
|
|
177847
|
+
'MCowBQYDK2VwAyEAI/Zd08EFmgIdrDm/HXd0l3/5GBt7R1PrdvhdmEXhJlU=\n' +
|
|
177848
|
+
'-----END PUBLIC KEY-----\n';
|
|
177849
|
+
cache = null;
|
|
177850
|
+
static CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
177851
|
+
static GRACE_PERIOD = 72 * 3600 * 1000; // 72 hours after expiry
|
|
177852
|
+
/**
|
|
177853
|
+
* Load and validate license from environment or file.
|
|
177854
|
+
*
|
|
177855
|
+
* Resolution order:
|
|
177856
|
+
* 1. VISOR_LICENSE env var (JWT string)
|
|
177857
|
+
* 2. VISOR_LICENSE_FILE env var (path to file)
|
|
177858
|
+
* 3. .visor-license in project root (cwd)
|
|
177859
|
+
* 4. .visor-license in ~/.config/visor/
|
|
177860
|
+
*/
|
|
177861
|
+
async loadAndValidate() {
|
|
177862
|
+
// Return cached result if still fresh
|
|
177863
|
+
if (this.cache && Date.now() - this.cache.validatedAt < LicenseValidator.CACHE_TTL) {
|
|
177864
|
+
return this.cache.payload;
|
|
177865
|
+
}
|
|
177866
|
+
const token = this.resolveToken();
|
|
177867
|
+
if (!token)
|
|
177868
|
+
return null;
|
|
177869
|
+
const payload = this.verifyAndDecode(token);
|
|
177870
|
+
if (!payload)
|
|
177871
|
+
return null;
|
|
177872
|
+
this.cache = { payload, validatedAt: Date.now() };
|
|
177873
|
+
return payload;
|
|
177874
|
+
}
|
|
177875
|
+
/** Check if a specific feature is licensed */
|
|
177876
|
+
hasFeature(feature) {
|
|
177877
|
+
if (!this.cache)
|
|
177878
|
+
return false;
|
|
177879
|
+
return this.cache.payload.features.includes(feature);
|
|
177880
|
+
}
|
|
177881
|
+
/** Check if license is valid (with grace period) */
|
|
177882
|
+
isValid() {
|
|
177883
|
+
if (!this.cache)
|
|
177884
|
+
return false;
|
|
177885
|
+
const now = Date.now();
|
|
177886
|
+
const expiryMs = this.cache.payload.exp * 1000;
|
|
177887
|
+
return now < expiryMs + LicenseValidator.GRACE_PERIOD;
|
|
177888
|
+
}
|
|
177889
|
+
/** Check if the license is within its grace period (expired but still valid) */
|
|
177890
|
+
isInGracePeriod() {
|
|
177891
|
+
if (!this.cache)
|
|
177892
|
+
return false;
|
|
177893
|
+
const now = Date.now();
|
|
177894
|
+
const expiryMs = this.cache.payload.exp * 1000;
|
|
177895
|
+
return now >= expiryMs && now < expiryMs + LicenseValidator.GRACE_PERIOD;
|
|
177896
|
+
}
|
|
177897
|
+
resolveToken() {
|
|
177898
|
+
// 1. Direct env var
|
|
177899
|
+
if (process.env.VISOR_LICENSE) {
|
|
177900
|
+
return process.env.VISOR_LICENSE.trim();
|
|
177901
|
+
}
|
|
177902
|
+
// 2. File path from env (validate against path traversal)
|
|
177903
|
+
if (process.env.VISOR_LICENSE_FILE) {
|
|
177904
|
+
// path.resolve() produces an absolute path with all '..' segments resolved,
|
|
177905
|
+
// so a separate resolved.includes('..') check is unnecessary.
|
|
177906
|
+
const resolved = path.resolve(process.env.VISOR_LICENSE_FILE);
|
|
177907
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
177908
|
+
const allowedPrefixes = [path.normalize(process.cwd())];
|
|
177909
|
+
if (home)
|
|
177910
|
+
allowedPrefixes.push(path.normalize(path.join(home, '.config', 'visor')));
|
|
177911
|
+
// Resolve symlinks so an attacker cannot create a symlink inside an
|
|
177912
|
+
// allowed prefix that points to an arbitrary file outside it.
|
|
177913
|
+
let realPath;
|
|
177914
|
+
try {
|
|
177915
|
+
realPath = fs.realpathSync(resolved);
|
|
177916
|
+
}
|
|
177917
|
+
catch {
|
|
177918
|
+
return null; // File doesn't exist or isn't accessible
|
|
177919
|
+
}
|
|
177920
|
+
const isSafe = allowedPrefixes.some(prefix => realPath === prefix || realPath.startsWith(prefix + path.sep));
|
|
177921
|
+
if (!isSafe)
|
|
177922
|
+
return null;
|
|
177923
|
+
return this.readFile(realPath);
|
|
177924
|
+
}
|
|
177925
|
+
// 3. .visor-license in cwd
|
|
177926
|
+
const cwdPath = path.join(process.cwd(), '.visor-license');
|
|
177927
|
+
const cwdToken = this.readFile(cwdPath);
|
|
177928
|
+
if (cwdToken)
|
|
177929
|
+
return cwdToken;
|
|
177930
|
+
// 4. ~/.config/visor/.visor-license
|
|
177931
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
177932
|
+
if (home) {
|
|
177933
|
+
const configPath = path.join(home, '.config', 'visor', '.visor-license');
|
|
177934
|
+
const configToken = this.readFile(configPath);
|
|
177935
|
+
if (configToken)
|
|
177936
|
+
return configToken;
|
|
177937
|
+
}
|
|
177938
|
+
return null;
|
|
177939
|
+
}
|
|
177940
|
+
readFile(filePath) {
|
|
177941
|
+
try {
|
|
177942
|
+
return fs.readFileSync(filePath, 'utf-8').trim();
|
|
177943
|
+
}
|
|
177944
|
+
catch {
|
|
177945
|
+
return null;
|
|
177946
|
+
}
|
|
177947
|
+
}
|
|
177948
|
+
verifyAndDecode(token) {
|
|
177949
|
+
try {
|
|
177950
|
+
const parts = token.split('.');
|
|
177951
|
+
if (parts.length !== 3)
|
|
177952
|
+
return null;
|
|
177953
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
177954
|
+
// Decode header to verify algorithm
|
|
177955
|
+
const header = JSON.parse(Buffer.from(headerB64, 'base64url').toString());
|
|
177956
|
+
if (header.alg !== 'EdDSA')
|
|
177957
|
+
return null;
|
|
177958
|
+
// Verify signature
|
|
177959
|
+
const data = `${headerB64}.${payloadB64}`;
|
|
177960
|
+
const signature = Buffer.from(signatureB64, 'base64url');
|
|
177961
|
+
const publicKey = crypto.createPublicKey(LicenseValidator.PUBLIC_KEY);
|
|
177962
|
+
// Validate that the loaded public key is actually Ed25519 (OID 1.3.101.112).
|
|
177963
|
+
// This prevents algorithm-confusion attacks if the embedded key were ever
|
|
177964
|
+
// swapped to a different type.
|
|
177965
|
+
if (publicKey.asymmetricKeyType !== 'ed25519') {
|
|
177966
|
+
return null;
|
|
177967
|
+
}
|
|
177968
|
+
// Ed25519 verification: algorithm must be null because EdDSA performs its
|
|
177969
|
+
// own internal hashing (SHA-512) — passing a digest algorithm here would
|
|
177970
|
+
// cause Node.js to throw. The key type is validated above.
|
|
177971
|
+
const isValid = crypto.verify(null, Buffer.from(data), publicKey, signature);
|
|
177972
|
+
if (!isValid)
|
|
177973
|
+
return null;
|
|
177974
|
+
// Decode payload
|
|
177975
|
+
const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString());
|
|
177976
|
+
// Validate required fields
|
|
177977
|
+
if (!payload.org ||
|
|
177978
|
+
!Array.isArray(payload.features) ||
|
|
177979
|
+
typeof payload.exp !== 'number' ||
|
|
177980
|
+
typeof payload.iat !== 'number' ||
|
|
177981
|
+
!payload.sub) {
|
|
177982
|
+
return null;
|
|
177983
|
+
}
|
|
177984
|
+
// Check expiry (with grace period)
|
|
177985
|
+
const now = Date.now();
|
|
177986
|
+
const expiryMs = payload.exp * 1000;
|
|
177987
|
+
if (now >= expiryMs + LicenseValidator.GRACE_PERIOD) {
|
|
177988
|
+
return null;
|
|
177989
|
+
}
|
|
177990
|
+
return payload;
|
|
177991
|
+
}
|
|
177992
|
+
catch {
|
|
177993
|
+
return null;
|
|
177994
|
+
}
|
|
177995
|
+
}
|
|
177996
|
+
}
|
|
177997
|
+
exports.LicenseValidator = LicenseValidator;
|
|
177998
|
+
|
|
177999
|
+
|
|
178000
|
+
/***/ }),
|
|
178001
|
+
|
|
178002
|
+
/***/ 87068:
|
|
178003
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
178004
|
+
|
|
178005
|
+
"use strict";
|
|
178006
|
+
|
|
178007
|
+
/**
|
|
178008
|
+
* Copyright (c) ProbeLabs. All rights reserved.
|
|
178009
|
+
* Licensed under the Elastic License 2.0; you may not use this file except
|
|
178010
|
+
* in compliance with the Elastic License 2.0.
|
|
178011
|
+
*/
|
|
178012
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
178013
|
+
if (k2 === undefined) k2 = k;
|
|
178014
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
178015
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
178016
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
178017
|
+
}
|
|
178018
|
+
Object.defineProperty(o, k2, desc);
|
|
178019
|
+
}) : (function(o, m, k, k2) {
|
|
178020
|
+
if (k2 === undefined) k2 = k;
|
|
178021
|
+
o[k2] = m[k];
|
|
178022
|
+
}));
|
|
178023
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
178024
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
178025
|
+
}) : function(o, v) {
|
|
178026
|
+
o["default"] = v;
|
|
178027
|
+
});
|
|
178028
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
178029
|
+
var ownKeys = function(o) {
|
|
178030
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
178031
|
+
var ar = [];
|
|
178032
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
178033
|
+
return ar;
|
|
178034
|
+
};
|
|
178035
|
+
return ownKeys(o);
|
|
178036
|
+
};
|
|
178037
|
+
return function (mod) {
|
|
178038
|
+
if (mod && mod.__esModule) return mod;
|
|
178039
|
+
var result = {};
|
|
178040
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
178041
|
+
__setModuleDefault(result, mod);
|
|
178042
|
+
return result;
|
|
178043
|
+
};
|
|
178044
|
+
})();
|
|
178045
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
178046
|
+
exports.loadEnterprisePolicyEngine = loadEnterprisePolicyEngine;
|
|
178047
|
+
exports.loadEnterpriseStoreBackend = loadEnterpriseStoreBackend;
|
|
178048
|
+
const default_engine_1 = __nccwpck_require__(93866);
|
|
178049
|
+
/**
|
|
178050
|
+
* Load the enterprise policy engine if licensed, otherwise return the default no-op engine.
|
|
178051
|
+
*
|
|
178052
|
+
* This is the sole import boundary between OSS and enterprise code. Core code
|
|
178053
|
+
* must only import from this module (via dynamic `await import()`), never from
|
|
178054
|
+
* individual enterprise submodules.
|
|
178055
|
+
*/
|
|
178056
|
+
async function loadEnterprisePolicyEngine(config) {
|
|
178057
|
+
try {
|
|
178058
|
+
const { LicenseValidator } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(50069)));
|
|
178059
|
+
const validator = new LicenseValidator();
|
|
178060
|
+
const license = await validator.loadAndValidate();
|
|
178061
|
+
if (!license || !validator.hasFeature('policy')) {
|
|
178062
|
+
return new default_engine_1.DefaultPolicyEngine();
|
|
178063
|
+
}
|
|
178064
|
+
if (validator.isInGracePeriod()) {
|
|
178065
|
+
// eslint-disable-next-line no-console
|
|
178066
|
+
console.warn('[visor:enterprise] License has expired but is within the 72-hour grace period. ' +
|
|
178067
|
+
'Please renew your license.');
|
|
178068
|
+
}
|
|
178069
|
+
const { OpaPolicyEngine } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(39530)));
|
|
178070
|
+
const engine = new OpaPolicyEngine(config);
|
|
178071
|
+
await engine.initialize(config);
|
|
178072
|
+
return engine;
|
|
178073
|
+
}
|
|
178074
|
+
catch (err) {
|
|
178075
|
+
// Enterprise code not available or initialization failed
|
|
178076
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
178077
|
+
try {
|
|
178078
|
+
const { logger } = __nccwpck_require__(86999);
|
|
178079
|
+
logger.warn(`[PolicyEngine] Enterprise policy init failed, falling back to default: ${msg}`);
|
|
178080
|
+
}
|
|
178081
|
+
catch {
|
|
178082
|
+
// silent
|
|
178083
|
+
}
|
|
178084
|
+
return new default_engine_1.DefaultPolicyEngine();
|
|
178085
|
+
}
|
|
178086
|
+
}
|
|
178087
|
+
/**
|
|
178088
|
+
* Load the enterprise schedule store backend if licensed.
|
|
178089
|
+
*
|
|
178090
|
+
* @param driver Database driver ('postgresql', 'mysql', or 'mssql')
|
|
178091
|
+
* @param storageConfig Storage configuration with connection details
|
|
178092
|
+
* @param haConfig Optional HA configuration
|
|
178093
|
+
* @throws Error if enterprise license is not available or missing 'scheduler-sql' feature
|
|
178094
|
+
*/
|
|
178095
|
+
async function loadEnterpriseStoreBackend(driver, storageConfig, haConfig) {
|
|
178096
|
+
const { LicenseValidator } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(50069)));
|
|
178097
|
+
const validator = new LicenseValidator();
|
|
178098
|
+
const license = await validator.loadAndValidate();
|
|
178099
|
+
if (!license || !validator.hasFeature('scheduler-sql')) {
|
|
178100
|
+
throw new Error(`The ${driver} schedule storage driver requires a Visor Enterprise license ` +
|
|
178101
|
+
`with the 'scheduler-sql' feature. Please upgrade or use driver: 'sqlite' (default).`);
|
|
178102
|
+
}
|
|
178103
|
+
if (validator.isInGracePeriod()) {
|
|
178104
|
+
// eslint-disable-next-line no-console
|
|
178105
|
+
console.warn('[visor:enterprise] License has expired but is within the 72-hour grace period. ' +
|
|
178106
|
+
'Please renew your license.');
|
|
178107
|
+
}
|
|
178108
|
+
const { KnexStoreBackend } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(63737)));
|
|
178109
|
+
return new KnexStoreBackend(driver, storageConfig, haConfig);
|
|
178110
|
+
}
|
|
178111
|
+
|
|
178112
|
+
|
|
178113
|
+
/***/ }),
|
|
178114
|
+
|
|
178115
|
+
/***/ 628:
|
|
178116
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
178117
|
+
|
|
178118
|
+
"use strict";
|
|
178119
|
+
|
|
178120
|
+
/**
|
|
178121
|
+
* Copyright (c) ProbeLabs. All rights reserved.
|
|
178122
|
+
* Licensed under the Elastic License 2.0; you may not use this file except
|
|
178123
|
+
* in compliance with the Elastic License 2.0.
|
|
178124
|
+
*/
|
|
178125
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
178126
|
+
if (k2 === undefined) k2 = k;
|
|
178127
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
178128
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
178129
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
178130
|
+
}
|
|
178131
|
+
Object.defineProperty(o, k2, desc);
|
|
178132
|
+
}) : (function(o, m, k, k2) {
|
|
178133
|
+
if (k2 === undefined) k2 = k;
|
|
178134
|
+
o[k2] = m[k];
|
|
178135
|
+
}));
|
|
178136
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
178137
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
178138
|
+
}) : function(o, v) {
|
|
178139
|
+
o["default"] = v;
|
|
178140
|
+
});
|
|
178141
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
178142
|
+
var ownKeys = function(o) {
|
|
178143
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
178144
|
+
var ar = [];
|
|
178145
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
178146
|
+
return ar;
|
|
178147
|
+
};
|
|
178148
|
+
return ownKeys(o);
|
|
178149
|
+
};
|
|
178150
|
+
return function (mod) {
|
|
178151
|
+
if (mod && mod.__esModule) return mod;
|
|
178152
|
+
var result = {};
|
|
178153
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
178154
|
+
__setModuleDefault(result, mod);
|
|
178155
|
+
return result;
|
|
178156
|
+
};
|
|
178157
|
+
})();
|
|
178158
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
178159
|
+
exports.OpaCompiler = void 0;
|
|
178160
|
+
const fs = __importStar(__nccwpck_require__(79896));
|
|
178161
|
+
const path = __importStar(__nccwpck_require__(16928));
|
|
178162
|
+
const os = __importStar(__nccwpck_require__(70857));
|
|
178163
|
+
const crypto = __importStar(__nccwpck_require__(76982));
|
|
178164
|
+
const child_process_1 = __nccwpck_require__(35317);
|
|
178165
|
+
/**
|
|
178166
|
+
* OPA Rego Compiler - compiles .rego policy files to WASM bundles using the `opa` CLI.
|
|
178167
|
+
*
|
|
178168
|
+
* Handles:
|
|
178169
|
+
* - Resolving input paths to WASM bytes (direct .wasm, directory with policy.wasm, or .rego files)
|
|
178170
|
+
* - Compiling .rego files to WASM via `opa build`
|
|
178171
|
+
* - Caching compiled bundles based on content hashes
|
|
178172
|
+
* - Extracting policy.wasm from OPA tar.gz bundles
|
|
178173
|
+
*
|
|
178174
|
+
* Requires:
|
|
178175
|
+
* - `opa` CLI on PATH (only when auto-compiling .rego files)
|
|
178176
|
+
*/
|
|
178177
|
+
class OpaCompiler {
|
|
178178
|
+
static CACHE_DIR = path.join(os.tmpdir(), 'visor-opa-cache');
|
|
178179
|
+
/**
|
|
178180
|
+
* Resolve the input paths to WASM bytes.
|
|
178181
|
+
*
|
|
178182
|
+
* Strategy:
|
|
178183
|
+
* 1. If any path is a .wasm file, read it directly
|
|
178184
|
+
* 2. If a directory contains policy.wasm, read it
|
|
178185
|
+
* 3. Otherwise, collect all .rego files and auto-compile via `opa build`
|
|
178186
|
+
*/
|
|
178187
|
+
async resolveWasmBytes(paths) {
|
|
178188
|
+
// Collect .rego files and check for existing .wasm
|
|
178189
|
+
const regoFiles = [];
|
|
178190
|
+
for (const p of paths) {
|
|
178191
|
+
const resolved = path.resolve(p);
|
|
178192
|
+
// Reject paths containing '..' after resolution (path traversal)
|
|
178193
|
+
if (path.normalize(resolved).includes('..')) {
|
|
178194
|
+
throw new Error(`Policy path contains traversal sequences: ${p}`);
|
|
178195
|
+
}
|
|
178196
|
+
// Direct .wasm file
|
|
178197
|
+
if (resolved.endsWith('.wasm') && fs.existsSync(resolved)) {
|
|
178198
|
+
return fs.readFileSync(resolved);
|
|
178199
|
+
}
|
|
178200
|
+
if (!fs.existsSync(resolved))
|
|
178201
|
+
continue;
|
|
178202
|
+
const stat = fs.statSync(resolved);
|
|
178203
|
+
if (stat.isDirectory()) {
|
|
178204
|
+
// Check for pre-compiled policy.wasm in directory
|
|
178205
|
+
const wasmCandidate = path.join(resolved, 'policy.wasm');
|
|
178206
|
+
if (fs.existsSync(wasmCandidate)) {
|
|
178207
|
+
return fs.readFileSync(wasmCandidate);
|
|
178208
|
+
}
|
|
178209
|
+
// Collect all .rego files from directory
|
|
178210
|
+
const files = fs.readdirSync(resolved);
|
|
178211
|
+
for (const f of files) {
|
|
178212
|
+
if (f.endsWith('.rego')) {
|
|
178213
|
+
regoFiles.push(path.join(resolved, f));
|
|
178214
|
+
}
|
|
178215
|
+
}
|
|
178216
|
+
}
|
|
178217
|
+
else if (resolved.endsWith('.rego')) {
|
|
178218
|
+
regoFiles.push(resolved);
|
|
178219
|
+
}
|
|
178220
|
+
}
|
|
178221
|
+
if (regoFiles.length === 0) {
|
|
178222
|
+
throw new Error(`OPA WASM evaluator: no .wasm bundle or .rego files found in: ${paths.join(', ')}`);
|
|
178223
|
+
}
|
|
178224
|
+
// Auto-compile .rego -> .wasm
|
|
178225
|
+
return this.compileRego(regoFiles);
|
|
178226
|
+
}
|
|
178227
|
+
/**
|
|
178228
|
+
* Auto-compile .rego files to a WASM bundle using the `opa` CLI.
|
|
178229
|
+
*
|
|
178230
|
+
* Caches the compiled bundle based on a content hash of all input .rego files
|
|
178231
|
+
* so subsequent runs skip compilation if policies haven't changed.
|
|
178232
|
+
*/
|
|
178233
|
+
compileRego(regoFiles) {
|
|
178234
|
+
// Check that `opa` CLI is available
|
|
178235
|
+
try {
|
|
178236
|
+
(0, child_process_1.execFileSync)('opa', ['version'], { stdio: 'pipe' });
|
|
178237
|
+
}
|
|
178238
|
+
catch {
|
|
178239
|
+
throw new Error('OPA CLI (`opa`) not found on PATH. Install it from https://www.openpolicyagent.org/docs/latest/#running-opa\n' +
|
|
178240
|
+
'Or pre-compile your .rego files: opa build -t wasm -e visor -o bundle.tar.gz ' +
|
|
178241
|
+
regoFiles.join(' '));
|
|
178242
|
+
}
|
|
178243
|
+
// Compute content hash for cache key
|
|
178244
|
+
const hash = crypto.createHash('sha256');
|
|
178245
|
+
for (const f of regoFiles.sort()) {
|
|
178246
|
+
hash.update(fs.readFileSync(f));
|
|
178247
|
+
hash.update(f); // include filename for disambiguation
|
|
178248
|
+
}
|
|
178249
|
+
const cacheKey = hash.digest('hex').slice(0, 16);
|
|
178250
|
+
const cacheDir = OpaCompiler.CACHE_DIR;
|
|
178251
|
+
const cachedWasm = path.join(cacheDir, `${cacheKey}.wasm`);
|
|
178252
|
+
// Return cached bundle if still valid
|
|
178253
|
+
if (fs.existsSync(cachedWasm)) {
|
|
178254
|
+
return fs.readFileSync(cachedWasm);
|
|
178255
|
+
}
|
|
178256
|
+
// Compile to WASM via opa build
|
|
178257
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
178258
|
+
const bundleTar = path.join(cacheDir, `${cacheKey}-bundle.tar.gz`);
|
|
178259
|
+
try {
|
|
178260
|
+
const args = [
|
|
178261
|
+
'build',
|
|
178262
|
+
'-t',
|
|
178263
|
+
'wasm',
|
|
178264
|
+
'-e',
|
|
178265
|
+
'visor', // entrypoint: the visor package tree
|
|
178266
|
+
'-o',
|
|
178267
|
+
bundleTar,
|
|
178268
|
+
...regoFiles,
|
|
178269
|
+
];
|
|
178270
|
+
(0, child_process_1.execFileSync)('opa', args, {
|
|
178271
|
+
stdio: 'pipe',
|
|
178272
|
+
timeout: 30000,
|
|
178273
|
+
});
|
|
178274
|
+
}
|
|
178275
|
+
catch (err) {
|
|
178276
|
+
const stderr = err?.stderr?.toString() || '';
|
|
178277
|
+
throw new Error(`Failed to compile .rego files to WASM:\n${stderr}\n` +
|
|
178278
|
+
'Ensure your .rego files are valid and the `opa` CLI is installed.');
|
|
178279
|
+
}
|
|
178280
|
+
// Extract policy.wasm from the tar.gz bundle
|
|
178281
|
+
// OPA bundles are tar.gz with /policy.wasm inside
|
|
178282
|
+
try {
|
|
178283
|
+
(0, child_process_1.execFileSync)('tar', ['-xzf', bundleTar, '-C', cacheDir, '/policy.wasm'], {
|
|
178284
|
+
stdio: 'pipe',
|
|
178285
|
+
});
|
|
178286
|
+
const extractedWasm = path.join(cacheDir, 'policy.wasm');
|
|
178287
|
+
if (fs.existsSync(extractedWasm)) {
|
|
178288
|
+
// Move to cache-key named file
|
|
178289
|
+
fs.renameSync(extractedWasm, cachedWasm);
|
|
178290
|
+
}
|
|
178291
|
+
}
|
|
178292
|
+
catch {
|
|
178293
|
+
// Some tar implementations don't like leading /
|
|
178294
|
+
try {
|
|
178295
|
+
(0, child_process_1.execFileSync)('tar', ['-xzf', bundleTar, '-C', cacheDir, 'policy.wasm'], {
|
|
178296
|
+
stdio: 'pipe',
|
|
178297
|
+
});
|
|
178298
|
+
const extractedWasm = path.join(cacheDir, 'policy.wasm');
|
|
178299
|
+
if (fs.existsSync(extractedWasm)) {
|
|
178300
|
+
fs.renameSync(extractedWasm, cachedWasm);
|
|
178301
|
+
}
|
|
178302
|
+
}
|
|
178303
|
+
catch (err2) {
|
|
178304
|
+
throw new Error(`Failed to extract policy.wasm from OPA bundle: ${err2?.message || err2}`);
|
|
178305
|
+
}
|
|
178306
|
+
}
|
|
178307
|
+
// Clean up tar
|
|
178308
|
+
try {
|
|
178309
|
+
fs.unlinkSync(bundleTar);
|
|
178310
|
+
}
|
|
178311
|
+
catch { }
|
|
178312
|
+
if (!fs.existsSync(cachedWasm)) {
|
|
178313
|
+
throw new Error('OPA build succeeded but policy.wasm was not found in the bundle');
|
|
178314
|
+
}
|
|
178315
|
+
return fs.readFileSync(cachedWasm);
|
|
178316
|
+
}
|
|
178317
|
+
}
|
|
178318
|
+
exports.OpaCompiler = OpaCompiler;
|
|
178319
|
+
|
|
178320
|
+
|
|
178321
|
+
/***/ }),
|
|
178322
|
+
|
|
178323
|
+
/***/ 44693:
|
|
178324
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
178325
|
+
|
|
178326
|
+
"use strict";
|
|
178327
|
+
|
|
178328
|
+
/**
|
|
178329
|
+
* Copyright (c) ProbeLabs. All rights reserved.
|
|
178330
|
+
* Licensed under the Elastic License 2.0; you may not use this file except
|
|
178331
|
+
* in compliance with the Elastic License 2.0.
|
|
178332
|
+
*/
|
|
178333
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
178334
|
+
exports.OpaHttpEvaluator = void 0;
|
|
178335
|
+
/**
|
|
178336
|
+
* OPA HTTP Evaluator - evaluates policies via an external OPA server's REST API.
|
|
178337
|
+
*
|
|
178338
|
+
* Uses the built-in `fetch` API (Node 18+), so no extra dependencies are needed.
|
|
178339
|
+
*/
|
|
178340
|
+
class OpaHttpEvaluator {
|
|
178341
|
+
baseUrl;
|
|
178342
|
+
timeout;
|
|
178343
|
+
constructor(baseUrl, timeout = 5000) {
|
|
178344
|
+
// Validate URL format and protocol
|
|
178345
|
+
let parsed;
|
|
178346
|
+
try {
|
|
178347
|
+
parsed = new URL(baseUrl);
|
|
178348
|
+
}
|
|
178349
|
+
catch {
|
|
178350
|
+
throw new Error(`OPA HTTP evaluator: invalid URL: ${baseUrl}`);
|
|
178351
|
+
}
|
|
178352
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
178353
|
+
throw new Error(`OPA HTTP evaluator: url must use http:// or https:// protocol, got: ${baseUrl}`);
|
|
178354
|
+
}
|
|
178355
|
+
// Block cloud metadata, loopback, link-local, and private network addresses
|
|
178356
|
+
const hostname = parsed.hostname;
|
|
178357
|
+
if (this.isBlockedHostname(hostname)) {
|
|
178358
|
+
throw new Error(`OPA HTTP evaluator: url must not point to internal, loopback, or private network addresses`);
|
|
178359
|
+
}
|
|
178360
|
+
// Normalize: strip trailing slash
|
|
178361
|
+
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
|
178362
|
+
this.timeout = timeout;
|
|
178363
|
+
}
|
|
178364
|
+
/**
|
|
178365
|
+
* Check if a hostname is blocked due to SSRF concerns.
|
|
178366
|
+
*
|
|
178367
|
+
* Blocks:
|
|
178368
|
+
* - Loopback addresses (127.x.x.x, localhost, 0.0.0.0, ::1)
|
|
178369
|
+
* - Link-local addresses (169.254.x.x)
|
|
178370
|
+
* - Private networks (10.x.x.x, 172.16-31.x.x, 192.168.x.x)
|
|
178371
|
+
* - IPv6 unique local addresses (fd00::/8)
|
|
178372
|
+
* - Cloud metadata services (*.internal)
|
|
178373
|
+
*/
|
|
178374
|
+
isBlockedHostname(hostname) {
|
|
178375
|
+
if (!hostname)
|
|
178376
|
+
return true; // block empty hostnames
|
|
178377
|
+
// Normalize hostname: lowercase and remove brackets for IPv6
|
|
178378
|
+
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
178379
|
+
// Block .internal domains (cloud metadata services)
|
|
178380
|
+
if (normalized === 'metadata.google.internal' || normalized.endsWith('.internal')) {
|
|
178381
|
+
return true;
|
|
178382
|
+
}
|
|
178383
|
+
// Block localhost variants
|
|
178384
|
+
if (normalized === 'localhost' || normalized === 'localhost.localdomain') {
|
|
178385
|
+
return true;
|
|
178386
|
+
}
|
|
178387
|
+
// Block IPv6 loopback
|
|
178388
|
+
if (normalized === '::1' || normalized === '0:0:0:0:0:0:0:1') {
|
|
178389
|
+
return true;
|
|
178390
|
+
}
|
|
178391
|
+
// Check IPv4 patterns
|
|
178392
|
+
const ipv4Pattern = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
178393
|
+
const ipv4Match = normalized.match(ipv4Pattern);
|
|
178394
|
+
if (ipv4Match) {
|
|
178395
|
+
const octets = ipv4Match.slice(1, 5).map(Number);
|
|
178396
|
+
// Validate octets are in range [0, 255]
|
|
178397
|
+
if (octets.some(octet => octet > 255)) {
|
|
178398
|
+
return false;
|
|
178399
|
+
}
|
|
178400
|
+
const [a, b] = octets;
|
|
178401
|
+
// Block loopback: 127.0.0.0/8
|
|
178402
|
+
if (a === 127) {
|
|
178403
|
+
return true;
|
|
178404
|
+
}
|
|
178405
|
+
// Block 0.0.0.0/8 (this host)
|
|
178406
|
+
if (a === 0) {
|
|
178407
|
+
return true;
|
|
178408
|
+
}
|
|
178409
|
+
// Block link-local: 169.254.0.0/16
|
|
178410
|
+
if (a === 169 && b === 254) {
|
|
178411
|
+
return true;
|
|
178412
|
+
}
|
|
178413
|
+
// Block private networks
|
|
178414
|
+
// 10.0.0.0/8
|
|
178415
|
+
if (a === 10) {
|
|
178416
|
+
return true;
|
|
178417
|
+
}
|
|
178418
|
+
// 172.16.0.0/12 (172.16.x.x through 172.31.x.x)
|
|
178419
|
+
if (a === 172 && b >= 16 && b <= 31) {
|
|
178420
|
+
return true;
|
|
178421
|
+
}
|
|
178422
|
+
// 192.168.0.0/16
|
|
178423
|
+
if (a === 192 && b === 168) {
|
|
178424
|
+
return true;
|
|
178425
|
+
}
|
|
178426
|
+
}
|
|
178427
|
+
// Check IPv6 patterns
|
|
178428
|
+
// Block unique local addresses: fd00::/8
|
|
178429
|
+
if (normalized.startsWith('fd') || normalized.startsWith('fc')) {
|
|
178430
|
+
return true;
|
|
178431
|
+
}
|
|
178432
|
+
// Block link-local: fe80::/10
|
|
178433
|
+
if (normalized.startsWith('fe80:')) {
|
|
178434
|
+
return true;
|
|
178435
|
+
}
|
|
178436
|
+
return false;
|
|
178437
|
+
}
|
|
178438
|
+
/**
|
|
178439
|
+
* Evaluate a policy rule against an input document via OPA REST API.
|
|
178440
|
+
*
|
|
178441
|
+
* @param input - The input document to evaluate
|
|
178442
|
+
* @param rulePath - OPA rule path (e.g., 'visor/check/execute')
|
|
178443
|
+
* @returns The result object from OPA, or undefined on error
|
|
178444
|
+
*/
|
|
178445
|
+
async evaluate(input, rulePath) {
|
|
178446
|
+
// OPA Data API: POST /v1/data/<path>
|
|
178447
|
+
const encodedPath = rulePath
|
|
178448
|
+
.split('/')
|
|
178449
|
+
.map(s => encodeURIComponent(s))
|
|
178450
|
+
.join('/');
|
|
178451
|
+
const url = `${this.baseUrl}/v1/data/${encodedPath}`;
|
|
178452
|
+
const controller = new AbortController();
|
|
178453
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
178454
|
+
try {
|
|
178455
|
+
const response = await fetch(url, {
|
|
178456
|
+
method: 'POST',
|
|
178457
|
+
headers: { 'Content-Type': 'application/json' },
|
|
178458
|
+
body: JSON.stringify({ input }),
|
|
178459
|
+
signal: controller.signal,
|
|
178460
|
+
});
|
|
178461
|
+
if (!response.ok) {
|
|
178462
|
+
throw new Error(`OPA HTTP ${response.status}: ${response.statusText}`);
|
|
178463
|
+
}
|
|
178464
|
+
let body;
|
|
178465
|
+
try {
|
|
178466
|
+
body = await response.json();
|
|
178467
|
+
}
|
|
178468
|
+
catch (jsonErr) {
|
|
178469
|
+
throw new Error(`OPA HTTP evaluator: failed to parse JSON response: ${jsonErr instanceof Error ? jsonErr.message : String(jsonErr)}`);
|
|
178470
|
+
}
|
|
178471
|
+
// OPA returns { result: { ... } }
|
|
178472
|
+
return body?.result;
|
|
178473
|
+
}
|
|
178474
|
+
finally {
|
|
178475
|
+
clearTimeout(timer);
|
|
178476
|
+
}
|
|
178477
|
+
}
|
|
178478
|
+
async shutdown() {
|
|
178479
|
+
// No persistent connections to close
|
|
178480
|
+
}
|
|
178481
|
+
}
|
|
178482
|
+
exports.OpaHttpEvaluator = OpaHttpEvaluator;
|
|
178483
|
+
|
|
178484
|
+
|
|
178485
|
+
/***/ }),
|
|
178486
|
+
|
|
178487
|
+
/***/ 39530:
|
|
178488
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
178489
|
+
|
|
178490
|
+
"use strict";
|
|
178491
|
+
|
|
178492
|
+
/**
|
|
178493
|
+
* Copyright (c) ProbeLabs. All rights reserved.
|
|
178494
|
+
* Licensed under the Elastic License 2.0; you may not use this file except
|
|
178495
|
+
* in compliance with the Elastic License 2.0.
|
|
178496
|
+
*/
|
|
178497
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
178498
|
+
exports.OpaPolicyEngine = void 0;
|
|
178499
|
+
const opa_wasm_evaluator_1 = __nccwpck_require__(8613);
|
|
178500
|
+
const opa_http_evaluator_1 = __nccwpck_require__(44693);
|
|
178501
|
+
const policy_input_builder_1 = __nccwpck_require__(17117);
|
|
178502
|
+
/**
|
|
178503
|
+
* Enterprise OPA Policy Engine.
|
|
178504
|
+
*
|
|
178505
|
+
* Wraps both WASM (local) and HTTP (remote) OPA evaluators behind the
|
|
178506
|
+
* OSS PolicyEngine interface. All OPA input building and role resolution
|
|
178507
|
+
* is handled internally — the OSS call sites pass only plain types.
|
|
178508
|
+
*/
|
|
178509
|
+
class OpaPolicyEngine {
|
|
178510
|
+
evaluator = null;
|
|
178511
|
+
fallback;
|
|
178512
|
+
timeout;
|
|
178513
|
+
config;
|
|
178514
|
+
inputBuilder = null;
|
|
178515
|
+
logger = null;
|
|
178516
|
+
constructor(config) {
|
|
178517
|
+
this.config = config;
|
|
178518
|
+
this.fallback = config.fallback || 'deny';
|
|
178519
|
+
this.timeout = config.timeout || 5000;
|
|
178520
|
+
}
|
|
178521
|
+
async initialize(config) {
|
|
178522
|
+
// Resolve logger once at initialization
|
|
178523
|
+
try {
|
|
178524
|
+
this.logger = (__nccwpck_require__(86999).logger);
|
|
178525
|
+
}
|
|
178526
|
+
catch {
|
|
178527
|
+
// logger not available in this context
|
|
178528
|
+
}
|
|
178529
|
+
// Build actor/repo context from environment (available at engine init time)
|
|
178530
|
+
const actor = {
|
|
178531
|
+
authorAssociation: process.env.VISOR_AUTHOR_ASSOCIATION,
|
|
178532
|
+
login: process.env.VISOR_AUTHOR_LOGIN || process.env.GITHUB_ACTOR,
|
|
178533
|
+
isLocalMode: !process.env.GITHUB_ACTIONS,
|
|
178534
|
+
};
|
|
178535
|
+
const repo = {
|
|
178536
|
+
owner: process.env.GITHUB_REPOSITORY_OWNER,
|
|
178537
|
+
name: process.env.GITHUB_REPOSITORY?.split('/')[1],
|
|
178538
|
+
branch: process.env.GITHUB_HEAD_REF,
|
|
178539
|
+
baseBranch: process.env.GITHUB_BASE_REF,
|
|
178540
|
+
event: process.env.GITHUB_EVENT_NAME,
|
|
178541
|
+
};
|
|
178542
|
+
const prNum = process.env.GITHUB_PR_NUMBER
|
|
178543
|
+
? parseInt(process.env.GITHUB_PR_NUMBER, 10)
|
|
178544
|
+
: undefined;
|
|
178545
|
+
const pullRequest = {
|
|
178546
|
+
number: prNum !== undefined && Number.isFinite(prNum) ? prNum : undefined,
|
|
178547
|
+
};
|
|
178548
|
+
this.inputBuilder = new policy_input_builder_1.PolicyInputBuilder(config, actor, repo, pullRequest);
|
|
178549
|
+
if (config.engine === 'local') {
|
|
178550
|
+
if (!config.rules) {
|
|
178551
|
+
throw new Error('OPA local mode requires `policy.rules` path to .wasm or .rego files');
|
|
178552
|
+
}
|
|
178553
|
+
const wasm = new opa_wasm_evaluator_1.OpaWasmEvaluator();
|
|
178554
|
+
await wasm.initialize(config.rules);
|
|
178555
|
+
if (config.data) {
|
|
178556
|
+
wasm.loadData(config.data);
|
|
178557
|
+
}
|
|
178558
|
+
this.evaluator = wasm;
|
|
178559
|
+
}
|
|
178560
|
+
else if (config.engine === 'remote') {
|
|
178561
|
+
if (!config.url) {
|
|
178562
|
+
throw new Error('OPA remote mode requires `policy.url` pointing to OPA server');
|
|
178563
|
+
}
|
|
178564
|
+
this.evaluator = new opa_http_evaluator_1.OpaHttpEvaluator(config.url, this.timeout);
|
|
178565
|
+
}
|
|
178566
|
+
else {
|
|
178567
|
+
this.evaluator = null;
|
|
178568
|
+
}
|
|
178569
|
+
}
|
|
178570
|
+
/**
|
|
178571
|
+
* Update actor/repo/PR context (e.g., after PR info becomes available).
|
|
178572
|
+
* Called by the enterprise loader when engine context is enriched.
|
|
178573
|
+
*/
|
|
178574
|
+
setActorContext(actor, repo, pullRequest) {
|
|
178575
|
+
this.inputBuilder = new policy_input_builder_1.PolicyInputBuilder(this.config, actor, repo, pullRequest);
|
|
178576
|
+
}
|
|
178577
|
+
async evaluateCheckExecution(checkId, checkConfig) {
|
|
178578
|
+
if (!this.evaluator || !this.inputBuilder)
|
|
178579
|
+
return { allowed: true };
|
|
178580
|
+
const cfg = checkConfig && typeof checkConfig === 'object'
|
|
178581
|
+
? checkConfig
|
|
178582
|
+
: {};
|
|
178583
|
+
const policyOverride = cfg.policy;
|
|
178584
|
+
const input = this.inputBuilder.forCheckExecution({
|
|
178585
|
+
id: checkId,
|
|
178586
|
+
type: cfg.type || 'ai',
|
|
178587
|
+
group: cfg.group,
|
|
178588
|
+
tags: cfg.tags,
|
|
178589
|
+
criticality: cfg.criticality,
|
|
178590
|
+
sandbox: cfg.sandbox,
|
|
178591
|
+
policy: policyOverride,
|
|
178592
|
+
});
|
|
178593
|
+
return this.doEvaluate(input, this.resolveRulePath('check.execute', policyOverride?.rule));
|
|
178594
|
+
}
|
|
178595
|
+
async evaluateToolInvocation(serverName, methodName, transport) {
|
|
178596
|
+
if (!this.evaluator || !this.inputBuilder)
|
|
178597
|
+
return { allowed: true };
|
|
178598
|
+
const input = this.inputBuilder.forToolInvocation(serverName, methodName, transport);
|
|
178599
|
+
return this.doEvaluate(input, 'visor/tool/invoke');
|
|
178600
|
+
}
|
|
178601
|
+
async evaluateCapabilities(checkId, capabilities) {
|
|
178602
|
+
if (!this.evaluator || !this.inputBuilder)
|
|
178603
|
+
return { allowed: true };
|
|
178604
|
+
const input = this.inputBuilder.forCapabilityResolve(checkId, capabilities);
|
|
178605
|
+
return this.doEvaluate(input, 'visor/capability/resolve');
|
|
178606
|
+
}
|
|
178607
|
+
async shutdown() {
|
|
178608
|
+
if (this.evaluator && 'shutdown' in this.evaluator) {
|
|
178609
|
+
await this.evaluator.shutdown();
|
|
178610
|
+
}
|
|
178611
|
+
this.evaluator = null;
|
|
178612
|
+
this.inputBuilder = null;
|
|
178613
|
+
}
|
|
178614
|
+
resolveRulePath(defaultScope, override) {
|
|
178615
|
+
if (override) {
|
|
178616
|
+
return override.startsWith('visor/') ? override : `visor/${override}`;
|
|
178617
|
+
}
|
|
178618
|
+
return `visor/${defaultScope.replace(/\./g, '/')}`;
|
|
178619
|
+
}
|
|
178620
|
+
async doEvaluate(input, rulePath) {
|
|
178621
|
+
try {
|
|
178622
|
+
this.logger?.debug(`[PolicyEngine] Evaluating ${rulePath}`, JSON.stringify(input));
|
|
178623
|
+
let timer;
|
|
178624
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
178625
|
+
timer = setTimeout(() => reject(new Error('policy evaluation timeout')), this.timeout);
|
|
178626
|
+
});
|
|
178627
|
+
try {
|
|
178628
|
+
const result = await Promise.race([this.rawEvaluate(input, rulePath), timeoutPromise]);
|
|
178629
|
+
const decision = this.parseDecision(result);
|
|
178630
|
+
// In warn mode, override denied decisions to allowed but flag as warn
|
|
178631
|
+
if (!decision.allowed && this.fallback === 'warn') {
|
|
178632
|
+
decision.allowed = true;
|
|
178633
|
+
decision.warn = true;
|
|
178634
|
+
decision.reason = `audit: ${decision.reason || 'policy denied'}`;
|
|
178635
|
+
}
|
|
178636
|
+
this.logger?.debug(`[PolicyEngine] Decision for ${rulePath}: allowed=${decision.allowed}, warn=${decision.warn || false}, reason=${decision.reason || 'none'}`);
|
|
178637
|
+
return decision;
|
|
178638
|
+
}
|
|
178639
|
+
finally {
|
|
178640
|
+
if (timer)
|
|
178641
|
+
clearTimeout(timer);
|
|
178642
|
+
}
|
|
178643
|
+
}
|
|
178644
|
+
catch (err) {
|
|
178645
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
178646
|
+
this.logger?.warn(`[PolicyEngine] Evaluation failed for ${rulePath}: ${msg}`);
|
|
178647
|
+
return {
|
|
178648
|
+
allowed: this.fallback === 'allow' || this.fallback === 'warn',
|
|
178649
|
+
warn: this.fallback === 'warn' ? true : undefined,
|
|
178650
|
+
reason: `policy evaluation failed, fallback=${this.fallback}`,
|
|
178651
|
+
};
|
|
178652
|
+
}
|
|
178653
|
+
}
|
|
178654
|
+
async rawEvaluate(input, rulePath) {
|
|
178655
|
+
if (this.evaluator instanceof opa_wasm_evaluator_1.OpaWasmEvaluator) {
|
|
178656
|
+
const result = await this.evaluator.evaluate(input);
|
|
178657
|
+
// WASM compiled with `-e visor` entrypoint returns the full visor package tree.
|
|
178658
|
+
// Navigate to the specific rule subtree using rulePath segments.
|
|
178659
|
+
// e.g., 'visor/check/execute' → result.check.execute
|
|
178660
|
+
return this.navigateWasmResult(result, rulePath);
|
|
178661
|
+
}
|
|
178662
|
+
return this.evaluator.evaluate(input, rulePath);
|
|
178663
|
+
}
|
|
178664
|
+
/**
|
|
178665
|
+
* Navigate nested OPA WASM result tree to reach the specific rule's output.
|
|
178666
|
+
* The WASM entrypoint `-e visor` means the result root IS the visor package,
|
|
178667
|
+
* so we strip the `visor/` prefix and walk the remaining segments.
|
|
178668
|
+
*/
|
|
178669
|
+
navigateWasmResult(result, rulePath) {
|
|
178670
|
+
if (!result || typeof result !== 'object')
|
|
178671
|
+
return result;
|
|
178672
|
+
// Strip the 'visor/' prefix (matches our compilation entrypoint)
|
|
178673
|
+
const segments = rulePath.replace(/^visor\//, '').split('/');
|
|
178674
|
+
let current = result;
|
|
178675
|
+
for (const seg of segments) {
|
|
178676
|
+
if (current && typeof current === 'object' && seg in current) {
|
|
178677
|
+
current = current[seg];
|
|
178678
|
+
}
|
|
178679
|
+
else {
|
|
178680
|
+
return undefined; // path not found in result tree
|
|
178681
|
+
}
|
|
178682
|
+
}
|
|
178683
|
+
return current;
|
|
178684
|
+
}
|
|
178685
|
+
parseDecision(result) {
|
|
178686
|
+
if (result === undefined || result === null) {
|
|
178687
|
+
return {
|
|
178688
|
+
allowed: this.fallback === 'allow' || this.fallback === 'warn',
|
|
178689
|
+
warn: this.fallback === 'warn' ? true : undefined,
|
|
178690
|
+
reason: this.fallback === 'warn' ? 'audit: no policy result' : 'no policy result',
|
|
178691
|
+
};
|
|
178692
|
+
}
|
|
178693
|
+
const allowed = result.allowed !== false;
|
|
178694
|
+
const decision = {
|
|
178695
|
+
allowed,
|
|
178696
|
+
reason: result.reason,
|
|
178697
|
+
};
|
|
178698
|
+
if (result.capabilities) {
|
|
178699
|
+
decision.capabilities = result.capabilities;
|
|
178700
|
+
}
|
|
178701
|
+
return decision;
|
|
178702
|
+
}
|
|
178703
|
+
}
|
|
178704
|
+
exports.OpaPolicyEngine = OpaPolicyEngine;
|
|
178705
|
+
|
|
178706
|
+
|
|
178707
|
+
/***/ }),
|
|
178708
|
+
|
|
178709
|
+
/***/ 8613:
|
|
178710
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
178711
|
+
|
|
178712
|
+
"use strict";
|
|
178713
|
+
|
|
178714
|
+
/**
|
|
178715
|
+
* Copyright (c) ProbeLabs. All rights reserved.
|
|
178716
|
+
* Licensed under the Elastic License 2.0; you may not use this file except
|
|
178717
|
+
* in compliance with the Elastic License 2.0.
|
|
178718
|
+
*/
|
|
178719
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
178720
|
+
if (k2 === undefined) k2 = k;
|
|
178721
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
178722
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
178723
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
178724
|
+
}
|
|
178725
|
+
Object.defineProperty(o, k2, desc);
|
|
178726
|
+
}) : (function(o, m, k, k2) {
|
|
178727
|
+
if (k2 === undefined) k2 = k;
|
|
178728
|
+
o[k2] = m[k];
|
|
178729
|
+
}));
|
|
178730
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
178731
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
178732
|
+
}) : function(o, v) {
|
|
178733
|
+
o["default"] = v;
|
|
178734
|
+
});
|
|
178735
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
178736
|
+
var ownKeys = function(o) {
|
|
178737
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
178738
|
+
var ar = [];
|
|
178739
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
178740
|
+
return ar;
|
|
178741
|
+
};
|
|
178742
|
+
return ownKeys(o);
|
|
178743
|
+
};
|
|
178744
|
+
return function (mod) {
|
|
178745
|
+
if (mod && mod.__esModule) return mod;
|
|
178746
|
+
var result = {};
|
|
178747
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
178748
|
+
__setModuleDefault(result, mod);
|
|
178749
|
+
return result;
|
|
178750
|
+
};
|
|
178751
|
+
})();
|
|
178752
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
178753
|
+
exports.OpaWasmEvaluator = void 0;
|
|
178754
|
+
const fs = __importStar(__nccwpck_require__(79896));
|
|
178755
|
+
const path = __importStar(__nccwpck_require__(16928));
|
|
178756
|
+
const opa_compiler_1 = __nccwpck_require__(628);
|
|
178757
|
+
/**
|
|
178758
|
+
* OPA WASM Evaluator - loads and evaluates OPA policies locally.
|
|
178759
|
+
*
|
|
178760
|
+
* Supports three input formats:
|
|
178761
|
+
* 1. Pre-compiled `.wasm` bundle — loaded directly (fastest startup)
|
|
178762
|
+
* 2. `.rego` files or directory — auto-compiled to WASM via `opa build` CLI
|
|
178763
|
+
* 3. Directory with `policy.wasm` inside — loaded directly
|
|
178764
|
+
*
|
|
178765
|
+
* Compilation and caching of .rego files is delegated to {@link OpaCompiler}.
|
|
178766
|
+
*
|
|
178767
|
+
* Requires:
|
|
178768
|
+
* - `@open-policy-agent/opa-wasm` npm package (optional dep)
|
|
178769
|
+
* - `opa` CLI on PATH (only when auto-compiling .rego files)
|
|
178770
|
+
*/
|
|
178771
|
+
class OpaWasmEvaluator {
|
|
178772
|
+
policy = null;
|
|
178773
|
+
dataDocument = {};
|
|
178774
|
+
compiler = new opa_compiler_1.OpaCompiler();
|
|
178775
|
+
async initialize(rulesPath) {
|
|
178776
|
+
const paths = Array.isArray(rulesPath) ? rulesPath : [rulesPath];
|
|
178777
|
+
const wasmBytes = await this.compiler.resolveWasmBytes(paths);
|
|
178778
|
+
try {
|
|
178779
|
+
// Use createRequire to load the optional dep at runtime without ncc bundling it.
|
|
178780
|
+
// `new Function('id', 'return require(id)')` fails in ncc bundles because
|
|
178781
|
+
// `require` is not in the `new Function` scope. `createRequire` works correctly
|
|
178782
|
+
// because it creates a real Node.js require rooted at the given path.
|
|
178783
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
178784
|
+
const { createRequire } = __nccwpck_require__(73339);
|
|
178785
|
+
const runtimeRequire = createRequire(__filename);
|
|
178786
|
+
const opaWasm = runtimeRequire('@open-policy-agent/opa-wasm');
|
|
178787
|
+
const loadPolicy = opaWasm.loadPolicy || opaWasm.default?.loadPolicy;
|
|
178788
|
+
if (!loadPolicy) {
|
|
178789
|
+
throw new Error('loadPolicy not found in @open-policy-agent/opa-wasm');
|
|
178790
|
+
}
|
|
178791
|
+
this.policy = await loadPolicy(wasmBytes);
|
|
178792
|
+
}
|
|
178793
|
+
catch (err) {
|
|
178794
|
+
if (err?.code === 'MODULE_NOT_FOUND' || err?.code === 'ERR_MODULE_NOT_FOUND') {
|
|
178795
|
+
throw new Error('OPA WASM evaluator requires @open-policy-agent/opa-wasm. ' +
|
|
178796
|
+
'Install it with: npm install @open-policy-agent/opa-wasm');
|
|
178797
|
+
}
|
|
178798
|
+
throw err;
|
|
178799
|
+
}
|
|
178800
|
+
}
|
|
178801
|
+
/**
|
|
178802
|
+
* Load external data from a JSON file to use as the OPA data document.
|
|
178803
|
+
* The loaded data will be passed to `policy.setData()` during evaluation,
|
|
178804
|
+
* making it available in Rego via `data.<key>`.
|
|
178805
|
+
*/
|
|
178806
|
+
loadData(dataPath) {
|
|
178807
|
+
const resolved = path.resolve(dataPath);
|
|
178808
|
+
if (path.normalize(resolved).includes('..')) {
|
|
178809
|
+
throw new Error(`Data path contains traversal sequences: ${dataPath}`);
|
|
178810
|
+
}
|
|
178811
|
+
if (!fs.existsSync(resolved)) {
|
|
178812
|
+
throw new Error(`OPA data file not found: ${resolved}`);
|
|
178813
|
+
}
|
|
178814
|
+
const stat = fs.statSync(resolved);
|
|
178815
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
178816
|
+
throw new Error(`OPA data file exceeds 10MB limit: ${resolved} (${stat.size} bytes)`);
|
|
178817
|
+
}
|
|
178818
|
+
const raw = fs.readFileSync(resolved, 'utf-8');
|
|
178819
|
+
try {
|
|
178820
|
+
const parsed = JSON.parse(raw);
|
|
178821
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
178822
|
+
throw new Error('OPA data file must contain a JSON object (not an array or primitive)');
|
|
178823
|
+
}
|
|
178824
|
+
this.dataDocument = parsed;
|
|
178825
|
+
}
|
|
178826
|
+
catch (err) {
|
|
178827
|
+
if (err.message.startsWith('OPA data file must')) {
|
|
178828
|
+
throw err;
|
|
178829
|
+
}
|
|
178830
|
+
throw new Error(`Failed to parse OPA data file ${resolved}: ${err.message}`);
|
|
178831
|
+
}
|
|
178832
|
+
}
|
|
178833
|
+
async evaluate(input) {
|
|
178834
|
+
if (!this.policy) {
|
|
178835
|
+
throw new Error('OPA WASM evaluator not initialized');
|
|
178836
|
+
}
|
|
178837
|
+
this.policy.setData(this.dataDocument);
|
|
178838
|
+
const resultSet = this.policy.evaluate(input);
|
|
178839
|
+
if (Array.isArray(resultSet) && resultSet.length > 0) {
|
|
178840
|
+
return resultSet[0].result;
|
|
178841
|
+
}
|
|
178842
|
+
return undefined;
|
|
178843
|
+
}
|
|
178844
|
+
async shutdown() {
|
|
178845
|
+
if (this.policy) {
|
|
178846
|
+
// opa-wasm policy objects may have a close/free method for WASM cleanup
|
|
178847
|
+
if (typeof this.policy.close === 'function') {
|
|
178848
|
+
try {
|
|
178849
|
+
this.policy.close();
|
|
178850
|
+
}
|
|
178851
|
+
catch { }
|
|
178852
|
+
}
|
|
178853
|
+
else if (typeof this.policy.free === 'function') {
|
|
178854
|
+
try {
|
|
178855
|
+
this.policy.free();
|
|
178856
|
+
}
|
|
178857
|
+
catch { }
|
|
178858
|
+
}
|
|
178859
|
+
}
|
|
178860
|
+
this.policy = null;
|
|
178861
|
+
}
|
|
178862
|
+
}
|
|
178863
|
+
exports.OpaWasmEvaluator = OpaWasmEvaluator;
|
|
178864
|
+
|
|
178865
|
+
|
|
178866
|
+
/***/ }),
|
|
178867
|
+
|
|
178868
|
+
/***/ 17117:
|
|
178869
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
178870
|
+
|
|
178871
|
+
"use strict";
|
|
178872
|
+
|
|
178873
|
+
/**
|
|
178874
|
+
* Copyright (c) ProbeLabs. All rights reserved.
|
|
178875
|
+
* Licensed under the Elastic License 2.0; you may not use this file except
|
|
178876
|
+
* in compliance with the Elastic License 2.0.
|
|
178877
|
+
*/
|
|
178878
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
178879
|
+
exports.PolicyInputBuilder = void 0;
|
|
178880
|
+
/**
|
|
178881
|
+
* Builds OPA-compatible input documents from engine context.
|
|
178882
|
+
*
|
|
178883
|
+
* Resolves actor roles from the `policy.roles` config section by matching
|
|
178884
|
+
* the actor's authorAssociation and login against role definitions.
|
|
178885
|
+
*/
|
|
178886
|
+
class PolicyInputBuilder {
|
|
178887
|
+
roles;
|
|
178888
|
+
actor;
|
|
178889
|
+
repository;
|
|
178890
|
+
pullRequest;
|
|
178891
|
+
constructor(policyConfig, actor, repository, pullRequest) {
|
|
178892
|
+
this.roles = policyConfig.roles || {};
|
|
178893
|
+
this.actor = actor;
|
|
178894
|
+
this.repository = repository;
|
|
178895
|
+
this.pullRequest = pullRequest;
|
|
178896
|
+
}
|
|
178897
|
+
/** Resolve which roles apply to the current actor. */
|
|
178898
|
+
resolveRoles() {
|
|
178899
|
+
const matched = [];
|
|
178900
|
+
for (const [roleName, roleConfig] of Object.entries(this.roles)) {
|
|
178901
|
+
let identityMatch = false;
|
|
178902
|
+
if (roleConfig.author_association &&
|
|
178903
|
+
this.actor.authorAssociation &&
|
|
178904
|
+
roleConfig.author_association.includes(this.actor.authorAssociation)) {
|
|
178905
|
+
identityMatch = true;
|
|
178906
|
+
}
|
|
178907
|
+
if (!identityMatch &&
|
|
178908
|
+
roleConfig.users &&
|
|
178909
|
+
this.actor.login &&
|
|
178910
|
+
roleConfig.users.includes(this.actor.login)) {
|
|
178911
|
+
identityMatch = true;
|
|
178912
|
+
}
|
|
178913
|
+
// Slack user ID match
|
|
178914
|
+
if (!identityMatch &&
|
|
178915
|
+
roleConfig.slack_users &&
|
|
178916
|
+
this.actor.slack?.userId &&
|
|
178917
|
+
roleConfig.slack_users.includes(this.actor.slack.userId)) {
|
|
178918
|
+
identityMatch = true;
|
|
178919
|
+
}
|
|
178920
|
+
// Email match (case-insensitive)
|
|
178921
|
+
if (!identityMatch && roleConfig.emails && this.actor.slack?.email) {
|
|
178922
|
+
const actorEmail = this.actor.slack.email.toLowerCase();
|
|
178923
|
+
if (roleConfig.emails.some(e => e.toLowerCase() === actorEmail)) {
|
|
178924
|
+
identityMatch = true;
|
|
178925
|
+
}
|
|
178926
|
+
}
|
|
178927
|
+
// Note: teams-based role resolution requires GitHub API access (read:org scope)
|
|
178928
|
+
// and is not yet implemented. If configured, the role will not match via teams.
|
|
178929
|
+
if (!identityMatch)
|
|
178930
|
+
continue;
|
|
178931
|
+
// slack_channels gate: if set, the role only applies when triggered from one of these channels
|
|
178932
|
+
if (roleConfig.slack_channels && roleConfig.slack_channels.length > 0) {
|
|
178933
|
+
if (!this.actor.slack?.channelId ||
|
|
178934
|
+
!roleConfig.slack_channels.includes(this.actor.slack.channelId)) {
|
|
178935
|
+
continue;
|
|
178936
|
+
}
|
|
178937
|
+
}
|
|
178938
|
+
matched.push(roleName);
|
|
178939
|
+
}
|
|
178940
|
+
return matched;
|
|
178941
|
+
}
|
|
178942
|
+
buildActor() {
|
|
178943
|
+
return {
|
|
178944
|
+
authorAssociation: this.actor.authorAssociation,
|
|
178945
|
+
login: this.actor.login,
|
|
178946
|
+
roles: this.resolveRoles(),
|
|
178947
|
+
isLocalMode: this.actor.isLocalMode,
|
|
178948
|
+
...(this.actor.slack && { slack: this.actor.slack }),
|
|
178949
|
+
};
|
|
178950
|
+
}
|
|
178951
|
+
forCheckExecution(check) {
|
|
178952
|
+
return {
|
|
178953
|
+
scope: 'check.execute',
|
|
178954
|
+
check: {
|
|
178955
|
+
id: check.id,
|
|
178956
|
+
type: check.type,
|
|
178957
|
+
group: check.group,
|
|
178958
|
+
tags: check.tags,
|
|
178959
|
+
criticality: check.criticality,
|
|
178960
|
+
sandbox: check.sandbox,
|
|
178961
|
+
policy: check.policy,
|
|
178962
|
+
},
|
|
178963
|
+
actor: this.buildActor(),
|
|
178964
|
+
repository: this.repository,
|
|
178965
|
+
pullRequest: this.pullRequest,
|
|
178966
|
+
};
|
|
178967
|
+
}
|
|
178968
|
+
forToolInvocation(serverName, methodName, transport) {
|
|
178969
|
+
return {
|
|
178970
|
+
scope: 'tool.invoke',
|
|
178971
|
+
tool: { serverName, methodName, transport },
|
|
178972
|
+
actor: this.buildActor(),
|
|
178973
|
+
repository: this.repository,
|
|
178974
|
+
pullRequest: this.pullRequest,
|
|
178975
|
+
};
|
|
178976
|
+
}
|
|
178977
|
+
forCapabilityResolve(checkId, capabilities) {
|
|
178978
|
+
return {
|
|
178979
|
+
scope: 'capability.resolve',
|
|
178980
|
+
check: { id: checkId, type: 'ai' },
|
|
178981
|
+
capability: capabilities,
|
|
178982
|
+
actor: this.buildActor(),
|
|
178983
|
+
repository: this.repository,
|
|
178984
|
+
pullRequest: this.pullRequest,
|
|
178985
|
+
};
|
|
178986
|
+
}
|
|
178987
|
+
}
|
|
178988
|
+
exports.PolicyInputBuilder = PolicyInputBuilder;
|
|
178989
|
+
|
|
178990
|
+
|
|
178991
|
+
/***/ }),
|
|
178992
|
+
|
|
178993
|
+
/***/ 63737:
|
|
178994
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
178995
|
+
|
|
178996
|
+
"use strict";
|
|
178997
|
+
|
|
178998
|
+
/**
|
|
178999
|
+
* Copyright (c) ProbeLabs. All rights reserved.
|
|
179000
|
+
* Licensed under the Elastic License 2.0; you may not use this file except
|
|
179001
|
+
* in compliance with the Elastic License 2.0.
|
|
179002
|
+
*/
|
|
179003
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
179004
|
+
if (k2 === undefined) k2 = k;
|
|
179005
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
179006
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
179007
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
179008
|
+
}
|
|
179009
|
+
Object.defineProperty(o, k2, desc);
|
|
179010
|
+
}) : (function(o, m, k, k2) {
|
|
179011
|
+
if (k2 === undefined) k2 = k;
|
|
179012
|
+
o[k2] = m[k];
|
|
179013
|
+
}));
|
|
179014
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
179015
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
179016
|
+
}) : function(o, v) {
|
|
179017
|
+
o["default"] = v;
|
|
179018
|
+
});
|
|
179019
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
179020
|
+
var ownKeys = function(o) {
|
|
179021
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
179022
|
+
var ar = [];
|
|
179023
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
179024
|
+
return ar;
|
|
179025
|
+
};
|
|
179026
|
+
return ownKeys(o);
|
|
179027
|
+
};
|
|
179028
|
+
return function (mod) {
|
|
179029
|
+
if (mod && mod.__esModule) return mod;
|
|
179030
|
+
var result = {};
|
|
179031
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
179032
|
+
__setModuleDefault(result, mod);
|
|
179033
|
+
return result;
|
|
179034
|
+
};
|
|
179035
|
+
})();
|
|
179036
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
179037
|
+
exports.KnexStoreBackend = void 0;
|
|
179038
|
+
/**
|
|
179039
|
+
* Knex-backed schedule store for PostgreSQL, MySQL, and MSSQL (Enterprise)
|
|
179040
|
+
*
|
|
179041
|
+
* Uses Knex query builder for database-agnostic SQL. Same schema as SQLite backend
|
|
179042
|
+
* but with real distributed locking via row-level claims (claimed_by/claimed_at/lock_token).
|
|
179043
|
+
*/
|
|
179044
|
+
const fs = __importStar(__nccwpck_require__(79896));
|
|
179045
|
+
const path = __importStar(__nccwpck_require__(16928));
|
|
179046
|
+
const uuid_1 = __nccwpck_require__(31914);
|
|
179047
|
+
const logger_1 = __nccwpck_require__(86999);
|
|
179048
|
+
function toNum(val) {
|
|
179049
|
+
if (val === null || val === undefined)
|
|
179050
|
+
return undefined;
|
|
179051
|
+
return typeof val === 'string' ? parseInt(val, 10) : val;
|
|
179052
|
+
}
|
|
179053
|
+
function safeJsonParse(value) {
|
|
179054
|
+
if (!value)
|
|
179055
|
+
return undefined;
|
|
179056
|
+
try {
|
|
179057
|
+
return JSON.parse(value);
|
|
179058
|
+
}
|
|
179059
|
+
catch {
|
|
179060
|
+
return undefined;
|
|
179061
|
+
}
|
|
179062
|
+
}
|
|
179063
|
+
function fromTriggerRow(row) {
|
|
179064
|
+
return {
|
|
179065
|
+
id: row.id,
|
|
179066
|
+
creatorId: row.creator_id,
|
|
179067
|
+
creatorContext: row.creator_context ?? undefined,
|
|
179068
|
+
creatorName: row.creator_name ?? undefined,
|
|
179069
|
+
description: row.description ?? undefined,
|
|
179070
|
+
channels: safeJsonParse(row.channels),
|
|
179071
|
+
fromUsers: safeJsonParse(row.from_users),
|
|
179072
|
+
fromBots: row.from_bots === true || row.from_bots === 1,
|
|
179073
|
+
contains: safeJsonParse(row.contains),
|
|
179074
|
+
matchPattern: row.match_pattern ?? undefined,
|
|
179075
|
+
threads: row.threads,
|
|
179076
|
+
workflow: row.workflow,
|
|
179077
|
+
inputs: safeJsonParse(row.inputs),
|
|
179078
|
+
outputContext: safeJsonParse(row.output_context),
|
|
179079
|
+
status: row.status,
|
|
179080
|
+
enabled: row.enabled === true || row.enabled === 1,
|
|
179081
|
+
createdAt: toNum(row.created_at),
|
|
179082
|
+
};
|
|
179083
|
+
}
|
|
179084
|
+
function toTriggerInsertRow(trigger) {
|
|
179085
|
+
return {
|
|
179086
|
+
id: trigger.id,
|
|
179087
|
+
creator_id: trigger.creatorId,
|
|
179088
|
+
creator_context: trigger.creatorContext ?? null,
|
|
179089
|
+
creator_name: trigger.creatorName ?? null,
|
|
179090
|
+
description: trigger.description ?? null,
|
|
179091
|
+
channels: trigger.channels ? JSON.stringify(trigger.channels) : null,
|
|
179092
|
+
from_users: trigger.fromUsers ? JSON.stringify(trigger.fromUsers) : null,
|
|
179093
|
+
from_bots: trigger.fromBots,
|
|
179094
|
+
contains: trigger.contains ? JSON.stringify(trigger.contains) : null,
|
|
179095
|
+
match_pattern: trigger.matchPattern ?? null,
|
|
179096
|
+
threads: trigger.threads,
|
|
179097
|
+
workflow: trigger.workflow,
|
|
179098
|
+
inputs: trigger.inputs ? JSON.stringify(trigger.inputs) : null,
|
|
179099
|
+
output_context: trigger.outputContext ? JSON.stringify(trigger.outputContext) : null,
|
|
179100
|
+
status: trigger.status,
|
|
179101
|
+
enabled: trigger.enabled,
|
|
179102
|
+
created_at: trigger.createdAt,
|
|
179103
|
+
};
|
|
179104
|
+
}
|
|
179105
|
+
function fromDbRow(row) {
|
|
179106
|
+
return {
|
|
179107
|
+
id: row.id,
|
|
179108
|
+
creatorId: row.creator_id,
|
|
179109
|
+
creatorContext: row.creator_context ?? undefined,
|
|
179110
|
+
creatorName: row.creator_name ?? undefined,
|
|
179111
|
+
timezone: row.timezone,
|
|
179112
|
+
schedule: row.schedule_expr,
|
|
179113
|
+
runAt: toNum(row.run_at),
|
|
179114
|
+
isRecurring: row.is_recurring === true || row.is_recurring === 1,
|
|
179115
|
+
originalExpression: row.original_expression,
|
|
179116
|
+
workflow: row.workflow ?? undefined,
|
|
179117
|
+
workflowInputs: safeJsonParse(row.workflow_inputs),
|
|
179118
|
+
outputContext: safeJsonParse(row.output_context),
|
|
179119
|
+
status: row.status,
|
|
179120
|
+
createdAt: toNum(row.created_at),
|
|
179121
|
+
lastRunAt: toNum(row.last_run_at),
|
|
179122
|
+
nextRunAt: toNum(row.next_run_at),
|
|
179123
|
+
runCount: row.run_count,
|
|
179124
|
+
failureCount: row.failure_count,
|
|
179125
|
+
lastError: row.last_error ?? undefined,
|
|
179126
|
+
previousResponse: row.previous_response ?? undefined,
|
|
179127
|
+
};
|
|
179128
|
+
}
|
|
179129
|
+
function toInsertRow(schedule) {
|
|
179130
|
+
return {
|
|
179131
|
+
id: schedule.id,
|
|
179132
|
+
creator_id: schedule.creatorId,
|
|
179133
|
+
creator_context: schedule.creatorContext ?? null,
|
|
179134
|
+
creator_name: schedule.creatorName ?? null,
|
|
179135
|
+
timezone: schedule.timezone,
|
|
179136
|
+
schedule_expr: schedule.schedule,
|
|
179137
|
+
run_at: schedule.runAt ?? null,
|
|
179138
|
+
is_recurring: schedule.isRecurring,
|
|
179139
|
+
original_expression: schedule.originalExpression,
|
|
179140
|
+
workflow: schedule.workflow ?? null,
|
|
179141
|
+
workflow_inputs: schedule.workflowInputs ? JSON.stringify(schedule.workflowInputs) : null,
|
|
179142
|
+
output_context: schedule.outputContext ? JSON.stringify(schedule.outputContext) : null,
|
|
179143
|
+
status: schedule.status,
|
|
179144
|
+
created_at: schedule.createdAt,
|
|
179145
|
+
last_run_at: schedule.lastRunAt ?? null,
|
|
179146
|
+
next_run_at: schedule.nextRunAt ?? null,
|
|
179147
|
+
run_count: schedule.runCount,
|
|
179148
|
+
failure_count: schedule.failureCount,
|
|
179149
|
+
last_error: schedule.lastError ?? null,
|
|
179150
|
+
previous_response: schedule.previousResponse ?? null,
|
|
179151
|
+
};
|
|
179152
|
+
}
|
|
179153
|
+
/**
|
|
179154
|
+
* Enterprise Knex-backed store for PostgreSQL, MySQL, and MSSQL
|
|
179155
|
+
*/
|
|
179156
|
+
class KnexStoreBackend {
|
|
179157
|
+
knex = null;
|
|
179158
|
+
driver;
|
|
179159
|
+
connection;
|
|
179160
|
+
constructor(driver, storageConfig, _haConfig) {
|
|
179161
|
+
this.driver = driver;
|
|
179162
|
+
this.connection = (storageConfig.connection || {});
|
|
179163
|
+
}
|
|
179164
|
+
async initialize() {
|
|
179165
|
+
// Load knex dynamically
|
|
179166
|
+
const { createRequire } = __nccwpck_require__(73339);
|
|
179167
|
+
const runtimeRequire = createRequire(__filename);
|
|
179168
|
+
let knexFactory;
|
|
179169
|
+
try {
|
|
179170
|
+
knexFactory = runtimeRequire('knex');
|
|
179171
|
+
}
|
|
179172
|
+
catch (err) {
|
|
179173
|
+
const code = err?.code;
|
|
179174
|
+
if (code === 'MODULE_NOT_FOUND' || code === 'ERR_MODULE_NOT_FOUND') {
|
|
179175
|
+
throw new Error('knex is required for PostgreSQL/MySQL/MSSQL schedule storage. ' +
|
|
179176
|
+
'Install it with: npm install knex');
|
|
179177
|
+
}
|
|
179178
|
+
throw err;
|
|
179179
|
+
}
|
|
179180
|
+
const clientMap = {
|
|
179181
|
+
postgresql: 'pg',
|
|
179182
|
+
mysql: 'mysql2',
|
|
179183
|
+
mssql: 'tedious',
|
|
179184
|
+
};
|
|
179185
|
+
const client = clientMap[this.driver];
|
|
179186
|
+
// Build connection config
|
|
179187
|
+
let connection;
|
|
179188
|
+
if (this.connection.connection_string) {
|
|
179189
|
+
connection = this.connection.connection_string;
|
|
179190
|
+
}
|
|
179191
|
+
else if (this.driver === 'mssql') {
|
|
179192
|
+
connection = this.buildMssqlConnection();
|
|
179193
|
+
}
|
|
179194
|
+
else {
|
|
179195
|
+
connection = this.buildStandardConnection();
|
|
179196
|
+
}
|
|
179197
|
+
this.knex = knexFactory({
|
|
179198
|
+
client,
|
|
179199
|
+
connection,
|
|
179200
|
+
pool: {
|
|
179201
|
+
min: this.connection.pool?.min ?? 0,
|
|
179202
|
+
max: this.connection.pool?.max ?? 10,
|
|
179203
|
+
},
|
|
179204
|
+
});
|
|
179205
|
+
// Run schema migration
|
|
179206
|
+
await this.migrateSchema();
|
|
179207
|
+
logger_1.logger.info(`[KnexStore] Initialized (${this.driver})`);
|
|
179208
|
+
}
|
|
179209
|
+
buildStandardConnection() {
|
|
179210
|
+
return {
|
|
179211
|
+
host: this.connection.host || 'localhost',
|
|
179212
|
+
port: this.connection.port,
|
|
179213
|
+
database: this.connection.database || 'visor',
|
|
179214
|
+
user: this.connection.user,
|
|
179215
|
+
password: this.connection.password,
|
|
179216
|
+
ssl: this.resolveSslConfig(),
|
|
179217
|
+
};
|
|
179218
|
+
}
|
|
179219
|
+
buildMssqlConnection() {
|
|
179220
|
+
const ssl = this.connection.ssl;
|
|
179221
|
+
const sslEnabled = ssl === true || (typeof ssl === 'object' && ssl.enabled !== false);
|
|
179222
|
+
return {
|
|
179223
|
+
server: this.connection.host || 'localhost',
|
|
179224
|
+
port: this.connection.port,
|
|
179225
|
+
database: this.connection.database || 'visor',
|
|
179226
|
+
user: this.connection.user,
|
|
179227
|
+
password: this.connection.password,
|
|
179228
|
+
options: {
|
|
179229
|
+
encrypt: sslEnabled,
|
|
179230
|
+
trustServerCertificate: typeof ssl === 'object' ? ssl.reject_unauthorized === false : !sslEnabled,
|
|
179231
|
+
},
|
|
179232
|
+
};
|
|
179233
|
+
}
|
|
179234
|
+
resolveSslConfig() {
|
|
179235
|
+
const ssl = this.connection.ssl;
|
|
179236
|
+
if (ssl === false || ssl === undefined)
|
|
179237
|
+
return false;
|
|
179238
|
+
if (ssl === true)
|
|
179239
|
+
return { rejectUnauthorized: true };
|
|
179240
|
+
// Object config
|
|
179241
|
+
if (ssl.enabled === false)
|
|
179242
|
+
return false;
|
|
179243
|
+
const result = {
|
|
179244
|
+
rejectUnauthorized: ssl.reject_unauthorized !== false,
|
|
179245
|
+
};
|
|
179246
|
+
if (ssl.ca) {
|
|
179247
|
+
const caPath = this.validateSslPath(ssl.ca, 'CA certificate');
|
|
179248
|
+
result.ca = fs.readFileSync(caPath, 'utf8');
|
|
179249
|
+
}
|
|
179250
|
+
if (ssl.cert) {
|
|
179251
|
+
const certPath = this.validateSslPath(ssl.cert, 'client certificate');
|
|
179252
|
+
result.cert = fs.readFileSync(certPath, 'utf8');
|
|
179253
|
+
}
|
|
179254
|
+
if (ssl.key) {
|
|
179255
|
+
const keyPath = this.validateSslPath(ssl.key, 'client key');
|
|
179256
|
+
result.key = fs.readFileSync(keyPath, 'utf8');
|
|
179257
|
+
}
|
|
179258
|
+
return result;
|
|
179259
|
+
}
|
|
179260
|
+
validateSslPath(filePath, label) {
|
|
179261
|
+
const resolved = path.resolve(filePath);
|
|
179262
|
+
if (resolved !== path.normalize(resolved)) {
|
|
179263
|
+
throw new Error(`SSL ${label} path contains invalid sequences: ${filePath}`);
|
|
179264
|
+
}
|
|
179265
|
+
if (!fs.existsSync(resolved)) {
|
|
179266
|
+
throw new Error(`SSL ${label} not found: ${filePath}`);
|
|
179267
|
+
}
|
|
179268
|
+
return resolved;
|
|
179269
|
+
}
|
|
179270
|
+
async shutdown() {
|
|
179271
|
+
if (this.knex) {
|
|
179272
|
+
await this.knex.destroy();
|
|
179273
|
+
this.knex = null;
|
|
179274
|
+
}
|
|
179275
|
+
}
|
|
179276
|
+
async migrateSchema() {
|
|
179277
|
+
const knex = this.getKnex();
|
|
179278
|
+
const exists = await knex.schema.hasTable('schedules');
|
|
179279
|
+
if (!exists) {
|
|
179280
|
+
await knex.schema.createTable('schedules', table => {
|
|
179281
|
+
table.string('id', 36).primary();
|
|
179282
|
+
table.string('creator_id', 255).notNullable().index();
|
|
179283
|
+
table.string('creator_context', 255);
|
|
179284
|
+
table.string('creator_name', 255);
|
|
179285
|
+
table.string('timezone', 64).notNullable().defaultTo('UTC');
|
|
179286
|
+
table.string('schedule_expr', 255);
|
|
179287
|
+
table.bigInteger('run_at');
|
|
179288
|
+
table.boolean('is_recurring').notNullable();
|
|
179289
|
+
table.text('original_expression');
|
|
179290
|
+
table.string('workflow', 255);
|
|
179291
|
+
table.text('workflow_inputs');
|
|
179292
|
+
table.text('output_context');
|
|
179293
|
+
table.string('status', 20).notNullable().index();
|
|
179294
|
+
table.bigInteger('created_at').notNullable();
|
|
179295
|
+
table.bigInteger('last_run_at');
|
|
179296
|
+
table.bigInteger('next_run_at');
|
|
179297
|
+
table.integer('run_count').notNullable().defaultTo(0);
|
|
179298
|
+
table.integer('failure_count').notNullable().defaultTo(0);
|
|
179299
|
+
table.text('last_error');
|
|
179300
|
+
table.text('previous_response');
|
|
179301
|
+
table.index(['status', 'next_run_at']);
|
|
179302
|
+
});
|
|
179303
|
+
}
|
|
179304
|
+
// Create message_triggers table
|
|
179305
|
+
const triggersExist = await knex.schema.hasTable('message_triggers');
|
|
179306
|
+
if (!triggersExist) {
|
|
179307
|
+
await knex.schema.createTable('message_triggers', table => {
|
|
179308
|
+
table.string('id', 36).primary();
|
|
179309
|
+
table.string('creator_id', 255).notNullable().index();
|
|
179310
|
+
table.string('creator_context', 255);
|
|
179311
|
+
table.string('creator_name', 255);
|
|
179312
|
+
table.text('description');
|
|
179313
|
+
table.text('channels'); // JSON array
|
|
179314
|
+
table.text('from_users'); // JSON array
|
|
179315
|
+
table.boolean('from_bots').notNullable().defaultTo(false);
|
|
179316
|
+
table.text('contains'); // JSON array
|
|
179317
|
+
table.text('match_pattern');
|
|
179318
|
+
table.string('threads', 20).notNullable().defaultTo('any');
|
|
179319
|
+
table.string('workflow', 255).notNullable();
|
|
179320
|
+
table.text('inputs'); // JSON
|
|
179321
|
+
table.text('output_context'); // JSON
|
|
179322
|
+
table.string('status', 20).notNullable().defaultTo('active').index();
|
|
179323
|
+
table.boolean('enabled').notNullable().defaultTo(true);
|
|
179324
|
+
table.bigInteger('created_at').notNullable();
|
|
179325
|
+
});
|
|
179326
|
+
}
|
|
179327
|
+
// Create scheduler_locks table for distributed locking
|
|
179328
|
+
const locksExist = await knex.schema.hasTable('scheduler_locks');
|
|
179329
|
+
if (!locksExist) {
|
|
179330
|
+
await knex.schema.createTable('scheduler_locks', table => {
|
|
179331
|
+
table.string('lock_id', 255).primary();
|
|
179332
|
+
table.string('node_id', 255).notNullable();
|
|
179333
|
+
table.string('lock_token', 36).notNullable();
|
|
179334
|
+
table.bigInteger('acquired_at').notNullable();
|
|
179335
|
+
table.bigInteger('expires_at').notNullable();
|
|
179336
|
+
});
|
|
179337
|
+
}
|
|
179338
|
+
}
|
|
179339
|
+
getKnex() {
|
|
179340
|
+
if (!this.knex) {
|
|
179341
|
+
throw new Error('[KnexStore] Not initialized. Call initialize() first.');
|
|
179342
|
+
}
|
|
179343
|
+
return this.knex;
|
|
179344
|
+
}
|
|
179345
|
+
// --- CRUD ---
|
|
179346
|
+
async create(schedule) {
|
|
179347
|
+
const knex = this.getKnex();
|
|
179348
|
+
const newSchedule = {
|
|
179349
|
+
...schedule,
|
|
179350
|
+
id: (0, uuid_1.v4)(),
|
|
179351
|
+
createdAt: Date.now(),
|
|
179352
|
+
runCount: 0,
|
|
179353
|
+
failureCount: 0,
|
|
179354
|
+
status: 'active',
|
|
179355
|
+
};
|
|
179356
|
+
await knex('schedules').insert(toInsertRow(newSchedule));
|
|
179357
|
+
logger_1.logger.info(`[KnexStore] Created schedule ${newSchedule.id} for user ${newSchedule.creatorId}`);
|
|
179358
|
+
return newSchedule;
|
|
179359
|
+
}
|
|
179360
|
+
async importSchedule(schedule) {
|
|
179361
|
+
const knex = this.getKnex();
|
|
179362
|
+
const existing = await knex('schedules').where('id', schedule.id).first();
|
|
179363
|
+
if (existing)
|
|
179364
|
+
return; // Already imported (idempotent)
|
|
179365
|
+
await knex('schedules').insert(toInsertRow(schedule));
|
|
179366
|
+
}
|
|
179367
|
+
async get(id) {
|
|
179368
|
+
const knex = this.getKnex();
|
|
179369
|
+
const row = await knex('schedules').where('id', id).first();
|
|
179370
|
+
return row ? fromDbRow(row) : undefined;
|
|
179371
|
+
}
|
|
179372
|
+
async update(id, patch) {
|
|
179373
|
+
const knex = this.getKnex();
|
|
179374
|
+
const existing = await knex('schedules').where('id', id).first();
|
|
179375
|
+
if (!existing)
|
|
179376
|
+
return undefined;
|
|
179377
|
+
const current = fromDbRow(existing);
|
|
179378
|
+
const updated = { ...current, ...patch, id: current.id };
|
|
179379
|
+
const row = toInsertRow(updated);
|
|
179380
|
+
// Remove id from update (PK cannot change)
|
|
179381
|
+
delete row.id;
|
|
179382
|
+
await knex('schedules').where('id', id).update(row);
|
|
179383
|
+
return updated;
|
|
179384
|
+
}
|
|
179385
|
+
async delete(id) {
|
|
179386
|
+
const knex = this.getKnex();
|
|
179387
|
+
const deleted = await knex('schedules').where('id', id).del();
|
|
179388
|
+
if (deleted > 0) {
|
|
179389
|
+
logger_1.logger.info(`[KnexStore] Deleted schedule ${id}`);
|
|
179390
|
+
return true;
|
|
179391
|
+
}
|
|
179392
|
+
return false;
|
|
179393
|
+
}
|
|
179394
|
+
// --- Queries ---
|
|
179395
|
+
async getByCreator(creatorId) {
|
|
179396
|
+
const knex = this.getKnex();
|
|
179397
|
+
const rows = await knex('schedules').where('creator_id', creatorId);
|
|
179398
|
+
return rows.map((r) => fromDbRow(r));
|
|
179399
|
+
}
|
|
179400
|
+
async getActiveSchedules() {
|
|
179401
|
+
const knex = this.getKnex();
|
|
179402
|
+
const rows = await knex('schedules').where('status', 'active');
|
|
179403
|
+
return rows.map((r) => fromDbRow(r));
|
|
179404
|
+
}
|
|
179405
|
+
async getDueSchedules(now) {
|
|
179406
|
+
const ts = now ?? Date.now();
|
|
179407
|
+
const knex = this.getKnex();
|
|
179408
|
+
// MSSQL uses 1/0 for booleans
|
|
179409
|
+
const bFalse = this.driver === 'mssql' ? 0 : false;
|
|
179410
|
+
const bTrue = this.driver === 'mssql' ? 1 : true;
|
|
179411
|
+
const rows = await knex('schedules')
|
|
179412
|
+
.where('status', 'active')
|
|
179413
|
+
.andWhere(function () {
|
|
179414
|
+
this.where(function () {
|
|
179415
|
+
this.where('is_recurring', bFalse)
|
|
179416
|
+
.whereNotNull('run_at')
|
|
179417
|
+
.where('run_at', '<=', ts);
|
|
179418
|
+
}).orWhere(function () {
|
|
179419
|
+
this.where('is_recurring', bTrue)
|
|
179420
|
+
.whereNotNull('next_run_at')
|
|
179421
|
+
.where('next_run_at', '<=', ts);
|
|
179422
|
+
});
|
|
179423
|
+
});
|
|
179424
|
+
return rows.map((r) => fromDbRow(r));
|
|
179425
|
+
}
|
|
179426
|
+
async findByWorkflow(creatorId, workflowName) {
|
|
179427
|
+
const knex = this.getKnex();
|
|
179428
|
+
const escaped = workflowName.toLowerCase().replace(/[%_\\]/g, '\\$&');
|
|
179429
|
+
const pattern = `%${escaped}%`;
|
|
179430
|
+
const rows = await knex('schedules')
|
|
179431
|
+
.where('creator_id', creatorId)
|
|
179432
|
+
.where('status', 'active')
|
|
179433
|
+
.whereRaw("LOWER(workflow) LIKE ? ESCAPE '\\'", [pattern]);
|
|
179434
|
+
return rows.map((r) => fromDbRow(r));
|
|
179435
|
+
}
|
|
179436
|
+
async getAll() {
|
|
179437
|
+
const knex = this.getKnex();
|
|
179438
|
+
const rows = await knex('schedules');
|
|
179439
|
+
return rows.map((r) => fromDbRow(r));
|
|
179440
|
+
}
|
|
179441
|
+
async getStats() {
|
|
179442
|
+
const knex = this.getKnex();
|
|
179443
|
+
// MSSQL uses 1/0 for booleans; PostgreSQL/MySQL accept both true/1
|
|
179444
|
+
const boolTrue = this.driver === 'mssql' ? '1' : 'true';
|
|
179445
|
+
const boolFalse = this.driver === 'mssql' ? '0' : 'false';
|
|
179446
|
+
const result = await knex('schedules')
|
|
179447
|
+
.select(knex.raw('COUNT(*) as total'), knex.raw("SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active"), knex.raw("SUM(CASE WHEN status = 'paused' THEN 1 ELSE 0 END) as paused"), knex.raw("SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed"), knex.raw("SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed"), knex.raw(`SUM(CASE WHEN is_recurring = ${boolTrue} THEN 1 ELSE 0 END) as recurring`), knex.raw(`SUM(CASE WHEN is_recurring = ${boolFalse} THEN 1 ELSE 0 END) as one_time`))
|
|
179448
|
+
.first();
|
|
179449
|
+
return {
|
|
179450
|
+
total: Number(result.total) || 0,
|
|
179451
|
+
active: Number(result.active) || 0,
|
|
179452
|
+
paused: Number(result.paused) || 0,
|
|
179453
|
+
completed: Number(result.completed) || 0,
|
|
179454
|
+
failed: Number(result.failed) || 0,
|
|
179455
|
+
recurring: Number(result.recurring) || 0,
|
|
179456
|
+
oneTime: Number(result.one_time) || 0,
|
|
179457
|
+
};
|
|
179458
|
+
}
|
|
179459
|
+
async validateLimits(creatorId, isRecurring, limits) {
|
|
179460
|
+
const knex = this.getKnex();
|
|
179461
|
+
if (limits.maxGlobal) {
|
|
179462
|
+
const result = await knex('schedules').count('* as cnt').first();
|
|
179463
|
+
if (Number(result?.cnt) >= limits.maxGlobal) {
|
|
179464
|
+
throw new Error(`Global schedule limit reached (${limits.maxGlobal})`);
|
|
179465
|
+
}
|
|
179466
|
+
}
|
|
179467
|
+
if (limits.maxPerUser) {
|
|
179468
|
+
const result = await knex('schedules')
|
|
179469
|
+
.where('creator_id', creatorId)
|
|
179470
|
+
.count('* as cnt')
|
|
179471
|
+
.first();
|
|
179472
|
+
if (Number(result?.cnt) >= limits.maxPerUser) {
|
|
179473
|
+
throw new Error(`You have reached the maximum number of schedules (${limits.maxPerUser})`);
|
|
179474
|
+
}
|
|
179475
|
+
}
|
|
179476
|
+
if (isRecurring && limits.maxRecurringPerUser) {
|
|
179477
|
+
const bTrue = this.driver === 'mssql' ? 1 : true;
|
|
179478
|
+
const result = await knex('schedules')
|
|
179479
|
+
.where('creator_id', creatorId)
|
|
179480
|
+
.where('is_recurring', bTrue)
|
|
179481
|
+
.count('* as cnt')
|
|
179482
|
+
.first();
|
|
179483
|
+
if (Number(result?.cnt) >= limits.maxRecurringPerUser) {
|
|
179484
|
+
throw new Error(`You have reached the maximum number of recurring schedules (${limits.maxRecurringPerUser})`);
|
|
179485
|
+
}
|
|
179486
|
+
}
|
|
179487
|
+
}
|
|
179488
|
+
// --- HA Distributed Locking (via scheduler_locks table) ---
|
|
179489
|
+
async tryAcquireLock(lockId, nodeId, ttlSeconds) {
|
|
179490
|
+
const knex = this.getKnex();
|
|
179491
|
+
const now = Date.now();
|
|
179492
|
+
const expiresAt = now + ttlSeconds * 1000;
|
|
179493
|
+
const token = (0, uuid_1.v4)();
|
|
179494
|
+
// Step 1: Try to claim an existing expired lock
|
|
179495
|
+
const updated = await knex('scheduler_locks')
|
|
179496
|
+
.where('lock_id', lockId)
|
|
179497
|
+
.where('expires_at', '<', now)
|
|
179498
|
+
.update({
|
|
179499
|
+
node_id: nodeId,
|
|
179500
|
+
lock_token: token,
|
|
179501
|
+
acquired_at: now,
|
|
179502
|
+
expires_at: expiresAt,
|
|
179503
|
+
});
|
|
179504
|
+
if (updated > 0)
|
|
179505
|
+
return token;
|
|
179506
|
+
// Step 2: Try to INSERT a new lock row
|
|
179507
|
+
try {
|
|
179508
|
+
await knex('scheduler_locks').insert({
|
|
179509
|
+
lock_id: lockId,
|
|
179510
|
+
node_id: nodeId,
|
|
179511
|
+
lock_token: token,
|
|
179512
|
+
acquired_at: now,
|
|
179513
|
+
expires_at: expiresAt,
|
|
179514
|
+
});
|
|
179515
|
+
return token;
|
|
179516
|
+
}
|
|
179517
|
+
catch {
|
|
179518
|
+
// Unique constraint violation — another node holds the lock
|
|
179519
|
+
return null;
|
|
179520
|
+
}
|
|
179521
|
+
}
|
|
179522
|
+
async releaseLock(lockId, lockToken) {
|
|
179523
|
+
const knex = this.getKnex();
|
|
179524
|
+
await knex('scheduler_locks').where('lock_id', lockId).where('lock_token', lockToken).del();
|
|
179525
|
+
}
|
|
179526
|
+
async renewLock(lockId, lockToken, ttlSeconds) {
|
|
179527
|
+
const knex = this.getKnex();
|
|
179528
|
+
const now = Date.now();
|
|
179529
|
+
const expiresAt = now + ttlSeconds * 1000;
|
|
179530
|
+
const updated = await knex('scheduler_locks')
|
|
179531
|
+
.where('lock_id', lockId)
|
|
179532
|
+
.where('lock_token', lockToken)
|
|
179533
|
+
.update({ acquired_at: now, expires_at: expiresAt });
|
|
179534
|
+
return updated > 0;
|
|
179535
|
+
}
|
|
179536
|
+
async flush() {
|
|
179537
|
+
// No-op for server-based backends
|
|
179538
|
+
}
|
|
179539
|
+
// --- Message Trigger CRUD ---
|
|
179540
|
+
async createTrigger(trigger) {
|
|
179541
|
+
const knex = this.getKnex();
|
|
179542
|
+
const newTrigger = {
|
|
179543
|
+
...trigger,
|
|
179544
|
+
id: (0, uuid_1.v4)(),
|
|
179545
|
+
createdAt: Date.now(),
|
|
179546
|
+
};
|
|
179547
|
+
await knex('message_triggers').insert(toTriggerInsertRow(newTrigger));
|
|
179548
|
+
logger_1.logger.info(`[KnexStore] Created trigger ${newTrigger.id} for user ${newTrigger.creatorId}`);
|
|
179549
|
+
return newTrigger;
|
|
179550
|
+
}
|
|
179551
|
+
async getTrigger(id) {
|
|
179552
|
+
const knex = this.getKnex();
|
|
179553
|
+
const row = await knex('message_triggers').where('id', id).first();
|
|
179554
|
+
return row ? fromTriggerRow(row) : undefined;
|
|
179555
|
+
}
|
|
179556
|
+
async updateTrigger(id, patch) {
|
|
179557
|
+
const knex = this.getKnex();
|
|
179558
|
+
const existing = await knex('message_triggers').where('id', id).first();
|
|
179559
|
+
if (!existing)
|
|
179560
|
+
return undefined;
|
|
179561
|
+
const current = fromTriggerRow(existing);
|
|
179562
|
+
const updated = {
|
|
179563
|
+
...current,
|
|
179564
|
+
...patch,
|
|
179565
|
+
id: current.id,
|
|
179566
|
+
createdAt: current.createdAt,
|
|
179567
|
+
};
|
|
179568
|
+
const row = toTriggerInsertRow(updated);
|
|
179569
|
+
delete row.id;
|
|
179570
|
+
await knex('message_triggers').where('id', id).update(row);
|
|
179571
|
+
return updated;
|
|
179572
|
+
}
|
|
179573
|
+
async deleteTrigger(id) {
|
|
179574
|
+
const knex = this.getKnex();
|
|
179575
|
+
const deleted = await knex('message_triggers').where('id', id).del();
|
|
179576
|
+
if (deleted > 0) {
|
|
179577
|
+
logger_1.logger.info(`[KnexStore] Deleted trigger ${id}`);
|
|
179578
|
+
return true;
|
|
179579
|
+
}
|
|
179580
|
+
return false;
|
|
179581
|
+
}
|
|
179582
|
+
async getTriggersByCreator(creatorId) {
|
|
179583
|
+
const knex = this.getKnex();
|
|
179584
|
+
const rows = await knex('message_triggers').where('creator_id', creatorId);
|
|
179585
|
+
return rows.map((r) => fromTriggerRow(r));
|
|
179586
|
+
}
|
|
179587
|
+
async getActiveTriggers() {
|
|
179588
|
+
const knex = this.getKnex();
|
|
179589
|
+
const rows = await knex('message_triggers')
|
|
179590
|
+
.where('status', 'active')
|
|
179591
|
+
.where('enabled', this.driver === 'mssql' ? 1 : true);
|
|
179592
|
+
return rows.map((r) => fromTriggerRow(r));
|
|
179593
|
+
}
|
|
179594
|
+
}
|
|
179595
|
+
exports.KnexStoreBackend = KnexStoreBackend;
|
|
179596
|
+
|
|
179597
|
+
|
|
177563
179598
|
/***/ }),
|
|
177564
179599
|
|
|
177565
179600
|
/***/ 83864:
|
|
@@ -179969,6 +182004,10 @@ exports.configSchema = {
|
|
|
179969
182004
|
$ref: '#/definitions/AgentProtocolConfig',
|
|
179970
182005
|
description: 'Agent protocol (A2A) server configuration',
|
|
179971
182006
|
},
|
|
182007
|
+
task_tracking: {
|
|
182008
|
+
type: 'boolean',
|
|
182009
|
+
description: 'Enable cross-frontend task tracking (default: false). When true, all workflow executions (CLI, Slack, TUI, Scheduler) are recorded in a shared SQLite TaskStore visible via `visor tasks`.',
|
|
182010
|
+
},
|
|
179972
182011
|
},
|
|
179973
182012
|
required: ['version'],
|
|
179974
182013
|
patternProperties: {
|
|
@@ -180704,7 +182743,7 @@ exports.configSchema = {
|
|
|
180704
182743
|
description: 'Arguments/inputs for the workflow',
|
|
180705
182744
|
},
|
|
180706
182745
|
overrides: {
|
|
180707
|
-
$ref: '#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13844-28438-src_types_config.ts-0-
|
|
182746
|
+
$ref: '#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13844-28438-src_types_config.ts-0-55916%3E%3E',
|
|
180708
182747
|
description: 'Override specific step configurations in the workflow',
|
|
180709
182748
|
},
|
|
180710
182749
|
output_mapping: {
|
|
@@ -180720,7 +182759,7 @@ exports.configSchema = {
|
|
|
180720
182759
|
description: 'Config file path - alternative to workflow ID (loads a Visor config file as workflow)',
|
|
180721
182760
|
},
|
|
180722
182761
|
workflow_overrides: {
|
|
180723
|
-
$ref: '#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13844-28438-src_types_config.ts-0-
|
|
182762
|
+
$ref: '#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13844-28438-src_types_config.ts-0-55916%3E%3E',
|
|
180724
182763
|
description: 'Alias for overrides - workflow step overrides (backward compatibility)',
|
|
180725
182764
|
},
|
|
180726
182765
|
ref: {
|
|
@@ -181418,7 +183457,7 @@ exports.configSchema = {
|
|
|
181418
183457
|
description: 'Custom output name (defaults to workflow name)',
|
|
181419
183458
|
},
|
|
181420
183459
|
overrides: {
|
|
181421
|
-
$ref: '#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13844-28438-src_types_config.ts-0-
|
|
183460
|
+
$ref: '#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-13844-28438-src_types_config.ts-0-55916%3E%3E',
|
|
181422
183461
|
description: 'Step overrides',
|
|
181423
183462
|
},
|
|
181424
183463
|
output_mapping: {
|
|
@@ -181433,13 +183472,13 @@ exports.configSchema = {
|
|
|
181433
183472
|
'^x-': {},
|
|
181434
183473
|
},
|
|
181435
183474
|
},
|
|
181436
|
-
'Record<string,Partial<interface-src_types_config.ts-13844-28438-src_types_config.ts-0-
|
|
183475
|
+
'Record<string,Partial<interface-src_types_config.ts-13844-28438-src_types_config.ts-0-55916>>': {
|
|
181437
183476
|
type: 'object',
|
|
181438
183477
|
additionalProperties: {
|
|
181439
|
-
$ref: '#/definitions/Partial%3Cinterface-src_types_config.ts-13844-28438-src_types_config.ts-0-
|
|
183478
|
+
$ref: '#/definitions/Partial%3Cinterface-src_types_config.ts-13844-28438-src_types_config.ts-0-55916%3E',
|
|
181440
183479
|
},
|
|
181441
183480
|
},
|
|
181442
|
-
'Partial<interface-src_types_config.ts-13844-28438-src_types_config.ts-0-
|
|
183481
|
+
'Partial<interface-src_types_config.ts-13844-28438-src_types_config.ts-0-55916>': {
|
|
181443
183482
|
type: 'object',
|
|
181444
183483
|
additionalProperties: false,
|
|
181445
183484
|
},
|
|
@@ -188869,6 +190908,35 @@ class OutputFormatters {
|
|
|
188869
190908
|
exports.OutputFormatters = OutputFormatters;
|
|
188870
190909
|
|
|
188871
190910
|
|
|
190911
|
+
/***/ }),
|
|
190912
|
+
|
|
190913
|
+
/***/ 93866:
|
|
190914
|
+
/***/ ((__unused_webpack_module, exports) => {
|
|
190915
|
+
|
|
190916
|
+
"use strict";
|
|
190917
|
+
|
|
190918
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
190919
|
+
exports.DefaultPolicyEngine = void 0;
|
|
190920
|
+
/**
|
|
190921
|
+
* Default (no-op) policy engine — always allows everything.
|
|
190922
|
+
* Used when no enterprise license is present or policy is disabled.
|
|
190923
|
+
*/
|
|
190924
|
+
class DefaultPolicyEngine {
|
|
190925
|
+
async initialize(_config) { }
|
|
190926
|
+
async evaluateCheckExecution(_checkId, _checkConfig) {
|
|
190927
|
+
return { allowed: true };
|
|
190928
|
+
}
|
|
190929
|
+
async evaluateToolInvocation(_serverName, _methodName, _transport) {
|
|
190930
|
+
return { allowed: true };
|
|
190931
|
+
}
|
|
190932
|
+
async evaluateCapabilities(_checkId, _capabilities) {
|
|
190933
|
+
return { allowed: true };
|
|
190934
|
+
}
|
|
190935
|
+
async shutdown() { }
|
|
190936
|
+
}
|
|
190937
|
+
exports.DefaultPolicyEngine = DefaultPolicyEngine;
|
|
190938
|
+
|
|
190939
|
+
|
|
188872
190940
|
/***/ }),
|
|
188873
190941
|
|
|
188874
190942
|
/***/ 96611:
|
|
@@ -206231,6 +208299,8 @@ class Scheduler {
|
|
|
206231
208299
|
outputAdapters = new Map();
|
|
206232
208300
|
executionContext = {};
|
|
206233
208301
|
contextEnricher;
|
|
208302
|
+
taskStore;
|
|
208303
|
+
configPath;
|
|
206234
208304
|
// HA fields
|
|
206235
208305
|
haConfig;
|
|
206236
208306
|
nodeId;
|
|
@@ -206256,6 +208326,11 @@ class Scheduler {
|
|
|
206256
208326
|
setEngine(engine) {
|
|
206257
208327
|
this.engine = engine;
|
|
206258
208328
|
}
|
|
208329
|
+
/** Set shared task store for execution tracking. */
|
|
208330
|
+
setTaskStore(store, configPath) {
|
|
208331
|
+
this.taskStore = store;
|
|
208332
|
+
this.configPath = configPath;
|
|
208333
|
+
}
|
|
206259
208334
|
/**
|
|
206260
208335
|
* Set the execution context (e.g., Slack client) for workflow executions
|
|
206261
208336
|
*/
|
|
@@ -206843,7 +208918,7 @@ class Scheduler {
|
|
|
206843
208918
|
// Use common preparation helper
|
|
206844
208919
|
const { engine: runEngine, config: cfgForRun } = this.prepareExecution(schedule);
|
|
206845
208920
|
// Execute the workflow
|
|
206846
|
-
|
|
208921
|
+
const schedExecFn = () => runEngine.executeChecks({
|
|
206847
208922
|
checks: checksToRun,
|
|
206848
208923
|
showDetails: true,
|
|
206849
208924
|
outputFormat: 'json',
|
|
@@ -206852,6 +208927,20 @@ class Scheduler {
|
|
|
206852
208927
|
debug: process.env.VISOR_DEBUG === 'true',
|
|
206853
208928
|
inputs: schedule.workflowInputs,
|
|
206854
208929
|
});
|
|
208930
|
+
if (this.taskStore) {
|
|
208931
|
+
const { trackExecution } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(7628)));
|
|
208932
|
+
await trackExecution({
|
|
208933
|
+
taskStore: this.taskStore,
|
|
208934
|
+
source: 'scheduler',
|
|
208935
|
+
workflowId: schedule.workflow,
|
|
208936
|
+
configPath: this.configPath,
|
|
208937
|
+
messageText: `Scheduled: ${schedule.workflow} (${schedule.id})`,
|
|
208938
|
+
metadata: { schedule_id: schedule.id, is_recurring: schedule.isRecurring },
|
|
208939
|
+
}, schedExecFn);
|
|
208940
|
+
}
|
|
208941
|
+
else {
|
|
208942
|
+
await schedExecFn();
|
|
208943
|
+
}
|
|
206855
208944
|
return { message: 'Workflow completed', workflow: schedule.workflow };
|
|
206856
208945
|
}
|
|
206857
208946
|
/**
|
|
@@ -206964,7 +209053,7 @@ Please provide an updated response based on the reminder above. You may referenc
|
|
|
206964
209053
|
const { engine: runEngine, config: cfgForRun, responseRef, } = this.prepareExecution(schedule, reminderText);
|
|
206965
209054
|
try {
|
|
206966
209055
|
// Execute ALL checks - let the visor engine route based on config
|
|
206967
|
-
|
|
209056
|
+
const reminderExecFn = () => runEngine.executeChecks({
|
|
206968
209057
|
checks: allChecks,
|
|
206969
209058
|
showDetails: true,
|
|
206970
209059
|
outputFormat: 'json',
|
|
@@ -206972,6 +209061,20 @@ Please provide an updated response based on the reminder above. You may referenc
|
|
|
206972
209061
|
webhookContext: { webhookData, eventType: 'schedule' },
|
|
206973
209062
|
debug: process.env.VISOR_DEBUG === 'true',
|
|
206974
209063
|
});
|
|
209064
|
+
if (this.taskStore) {
|
|
209065
|
+
const { trackExecution } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(7628)));
|
|
209066
|
+
await trackExecution({
|
|
209067
|
+
taskStore: this.taskStore,
|
|
209068
|
+
source: 'scheduler',
|
|
209069
|
+
workflowId: schedule.workflow,
|
|
209070
|
+
configPath: this.configPath,
|
|
209071
|
+
messageText: reminderText || `Reminder: ${schedule.id}`,
|
|
209072
|
+
metadata: { schedule_id: schedule.id, is_recurring: schedule.isRecurring },
|
|
209073
|
+
}, reminderExecFn);
|
|
209074
|
+
}
|
|
209075
|
+
else {
|
|
209076
|
+
await reminderExecFn();
|
|
209077
|
+
}
|
|
206975
209078
|
// The visor pipeline handles output via frontends (Slack, etc.)
|
|
206976
209079
|
// We return success - the actual response was posted by the pipeline
|
|
206977
209080
|
// Save captured response for recurring reminders (previousResponse feature)
|
|
@@ -209872,6 +211975,8 @@ class SlackSocketRunner {
|
|
|
209872
211975
|
heartbeatTimer;
|
|
209873
211976
|
lastPong = 0;
|
|
209874
211977
|
closing = false; // prevent duplicate reconnects
|
|
211978
|
+
taskStore;
|
|
211979
|
+
configPath;
|
|
209875
211980
|
constructor(engine, cfg, opts) {
|
|
209876
211981
|
const app = opts.appToken || process.env.SLACK_APP_TOKEN || '';
|
|
209877
211982
|
if (!app)
|
|
@@ -209894,6 +211999,11 @@ class SlackSocketRunner {
|
|
|
209894
211999
|
this.cfg = cfg;
|
|
209895
212000
|
this.initMessageTriggersFromConfig();
|
|
209896
212001
|
}
|
|
212002
|
+
/** Set shared task store for execution tracking. */
|
|
212003
|
+
setTaskStore(store, configPath) {
|
|
212004
|
+
this.taskStore = store;
|
|
212005
|
+
this.configPath = configPath;
|
|
212006
|
+
}
|
|
209897
212007
|
/** Hot-swap the config used for future requests (does not affect in-flight ones). */
|
|
209898
212008
|
updateConfig(cfg) {
|
|
209899
212009
|
this.cfg = cfg;
|
|
@@ -210012,6 +212122,8 @@ class SlackSocketRunner {
|
|
|
210012
212122
|
defaultTimezone: schedulerCfg?.default_timezone,
|
|
210013
212123
|
});
|
|
210014
212124
|
this.genericScheduler.setEngine(this.engine);
|
|
212125
|
+
if (this.taskStore)
|
|
212126
|
+
this.genericScheduler.setTaskStore(this.taskStore, this.configPath);
|
|
210015
212127
|
// Pass Slack client to scheduler so it can inject into workflow executions
|
|
210016
212128
|
this.genericScheduler.setExecutionContext({
|
|
210017
212129
|
slack: this.client,
|
|
@@ -210567,7 +212679,7 @@ class SlackSocketRunner {
|
|
|
210567
212679
|
}
|
|
210568
212680
|
}
|
|
210569
212681
|
// Cold run (no snapshot)
|
|
210570
|
-
|
|
212682
|
+
const execFn = () => runEngine.executeChecks({
|
|
210571
212683
|
checks: allChecks,
|
|
210572
212684
|
showDetails: true,
|
|
210573
212685
|
outputFormat: 'json',
|
|
@@ -210575,6 +212687,24 @@ class SlackSocketRunner {
|
|
|
210575
212687
|
webhookContext: { webhookData: map, eventType: 'manual' },
|
|
210576
212688
|
debug: process.env.VISOR_DEBUG === 'true',
|
|
210577
212689
|
});
|
|
212690
|
+
if (this.taskStore) {
|
|
212691
|
+
const { trackExecution } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(7628)));
|
|
212692
|
+
await trackExecution({
|
|
212693
|
+
taskStore: this.taskStore,
|
|
212694
|
+
source: 'slack',
|
|
212695
|
+
workflowId: allChecks.join(','),
|
|
212696
|
+
configPath: this.configPath,
|
|
212697
|
+
messageText: String(ev.text || 'Slack message'),
|
|
212698
|
+
metadata: {
|
|
212699
|
+
slack_channel: channelId,
|
|
212700
|
+
slack_thread_ts: threadTs,
|
|
212701
|
+
slack_user: userId,
|
|
212702
|
+
},
|
|
212703
|
+
}, execFn);
|
|
212704
|
+
}
|
|
212705
|
+
else {
|
|
212706
|
+
await execFn();
|
|
212707
|
+
}
|
|
210578
212708
|
});
|
|
210579
212709
|
}
|
|
210580
212710
|
finally {
|
|
@@ -210707,7 +212837,7 @@ class SlackSocketRunner {
|
|
|
210707
212837
|
'slack.thread_ts': threadTs || ts,
|
|
210708
212838
|
'slack.user_id': user,
|
|
210709
212839
|
}, async () => {
|
|
210710
|
-
|
|
212840
|
+
const triggerExecFn = () => runEngine.executeChecks({
|
|
210711
212841
|
checks: checksToRun,
|
|
210712
212842
|
showDetails: true,
|
|
210713
212843
|
outputFormat: 'json',
|
|
@@ -210716,6 +212846,25 @@ class SlackSocketRunner {
|
|
|
210716
212846
|
debug: process.env.VISOR_DEBUG === 'true',
|
|
210717
212847
|
inputs: trigger.inputs,
|
|
210718
212848
|
});
|
|
212849
|
+
if (this.taskStore) {
|
|
212850
|
+
const { trackExecution } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(7628)));
|
|
212851
|
+
await trackExecution({
|
|
212852
|
+
taskStore: this.taskStore,
|
|
212853
|
+
source: 'slack',
|
|
212854
|
+
workflowId: trigger.workflow || checksToRun.join(','),
|
|
212855
|
+
configPath: this.configPath,
|
|
212856
|
+
messageText: String(ev.text || `Trigger: ${id}`),
|
|
212857
|
+
metadata: {
|
|
212858
|
+
slack_channel: channel,
|
|
212859
|
+
slack_thread_ts: threadTs || ts,
|
|
212860
|
+
slack_user: user,
|
|
212861
|
+
trigger_id: id,
|
|
212862
|
+
},
|
|
212863
|
+
}, triggerExecFn);
|
|
212864
|
+
}
|
|
212865
|
+
else {
|
|
212866
|
+
await triggerExecFn();
|
|
212867
|
+
}
|
|
210719
212868
|
});
|
|
210720
212869
|
logger_1.logger.info(`[SlackSocket] Message trigger '${id}' workflow completed`);
|
|
210721
212870
|
}
|
|
@@ -211567,7 +213716,7 @@ class StateMachineExecutionEngine {
|
|
|
211567
213716
|
try {
|
|
211568
213717
|
logger_1.logger.debug(`[PolicyEngine] Loading enterprise policy engine (engine=${configWithTagFilter.policy.engine})`);
|
|
211569
213718
|
// @ts-ignore — enterprise/ may not exist in OSS builds (caught at runtime)
|
|
211570
|
-
const { loadEnterprisePolicyEngine } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(
|
|
213719
|
+
const { loadEnterprisePolicyEngine } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(87068)));
|
|
211571
213720
|
context.policyEngine = await loadEnterprisePolicyEngine(configWithTagFilter.policy);
|
|
211572
213721
|
logger_1.logger.debug(`[PolicyEngine] Initialized: ${context.policyEngine?.constructor?.name || 'unknown'}`);
|
|
211573
213722
|
}
|
|
@@ -221878,7 +224027,7 @@ async function initTelemetry(opts = {}) {
|
|
|
221878
224027
|
const path = __nccwpck_require__(16928);
|
|
221879
224028
|
const outDir = opts.file?.dir ||
|
|
221880
224029
|
process.env.VISOR_TRACE_DIR ||
|
|
221881
|
-
|
|
224030
|
+
path.join(process.cwd(), 'output', 'traces');
|
|
221882
224031
|
fs.mkdirSync(outDir, { recursive: true });
|
|
221883
224032
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
221884
224033
|
process.env.VISOR_FALLBACK_TRACE_FILE = path.join(outDir, `run-${ts}.ndjson`);
|
|
@@ -222083,7 +224232,7 @@ async function shutdownTelemetry() {
|
|
|
222083
224232
|
if (process.env.VISOR_TRACE_REPORT === 'true') {
|
|
222084
224233
|
const fs = __nccwpck_require__(79896);
|
|
222085
224234
|
const path = __nccwpck_require__(16928);
|
|
222086
|
-
const outDir = process.env.VISOR_TRACE_DIR ||
|
|
224235
|
+
const outDir = process.env.VISOR_TRACE_DIR || path.join(process.cwd(), 'output', 'traces');
|
|
222087
224236
|
if (!fs.existsSync(outDir))
|
|
222088
224237
|
fs.mkdirSync(outDir, { recursive: true });
|
|
222089
224238
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
@@ -222456,6 +224605,7 @@ exports.getVisorRunAttributes = getVisorRunAttributes;
|
|
|
222456
224605
|
exports.__getOrCreateNdjsonPath = __getOrCreateNdjsonPath;
|
|
222457
224606
|
exports._appendRunMarker = _appendRunMarker;
|
|
222458
224607
|
const lazy_otel_1 = __nccwpck_require__(21084);
|
|
224608
|
+
const instance_id_1 = __nccwpck_require__(89942);
|
|
222459
224609
|
function getTracer() {
|
|
222460
224610
|
return lazy_otel_1.trace.getTracer('visor');
|
|
222461
224611
|
}
|
|
@@ -222563,6 +224713,7 @@ function getVisorRunAttributes() {
|
|
|
222563
224713
|
if (commitFull) {
|
|
222564
224714
|
attrs['visor.commit.sha'] = commitFull;
|
|
222565
224715
|
}
|
|
224716
|
+
attrs['visor.instance_id'] = (0, instance_id_1.getInstanceId)();
|
|
222566
224717
|
return attrs;
|
|
222567
224718
|
}
|
|
222568
224719
|
// Internal helper for tests: write a minimal run marker to NDJSON when using file sink
|
|
@@ -222582,7 +224733,7 @@ function __getOrCreateNdjsonPath() {
|
|
|
222582
224733
|
fs.mkdirSync(dir, { recursive: true });
|
|
222583
224734
|
return __ndjsonPath;
|
|
222584
224735
|
}
|
|
222585
|
-
const outDir = process.env.VISOR_TRACE_DIR ||
|
|
224736
|
+
const outDir = process.env.VISOR_TRACE_DIR || path.join(process.cwd(), 'output', 'traces');
|
|
222586
224737
|
if (!fs.existsSync(outDir))
|
|
222587
224738
|
fs.mkdirSync(outDir, { recursive: true });
|
|
222588
224739
|
if (!__ndjsonPath) {
|
|
@@ -227075,10 +229226,43 @@ function validateTestsDoc(doc) {
|
|
|
227075
229226
|
/***/ }),
|
|
227076
229227
|
|
|
227077
229228
|
/***/ 20881:
|
|
227078
|
-
/***/ ((__unused_webpack_module, exports, __nccwpck_require__)
|
|
229229
|
+
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
|
227079
229230
|
|
|
227080
229231
|
"use strict";
|
|
227081
229232
|
|
|
229233
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
229234
|
+
if (k2 === undefined) k2 = k;
|
|
229235
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
229236
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
229237
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
229238
|
+
}
|
|
229239
|
+
Object.defineProperty(o, k2, desc);
|
|
229240
|
+
}) : (function(o, m, k, k2) {
|
|
229241
|
+
if (k2 === undefined) k2 = k;
|
|
229242
|
+
o[k2] = m[k];
|
|
229243
|
+
}));
|
|
229244
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
229245
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
229246
|
+
}) : function(o, v) {
|
|
229247
|
+
o["default"] = v;
|
|
229248
|
+
});
|
|
229249
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
229250
|
+
var ownKeys = function(o) {
|
|
229251
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
229252
|
+
var ar = [];
|
|
229253
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
229254
|
+
return ar;
|
|
229255
|
+
};
|
|
229256
|
+
return ownKeys(o);
|
|
229257
|
+
};
|
|
229258
|
+
return function (mod) {
|
|
229259
|
+
if (mod && mod.__esModule) return mod;
|
|
229260
|
+
var result = {};
|
|
229261
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
229262
|
+
__setModuleDefault(result, mod);
|
|
229263
|
+
return result;
|
|
229264
|
+
};
|
|
229265
|
+
})();
|
|
227082
229266
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
227083
229267
|
exports.TuiChatRunner = void 0;
|
|
227084
229268
|
exports.startChatTUI = startChatTUI;
|
|
@@ -227106,6 +229290,8 @@ class TuiChatRunner {
|
|
|
227106
229290
|
messageCounter = 0;
|
|
227107
229291
|
isRunning = false;
|
|
227108
229292
|
currentExecution;
|
|
229293
|
+
taskStore;
|
|
229294
|
+
configPath;
|
|
227109
229295
|
constructor(config) {
|
|
227110
229296
|
this.cfg = config.config;
|
|
227111
229297
|
this.stateManager = config.stateManager ?? new chat_state_1.ChatStateManager();
|
|
@@ -227114,6 +229300,11 @@ class TuiChatRunner {
|
|
|
227114
229300
|
// Set as global state manager
|
|
227115
229301
|
(0, chat_state_1.setChatStateManager)(this.stateManager);
|
|
227116
229302
|
}
|
|
229303
|
+
/** Set shared task store for execution tracking. */
|
|
229304
|
+
setTaskStore(store, configPath) {
|
|
229305
|
+
this.taskStore = store;
|
|
229306
|
+
this.configPath = configPath;
|
|
229307
|
+
}
|
|
227117
229308
|
async start() {
|
|
227118
229309
|
// Create ChatTUI
|
|
227119
229310
|
this.chatTui = new chat_tui_1.ChatTUI({
|
|
@@ -227260,16 +229451,28 @@ class TuiChatRunner {
|
|
|
227260
229451
|
'tui.message_id': messageId,
|
|
227261
229452
|
}, async () => {
|
|
227262
229453
|
try {
|
|
227263
|
-
|
|
229454
|
+
const tuiExecFn = () => runEngine.executeChecks({
|
|
227264
229455
|
checks: allChecks,
|
|
227265
229456
|
showDetails: true,
|
|
227266
229457
|
outputFormat: 'json',
|
|
227267
229458
|
config: cfgForRun,
|
|
227268
229459
|
webhookContext: { webhookData, eventType: 'manual' },
|
|
227269
229460
|
debug: this.debug,
|
|
227270
|
-
// Pass conversation directly in options for TUI mode
|
|
227271
229461
|
conversation: ctx.conversation,
|
|
227272
229462
|
});
|
|
229463
|
+
if (this.taskStore) {
|
|
229464
|
+
const { trackExecution } = await Promise.resolve().then(() => __importStar(__nccwpck_require__(7628)));
|
|
229465
|
+
await trackExecution({
|
|
229466
|
+
taskStore: this.taskStore,
|
|
229467
|
+
source: 'tui',
|
|
229468
|
+
workflowId: allChecks.join(','),
|
|
229469
|
+
configPath: this.configPath,
|
|
229470
|
+
messageText: message,
|
|
229471
|
+
}, tuiExecFn);
|
|
229472
|
+
}
|
|
229473
|
+
else {
|
|
229474
|
+
await tuiExecFn();
|
|
229475
|
+
}
|
|
227273
229476
|
}
|
|
227274
229477
|
catch (error) {
|
|
227275
229478
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -232254,6 +234457,32 @@ function generateShortHumanId() {
|
|
|
232254
234457
|
}
|
|
232255
234458
|
|
|
232256
234459
|
|
|
234460
|
+
/***/ }),
|
|
234461
|
+
|
|
234462
|
+
/***/ 89942:
|
|
234463
|
+
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
|
|
234464
|
+
|
|
234465
|
+
"use strict";
|
|
234466
|
+
|
|
234467
|
+
/**
|
|
234468
|
+
* Visor instance ID — unique per process lifetime.
|
|
234469
|
+
*
|
|
234470
|
+
* Used to identify which visor instance created/owns a task,
|
|
234471
|
+
* especially in multi-node environments.
|
|
234472
|
+
*/
|
|
234473
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
234474
|
+
exports.getInstanceId = getInstanceId;
|
|
234475
|
+
const human_id_1 = __nccwpck_require__(30920);
|
|
234476
|
+
let _instanceId;
|
|
234477
|
+
/** Get or generate the visor instance ID for this process. */
|
|
234478
|
+
function getInstanceId() {
|
|
234479
|
+
if (!_instanceId) {
|
|
234480
|
+
_instanceId = (0, human_id_1.generateHumanId)();
|
|
234481
|
+
}
|
|
234482
|
+
return _instanceId;
|
|
234483
|
+
}
|
|
234484
|
+
|
|
234485
|
+
|
|
232257
234486
|
/***/ }),
|
|
232258
234487
|
|
|
232259
234488
|
/***/ 91784:
|
|
@@ -236960,22 +239189,6 @@ class WorkflowRegistry {
|
|
|
236960
239189
|
exports.WorkflowRegistry = WorkflowRegistry;
|
|
236961
239190
|
|
|
236962
239191
|
|
|
236963
|
-
/***/ }),
|
|
236964
|
-
|
|
236965
|
-
/***/ 7065:
|
|
236966
|
-
/***/ ((module) => {
|
|
236967
|
-
|
|
236968
|
-
module.exports = eval("require")("./enterprise/loader");
|
|
236969
|
-
|
|
236970
|
-
|
|
236971
|
-
/***/ }),
|
|
236972
|
-
|
|
236973
|
-
/***/ 71370:
|
|
236974
|
-
/***/ ((module) => {
|
|
236975
|
-
|
|
236976
|
-
module.exports = eval("require")("./enterprise/policy/policy-input-builder");
|
|
236977
|
-
|
|
236978
|
-
|
|
236979
239192
|
/***/ }),
|
|
236980
239193
|
|
|
236981
239194
|
/***/ 18327:
|
|
@@ -261212,7 +263425,8 @@ var init_search = __esm({
|
|
|
261212
263425
|
session: "--session",
|
|
261213
263426
|
timeout: "--timeout",
|
|
261214
263427
|
language: "--language",
|
|
261215
|
-
format: "--format"
|
|
263428
|
+
format: "--format",
|
|
263429
|
+
lsp: "--lsp"
|
|
261216
263430
|
};
|
|
261217
263431
|
}
|
|
261218
263432
|
});
|
|
@@ -261448,7 +263662,8 @@ var init_extract = __esm({
|
|
|
261448
263662
|
allowTests: "--allow-tests",
|
|
261449
263663
|
contextLines: "--context",
|
|
261450
263664
|
format: "--format",
|
|
261451
|
-
inputFile: "--input-file"
|
|
263665
|
+
inputFile: "--input-file",
|
|
263666
|
+
lsp: "--lsp"
|
|
261452
263667
|
};
|
|
261453
263668
|
}
|
|
261454
263669
|
});
|
|
@@ -286325,7 +288540,14 @@ function parseTargets(targets) {
|
|
|
286325
288540
|
}
|
|
286326
288541
|
function parseAndResolvePaths(pathStr, cwd) {
|
|
286327
288542
|
if (!pathStr) return [];
|
|
286328
|
-
|
|
288543
|
+
let paths = pathStr.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
288544
|
+
paths = paths.flatMap((p) => {
|
|
288545
|
+
if (!/\s/.test(p)) return [p];
|
|
288546
|
+
const parts = p.split(/\s+/).filter(Boolean);
|
|
288547
|
+
if (parts.length <= 1) return [p];
|
|
288548
|
+
const allLookLikePaths = parts.every((part) => /[/\\]/.test(part) || /\.\w+/.test(part));
|
|
288549
|
+
return allLookLikePaths ? parts : [p];
|
|
288550
|
+
});
|
|
286329
288551
|
return paths.map((p) => {
|
|
286330
288552
|
if ((0, import_path5.isAbsolute)(p)) {
|
|
286331
288553
|
return p;
|
|
@@ -305988,6 +308210,7 @@ var init_parser2 = __esm({
|
|
|
305988
308210
|
{ ALT: () => this.CONSUME(Identifier) },
|
|
305989
308211
|
{ ALT: () => this.CONSUME(Text) },
|
|
305990
308212
|
{ ALT: () => this.CONSUME(NumberLiteral) },
|
|
308213
|
+
{ ALT: () => this.CONSUME(ColorValue) },
|
|
305991
308214
|
// Note: RoundOpen and RoundClose (parentheses) are NOT allowed in unquoted labels
|
|
305992
308215
|
// to match Mermaid's behavior - use quoted labels like ["text (with parens)"] instead
|
|
305993
308216
|
// Allow HTML-like tags (e.g., <br/>) inside labels
|
|
@@ -306079,6 +308302,7 @@ var init_parser2 = __esm({
|
|
|
306079
308302
|
{ ALT: () => this.CONSUME(Identifier) },
|
|
306080
308303
|
{ ALT: () => this.CONSUME(Text) },
|
|
306081
308304
|
{ ALT: () => this.CONSUME(NumberLiteral) },
|
|
308305
|
+
{ ALT: () => this.CONSUME(ColorValue) },
|
|
306082
308306
|
// Allow HTML-like angle brackets and slashes for <br/>, <i>, etc.
|
|
306083
308307
|
{ ALT: () => this.CONSUME(AngleLess) },
|
|
306084
308308
|
{ ALT: () => this.CONSUME(AngleOpen) },
|
|
@@ -307254,13 +309478,24 @@ function mapFlowchartParserError(err, text) {
|
|
|
307254
309478
|
length: len
|
|
307255
309479
|
};
|
|
307256
309480
|
}
|
|
307257
|
-
if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose") {
|
|
309481
|
+
if (tokType === "QuotedString" || tokType === "SquareOpen" || tokType === "SquareClose" || tokType === "DiamondOpen" || tokType === "DiamondClose") {
|
|
307258
309482
|
const context = err?.context;
|
|
307259
309483
|
const inLinkRule = context?.ruleStack?.includes("linkTextInline") || context?.ruleStack?.includes("link") || false;
|
|
307260
309484
|
const lineContent = allLines[Math.max(0, line - 1)] || "";
|
|
307261
309485
|
const beforeQuote = lineContent.slice(0, Math.max(0, column - 1));
|
|
307262
309486
|
const hasLinkBefore = beforeQuote.match(/--\s*$|==\s*$|-\.\s*$|-\.-\s*$|\[\s*$/);
|
|
307263
309487
|
if (inLinkRule || hasLinkBefore) {
|
|
309488
|
+
if (tokType === "DiamondOpen" || tokType === "DiamondClose") {
|
|
309489
|
+
return {
|
|
309490
|
+
line,
|
|
309491
|
+
column,
|
|
309492
|
+
severity: "error",
|
|
309493
|
+
code: "FL-EDGE-LABEL-CURLY-IN-PIPES",
|
|
309494
|
+
message: "Curly braces { } are not supported inside pipe-delimited edge labels.",
|
|
309495
|
+
hint: "Use HTML entities { and } inside |...|, e.g., --|Resolve ${VAR}|-->",
|
|
309496
|
+
length: len
|
|
309497
|
+
};
|
|
309498
|
+
}
|
|
307264
309499
|
if (tokType === "SquareOpen" || tokType === "SquareClose") {
|
|
307265
309500
|
return {
|
|
307266
309501
|
line,
|
|
@@ -307322,6 +309557,17 @@ function mapFlowchartParserError(err, text) {
|
|
|
307322
309557
|
length: len
|
|
307323
309558
|
};
|
|
307324
309559
|
}
|
|
309560
|
+
if (tokType === "DiamondOpen" || tokType === "DiamondClose") {
|
|
309561
|
+
return {
|
|
309562
|
+
line,
|
|
309563
|
+
column,
|
|
309564
|
+
severity: "error",
|
|
309565
|
+
code: "FL-LABEL-CURLY-IN-UNQUOTED",
|
|
309566
|
+
message: "Curly braces are not supported inside unquoted node labels.",
|
|
309567
|
+
hint: "Use { and } for literal braces, e.g., C[Substitute {params}].",
|
|
309568
|
+
length: len
|
|
309569
|
+
};
|
|
309570
|
+
}
|
|
307325
309571
|
{
|
|
307326
309572
|
const caret0 = Math.max(0, column - 1);
|
|
307327
309573
|
const openIdx = lineStr.lastIndexOf("[", caret0);
|
|
@@ -307363,9 +309609,20 @@ function mapFlowchartParserError(err, text) {
|
|
|
307363
309609
|
length: len
|
|
307364
309610
|
};
|
|
307365
309611
|
}
|
|
309612
|
+
if (seg.includes("{") || seg.includes("}")) {
|
|
309613
|
+
return {
|
|
309614
|
+
line,
|
|
309615
|
+
column,
|
|
309616
|
+
severity: "error",
|
|
309617
|
+
code: "FL-LABEL-CURLY-IN-UNQUOTED",
|
|
309618
|
+
message: "Curly braces are not supported inside unquoted node labels.",
|
|
309619
|
+
hint: "Use { and } for literal braces, e.g., C[Substitute {params}].",
|
|
309620
|
+
length: len
|
|
309621
|
+
};
|
|
309622
|
+
}
|
|
307366
309623
|
}
|
|
307367
309624
|
}
|
|
307368
|
-
if (tokType === "QuotedString"
|
|
309625
|
+
if (tokType === "QuotedString") {
|
|
307369
309626
|
return {
|
|
307370
309627
|
line,
|
|
307371
309628
|
column,
|
|
@@ -307376,6 +309633,17 @@ function mapFlowchartParserError(err, text) {
|
|
|
307376
309633
|
length: len
|
|
307377
309634
|
};
|
|
307378
309635
|
}
|
|
309636
|
+
if (tokType === "SquareOpen" || tokType === "SquareClose") {
|
|
309637
|
+
return {
|
|
309638
|
+
line,
|
|
309639
|
+
column,
|
|
309640
|
+
severity: "error",
|
|
309641
|
+
code: "FL-LABEL-BRACKET-IN-UNQUOTED",
|
|
309642
|
+
message: "Square brackets are not supported inside unquoted node labels.",
|
|
309643
|
+
hint: 'Use [ and ] for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]',
|
|
309644
|
+
length: len
|
|
309645
|
+
};
|
|
309646
|
+
}
|
|
307379
309647
|
const q = findInnerQuoteIssue("[");
|
|
307380
309648
|
if (q?.kind === "escaped") {
|
|
307381
309649
|
return { line, column: q.column, severity: "error", code: "FL-LABEL-ESCAPED-QUOTE", message: 'Escaped quotes (\\") in node labels are not supported by Mermaid. Use " instead.', hint: 'Prefer "He said "Hi"".', length: 2 };
|
|
@@ -307386,7 +309654,7 @@ function mapFlowchartParserError(err, text) {
|
|
|
307386
309654
|
return { line, column, severity: "error", code: "FL-NODE-UNCLOSED-BRACKET", message: "Unclosed '['. Add a matching ']' before the arrow or newline.", hint: "Example: A[Label] --> B", length: 1 };
|
|
307387
309655
|
}
|
|
307388
309656
|
if (expecting(err, "RoundClose")) {
|
|
307389
|
-
if (tokType === "QuotedString"
|
|
309657
|
+
if (tokType === "QuotedString") {
|
|
307390
309658
|
return {
|
|
307391
309659
|
line,
|
|
307392
309660
|
column,
|
|
@@ -307397,6 +309665,17 @@ function mapFlowchartParserError(err, text) {
|
|
|
307397
309665
|
length: len
|
|
307398
309666
|
};
|
|
307399
309667
|
}
|
|
309668
|
+
if (tokType === "SquareOpen" || tokType === "SquareClose") {
|
|
309669
|
+
return {
|
|
309670
|
+
line,
|
|
309671
|
+
column,
|
|
309672
|
+
severity: "error",
|
|
309673
|
+
code: "FL-LABEL-BRACKET-IN-UNQUOTED",
|
|
309674
|
+
message: "Square brackets are not supported inside unquoted node labels.",
|
|
309675
|
+
hint: 'Use [ and ] for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]',
|
|
309676
|
+
length: len
|
|
309677
|
+
};
|
|
309678
|
+
}
|
|
307400
309679
|
{
|
|
307401
309680
|
const caret0 = Math.max(0, column - 1);
|
|
307402
309681
|
const openIdx = lineStr.lastIndexOf("(", caret0);
|
|
@@ -307425,7 +309704,7 @@ function mapFlowchartParserError(err, text) {
|
|
|
307425
309704
|
return { line, column, severity: "error", code: "FL-NODE-UNCLOSED-BRACKET", message: "Unclosed '('. Add a matching ')'.", hint: "Example: B(Label)", length: 1 };
|
|
307426
309705
|
}
|
|
307427
309706
|
if (expecting(err, "DiamondClose")) {
|
|
307428
|
-
if (tokType === "QuotedString"
|
|
309707
|
+
if (tokType === "QuotedString") {
|
|
307429
309708
|
return {
|
|
307430
309709
|
line,
|
|
307431
309710
|
column,
|
|
@@ -307436,6 +309715,17 @@ function mapFlowchartParserError(err, text) {
|
|
|
307436
309715
|
length: len
|
|
307437
309716
|
};
|
|
307438
309717
|
}
|
|
309718
|
+
if (tokType === "SquareOpen" || tokType === "SquareClose") {
|
|
309719
|
+
return {
|
|
309720
|
+
line,
|
|
309721
|
+
column,
|
|
309722
|
+
severity: "error",
|
|
309723
|
+
code: "FL-LABEL-BRACKET-IN-UNQUOTED",
|
|
309724
|
+
message: "Square brackets are not supported inside unquoted node labels.",
|
|
309725
|
+
hint: 'Use [ and ] for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]',
|
|
309726
|
+
length: len
|
|
309727
|
+
};
|
|
309728
|
+
}
|
|
307439
309729
|
{
|
|
307440
309730
|
const caret0 = Math.max(0, column - 1);
|
|
307441
309731
|
const openIdx = lineStr.lastIndexOf("{", caret0);
|
|
@@ -308278,7 +310568,7 @@ function validateFlowchart(text, options = {}) {
|
|
|
308278
310568
|
const byLine = /* @__PURE__ */ new Map();
|
|
308279
310569
|
const collect = (arr) => {
|
|
308280
310570
|
for (const e of arr || []) {
|
|
308281
|
-
if (e && (e.code === "FL-LABEL-PARENS-UNQUOTED" || e.code === "FL-LABEL-AT-IN-UNQUOTED" || e.code === "FL-LABEL-QUOTE-IN-UNQUOTED" || e.code === "FL-LABEL-SLASH-UNQUOTED")) {
|
|
310571
|
+
if (e && (e.code === "FL-LABEL-PARENS-UNQUOTED" || e.code === "FL-LABEL-AT-IN-UNQUOTED" || e.code === "FL-LABEL-QUOTE-IN-UNQUOTED" || e.code === "FL-LABEL-SLASH-UNQUOTED" || e.code === "FL-LABEL-CURLY-IN-UNQUOTED" || e.code === "FL-LABEL-BRACKET-IN-UNQUOTED")) {
|
|
308282
310572
|
const ln = e.line ?? 0;
|
|
308283
310573
|
const col = e.column ?? 1;
|
|
308284
310574
|
const list = byLine.get(ln) || [];
|
|
@@ -308360,6 +310650,9 @@ function validateFlowchart(text, options = {}) {
|
|
|
308360
310650
|
const hasParens = seg.includes("(") || seg.includes(")");
|
|
308361
310651
|
const hasAt = seg.includes("@");
|
|
308362
310652
|
const hasQuote = seg.includes('"');
|
|
310653
|
+
const hasCurly = seg.includes("{") || seg.includes("}");
|
|
310654
|
+
const hasBracket = seg.includes("[") || seg.includes("]");
|
|
310655
|
+
const isDoubleSquare = raw.slice(i, i + 2) === "[[" && raw.slice(j - 2, j) === "]]";
|
|
308363
310656
|
const isSingleQuoted = /^'[^]*'$/.test(trimmed);
|
|
308364
310657
|
const hasLeadingSlash = lsp === "/" || lsp === "\\";
|
|
308365
310658
|
if (!covered && !isQuoted && !isParenWrapped && hasParens) {
|
|
@@ -308377,6 +310670,16 @@ function validateFlowchart(text, options = {}) {
|
|
|
308377
310670
|
existing.push({ start: startCol, end: endCol });
|
|
308378
310671
|
byLine.set(ln, existing);
|
|
308379
310672
|
}
|
|
310673
|
+
if (!covered && !isQuoted && !isSlashPair && hasCurly) {
|
|
310674
|
+
errs.push({ line: ln, column: startCol, severity: "error", code: "FL-LABEL-CURLY-IN-UNQUOTED", message: "Curly braces are not supported inside unquoted node labels.", hint: "Use { and } for literal braces, e.g., C[Substitute {params}]." });
|
|
310675
|
+
existing.push({ start: startCol, end: endCol });
|
|
310676
|
+
byLine.set(ln, existing);
|
|
310677
|
+
}
|
|
310678
|
+
if (!covered && !isQuoted && !isSlashPair && !isDoubleSquare && hasBracket) {
|
|
310679
|
+
errs.push({ line: ln, column: startCol, severity: "error", code: "FL-LABEL-BRACKET-IN-UNQUOTED", message: "Square brackets are not supported inside unquoted node labels.", hint: 'Use [ and ] for literal brackets or wrap the label in quotes, e.g., C["bind_paths[]"]' });
|
|
310680
|
+
existing.push({ start: startCol, end: endCol });
|
|
310681
|
+
byLine.set(ln, existing);
|
|
310682
|
+
}
|
|
308380
310683
|
if (!covered && !isQuoted && !isSlashPair && hasQuote && !isSingleQuoted) {
|
|
308381
310684
|
errs.push({ line: ln, column: startCol, severity: "error", code: "FL-LABEL-QUOTE-IN-UNQUOTED", message: "Quotes are not allowed inside unquoted node labels. Use " for quotes or wrap the entire label in quotes.", hint: 'Example: C["HTML Output: data-trigger-visibility="true""]' });
|
|
308382
310685
|
existing.push({ start: startCol, end: endCol });
|
|
@@ -310737,7 +313040,7 @@ function computeFixes(text, errors, level = "safe") {
|
|
|
310737
313040
|
}
|
|
310738
313041
|
continue;
|
|
310739
313042
|
}
|
|
310740
|
-
if (is("FL-EDGE-LABEL-BRACKET", e)) {
|
|
313043
|
+
if (is("FL-EDGE-LABEL-BRACKET", e) || is("FL-EDGE-LABEL-CURLY-IN-PIPES", e)) {
|
|
310741
313044
|
const lineText = lineTextAt(text, e.line);
|
|
310742
313045
|
const firstBar = lineText.indexOf("|");
|
|
310743
313046
|
const secondBar = firstBar >= 0 ? lineText.indexOf("|", firstBar + 1) : -1;
|
|
@@ -310745,7 +313048,8 @@ function computeFixes(text, errors, level = "safe") {
|
|
|
310745
313048
|
const before = lineText.slice(0, firstBar + 1);
|
|
310746
313049
|
const label = lineText.slice(firstBar + 1, secondBar);
|
|
310747
313050
|
const after = lineText.slice(secondBar);
|
|
310748
|
-
|
|
313051
|
+
let fixedLabel = label.replace(/\[/g, "[").replace(/\]/g, "]");
|
|
313052
|
+
fixedLabel = fixedLabel.replace(/\{/g, "{").replace(/\}/g, "}");
|
|
310749
313053
|
const fixedLine = before + fixedLabel + after;
|
|
310750
313054
|
const finalLine = fixedLine.replace(/\[([^\]]*)\]/g, (m, seg) => "[" + String(seg).replace(/`/g, "") + "]");
|
|
310751
313055
|
edits.push({ start: { line: e.line, column: 1 }, end: { line: e.line, column: lineText.length + 1 }, newText: finalLine });
|
|
@@ -311385,7 +313689,7 @@ function computeFixes(text, errors, level = "safe") {
|
|
|
311385
313689
|
}
|
|
311386
313690
|
continue;
|
|
311387
313691
|
}
|
|
311388
|
-
if (is("FL-LABEL-PARENS-UNQUOTED", e) || is("FL-LABEL-AT-IN-UNQUOTED", e) || is("FL-LABEL-SLASH-UNQUOTED", e)) {
|
|
313692
|
+
if (is("FL-LABEL-PARENS-UNQUOTED", e) || is("FL-LABEL-AT-IN-UNQUOTED", e) || is("FL-LABEL-SLASH-UNQUOTED", e) || is("FL-LABEL-CURLY-IN-UNQUOTED", e) || is("FL-LABEL-BRACKET-IN-UNQUOTED", e)) {
|
|
311389
313693
|
if (level === "safe" || level === "all") {
|
|
311390
313694
|
if (patchedLines.has(e.line))
|
|
311391
313695
|
continue;
|
|
@@ -311426,7 +313730,7 @@ function computeFixes(text, errors, level = "safe") {
|
|
|
311426
313730
|
if (openIdx === -1)
|
|
311427
313731
|
break;
|
|
311428
313732
|
const contentStart = openIdx + shape.open.length;
|
|
311429
|
-
const closeIdx = shape.open === "(" && shape.close === ")" ? findMatchingCloser(lineText, openIdx, shape.open, shape.close) : lineText.indexOf(shape.close, contentStart);
|
|
313733
|
+
const closeIdx = shape.open === "(" && shape.close === ")" || shape.open === "[" && shape.close === "]" ? findMatchingCloser(lineText, openIdx, shape.open, shape.close) : lineText.indexOf(shape.close, contentStart);
|
|
311430
313734
|
if (closeIdx === -1)
|
|
311431
313735
|
break;
|
|
311432
313736
|
if (openIdx <= caret0 && caret0 < closeIdx) {
|
|
@@ -311446,11 +313750,21 @@ function computeFixes(text, errors, level = "safe") {
|
|
|
311446
313750
|
const isSlashPair = (l, r) => l === "/" && r === "/" || l === "\\" && r === "\\" || l === "/" && r === "\\" || l === "\\" && r === "/";
|
|
311447
313751
|
const isParallelogramShape = core.length >= 2 && isSlashPair(left, right);
|
|
311448
313752
|
let replaced;
|
|
311449
|
-
if (!isParallelogramShape) {
|
|
311450
|
-
const
|
|
313753
|
+
if (is("FL-LABEL-BRACKET-IN-UNQUOTED", e) && !isParallelogramShape) {
|
|
313754
|
+
const hasOtherHazards = /[(){}@]/.test(inner) || inner.includes('"') || inner.includes('\\"');
|
|
313755
|
+
if (hasOtherHazards) {
|
|
313756
|
+
const escaped = inner.replace(/`/g, "").replace(/\\"/g, """).replace(/"/g, """);
|
|
313757
|
+
replaced = '"' + escaped + '"';
|
|
313758
|
+
} else {
|
|
313759
|
+
replaced = inner.replace(/\[/g, "[").replace(/\]/g, "]");
|
|
313760
|
+
}
|
|
313761
|
+
} else if (is("FL-LABEL-CURLY-IN-UNQUOTED", e)) {
|
|
313762
|
+
replaced = inner.replace(/\{/g, "{").replace(/\}/g, "}");
|
|
313763
|
+
} else if (!isParallelogramShape) {
|
|
313764
|
+
const escaped = inner.replace(/`/g, "").replace(/\\"/g, """).replace(/"/g, """);
|
|
311451
313765
|
replaced = '"' + escaped + '"';
|
|
311452
313766
|
} else {
|
|
311453
|
-
replaced = inner.replace(/`/g, "").replace(/\(/g, "(").replace(/\)/g, ")").replace(/\"/g, """).replace(/"/g, """);
|
|
313767
|
+
replaced = inner.replace(/`/g, "").replace(/\(/g, "(").replace(/\)/g, ")").replace(/\[/g, "[").replace(/\]/g, "]").replace(/\\"/g, """).replace(/"/g, """);
|
|
311454
313768
|
}
|
|
311455
313769
|
if (replaced !== inner) {
|
|
311456
313770
|
edits.push({ start: { line: e.line, column: contentStart + 1 }, end: { line: e.line, column: closeIdx + 1 }, newText: replaced });
|
|
@@ -332239,26 +334553,46 @@ var init_prompts = __esm({
|
|
|
332239
334553
|
CRITICAL - You are READ-ONLY:
|
|
332240
334554
|
You must NEVER create, modify, delete, or write files. You are strictly an exploration and analysis tool. If asked to make changes, implement features, fix bugs, or modify a PR, refuse and explain that file modifications must be done by the engineer tool \u2014 your role is only to investigate code and answer questions. Do not attempt workarounds using bash commands (echo, cat, tee, sed, etc.) to write files.
|
|
332241
334555
|
|
|
334556
|
+
CRITICAL - ALWAYS search before answering:
|
|
334557
|
+
You must NEVER answer questions about the codebase from memory or general knowledge. ALWAYS use the search and extract tools first to find the actual code, then base your answer ONLY on what you found. Even if you think you know the answer, you MUST verify it against the actual code. Your answers must be grounded in code evidence, not assumptions.
|
|
334558
|
+
|
|
332242
334559
|
When exploring code:
|
|
332243
334560
|
- Provide clear, concise explanations based on user request
|
|
332244
334561
|
- Find and highlight the most relevant code snippets, if required
|
|
332245
|
-
- Trace function calls and data flow through the system
|
|
334562
|
+
- Trace function calls and data flow through the system \u2014 follow the FULL call chain, not just the entry point
|
|
332246
334563
|
- Try to understand the user's intent and provide relevant information
|
|
332247
334564
|
- Understand high level picture
|
|
332248
334565
|
- Balance detail with clarity in your explanations
|
|
334566
|
+
- Search using SYNONYMS and alternative terms \u2014 code naming often differs from the concept name (e.g., "authentication" might be named verify_credentials, check_token, validate_session)
|
|
334567
|
+
- When you find a key function, look at what it CALLS and what CALLS it to discover the complete picture
|
|
334568
|
+
- Before answering, ask yourself: "Did I cover all the major components? Are there related subsystems I missed?" If yes, do one more search round.
|
|
332249
334569
|
|
|
332250
334570
|
When providing answers:
|
|
334571
|
+
- Be EXHAUSTIVE: cover ALL components you discovered, not just the main ones. If you found 10 related files, discuss all 10, not just the top 3. Users want the complete picture.
|
|
334572
|
+
- After drafting your answer, do a self-check: "What did I find in my searches that I haven't mentioned yet?" Add any missing components.
|
|
334573
|
+
- Include data structures, configuration options, and error handling \u2014 not just the happy path.
|
|
332251
334574
|
- Always include a "References" section at the end of your response
|
|
332252
334575
|
- List all relevant source code locations you found during exploration
|
|
332253
334576
|
- Use the format: file_path:line_number or file_path#symbol_name
|
|
332254
334577
|
- Group references by file when multiple locations are from the same file
|
|
332255
334578
|
- Include brief descriptions of what each reference contains`,
|
|
332256
|
-
"code-searcher": `You are ProbeChat Code
|
|
334579
|
+
"code-searcher": `You are ProbeChat Code Explorer & Searcher. Your job is to EXPLORE the codebase to find ALL relevant code locations for the query, then return them as JSON targets.
|
|
334580
|
+
|
|
334581
|
+
You think like a code explorer \u2014 you understand that codebases have layers:
|
|
334582
|
+
- Core implementations (algorithms, data structures)
|
|
334583
|
+
- Middleware/integration layers (request handlers, interceptors)
|
|
334584
|
+
- Configuration and storage backends
|
|
334585
|
+
- Scoping mechanisms (per-user, per-org, per-tenant, global)
|
|
334586
|
+
- Supporting utilities and helpers
|
|
332257
334587
|
|
|
332258
334588
|
When searching:
|
|
332259
|
-
-
|
|
332260
|
-
-
|
|
332261
|
-
-
|
|
334589
|
+
- Search for the MAIN concept first, then think: "what RELATED subsystems would a real codebase have?"
|
|
334590
|
+
- Use extract to READ the code you find \u2014 look for function calls, type references, and imports that point to OTHER relevant code
|
|
334591
|
+
- If you find middleware, check: are there org-level or tenant-level variants?
|
|
334592
|
+
- If you find algorithms, check: are there different storage backends?
|
|
334593
|
+
- Search results are paginated \u2014 if results look relevant, call nextPage=true to check for more files
|
|
334594
|
+
- Stop paginating when results become irrelevant or you see "All results retrieved"
|
|
334595
|
+
- Search using SYNONYMS \u2014 code naming differs from concepts (e.g., "rate limiting" \u2192 throttle, quota, limiter, bucket)
|
|
332262
334596
|
|
|
332263
334597
|
Output format (MANDATORY):
|
|
332264
334598
|
- Return ONLY valid JSON with a single top-level key: "targets"
|
|
@@ -332268,7 +334602,8 @@ Output format (MANDATORY):
|
|
|
332268
334602
|
- "path/to/file.ext:line"
|
|
332269
334603
|
- "path/to/file.ext:start-end"
|
|
332270
334604
|
- Prefer #SymbolName when a function/class name is clear; otherwise use line numbers
|
|
332271
|
-
- Deduplicate targets and keep them concise
|
|
334605
|
+
- Deduplicate targets and keep them concise
|
|
334606
|
+
- Aim for 5-15 targets covering ALL aspects of the query`,
|
|
332272
334607
|
"architect": `You are ProbeChat Architect, a specialized AI assistant focused on software architecture and design. Your primary function is to help users understand, analyze, and design software systems using the provided code analysis tools.
|
|
332273
334608
|
|
|
332274
334609
|
When analyzing code:
|
|
@@ -357922,9 +360257,9 @@ Workspace: ${this.allowedFolders.join(", ")}`;
|
|
|
357922
360257
|
Follow these instructions carefully:
|
|
357923
360258
|
1. Analyze the user's request.
|
|
357924
360259
|
2. Use the available tools step-by-step to fulfill the request.
|
|
357925
|
-
3. You
|
|
357926
|
-
4. Ensure to get really deep and understand the full picture before answering.
|
|
357927
|
-
5. Once the task is fully completed, provide your final answer directly as text.
|
|
360260
|
+
3. You MUST use the search tool before answering ANY code-related question. NEVER answer from memory or general knowledge \u2014 your answers must be grounded in actual code found via search/extract.${this.searchDelegate ? " Ask natural language questions \u2014 the search subagent handles keyword formulation and returns extracted code blocks. Use extract only to expand context or read full files." : " Search handles stemming and case variations automatically \u2014 do NOT try keyword variations manually. Read full files only if really necessary."}
|
|
360261
|
+
4. Ensure to get really deep and understand the full picture before answering. Follow call chains \u2014 if function A calls B, search for B too. Look for related subsystems (e.g., if asked about rate limiting, also check for quota, throttling, smoothing).
|
|
360262
|
+
5. Once the task is fully completed, provide your final answer directly as text. Always cite specific files and line numbers as evidence. Do NOT output planning or thinking text \u2014 go straight to the answer.
|
|
357928
360263
|
6. ${this.searchDelegate ? "Ask clear, specific questions when searching. Each search should target a distinct concept or question." : "Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results."}
|
|
357929
360264
|
7. NEVER use bash for code exploration (no grep, cat, find, head, tail, awk, sed) \u2014 always use search and extract tools instead. Bash is only for system operations like building, running tests, or git commands.${this.allowEdit ? `
|
|
357930
360265
|
7. When modifying files, choose the appropriate tool:
|
|
@@ -358310,6 +360645,22 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
358310
360645
|
if (recentTexts.every((t) => t && t === recentTexts[0])) return true;
|
|
358311
360646
|
if (recentTexts.every((t) => detectStuckResponse(t))) return true;
|
|
358312
360647
|
}
|
|
360648
|
+
if (steps.length >= 3) {
|
|
360649
|
+
const last3 = steps.slice(-3);
|
|
360650
|
+
const allHaveTools = last3.every((s) => s.toolCalls?.length === 1);
|
|
360651
|
+
if (allHaveTools) {
|
|
360652
|
+
const signatures = last3.map((s) => {
|
|
360653
|
+
const tc = s.toolCalls[0];
|
|
360654
|
+
return `${tc.toolName}::${JSON.stringify(tc.args ?? tc.input)}`;
|
|
360655
|
+
});
|
|
360656
|
+
if (signatures[0] === signatures[1] && signatures[1] === signatures[2]) {
|
|
360657
|
+
if (this.debug) {
|
|
360658
|
+
console.log(`[DEBUG] Circuit breaker: 3 consecutive identical tool calls detected (${last3[0].toolCalls[0].toolName}), forcing stop`);
|
|
360659
|
+
}
|
|
360660
|
+
return true;
|
|
360661
|
+
}
|
|
360662
|
+
}
|
|
360663
|
+
}
|
|
358313
360664
|
return false;
|
|
358314
360665
|
},
|
|
358315
360666
|
prepareStep: ({ steps, stepNumber }) => {
|
|
@@ -358318,6 +360669,22 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
358318
360669
|
toolChoice: "none"
|
|
358319
360670
|
};
|
|
358320
360671
|
}
|
|
360672
|
+
if (steps.length >= 2) {
|
|
360673
|
+
const last2 = steps.slice(-2);
|
|
360674
|
+
if (last2.every((s) => s.toolCalls?.length === 1)) {
|
|
360675
|
+
const tc1 = last2[0].toolCalls[0];
|
|
360676
|
+
const tc2 = last2[1].toolCalls[0];
|
|
360677
|
+
const sig1 = `${tc1.toolName}::${JSON.stringify(tc1.args ?? tc1.input)}`;
|
|
360678
|
+
const sig2 = `${tc2.toolName}::${JSON.stringify(tc2.args ?? tc2.input)}`;
|
|
360679
|
+
if (sig1 === sig2) {
|
|
360680
|
+
if (this.debug) {
|
|
360681
|
+
console.log(`[DEBUG] prepareStep: 2 consecutive identical tool calls (${tc1.toolName}), forcing toolChoice=none`);
|
|
360682
|
+
console.log(`[DEBUG] sig: ${sig1.substring(0, 200)}`);
|
|
360683
|
+
}
|
|
360684
|
+
return { toolChoice: "none" };
|
|
360685
|
+
}
|
|
360686
|
+
}
|
|
360687
|
+
}
|
|
358321
360688
|
const lastStep = steps[steps.length - 1];
|
|
358322
360689
|
const modelJustStopped = lastStep?.finishReason === "stop" && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
|
|
358323
360690
|
if (modelJustStopped) {
|
|
@@ -358348,7 +360715,9 @@ ${resultToReview}
|
|
|
358348
360715
|
|
|
358349
360716
|
Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
|
|
358350
360717
|
return {
|
|
358351
|
-
userMessage: completionPromptMessage
|
|
360718
|
+
userMessage: completionPromptMessage,
|
|
360719
|
+
toolChoice: "none"
|
|
360720
|
+
// Force text-only review — no tool calls
|
|
358352
360721
|
};
|
|
358353
360722
|
}
|
|
358354
360723
|
}
|
|
@@ -358390,7 +360759,11 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
358390
360759
|
options.onStream(text);
|
|
358391
360760
|
}
|
|
358392
360761
|
if (this.debug) {
|
|
358393
|
-
|
|
360762
|
+
const toolSummary = toolCalls?.length ? toolCalls.map((tc) => {
|
|
360763
|
+
const args = tc.args ? JSON.stringify(tc.args) : "";
|
|
360764
|
+
return args ? `${tc.toolName}(${debugTruncate(args, 120)})` : tc.toolName;
|
|
360765
|
+
}).join(", ") : "none";
|
|
360766
|
+
console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: [${toolSummary}])`);
|
|
358394
360767
|
if (text) {
|
|
358395
360768
|
console.log(`[DEBUG] model text: ${debugTruncate(text)}`);
|
|
358396
360769
|
}
|
|
@@ -358423,9 +360796,15 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
358423
360796
|
}
|
|
358424
360797
|
const executeAIRequest = async () => {
|
|
358425
360798
|
const result = await this.streamTextWithRetryAndFallback(streamOptions);
|
|
358426
|
-
const
|
|
360799
|
+
const steps = await result.steps;
|
|
360800
|
+
let finalText;
|
|
360801
|
+
if (steps && steps.length > 1) {
|
|
360802
|
+
const lastStepText = steps[steps.length - 1].text;
|
|
360803
|
+
finalText = lastStepText || await result.text;
|
|
360804
|
+
} else {
|
|
360805
|
+
finalText = await result.text;
|
|
360806
|
+
}
|
|
358427
360807
|
if (this.debug) {
|
|
358428
|
-
const steps = await result.steps;
|
|
358429
360808
|
console.log(`[DEBUG] streamText completed: ${steps?.length || 0} steps, finalText=${finalText?.length || 0} chars`);
|
|
358430
360809
|
}
|
|
358431
360810
|
const usage = await result.usage;
|
|
@@ -358495,12 +360874,12 @@ ${finalResult}
|
|
|
358495
360874
|
|
|
358496
360875
|
Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
|
|
358497
360876
|
currentMessages.push({ role: "user", content: completionPromptMessage });
|
|
358498
|
-
const completionMaxIterations = 5;
|
|
358499
360877
|
const completionStreamOptions = {
|
|
358500
360878
|
model: this.provider ? this.provider(this.model) : this.model,
|
|
358501
360879
|
messages: this.prepareMessagesWithImages(currentMessages),
|
|
358502
360880
|
tools: tools2,
|
|
358503
|
-
|
|
360881
|
+
toolChoice: "none",
|
|
360882
|
+
// Force text-only response — no tool calls during review
|
|
358504
360883
|
maxTokens: maxResponseTokens,
|
|
358505
360884
|
temperature: 0.3,
|
|
358506
360885
|
onStepFinish: ({ toolResults, text, finishReason, usage }) => {
|
|
@@ -360169,11 +362548,9 @@ function autoQuoteSearchTerms(query2) {
|
|
|
360169
362548
|
const result = tokens.map((token) => {
|
|
360170
362549
|
if (token.startsWith('"')) return token;
|
|
360171
362550
|
if (operators.has(token)) return token;
|
|
360172
|
-
const hasUpper = /[A-Z]/.test(token);
|
|
360173
|
-
const hasLower = /[a-z]/.test(token);
|
|
360174
362551
|
const hasUnderscore = token.includes("_");
|
|
360175
|
-
const
|
|
360176
|
-
if (
|
|
362552
|
+
const hasCaseTransition = /[a-z][A-Z]/.test(token) || /[A-Z]{2,}[a-z]/.test(token);
|
|
362553
|
+
if (hasCaseTransition || hasUnderscore) {
|
|
360177
362554
|
return `"${token}"`;
|
|
360178
362555
|
}
|
|
360179
362556
|
return token;
|
|
@@ -360188,11 +362565,24 @@ function normalizeTargets(targets) {
|
|
|
360188
362565
|
if (typeof target !== "string") continue;
|
|
360189
362566
|
const trimmed = target.trim();
|
|
360190
362567
|
if (!trimmed || seen.has(trimmed)) continue;
|
|
360191
|
-
|
|
360192
|
-
|
|
362568
|
+
const subTargets = splitSpaceSeparatedPaths(trimmed);
|
|
362569
|
+
for (const sub of subTargets) {
|
|
362570
|
+
if (!seen.has(sub)) {
|
|
362571
|
+
seen.add(sub);
|
|
362572
|
+
normalized.push(sub);
|
|
362573
|
+
}
|
|
362574
|
+
}
|
|
360193
362575
|
}
|
|
360194
362576
|
return normalized;
|
|
360195
362577
|
}
|
|
362578
|
+
function splitSpaceSeparatedPaths(target) {
|
|
362579
|
+
if (!/\s/.test(target)) return [target];
|
|
362580
|
+
const parts = target.split(/\s+/).filter(Boolean);
|
|
362581
|
+
if (parts.length <= 1) return [target];
|
|
362582
|
+
const allLookLikePaths = parts.every((p) => /[/\\]/.test(p) || /\.\w+/.test(p));
|
|
362583
|
+
if (allLookLikePaths) return parts;
|
|
362584
|
+
return [target];
|
|
362585
|
+
}
|
|
360196
362586
|
function extractJsonSnippet(text) {
|
|
360197
362587
|
const jsonBlockMatch = text.match(/```json\s*([\s\S]*?)```/i);
|
|
360198
362588
|
if (jsonBlockMatch) {
|
|
@@ -360275,7 +362665,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
360275
362665
|
"Break down complex queries into multiple searches to cover all aspects.",
|
|
360276
362666
|
"",
|
|
360277
362667
|
"Available tools:",
|
|
360278
|
-
"- search: Find code matching keywords or patterns. Run multiple searches for different aspects
|
|
362668
|
+
"- search: Find code matching keywords or patterns. Results are paginated \u2014 use nextPage=true when results are relevant to get more. Run multiple searches for different aspects.",
|
|
360279
362669
|
"- extract: Verify code snippets to ensure targets are actually relevant before including them.",
|
|
360280
362670
|
"- listFiles: Understand directory structure to find where relevant code might live.",
|
|
360281
362671
|
"",
|
|
@@ -360296,13 +362686,14 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
360296
362686
|
"",
|
|
360297
362687
|
"Combining searches with OR:",
|
|
360298
362688
|
'- Multiple unquoted words use OR logic: rate limit matches files containing EITHER "rate" OR "limit".',
|
|
360299
|
-
`-
|
|
362689
|
+
`- IMPORTANT: Multiple quoted terms use AND logic by default: '"RateLimit" "middleware"' requires BOTH in the same file.`,
|
|
362690
|
+
`- To search for ANY of several quoted symbols, use the explicit OR operator: '"ForwardMessage" OR "SessionLimiter"'.`,
|
|
360300
362691
|
'- Without quotes, camelCase like limitDRL gets split into "limit" + "DRL" \u2014 not what you want for symbol lookup.',
|
|
360301
362692
|
"- Use OR to search for multiple related symbols in ONE search instead of separate searches.",
|
|
360302
362693
|
"- This is much faster than running separate searches sequentially.",
|
|
360303
|
-
`- Example: search '"ForwardMessage" "SessionLimiter"' finds files with either exact symbol in one call.`,
|
|
360304
|
-
`- Example: search '"limitDRL" "doRollingWindowWrite"' finds both rate limiting functions at once.`,
|
|
360305
|
-
|
|
362694
|
+
`- Example: search '"ForwardMessage" OR "SessionLimiter"' finds files with either exact symbol in one call.`,
|
|
362695
|
+
`- Example: search '"limitDRL" OR "doRollingWindowWrite"' finds both rate limiting functions at once.`,
|
|
362696
|
+
"- Use AND (or just put quoted terms together) when you need both terms in the same file.",
|
|
360306
362697
|
"",
|
|
360307
362698
|
"Parallel tool calls:",
|
|
360308
362699
|
"- When you need to search for INDEPENDENT concepts, call multiple search tools IN PARALLEL (same response).",
|
|
@@ -360316,10 +362707,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
360316
362707
|
' Query: "Find the IP allowlist middleware"',
|
|
360317
362708
|
' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
|
|
360318
362709
|
' Query: "Find ForwardMessage and SessionLimiter"',
|
|
360319
|
-
` \u2192 search '"ForwardMessage" "SessionLimiter"' (one OR search finds both exact symbols)`,
|
|
362710
|
+
` \u2192 search '"ForwardMessage" OR "SessionLimiter"' (one OR search finds both exact symbols)`,
|
|
360320
362711
|
' OR: search exact=true "ForwardMessage" + search exact=true "SessionLimiter" IN PARALLEL',
|
|
360321
362712
|
' Query: "Find limitDRL and limitRedis functions"',
|
|
360322
|
-
` \u2192 search '"limitDRL" "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
|
|
362713
|
+
` \u2192 search '"limitDRL" OR "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
|
|
360323
362714
|
' Query: "Find ThrottleRetryLimit usage"',
|
|
360324
362715
|
' \u2192 search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist \u2014 stop)',
|
|
360325
362716
|
' Query: "How does BM25 scoring work with SIMD optimization?"',
|
|
@@ -360327,7 +362718,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
360327
362718
|
"",
|
|
360328
362719
|
"BAD search strategy (never do this):",
|
|
360329
362720
|
' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: case/style variations, probe handles them)',
|
|
360330
|
-
` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" "limitRedis"')`,
|
|
362721
|
+
` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" OR "limitRedis"')`,
|
|
360331
362722
|
' \u2192 search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
|
|
360332
362723
|
' \u2192 search "ThrottleRetryLimit" path=tyk \u2192 search "ThrottleRetryLimit" path=gateway \u2192 search "ThrottleRetryLimit" path=apidef (WRONG: same query on different paths \u2014 probe searches recursively)',
|
|
360333
362724
|
' \u2192 search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
|
|
@@ -360340,15 +362731,34 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
360340
362731
|
'- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
|
|
360341
362732
|
'- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
|
|
360342
362733
|
'- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
|
|
362734
|
+
'- Do NOT search for file names (e.g., "sliding_log.go"). Use listFiles to discover files by name.',
|
|
362735
|
+
"",
|
|
362736
|
+
"PAGINATION:",
|
|
362737
|
+
"- Search results are paginated (~20k tokens per page).",
|
|
362738
|
+
"- If your search returned relevant files, call the same query with nextPage=true to check for more.",
|
|
362739
|
+
'- Keep paginating while results stay relevant. Stop when results are off-topic or "All results retrieved".',
|
|
362740
|
+
"",
|
|
362741
|
+
"WHEN TO STOP:",
|
|
362742
|
+
"- After you have explored the main concept AND related subsystems.",
|
|
362743
|
+
"- Once you have 5-15 targets covering different aspects of the query.",
|
|
362744
|
+
'- If you get a "DUPLICATE SEARCH BLOCKED" message, move on.',
|
|
360343
362745
|
"",
|
|
360344
362746
|
"Strategy:",
|
|
360345
|
-
"1. Analyze the query
|
|
360346
|
-
|
|
360347
|
-
"
|
|
362747
|
+
"1. Analyze the query \u2014 identify key concepts, then brainstorm SYNONYMS and alternative terms for each.",
|
|
362748
|
+
' Code naming often differs from the concept: "authentication" \u2192 verify, credentials, login, auth;',
|
|
362749
|
+
' "rate limiting" \u2192 throttle, quota, limiter, bucket; "error handling" \u2192 catch, recover, panic.',
|
|
362750
|
+
" Think about what a developer would NAME the function/struct/variable, not just the concept.",
|
|
362751
|
+
"2. Run INDEPENDENT searches in PARALLEL \u2014 search for the main concept AND synonyms simultaneously.",
|
|
362752
|
+
" After each search, check if results are relevant. If yes, call nextPage=true for more results.",
|
|
362753
|
+
`3. Combine related symbols into OR searches: '"symbolA" OR "symbolB"' finds files with either.`,
|
|
360348
362754
|
"4. For known symbol names use exact=true. For concepts use default (exact=false).",
|
|
360349
|
-
"5.
|
|
360350
|
-
"
|
|
360351
|
-
"
|
|
362755
|
+
"5. After your first round of searches, READ the extracted code and look for connected code:",
|
|
362756
|
+
" - Function calls to other important functions \u2192 include those targets.",
|
|
362757
|
+
" - Type references and imports \u2192 include type definitions.",
|
|
362758
|
+
" - Registered handlers/middleware \u2192 include all registered items.",
|
|
362759
|
+
"6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
|
|
362760
|
+
"7. If a search returns NO results, the term does not exist. Do NOT retry with variations. Move on.",
|
|
362761
|
+
"8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.",
|
|
360352
362762
|
"",
|
|
360353
362763
|
`Query: ${searchQuery}`,
|
|
360354
362764
|
`Search path(s): ${searchPath}`,
|
|
@@ -360357,7 +362767,9 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
360357
362767
|
'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
|
|
360358
362768
|
'IMPORTANT: Use ABSOLUTE file paths in targets (e.g., "/full/path/to/file.ext#Symbol"). If you only have relative paths, make them relative to the search path above.',
|
|
360359
362769
|
"Prefer #Symbol when a function/class name is clear; otherwise use line numbers.",
|
|
360360
|
-
"Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets."
|
|
362770
|
+
"Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.",
|
|
362771
|
+
"",
|
|
362772
|
+
"Remember: if your search returned relevant results, use nextPage=true to check for more before outputting."
|
|
360361
362773
|
].join("\n");
|
|
360362
362774
|
}
|
|
360363
362775
|
var import_ai5, import_fs11, CODE_SEARCH_SCHEMA, searchTool, queryTool, extractTool, delegateTool, analyzeAllTool;
|
|
@@ -360402,6 +362814,7 @@ var init_vercel = __esm({
|
|
|
360402
362814
|
return result;
|
|
360403
362815
|
};
|
|
360404
362816
|
const previousSearches = /* @__PURE__ */ new Set();
|
|
362817
|
+
let consecutiveDupBlocks = 0;
|
|
360405
362818
|
const paginationCounts = /* @__PURE__ */ new Map();
|
|
360406
362819
|
const MAX_PAGES_PER_QUERY = 3;
|
|
360407
362820
|
return (0, import_ai5.tool)({
|
|
@@ -360454,12 +362867,17 @@ var init_vercel = __esm({
|
|
|
360454
362867
|
const searchKey = `${searchQuery}::${exact || false}`;
|
|
360455
362868
|
if (!nextPage) {
|
|
360456
362869
|
if (previousSearches.has(searchKey)) {
|
|
362870
|
+
consecutiveDupBlocks++;
|
|
360457
362871
|
if (debug) {
|
|
360458
|
-
console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" (path: "${searchPath}")`);
|
|
362872
|
+
console.error(`[DEDUP] Blocked duplicate search (${consecutiveDupBlocks}x): "${searchQuery}" (path: "${searchPath}")`);
|
|
362873
|
+
}
|
|
362874
|
+
if (consecutiveDupBlocks >= 3) {
|
|
362875
|
+
return "STOP. You have been blocked " + consecutiveDupBlocks + " times for repeating searches. You MUST output your final JSON answer NOW with whatever targets you have found. Do NOT call any more tools.";
|
|
360459
362876
|
}
|
|
360460
|
-
return "DUPLICATE SEARCH BLOCKED
|
|
362877
|
+
return "DUPLICATE SEARCH BLOCKED (" + consecutiveDupBlocks + "x). You already searched for this. Do NOT repeat \u2014 probe searches recursively across all paths. Either: (1) use extract on results you already found, (2) try a COMPLETELY different keyword, or (3) output your final answer NOW.";
|
|
360461
362878
|
}
|
|
360462
362879
|
previousSearches.add(searchKey);
|
|
362880
|
+
consecutiveDupBlocks = 0;
|
|
360463
362881
|
paginationCounts.set(searchKey, 0);
|
|
360464
362882
|
} else {
|
|
360465
362883
|
const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
|
|
@@ -398050,7 +400468,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"100":"Continue","101":"Switching Pro
|
|
|
398050
400468
|
/***/ ((module) => {
|
|
398051
400469
|
|
|
398052
400470
|
"use strict";
|
|
398053
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@probelabs/visor","version":"0.1.
|
|
400471
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@probelabs/visor","version":"0.1.42","main":"dist/index.js","bin":{"visor":"./dist/index.js"},"exports":{".":{"require":"./dist/index.js","import":"./dist/index.js"},"./sdk":{"types":"./dist/sdk/sdk.d.ts","import":"./dist/sdk/sdk.mjs","require":"./dist/sdk/sdk.js"},"./cli":{"require":"./dist/index.js"}},"files":["dist/","defaults/","action.yml","README.md","LICENSE"],"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"scripts":{"build:cli":"ncc build src/index.ts -o dist && cp -r defaults dist/ && cp -r output dist/ && cp -r docs dist/ && cp -r examples dist/ && cp -r src/debug-visualizer/ui dist/debug-visualizer/ && node scripts/inject-version.js && echo \'#!/usr/bin/env node\' | cat - dist/index.js > temp && mv temp dist/index.js && chmod +x dist/index.js","build:sdk":"tsup src/sdk.ts --dts --sourcemap --format esm,cjs --out-dir dist/sdk","build":"./scripts/build-oss.sh","build:ee":"npm run build:cli && npm run build:sdk","test":"jest && npm run test:yaml","test:unit":"jest","prepublishOnly":"npm run build","test:watch":"jest --watch","test:coverage":"jest --coverage","test:ee":"jest --testPathPatterns=\'tests/ee\' --testPathIgnorePatterns=\'/node_modules/\' --no-coverage","test:manual:bash":"RUN_MANUAL_TESTS=true jest tests/manual/bash-config-manual.test.ts","lint":"eslint src tests --ext .ts","lint:fix":"eslint src tests --ext .ts --fix","format":"prettier --write src tests","format:check":"prettier --check src tests","clean":"","clean:traces":"node scripts/clean-traces.js","prebuild":"npm run clean && node scripts/generate-config-schema.js","pretest":"npm run clean:traces && node scripts/generate-config-schema.js && npm run build:cli","pretest:unit":"npm run clean:traces && node scripts/generate-config-schema.js && npm run build:cli","test:with-build":"npm run build:cli && jest","test:yaml":"node dist/index.js test --progress compact","test:yaml:parallel":"node dist/index.js test --progress compact --max-parallel 4","prepare":"husky","pre-commit":"lint-staged","deploy:site":"cd site && npx wrangler pages deploy . --project-name=visor-site --commit-dirty=true","deploy:worker":"npx wrangler deploy","deploy":"npm run deploy:site && npm run deploy:worker","publish:ee":"./scripts/publish-ee.sh","release":"./scripts/release.sh","release:patch":"./scripts/release.sh patch","release:minor":"./scripts/release.sh minor","release:major":"./scripts/release.sh major","release:prerelease":"./scripts/release.sh prerelease","docs:validate":"node scripts/validate-readme-links.js","workshop:setup":"npm install -D reveal-md@6.1.2","workshop:serve":"cd workshop && reveal-md slides.md -w","workshop:export":"reveal-md workshop/slides.md --static workshop/build","workshop:pdf":"reveal-md workshop/slides.md --print workshop/Visor-Workshop.pdf --print-size letter","workshop:pdf:ci":"reveal-md workshop/slides.md --print workshop/Visor-Workshop.pdf --print-size letter --puppeteer-launch-args=\\"--no-sandbox --disable-dev-shm-usage\\"","workshop:pdf:a4":"reveal-md workshop/slides.md --print workshop/Visor-Workshop-A4.pdf --print-size A4","workshop:build":"npm run workshop:export && npm run workshop:pdf","simulate:issue":"TS_NODE_TRANSPILE_ONLY=1 ts-node scripts/simulate-gh-run.ts --event issues --action opened --debug","simulate:comment":"TS_NODE_TRANSPILE_ONLY=1 ts-node scripts/simulate-gh-run.ts --event issue_comment --action created --debug"},"keywords":["code-review","ai","github-action","cli","pr-review","visor"],"author":"Probe Labs","license":"MIT","description":"AI workflow engine for code review, assistants, and automation — orchestrate checks, MCP tools, and AI providers with YAML-driven pipelines","repository":{"type":"git","url":"git+https://github.com/probelabs/visor.git"},"bugs":{"url":"https://github.com/probelabs/visor/issues"},"homepage":"https://github.com/probelabs/visor#readme","dependencies":{"@actions/core":"^1.11.1","@apidevtools/swagger-parser":"^12.1.0","@modelcontextprotocol/sdk":"^1.25.3","@nyariv/sandboxjs":"github:probelabs/SandboxJS#f1c13b8eee98734a8ea024061eada4aa9a9ff2e9","@octokit/action":"^8.0.2","@octokit/auth-app":"^8.1.0","@octokit/core":"^7.0.3","@octokit/rest":"^22.0.0","@opentelemetry/api":"^1.9.0","@opentelemetry/core":"^1.30.1","@opentelemetry/exporter-trace-otlp-grpc":"^0.203.0","@opentelemetry/exporter-trace-otlp-http":"^0.203.0","@opentelemetry/instrumentation":"^0.203.0","@opentelemetry/resources":"^1.30.1","@opentelemetry/sdk-metrics":"^1.30.1","@opentelemetry/sdk-node":"^0.203.0","@opentelemetry/sdk-trace-base":"^1.30.1","@opentelemetry/semantic-conventions":"^1.30.1","@probelabs/probe":"^0.6.0-rc290","@types/commander":"^2.12.0","@types/uuid":"^10.0.0","acorn":"^8.16.0","acorn-walk":"^8.3.5","ajv":"^8.17.1","ajv-formats":"^3.0.1","better-sqlite3":"^11.0.0","blessed":"^0.1.81","cli-table3":"^0.6.5","commander":"^14.0.0","deepmerge":"^4.3.1","dotenv":"^17.2.3","ignore":"^7.0.5","js-yaml":"^4.1.0","jsonpath-plus":"^10.4.0","liquidjs":"^10.21.1","minimatch":"^10.2.2","node-cron":"^3.0.3","open":"^9.1.0","simple-git":"^3.28.0","uuid":"^11.1.0","ws":"^8.18.3"},"optionalDependencies":{"@anthropic/claude-code-sdk":"npm:null@*","@open-policy-agent/opa-wasm":"^1.10.0","knex":"^3.1.0","mysql2":"^3.11.0","pg":"^8.13.0","tedious":"^19.0.0"},"devDependencies":{"@eslint/js":"^9.34.0","@kie/act-js":"^2.6.2","@kie/mock-github":"^2.0.1","@swc/core":"^1.13.2","@swc/jest":"^0.2.37","@types/better-sqlite3":"^7.6.0","@types/blessed":"^0.1.27","@types/jest":"^30.0.0","@types/js-yaml":"^4.0.9","@types/node":"^24.3.0","@types/node-cron":"^3.0.11","@types/ws":"^8.18.1","@typescript-eslint/eslint-plugin":"^8.42.0","@typescript-eslint/parser":"^8.42.0","@vercel/ncc":"^0.38.4","eslint":"^9.34.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.4","husky":"^9.1.7","jest":"^30.1.3","lint-staged":"^16.1.6","prettier":"^3.6.2","reveal-md":"^6.1.2","ts-json-schema-generator":"^1.5.1","ts-node":"^10.9.2","tsup":"^8.5.0","typescript":"^5.9.2","wrangler":"^3.0.0"},"peerDependenciesMeta":{"@anthropic/claude-code-sdk":{"optional":true}},"directories":{"test":"tests"},"lint-staged":{"src/**/*.{ts,js}":["eslint --fix","prettier --write"],"tests/**/*.{ts,js}":["eslint --fix","prettier --write"],"*.{json,md,yml,yaml}":["prettier --write"]}}');
|
|
398054
400472
|
|
|
398055
400473
|
/***/ })
|
|
398056
400474
|
|