@suveren/gateway 0.37.0 → 0.39.0
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/content/integrations/erp.json +360 -19
- package/dist/mcp-server/http.mjs +576 -29
- package/dist/ui/assets/index-DNNXeRpZ.js +123 -0
- package/dist/ui/assets/index-Osk4E49r.css +1 -0
- package/dist/ui/index.html +2 -2
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/README.md +17 -13
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/read.js +74 -57
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/json.js +11 -11
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/urlencoded.js +5 -5
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/utils.js +0 -15
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/package.json +7 -12
- package/node_modules/body-parser/HISTORY.md +7 -2
- package/node_modules/body-parser/README.md +3 -3
- package/node_modules/body-parser/lib/read.js +26 -8
- package/node_modules/body-parser/package.json +1 -1
- package/node_modules/on-finished/index.js +14 -2
- package/node_modules/on-finished/package.json +11 -7
- package/package.json +1 -1
- package/profiles/index.json +2 -1
- package/profiles/master-data/0.1.profile.json +311 -0
- package/dist/ui/assets/index-BC2bXeCQ.js +0 -123
- package/dist/ui/assets/index-XiOgY6b5.css +0 -1
- package/node_modules/on-finished/HISTORY.md +0 -98
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -3600,7 +3600,6 @@ function setAsBaseUrl(url) {
|
|
|
3600
3600
|
AS_BASE = url.replace(/\/$/, "");
|
|
3601
3601
|
}
|
|
3602
3602
|
var OPERATOR_NAME = process.env.SUVEREN_OPERATOR_NAME ?? "Suveren";
|
|
3603
|
-
var HAP_URL = "https://www.humanagencyprotocol.org/";
|
|
3604
3603
|
var FOOTER_RE = /\n*(?:—|--) (?:Sent|Published) by (?:an AI agent|[^\n]+?'s AI agent)[\s\S]*$/;
|
|
3605
3604
|
function shouldAttachFooter() {
|
|
3606
3605
|
return true;
|
|
@@ -3608,16 +3607,11 @@ function shouldAttachFooter() {
|
|
|
3608
3607
|
function receiptUrl(receiptId) {
|
|
3609
3608
|
return `${AS_BASE}/t/${receiptId}`;
|
|
3610
3609
|
}
|
|
3611
|
-
function hapLine() {
|
|
3612
|
-
return `-- ${OPERATOR_NAME} implements HAP, the open Human Agency Protocol: ${HAP_URL}`;
|
|
3613
|
-
}
|
|
3614
3610
|
function footerText(receiptId, verb, subject) {
|
|
3615
3611
|
const identity = deriveIdentityLine(subject, { operatorName: OPERATOR_NAME }).replace(/^Sent\b/, verb);
|
|
3616
3612
|
return `
|
|
3617
3613
|
|
|
3618
|
-
-- ${identity}. Ticket: ${receiptUrl(receiptId)}
|
|
3619
|
-
|
|
3620
|
-
${hapLine()}`;
|
|
3614
|
+
-- ${identity}. Ticket: ${receiptUrl(receiptId)}`;
|
|
3621
3615
|
}
|
|
3622
3616
|
function detectContentField(tool) {
|
|
3623
3617
|
return tool.gating?.message?.text ?? null;
|
|
@@ -3650,6 +3644,8 @@ import { getProfile } from "@hap/core";
|
|
|
3650
3644
|
// src/lib/tool-gating-types.ts
|
|
3651
3645
|
var MAX_PREVIEW_READS = 10;
|
|
3652
3646
|
var MAX_FOREACH_ELEMENTS = 50;
|
|
3647
|
+
var CHECK_STATUSES = ["pass", "fail", "notice", "not_checked"];
|
|
3648
|
+
var MAX_CHECKS_PER_TOOL = 12;
|
|
3653
3649
|
var MAX_RECORD_KEY_STEPS = 2;
|
|
3654
3650
|
|
|
3655
3651
|
// src/lib/scope-specificity.ts
|
|
@@ -3937,8 +3933,28 @@ function invalidReadConfigReason(toolName, idx, entry) {
|
|
|
3937
3933
|
if (p.kind !== void 0 && p.kind !== "document") {
|
|
3938
3934
|
return `${label}.kind must be "document" (the only recognized value)`;
|
|
3939
3935
|
}
|
|
3936
|
+
if (p.when !== void 0 && (typeof p.when !== "string" || !PLAIN_PATH_RE.test(p.when))) {
|
|
3937
|
+
return `${label}.when must be a dotted argument path (e.g. "uid" or "item_id")`;
|
|
3938
|
+
}
|
|
3939
|
+
if (p.displayOnly !== void 0 && typeof p.displayOnly !== "boolean") {
|
|
3940
|
+
return `${label}.displayOnly must be a boolean`;
|
|
3941
|
+
}
|
|
3942
|
+
if (p.as !== void 0) {
|
|
3943
|
+
if (typeof p.as !== "string" || !/^[A-Za-z_][\w-]*$/.test(p.as)) return `${label}.as must be a plain name`;
|
|
3944
|
+
if (p.attach !== void 0 || p.forEach !== void 0 || p.kind !== void 0) {
|
|
3945
|
+
return `${label}.as cannot be combined with attach, forEach or kind`;
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
if (p.label !== void 0 && (typeof p.label !== "string" || p.label.trim().length === 0 || p.label.length > 60)) {
|
|
3949
|
+
return `${label}.label must be a non-empty string of at most 60 characters`;
|
|
3950
|
+
}
|
|
3951
|
+
if (p.displayOnly === true && p.version !== void 0) {
|
|
3952
|
+
return `${label} cannot be both displayOnly and version-checked \u2014 a versioned read is the record the action changes`;
|
|
3953
|
+
}
|
|
3940
3954
|
return null;
|
|
3941
3955
|
}
|
|
3956
|
+
var PLAIN_PATH_RE = /^[A-Za-z_][\w-]*(\.[A-Za-z_][\w-]*)*$/;
|
|
3957
|
+
var CHECK_PATH_RE = /^(?:[A-Za-z_][\w-]*(?:\.(?:[A-Za-z_][\w-]*|\$version))*|[A-Za-z_][\w-]*\[\](?:\.[A-Za-z_][\w-]*)+)$/;
|
|
3942
3958
|
function invalidPreviewReason(toolName, entry) {
|
|
3943
3959
|
if (entry === null || typeof entry !== "object") return null;
|
|
3944
3960
|
const preview = entry.preview;
|
|
@@ -3990,12 +4006,30 @@ function invalidDisplayHintsReason(toolName, entry) {
|
|
|
3990
4006
|
}
|
|
3991
4007
|
}
|
|
3992
4008
|
}
|
|
4009
|
+
if (d.compare !== void 0) {
|
|
4010
|
+
if (d.compare === null || typeof d.compare !== "object" || Array.isArray(d.compare)) {
|
|
4011
|
+
return `override "${toolName}".display.compare must be an object mapping arguments to paths`;
|
|
4012
|
+
}
|
|
4013
|
+
for (const [arg, path] of Object.entries(d.compare)) {
|
|
4014
|
+
if (typeof path !== "string" || !PLAIN_PATH_RE.test(path) || !path.includes(".")) {
|
|
4015
|
+
return `override "${toolName}".display.compare.${arg} must be "<attach>.<field>"`;
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
if (d.names !== void 0) {
|
|
4020
|
+
if (d.names === null || typeof d.names !== "object" || Array.isArray(d.names) || !Object.values(d.names).every((v) => typeof v === "string" && PLAIN_PATH_RE.test(v))) {
|
|
4021
|
+
return `override "${toolName}".display.names must map attach keys to dotted paths`;
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
if (d.verb !== void 0 && d.verb !== "created" && d.verb !== "changed") {
|
|
4025
|
+
return `override "${toolName}".display.verb must be "created" or "changed"`;
|
|
4026
|
+
}
|
|
3993
4027
|
if (d.aiText !== void 0 && (typeof d.aiText !== "string" || d.aiText.length === 0)) {
|
|
3994
4028
|
return `override "${toolName}".display.aiText must be a non-empty string naming an argument`;
|
|
3995
4029
|
}
|
|
3996
4030
|
return null;
|
|
3997
4031
|
}
|
|
3998
|
-
var SUMMARY_FORMATS = ["money", "percent", "list", "short", "date"];
|
|
4032
|
+
var SUMMARY_FORMATS = ["money", "percent", "list", "short", "date", "ref"];
|
|
3999
4033
|
function invalidSummaryReason(toolName, entry) {
|
|
4000
4034
|
if (entry === null || typeof entry !== "object") return null;
|
|
4001
4035
|
const summary = entry.summary;
|
|
@@ -4029,6 +4063,170 @@ function invalidSummaryReason(toolName, entry) {
|
|
|
4029
4063
|
}
|
|
4030
4064
|
return null;
|
|
4031
4065
|
}
|
|
4066
|
+
var CHECK_KEYS = /* @__PURE__ */ new Set([
|
|
4067
|
+
"key",
|
|
4068
|
+
"label",
|
|
4069
|
+
"path",
|
|
4070
|
+
"when",
|
|
4071
|
+
"forEach",
|
|
4072
|
+
"pass",
|
|
4073
|
+
"fail",
|
|
4074
|
+
"notice",
|
|
4075
|
+
"notChecked",
|
|
4076
|
+
"compare",
|
|
4077
|
+
"onTrue",
|
|
4078
|
+
"onFalse",
|
|
4079
|
+
"missing",
|
|
4080
|
+
"detail",
|
|
4081
|
+
"details",
|
|
4082
|
+
"blocking",
|
|
4083
|
+
"present",
|
|
4084
|
+
"absent",
|
|
4085
|
+
"showOnly"
|
|
4086
|
+
]);
|
|
4087
|
+
var CHECK_LISTS = ["pass", "fail", "notice", "notChecked"];
|
|
4088
|
+
var DETAIL_FORMATS = ["money", "percent", "list", "short", "date"];
|
|
4089
|
+
function checkPathReason(at, path, forEach) {
|
|
4090
|
+
if (typeof path !== "string" || !CHECK_PATH_RE.test(path)) return `${at} has an invalid path ${JSON.stringify(path)}`;
|
|
4091
|
+
const b = path.indexOf("[]");
|
|
4092
|
+
if (b >= 0 && path.slice(0, b) !== forEach) return `${at} path "${path}" reads an element but forEach is not "${path.slice(0, b)}"`;
|
|
4093
|
+
return null;
|
|
4094
|
+
}
|
|
4095
|
+
function checkDetailReason(at, detail, forEach) {
|
|
4096
|
+
if (typeof detail !== "string" || detail.trim().length === 0) return `${at}.detail must be a non-empty string`;
|
|
4097
|
+
if (detail.length > 200) return `${at}.detail must be at most 200 characters`;
|
|
4098
|
+
let brace = 0;
|
|
4099
|
+
let bracket = 0;
|
|
4100
|
+
for (const ch of detail) {
|
|
4101
|
+
if (ch === "{") {
|
|
4102
|
+
brace += 1;
|
|
4103
|
+
if (brace > 1) return `${at}.detail has nested { }`;
|
|
4104
|
+
continue;
|
|
4105
|
+
}
|
|
4106
|
+
if (ch === "}") {
|
|
4107
|
+
brace -= 1;
|
|
4108
|
+
if (brace < 0) return `${at}.detail has unbalanced { }`;
|
|
4109
|
+
continue;
|
|
4110
|
+
}
|
|
4111
|
+
if (brace > 0) continue;
|
|
4112
|
+
if (ch === "[") {
|
|
4113
|
+
bracket += 1;
|
|
4114
|
+
if (bracket > 1) return `${at}.detail has nested [ ]`;
|
|
4115
|
+
}
|
|
4116
|
+
if (ch === "]") {
|
|
4117
|
+
bracket -= 1;
|
|
4118
|
+
if (bracket < 0) return `${at}.detail has unbalanced [ ]`;
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
if (brace !== 0 || bracket !== 0) return `${at}.detail has unbalanced brackets`;
|
|
4122
|
+
for (const m of detail.matchAll(/\{([^{}]+)\}/g)) {
|
|
4123
|
+
const [path, fmt, extra] = m[1].split("|").map((x) => x.trim());
|
|
4124
|
+
if (extra !== void 0) return `${at}.detail {${m[1]}} has more than one format`;
|
|
4125
|
+
if (path === "#") {
|
|
4126
|
+
if (typeof forEach !== "string") return `${at}.detail uses {#} without forEach`;
|
|
4127
|
+
} else {
|
|
4128
|
+
const r = checkPathReason(`${at}.detail`, path, forEach);
|
|
4129
|
+
if (r) return r;
|
|
4130
|
+
}
|
|
4131
|
+
const isCount = fmt !== void 0 && /^count:[^/{}|]+\/[^/{}|]+$/.test(fmt);
|
|
4132
|
+
if (fmt !== void 0 && !DETAIL_FORMATS.includes(fmt) && !isCount) return `${at}.detail {${m[1]}} has unknown format "${fmt}"`;
|
|
4133
|
+
}
|
|
4134
|
+
return null;
|
|
4135
|
+
}
|
|
4136
|
+
function checkWhenReason(at, when, forEach) {
|
|
4137
|
+
if (when === void 0) return null;
|
|
4138
|
+
if (typeof when === "string") return checkPathReason(`${at}.when`, when, forEach);
|
|
4139
|
+
if (when === null || typeof when !== "object" || Array.isArray(when)) return `${at}.when must be a path or {path, in}`;
|
|
4140
|
+
const w = when;
|
|
4141
|
+
const extra = Object.keys(w).find((k) => k !== "path" && k !== "in");
|
|
4142
|
+
if (extra) return `${at}.when has unknown key "${extra}"`;
|
|
4143
|
+
return checkPathReason(`${at}.when.path`, w.path, forEach) ?? checkValueListReason(`${at}.when.in`, w.in);
|
|
4144
|
+
}
|
|
4145
|
+
function checkValueListReason(at, list) {
|
|
4146
|
+
if (!Array.isArray(list) || list.length === 0) return `${at} must be a non-empty list of values`;
|
|
4147
|
+
const ok = list.every((v) => v === null || ["string", "boolean"].includes(typeof v) || typeof v === "number" && Number.isFinite(v));
|
|
4148
|
+
return ok ? null : `${at} may hold only strings, numbers, booleans or null`;
|
|
4149
|
+
}
|
|
4150
|
+
function invalidCheckReason(at, raw, seen) {
|
|
4151
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return `${at} must be an object`;
|
|
4152
|
+
const c = raw;
|
|
4153
|
+
const unknownKey = Object.keys(c).find((k) => !CHECK_KEYS.has(k));
|
|
4154
|
+
if (unknownKey) return `${at} has unknown key "${unknownKey}"`;
|
|
4155
|
+
if (typeof c.key !== "string" || c.key.length === 0) return `${at}.key must be a non-empty string`;
|
|
4156
|
+
if (seen.has(c.key)) return `${at}.key "${c.key}" is declared twice`;
|
|
4157
|
+
seen.add(c.key);
|
|
4158
|
+
if (typeof c.label !== "string" || c.label.trim().length === 0 || c.label.length > 80) {
|
|
4159
|
+
return `${at}.label must be a non-empty string of at most 80 characters`;
|
|
4160
|
+
}
|
|
4161
|
+
if (c.forEach !== void 0 && (typeof c.forEach !== "string" || !/^[A-Za-z_][\w-]*$/.test(c.forEach))) {
|
|
4162
|
+
return `${at}.forEach must name an array argument`;
|
|
4163
|
+
}
|
|
4164
|
+
const pathReason = checkPathReason(`${at}.path`, c.path, c.forEach) ?? checkWhenReason(at, c.when, c.forEach);
|
|
4165
|
+
if (pathReason) return pathReason;
|
|
4166
|
+
const hasList = CHECK_LISTS.some((k) => c[k] !== void 0);
|
|
4167
|
+
const hasCompare = c.compare !== void 0;
|
|
4168
|
+
const hasPresence = c.present !== void 0 || c.absent !== void 0;
|
|
4169
|
+
if ([hasList, hasCompare, hasPresence].filter(Boolean).length !== 1) {
|
|
4170
|
+
return `${at} must declare exactly one form: value lists (pass/fail/notice/notChecked), compare, or present/absent`;
|
|
4171
|
+
}
|
|
4172
|
+
if (hasPresence) {
|
|
4173
|
+
for (const k of ["present", "absent"]) {
|
|
4174
|
+
if (!CHECK_STATUSES.includes(c[k])) return `${at}.${k} must be one of ${CHECK_STATUSES.join(", ")} (both required)`;
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
for (const k of CHECK_LISTS) {
|
|
4178
|
+
if (c[k] === void 0) continue;
|
|
4179
|
+
const r = checkValueListReason(`${at}.${k}`, c[k]);
|
|
4180
|
+
if (r) return r;
|
|
4181
|
+
}
|
|
4182
|
+
if (hasCompare) {
|
|
4183
|
+
const cmp = c.compare;
|
|
4184
|
+
if (cmp === null || typeof cmp !== "object" || Array.isArray(cmp)) return `${at}.compare must be an object`;
|
|
4185
|
+
if (!["eq", "ne", "gt", "lt"].includes(cmp.op)) return `${at}.compare.op must be one of eq, ne, gt, lt`;
|
|
4186
|
+
if (typeof cmp.to === "number") {
|
|
4187
|
+
if (!Number.isFinite(cmp.to)) return `${at}.compare.to must be a finite number or a path`;
|
|
4188
|
+
} else {
|
|
4189
|
+
const r = checkPathReason(`${at}.compare.to`, cmp.to, c.forEach);
|
|
4190
|
+
if (r) return r;
|
|
4191
|
+
}
|
|
4192
|
+
const extra = Object.keys(cmp).find((k) => k !== "op" && k !== "to");
|
|
4193
|
+
if (extra) return `${at}.compare has unknown key "${extra}"`;
|
|
4194
|
+
}
|
|
4195
|
+
for (const k of ["onTrue", "onFalse"]) {
|
|
4196
|
+
if (c[k] === void 0) continue;
|
|
4197
|
+
if (!hasCompare) return `${at}.${k} applies to compare only`;
|
|
4198
|
+
if (!CHECK_STATUSES.includes(c[k])) return `${at}.${k} must be one of ${CHECK_STATUSES.join(", ")}`;
|
|
4199
|
+
}
|
|
4200
|
+
if (c.blocking !== void 0 && typeof c.blocking !== "boolean") return `${at}.blocking must be a boolean`;
|
|
4201
|
+
if (c.showOnly !== void 0 && c.showOnly !== "fail" && c.showOnly !== "notice") return `${at}.showOnly must be "fail" or "notice"`;
|
|
4202
|
+
if (c.missing !== void 0 && c.missing !== "skip" && c.missing !== "not_checked") {
|
|
4203
|
+
return `${at}.missing must be "skip" or "not_checked"`;
|
|
4204
|
+
}
|
|
4205
|
+
if (c.details !== void 0) {
|
|
4206
|
+
if (c.details === null || typeof c.details !== "object" || Array.isArray(c.details)) return `${at}.details must map a status to a detail`;
|
|
4207
|
+
for (const [status, d] of Object.entries(c.details)) {
|
|
4208
|
+
if (!CHECK_STATUSES.includes(status)) return `${at}.details.${status} is not a status (${CHECK_STATUSES.join(", ")})`;
|
|
4209
|
+
const r = checkDetailReason(`${at}.details.${status}`, d, c.forEach);
|
|
4210
|
+
if (r) return r;
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
if (c.detail !== void 0) return checkDetailReason(at, c.detail, c.forEach);
|
|
4214
|
+
return null;
|
|
4215
|
+
}
|
|
4216
|
+
function invalidChecksReason(toolName, entry) {
|
|
4217
|
+
if (entry === null || typeof entry !== "object") return null;
|
|
4218
|
+
const checks = entry.checks;
|
|
4219
|
+
if (checks === void 0) return null;
|
|
4220
|
+
const base = `override "${toolName}".checks`;
|
|
4221
|
+
if (!Array.isArray(checks) || checks.length === 0) return `${base} must be a non-empty list`;
|
|
4222
|
+
if (checks.length > MAX_CHECKS_PER_TOOL) return `${base} declares ${checks.length} checks \u2014 max ${MAX_CHECKS_PER_TOOL}`;
|
|
4223
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4224
|
+
for (const [i, raw] of checks.entries()) {
|
|
4225
|
+
const reason = invalidCheckReason(`${base}[${i}]`, raw, seen);
|
|
4226
|
+
if (reason) return reason;
|
|
4227
|
+
}
|
|
4228
|
+
return null;
|
|
4229
|
+
}
|
|
4032
4230
|
function invalidInternalReason(toolName, entry) {
|
|
4033
4231
|
if (entry === null || typeof entry !== "object") return null;
|
|
4034
4232
|
const internal = entry.internal;
|
|
@@ -4092,7 +4290,7 @@ function invalidManifestMessageReason(manifest) {
|
|
|
4092
4290
|
function invalidManifestPreviewReason(manifest) {
|
|
4093
4291
|
const overrides = manifest.toolGating?.overrides ?? {};
|
|
4094
4292
|
for (const [toolName, entry] of Object.entries(overrides)) {
|
|
4095
|
-
const reason = invalidPreviewReason(toolName, entry) ?? invalidDisplayHintsReason(toolName, entry) ?? invalidSummaryReason(toolName, entry);
|
|
4293
|
+
const reason = invalidPreviewReason(toolName, entry) ?? invalidDisplayHintsReason(toolName, entry) ?? invalidSummaryReason(toolName, entry) ?? invalidChecksReason(toolName, entry);
|
|
4096
4294
|
if (reason) return reason;
|
|
4097
4295
|
}
|
|
4098
4296
|
return null;
|
|
@@ -4448,6 +4646,36 @@ function invalidManifestReadGateReason(manifest) {
|
|
|
4448
4646
|
}
|
|
4449
4647
|
return null;
|
|
4450
4648
|
}
|
|
4649
|
+
function invalidNetworkReason(manifest) {
|
|
4650
|
+
const network = manifest.network;
|
|
4651
|
+
if (network === void 0) return null;
|
|
4652
|
+
if (!Array.isArray(network) || network.length === 0) return "network must be a non-empty list of outside calls";
|
|
4653
|
+
for (const [i, raw] of network.entries()) {
|
|
4654
|
+
const at = `network[${i}]`;
|
|
4655
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return `${at} must be an object`;
|
|
4656
|
+
const n = raw;
|
|
4657
|
+
const extra = Object.keys(n).find((k) => !["modes", "host", "purpose", "sends"].includes(k));
|
|
4658
|
+
if (extra) return `${at} has unknown key "${extra}"`;
|
|
4659
|
+
if (typeof n.host !== "string" || !/^(?=.{1,253}$)[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)+$/.test(n.host)) {
|
|
4660
|
+
return `${at}.host must be a bare host name, e.g. "api.example.com"`;
|
|
4661
|
+
}
|
|
4662
|
+
for (const k of ["purpose", "sends"]) {
|
|
4663
|
+
if (typeof n[k] !== "string" || n[k].trim().length === 0 || n[k].length > 200) {
|
|
4664
|
+
return `${at}.${k} must be a non-empty string of at most 200 characters`;
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
if (n.modes !== void 0) {
|
|
4668
|
+
if (n.modes === null || typeof n.modes !== "object" || Array.isArray(n.modes) || Object.keys(n.modes).length === 0) {
|
|
4669
|
+
return `${at}.modes must be an object mapping a mode to true or a list of adapters`;
|
|
4670
|
+
}
|
|
4671
|
+
for (const [mode, v] of Object.entries(n.modes)) {
|
|
4672
|
+
const ok = v === true || Array.isArray(v) && v.length > 0 && v.every((a) => typeof a === "string" && a.length > 0);
|
|
4673
|
+
if (!ok) return `${at}.modes.${mode} must be true or a non-empty list of adapter names`;
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
return null;
|
|
4678
|
+
}
|
|
4451
4679
|
function invalidModeLabelsReason(manifest) {
|
|
4452
4680
|
if (!manifest.modeLabels) return null;
|
|
4453
4681
|
if (manifest.modeLabels === null || typeof manifest.modeLabels !== "object" || Array.isArray(manifest.modeLabels)) {
|
|
@@ -4558,6 +4786,11 @@ function loadManifests(integrationsDir) {
|
|
|
4558
4786
|
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${valuesReason}`);
|
|
4559
4787
|
continue;
|
|
4560
4788
|
}
|
|
4789
|
+
const networkReason = invalidNetworkReason(manifest);
|
|
4790
|
+
if (networkReason) {
|
|
4791
|
+
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${networkReason}`);
|
|
4792
|
+
continue;
|
|
4793
|
+
}
|
|
4561
4794
|
const modeLabelsReason = invalidModeLabelsReason(manifest) ?? (manifest.modeNotes ? invalidModeLabelsReason({ ...manifest, modeLabels: manifest.modeNotes })?.replace(/modeLabels/g, "modeNotes") ?? null : null);
|
|
4562
4795
|
if (modeLabelsReason) {
|
|
4563
4796
|
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${modeLabelsReason}`);
|
|
@@ -4727,6 +4960,241 @@ function candidatesAllowingAttachments(candidates, refs, profileOf) {
|
|
|
4727
4960
|
|
|
4728
4961
|
// src/lib/preview.ts
|
|
4729
4962
|
import { computeContentHash } from "@hap/core";
|
|
4963
|
+
|
|
4964
|
+
// src/lib/checks.ts
|
|
4965
|
+
var ABSENT = { ok: false, why: "absent" };
|
|
4966
|
+
var NO_ANSWER = { ok: false, why: "no_answer" };
|
|
4967
|
+
function isPlainRecord(v) {
|
|
4968
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4969
|
+
}
|
|
4970
|
+
function fromOutcome(outcome, rest) {
|
|
4971
|
+
if (!outcome || outcome.status !== "ok") return outcome?.status === "none" ? ABSENT : NO_ANSWER;
|
|
4972
|
+
const body = structuredOrJsonText(outcome.body);
|
|
4973
|
+
if (!body) return NO_ANSWER;
|
|
4974
|
+
const v = digNullAware(body, rest);
|
|
4975
|
+
return v === void 0 ? ABSENT : { ok: true, value: v };
|
|
4976
|
+
}
|
|
4977
|
+
function digNullAware(source, segs) {
|
|
4978
|
+
let cur = source;
|
|
4979
|
+
for (const part of segs) {
|
|
4980
|
+
if (cur === null) return null;
|
|
4981
|
+
if (typeof cur !== "object" || Array.isArray(cur)) return void 0;
|
|
4982
|
+
cur = cur[part];
|
|
4983
|
+
}
|
|
4984
|
+
return cur;
|
|
4985
|
+
}
|
|
4986
|
+
function resolveCheckPath(path, scope2) {
|
|
4987
|
+
const { ctx, reads, element } = scope2;
|
|
4988
|
+
const sections = ctx.sections ?? [];
|
|
4989
|
+
const bracket = path.indexOf("[]");
|
|
4990
|
+
if (bracket >= 0) {
|
|
4991
|
+
const arrayName = path.slice(0, bracket);
|
|
4992
|
+
const segs2 = path.slice(bracket + 3).split(".").filter(Boolean);
|
|
4993
|
+
if (!element || element.forEach !== arrayName || segs2.length === 0) return ABSENT;
|
|
4994
|
+
const readIdx = reads.findIndex((r) => r.forEach === arrayName && r.attach === segs2[0]);
|
|
4995
|
+
if (readIdx >= 0) {
|
|
4996
|
+
return fromOutcome(sections[readIdx]?.elements?.[element.readIndex], segs2.slice(1));
|
|
4997
|
+
}
|
|
4998
|
+
const v2 = valueAtPath(element.value, segs2.join("."));
|
|
4999
|
+
return v2 === void 0 ? ABSENT : { ok: true, value: v2 };
|
|
5000
|
+
}
|
|
5001
|
+
const segs = path.split(".");
|
|
5002
|
+
if (segs.length > 1) {
|
|
5003
|
+
const readIdx = reads.findIndex((r) => !r.forEach && (r.attach === segs[0] || r.as === segs[0]));
|
|
5004
|
+
if (readIdx >= 0 && segs[1] === "$version") {
|
|
5005
|
+
const version = sections[readIdx]?.version;
|
|
5006
|
+
if (!version) return ABSENT;
|
|
5007
|
+
const v2 = valueAtPath({ approved: version.approved, current: version.current, stale: version.stale }, segs.slice(2).join("."));
|
|
5008
|
+
return v2 === void 0 ? ABSENT : { ok: true, value: v2 };
|
|
5009
|
+
}
|
|
5010
|
+
if (readIdx >= 0) return fromOutcome(sections[readIdx]?.result, segs.slice(1));
|
|
5011
|
+
}
|
|
5012
|
+
const v = valueAtPath(ctx.args, path);
|
|
5013
|
+
return v === void 0 ? ABSENT : { ok: true, value: v };
|
|
5014
|
+
}
|
|
5015
|
+
function sameValue(a, b) {
|
|
5016
|
+
if (typeof a === "number" && typeof b === "number") return Math.abs(a - b) < 1e-9;
|
|
5017
|
+
return a === b;
|
|
5018
|
+
}
|
|
5019
|
+
function inList(list, v) {
|
|
5020
|
+
return Array.isArray(list) && list.some((x) => sameValue(x, v));
|
|
5021
|
+
}
|
|
5022
|
+
function show(v) {
|
|
5023
|
+
return typeof v === "string" ? v : JSON.stringify(v);
|
|
5024
|
+
}
|
|
5025
|
+
function statusFor(decl, value, scope2) {
|
|
5026
|
+
if (decl.compare) {
|
|
5027
|
+
let to = decl.compare.to;
|
|
5028
|
+
if (typeof decl.compare.to === "string") {
|
|
5029
|
+
const r = resolveCheckPath(decl.compare.to, scope2);
|
|
5030
|
+
if (!r.ok) return r;
|
|
5031
|
+
to = r.value;
|
|
5032
|
+
}
|
|
5033
|
+
if (value === null || to === null) return ABSENT;
|
|
5034
|
+
let result;
|
|
5035
|
+
switch (decl.compare.op) {
|
|
5036
|
+
case "eq":
|
|
5037
|
+
result = sameValue(value, to);
|
|
5038
|
+
break;
|
|
5039
|
+
case "ne":
|
|
5040
|
+
result = !sameValue(value, to);
|
|
5041
|
+
break;
|
|
5042
|
+
case "gt":
|
|
5043
|
+
case "lt":
|
|
5044
|
+
if (typeof value !== "number" || typeof to !== "number") {
|
|
5045
|
+
return { status: "not_checked", reason: `unexpected value ${show(value)}` };
|
|
5046
|
+
}
|
|
5047
|
+
result = decl.compare.op === "gt" ? value > to : value < to;
|
|
5048
|
+
break;
|
|
5049
|
+
}
|
|
5050
|
+
return { status: result ? decl.onTrue ?? "pass" : decl.onFalse ?? "fail" };
|
|
5051
|
+
}
|
|
5052
|
+
if (decl.present !== void 0 || decl.absent !== void 0) {
|
|
5053
|
+
return { status: isPresentForWhen(value) || value === false ? decl.present : decl.absent };
|
|
5054
|
+
}
|
|
5055
|
+
if (inList(decl.fail, value)) return { status: "fail" };
|
|
5056
|
+
if (inList(decl.pass, value)) return { status: "pass" };
|
|
5057
|
+
if (inList(decl.notChecked, value)) return { status: "not_checked" };
|
|
5058
|
+
if (inList(decl.notice, value)) return { status: "notice" };
|
|
5059
|
+
return { status: "not_checked", reason: `unexpected value ${show(value)}` };
|
|
5060
|
+
}
|
|
5061
|
+
function evaluateOne(decl, scope2) {
|
|
5062
|
+
if (typeof decl.when === "string") {
|
|
5063
|
+
const w = resolveCheckPath(decl.when, scope2);
|
|
5064
|
+
if (!w.ok || !isPresentForWhen(w.value)) return null;
|
|
5065
|
+
} else if (decl.when !== void 0) {
|
|
5066
|
+
const w = resolveCheckPath(decl.when.path, scope2);
|
|
5067
|
+
if (!w.ok || !inList(decl.when.in, w.value)) return null;
|
|
5068
|
+
}
|
|
5069
|
+
const base = {
|
|
5070
|
+
key: decl.key,
|
|
5071
|
+
label: decl.label,
|
|
5072
|
+
status: "not_checked",
|
|
5073
|
+
...decl.blocking ? { blocking: true } : {},
|
|
5074
|
+
...scope2.element ? { index: scope2.element.position } : {}
|
|
5075
|
+
};
|
|
5076
|
+
const missing = (why) => {
|
|
5077
|
+
if ((decl.missing ?? "skip") === "skip" || decl.showOnly) return null;
|
|
5078
|
+
return { ...base, status: "not_checked", reason: why === "no_answer" ? "no answer from the connector" : "no value to check" };
|
|
5079
|
+
};
|
|
5080
|
+
const v = resolveCheckPath(decl.path, scope2);
|
|
5081
|
+
if (!v.ok) return missing(v.why);
|
|
5082
|
+
const outcome = statusFor(decl, v.value, scope2);
|
|
5083
|
+
if ("ok" in outcome) return outcome.ok || decl.showOnly ? null : missing(outcome.why);
|
|
5084
|
+
if (decl.showOnly === "fail" && outcome.status !== "fail") return null;
|
|
5085
|
+
if (decl.showOnly === "notice" && outcome.status !== "fail" && outcome.status !== "notice") return null;
|
|
5086
|
+
const template = decl.details?.[outcome.status] ?? decl.detail;
|
|
5087
|
+
const detail = template !== void 0 ? renderCheckDetail(template, scope2) : null;
|
|
5088
|
+
const tested = v.value;
|
|
5089
|
+
const plain = tested === null || ["string", "number", "boolean"].includes(typeof tested);
|
|
5090
|
+
return {
|
|
5091
|
+
...base,
|
|
5092
|
+
...plain ? { value: tested } : {},
|
|
5093
|
+
status: outcome.status,
|
|
5094
|
+
...outcome.reason ? { reason: outcome.reason } : {},
|
|
5095
|
+
...detail ? { detail } : {}
|
|
5096
|
+
};
|
|
5097
|
+
}
|
|
5098
|
+
function evaluateChecks(checks, ctx) {
|
|
5099
|
+
if (!checks || checks.length === 0) return [];
|
|
5100
|
+
const reads = ctx.declaration === void 0 ? [] : Array.isArray(ctx.declaration) ? ctx.declaration : [];
|
|
5101
|
+
const out = [];
|
|
5102
|
+
for (const decl of checks) {
|
|
5103
|
+
if (!decl.forEach) {
|
|
5104
|
+
const r = evaluateOne(decl, { ctx, reads });
|
|
5105
|
+
if (r) out.push(r);
|
|
5106
|
+
continue;
|
|
5107
|
+
}
|
|
5108
|
+
const source = ctx.args[decl.forEach];
|
|
5109
|
+
const list = Array.isArray(source) ? source.slice(0, MAX_FOREACH_ELEMENTS) : [];
|
|
5110
|
+
let readIndex = 0;
|
|
5111
|
+
list.forEach((el, i) => {
|
|
5112
|
+
if (!isPlainRecord(el)) return;
|
|
5113
|
+
const r = evaluateOne(decl, { ctx, reads, element: { forEach: decl.forEach, value: el, readIndex, position: i + 1 } });
|
|
5114
|
+
readIndex += 1;
|
|
5115
|
+
if (r) out.push(r);
|
|
5116
|
+
});
|
|
5117
|
+
}
|
|
5118
|
+
return out;
|
|
5119
|
+
}
|
|
5120
|
+
var PLACEHOLDER = /\{([^{}]+)\}/g;
|
|
5121
|
+
function formatValue(v, fmt, currency) {
|
|
5122
|
+
if (fmt === "money") {
|
|
5123
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
5124
|
+
if (!Number.isFinite(n)) return show(v);
|
|
5125
|
+
return currency ? `${n.toFixed(2)} ${currency}` : n.toFixed(2);
|
|
5126
|
+
}
|
|
5127
|
+
if (fmt === "percent") return `${show(v)}%`;
|
|
5128
|
+
if (fmt === "date") {
|
|
5129
|
+
const d = new Date(String(v));
|
|
5130
|
+
return Number.isNaN(d.getTime()) ? show(v) : d.toISOString().slice(0, 10);
|
|
5131
|
+
}
|
|
5132
|
+
if (fmt === "list" && Array.isArray(v)) return v.map(show).join(", ");
|
|
5133
|
+
if (fmt?.startsWith("count:")) {
|
|
5134
|
+
const [one, many] = fmt.slice("count:".length).split("/");
|
|
5135
|
+
return `${show(v)} ${Number(v) === 1 ? one : many}`;
|
|
5136
|
+
}
|
|
5137
|
+
const s = show(v);
|
|
5138
|
+
if (fmt === "short" && s.length > 60) return `${s.slice(0, 59).trimEnd()}\u2026`;
|
|
5139
|
+
return s;
|
|
5140
|
+
}
|
|
5141
|
+
function fillDetail(text, scope2, currency) {
|
|
5142
|
+
let missing = false;
|
|
5143
|
+
const out = text.replace(PLACEHOLDER, (_m, inner) => {
|
|
5144
|
+
const [path, fmt] = inner.split("|").map((x) => x.trim());
|
|
5145
|
+
if (path === "#") {
|
|
5146
|
+
if (!scope2.element) {
|
|
5147
|
+
missing = true;
|
|
5148
|
+
return "";
|
|
5149
|
+
}
|
|
5150
|
+
return String(scope2.element.position);
|
|
5151
|
+
}
|
|
5152
|
+
const r = resolveCheckPath(path, scope2);
|
|
5153
|
+
if (!r.ok || !isPresentForWhen(r.value) && r.value !== false) {
|
|
5154
|
+
missing = true;
|
|
5155
|
+
return "";
|
|
5156
|
+
}
|
|
5157
|
+
return formatValue(r.value, fmt, currency);
|
|
5158
|
+
});
|
|
5159
|
+
return missing ? null : out;
|
|
5160
|
+
}
|
|
5161
|
+
function renderCheckDetail(template, scope2) {
|
|
5162
|
+
const curPath = scope2.ctx.currencyField ?? "currency";
|
|
5163
|
+
const cur = resolveCheckPath(curPath, scope2);
|
|
5164
|
+
const currency = cur.ok && typeof cur.value === "string" ? cur.value : void 0;
|
|
5165
|
+
const segments = [{ text: "", optional: false }];
|
|
5166
|
+
let depth = 0;
|
|
5167
|
+
for (const ch of template) {
|
|
5168
|
+
if (ch === "{") depth += 1;
|
|
5169
|
+
if (ch === "}") depth -= 1;
|
|
5170
|
+
const last = segments[segments.length - 1];
|
|
5171
|
+
if (depth === 0 && ch === "[" && !last.optional) {
|
|
5172
|
+
segments.push({ text: "", optional: true });
|
|
5173
|
+
continue;
|
|
5174
|
+
}
|
|
5175
|
+
if (depth === 0 && ch === "]" && last.optional) {
|
|
5176
|
+
segments.push({ text: "", optional: false });
|
|
5177
|
+
continue;
|
|
5178
|
+
}
|
|
5179
|
+
last.text += ch;
|
|
5180
|
+
}
|
|
5181
|
+
let out = "";
|
|
5182
|
+
for (const seg of segments) {
|
|
5183
|
+
const filled = fillDetail(seg.text, scope2, currency);
|
|
5184
|
+
if (filled === null && !seg.optional) return null;
|
|
5185
|
+
out += filled ?? "";
|
|
5186
|
+
}
|
|
5187
|
+
const clean = out.replace(/\s+/g, " ").trim().replace(/^[—–,;:\s]+/, "").replace(/[—–,;:\s]+$/, "");
|
|
5188
|
+
return clean.length > 0 ? clean : null;
|
|
5189
|
+
}
|
|
5190
|
+
function blockingFailures(results) {
|
|
5191
|
+
return results.filter((r) => r.blocking === true && r.status === "fail");
|
|
5192
|
+
}
|
|
5193
|
+
function blockingRefusalReason(failures) {
|
|
5194
|
+
return failures.map((f) => `${f.label}${f.index !== void 0 ? ` (line ${f.index})` : ""}: failed${f.value !== void 0 ? ` (${f.value === null ? "none" : String(f.value)})` : ""}${f.detail ? ` \u2014 ${f.detail}` : ""}`).join("; ");
|
|
5195
|
+
}
|
|
5196
|
+
|
|
5197
|
+
// src/lib/preview.ts
|
|
4730
5198
|
var PREVIEW_READ_TIMEOUT_MS = 5e3;
|
|
4731
5199
|
var PREVIEW_MESSAGE_CAP = 500;
|
|
4732
5200
|
function capText(text, max = PREVIEW_MESSAGE_CAP) {
|
|
@@ -4749,16 +5217,39 @@ function getToolDisplayHints(integrationManager2, integrationId, toolName) {
|
|
|
4749
5217
|
const entry = overrides[toolName];
|
|
4750
5218
|
return entry?.display;
|
|
4751
5219
|
}
|
|
5220
|
+
function getToolChecks(integrationManager2, integrationId, toolName) {
|
|
5221
|
+
if (integrationManager2.isBuiltin?.(integrationId)) return void 0;
|
|
5222
|
+
const entry = getManifest(integrationId)?.toolGating?.overrides?.[toolName];
|
|
5223
|
+
return entry?.checks;
|
|
5224
|
+
}
|
|
4752
5225
|
function mapPreviewArgs(preview, actionArgs) {
|
|
4753
5226
|
const mapped = {};
|
|
4754
5227
|
for (const [targetArg, sourceArg] of Object.entries(preview.args)) {
|
|
4755
|
-
|
|
5228
|
+
const value = actionArgs[sourceArg];
|
|
5229
|
+
if (value !== void 0 && value !== null) mapped[targetArg] = value;
|
|
4756
5230
|
}
|
|
4757
5231
|
return mapped;
|
|
4758
5232
|
}
|
|
5233
|
+
function isPresentForWhen(v) {
|
|
5234
|
+
if (v === void 0 || v === null || v === false) return false;
|
|
5235
|
+
if (typeof v === "string") return v.trim().length > 0;
|
|
5236
|
+
if (Array.isArray(v)) return v.length > 0;
|
|
5237
|
+
return true;
|
|
5238
|
+
}
|
|
5239
|
+
function valueAtPath(source, path) {
|
|
5240
|
+
let cur = source;
|
|
5241
|
+
for (const part of path.split(".")) {
|
|
5242
|
+
if (cur === null || typeof cur !== "object" || Array.isArray(cur)) return void 0;
|
|
5243
|
+
cur = cur[part];
|
|
5244
|
+
}
|
|
5245
|
+
return cur;
|
|
5246
|
+
}
|
|
4759
5247
|
function previewHasNoTarget(preview, mapped) {
|
|
4760
5248
|
return Object.keys(preview.args).length > 0 && Object.keys(mapped).length === 0;
|
|
4761
5249
|
}
|
|
5250
|
+
function whenAbsent(config, source) {
|
|
5251
|
+
return config.when !== void 0 && !isPresentForWhen(valueAtPath(source, config.when));
|
|
5252
|
+
}
|
|
4762
5253
|
var PreviewTimeoutError = class extends Error {
|
|
4763
5254
|
};
|
|
4764
5255
|
function withTimeout(p, ms) {
|
|
@@ -4780,7 +5271,7 @@ async function readPreview(integrationManager2, integrationId, previewTool, args
|
|
|
4780
5271
|
);
|
|
4781
5272
|
const text = result.content?.[0]?.text;
|
|
4782
5273
|
if (result.isError) {
|
|
4783
|
-
return { status: "not_found", message: capText(text) };
|
|
5274
|
+
return /^\s*Refused\b/i.test(text ?? "") ? { status: "refused", message: capText(text) } : { status: "not_found", message: capText(text) };
|
|
4784
5275
|
}
|
|
4785
5276
|
return {
|
|
4786
5277
|
status: "ok",
|
|
@@ -4816,7 +5307,7 @@ function hashPreviewBody(body) {
|
|
|
4816
5307
|
}
|
|
4817
5308
|
return computeContentHash({ kind: "text", version: "1" }, body.text ?? "");
|
|
4818
5309
|
}
|
|
4819
|
-
function
|
|
5310
|
+
function isPlainRecord2(v) {
|
|
4820
5311
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4821
5312
|
}
|
|
4822
5313
|
function planPreviewReads(declaration, actionArgs) {
|
|
@@ -4828,13 +5319,16 @@ function planPreviewReads(declaration, actionArgs) {
|
|
|
4828
5319
|
fields: config.fields,
|
|
4829
5320
|
version: config.version,
|
|
4830
5321
|
kind: config.kind,
|
|
4831
|
-
argsMapping: config.args
|
|
5322
|
+
argsMapping: config.args,
|
|
5323
|
+
...config.displayOnly ? { displayOnly: true } : {},
|
|
5324
|
+
...config.as ? { as: config.as } : {},
|
|
5325
|
+
...config.label ? { label: config.label } : {}
|
|
4832
5326
|
};
|
|
4833
5327
|
if (config.forEach) {
|
|
4834
5328
|
const source = actionArgs[config.forEach];
|
|
4835
5329
|
const list = Array.isArray(source) ? source : [];
|
|
4836
5330
|
const capped = list.slice(0, MAX_FOREACH_ELEMENTS);
|
|
4837
|
-
const elements = capped.filter(
|
|
5331
|
+
const elements = capped.filter(isPlainRecord2).map((el) => config.when !== void 0 && !isPresentForWhen(valueAtPath(el, config.when)) ? { args: {}, skip: true } : { args: mapPreviewArgs(config, el) });
|
|
4838
5332
|
return {
|
|
4839
5333
|
...base,
|
|
4840
5334
|
forEach: config.forEach,
|
|
@@ -4842,6 +5336,9 @@ function planPreviewReads(declaration, actionArgs) {
|
|
|
4842
5336
|
...list.length > MAX_FOREACH_ELEMENTS ? { overflow: { shown: MAX_FOREACH_ELEMENTS, total: list.length } } : {}
|
|
4843
5337
|
};
|
|
4844
5338
|
}
|
|
5339
|
+
if (config.when !== void 0 && !isPresentForWhen(valueAtPath(actionArgs, config.when))) {
|
|
5340
|
+
return { ...base, args: {}, rawSource: actionArgs, noTarget: true };
|
|
5341
|
+
}
|
|
4845
5342
|
const mapped = mapPreviewArgs(config, actionArgs);
|
|
4846
5343
|
return { ...base, args: mapped, rawSource: actionArgs, noTarget: previewHasNoTarget(config, mapped) };
|
|
4847
5344
|
});
|
|
@@ -4855,12 +5352,15 @@ async function runPreviewReadPlan(integrationManager2, integrationId, plan) {
|
|
|
4855
5352
|
...item.attach ? { attach: item.attach } : {},
|
|
4856
5353
|
...sourceArgNames.length === 1 ? { sourceArg: sourceArgNames[0] } : {},
|
|
4857
5354
|
...item.fields ? { fields: item.fields } : {},
|
|
4858
|
-
...item.kind ? { kind: item.kind } : {}
|
|
5355
|
+
...item.kind ? { kind: item.kind } : {},
|
|
5356
|
+
...item.displayOnly ? { displayOnly: true } : {},
|
|
5357
|
+
...item.as ? { as: item.as } : {},
|
|
5358
|
+
...item.label ? { label: item.label } : {}
|
|
4859
5359
|
};
|
|
4860
5360
|
if (item.elements !== void 0) {
|
|
4861
5361
|
const elements = [];
|
|
4862
5362
|
for (const el of item.elements) {
|
|
4863
|
-
elements.push(await readPreview(integrationManager2, integrationId, item.tool, el.args));
|
|
5363
|
+
elements.push(el.skip ? { status: "none", reason: "no_target" } : await readPreview(integrationManager2, integrationId, item.tool, el.args));
|
|
4864
5364
|
}
|
|
4865
5365
|
sections.push({
|
|
4866
5366
|
...base,
|
|
@@ -4887,11 +5387,12 @@ async function runPreviewReadPlan(integrationManager2, integrationId, plan) {
|
|
|
4887
5387
|
const { [item.version.arg]: _omit, ...withoutVersion } = item.rawSource;
|
|
4888
5388
|
const mappedCurrentArgs = mapPreviewArgs({ args: item.argsMapping }, withoutVersion);
|
|
4889
5389
|
const read2 = await readPreview(integrationManager2, integrationId, item.tool, mappedCurrentArgs);
|
|
4890
|
-
|
|
5390
|
+
const current2 = read2.status === "ok" ? structuredOrJsonText(read2.body) : void 0;
|
|
5391
|
+
if (current2 === void 0) {
|
|
4891
5392
|
sections.push({ ...base, result: read1 });
|
|
4892
5393
|
continue;
|
|
4893
5394
|
}
|
|
4894
|
-
const current =
|
|
5395
|
+
const current = current2[item.version.field];
|
|
4895
5396
|
if (typeof current !== "string" && typeof current !== "number") {
|
|
4896
5397
|
sections.push({ ...base, result: read1 });
|
|
4897
5398
|
continue;
|
|
@@ -4905,7 +5406,7 @@ async function runPreviewReadPlan(integrationManager2, integrationId, plan) {
|
|
|
4905
5406
|
approved,
|
|
4906
5407
|
current,
|
|
4907
5408
|
stale,
|
|
4908
|
-
...stale ? { currentBody: read2.body } : {}
|
|
5409
|
+
...stale && read2.status === "ok" ? { currentBody: read2.body } : {}
|
|
4909
5410
|
}
|
|
4910
5411
|
});
|
|
4911
5412
|
}
|
|
@@ -4916,9 +5417,9 @@ async function computeSubmissionPreviewHash(integrationManager2, integrationId,
|
|
|
4916
5417
|
const preview = getToolPreviewConfig(integrationManager2, integrationId, toolName);
|
|
4917
5418
|
if (!preview) return void 0;
|
|
4918
5419
|
if (Array.isArray(preview)) return combinedPreviewReadsHash(integrationManager2, integrationId, preview, actionArgs);
|
|
4919
|
-
if (preview.version) return void 0;
|
|
5420
|
+
if (preview.version || preview.displayOnly) return void 0;
|
|
4920
5421
|
const mapped = mapPreviewArgs(preview, actionArgs);
|
|
4921
|
-
if (previewHasNoTarget(preview, mapped)) return void 0;
|
|
5422
|
+
if (previewHasNoTarget(preview, mapped) || whenAbsent(preview, actionArgs)) return void 0;
|
|
4922
5423
|
const read = await readPreview(integrationManager2, integrationId, preview.tool, mapped);
|
|
4923
5424
|
if (read.status !== "ok") return void 0;
|
|
4924
5425
|
return hashPreviewBody(read.body);
|
|
@@ -4937,11 +5438,12 @@ async function previewChangedSinceSubmission(integrationManager2, integrationId,
|
|
|
4937
5438
|
return hashPreviewBody(read.body) !== previousHash;
|
|
4938
5439
|
}
|
|
4939
5440
|
async function combinedPreviewReadsHash(integrationManager2, integrationId, reads, actionArgs) {
|
|
4940
|
-
const plan = planPreviewReads(reads, actionArgs).filter((item) => !item.version);
|
|
5441
|
+
const plan = planPreviewReads(reads, actionArgs).filter((item) => !item.version && !item.displayOnly);
|
|
4941
5442
|
const hashes = [];
|
|
4942
5443
|
for (const item of plan) {
|
|
4943
5444
|
if (item.elements !== void 0) {
|
|
4944
5445
|
for (const el of item.elements) {
|
|
5446
|
+
if (el.skip) continue;
|
|
4945
5447
|
const read2 = await readPreview(integrationManager2, integrationId, item.tool, el.args);
|
|
4946
5448
|
if (read2.status !== "ok") return void 0;
|
|
4947
5449
|
hashes.push(hashPreviewBody(read2.body));
|
|
@@ -4957,6 +5459,32 @@ async function combinedPreviewReadsHash(integrationManager2, integrationId, read
|
|
|
4957
5459
|
return computeContentHash({ kind: "jcs", version: "1" }, { reads: hashes });
|
|
4958
5460
|
}
|
|
4959
5461
|
async function buildInternalPreview(integrationManager2, integrationId, toolName, actionArgs) {
|
|
5462
|
+
const result = await buildPreviewReads(integrationManager2, integrationId, toolName, actionArgs);
|
|
5463
|
+
const checks = getToolChecks(integrationManager2, integrationId, toolName);
|
|
5464
|
+
if (!checks || checks.length === 0) return result;
|
|
5465
|
+
const display = getToolDisplayHints(integrationManager2, integrationId, toolName);
|
|
5466
|
+
return {
|
|
5467
|
+
...result,
|
|
5468
|
+
checks: evaluateChecks(checks, {
|
|
5469
|
+
args: actionArgs,
|
|
5470
|
+
declaration: getToolPreviewConfig(integrationManager2, integrationId, toolName),
|
|
5471
|
+
sections: "reads" in result ? result.reads : void 0,
|
|
5472
|
+
currencyField: display?.currencyField
|
|
5473
|
+
})
|
|
5474
|
+
};
|
|
5475
|
+
}
|
|
5476
|
+
async function failedBlockingChecks(integrationManager2, integrationId, toolName, actionArgs) {
|
|
5477
|
+
const declared = getToolChecks(integrationManager2, integrationId, toolName);
|
|
5478
|
+
if (!declared?.some((c) => c.blocking === true)) return [];
|
|
5479
|
+
try {
|
|
5480
|
+
const result = await buildInternalPreview(integrationManager2, integrationId, toolName, actionArgs);
|
|
5481
|
+
return blockingFailures(result.checks ?? []);
|
|
5482
|
+
} catch (err) {
|
|
5483
|
+
console.error(`[Suveren MCP] blocking checks for ${integrationId}__${toolName} could not run:`, err);
|
|
5484
|
+
return [];
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
async function buildPreviewReads(integrationManager2, integrationId, toolName, actionArgs) {
|
|
4960
5488
|
const preview = getToolPreviewConfig(integrationManager2, integrationId, toolName);
|
|
4961
5489
|
if (!preview) return { status: "none" };
|
|
4962
5490
|
if (Array.isArray(preview)) {
|
|
@@ -4965,7 +5493,7 @@ async function buildInternalPreview(integrationManager2, integrationId, toolName
|
|
|
4965
5493
|
return { status: "ok", integration: integrationId, reads, ...display ? { display } : {} };
|
|
4966
5494
|
}
|
|
4967
5495
|
const mappedArgs = mapPreviewArgs(preview, actionArgs);
|
|
4968
|
-
if (previewHasNoTarget(preview, mappedArgs)) return { status: "none", reason: "no_target" };
|
|
5496
|
+
if (previewHasNoTarget(preview, mappedArgs) || whenAbsent(preview, actionArgs)) return { status: "none", reason: "no_target" };
|
|
4969
5497
|
const read1 = await readPreview(integrationManager2, integrationId, preview.tool, mappedArgs);
|
|
4970
5498
|
if (read1.status !== "ok") return read1;
|
|
4971
5499
|
const ok = (extra = {}) => ({
|
|
@@ -4986,10 +5514,11 @@ async function buildInternalPreview(integrationManager2, integrationId, toolName
|
|
|
4986
5514
|
const { [preview.version.arg]: _omit, ...withoutVersion } = actionArgs;
|
|
4987
5515
|
const mappedCurrentArgs = mapPreviewArgs(preview, withoutVersion);
|
|
4988
5516
|
const read2 = await readPreview(integrationManager2, integrationId, preview.tool, mappedCurrentArgs);
|
|
4989
|
-
|
|
5517
|
+
const current2 = read2.status === "ok" ? structuredOrJsonText(read2.body) : void 0;
|
|
5518
|
+
if (current2 === void 0) {
|
|
4990
5519
|
return ok();
|
|
4991
5520
|
}
|
|
4992
|
-
const current =
|
|
5521
|
+
const current = current2[preview.version.field];
|
|
4993
5522
|
if (typeof current !== "string" && typeof current !== "number") {
|
|
4994
5523
|
return ok();
|
|
4995
5524
|
}
|
|
@@ -5000,7 +5529,7 @@ async function buildInternalPreview(integrationManager2, integrationId, toolName
|
|
|
5000
5529
|
approved,
|
|
5001
5530
|
current,
|
|
5002
5531
|
stale,
|
|
5003
|
-
...stale ? { currentBody: read2.body } : {}
|
|
5532
|
+
...stale && read2.status === "ok" ? { currentBody: read2.body } : {}
|
|
5004
5533
|
}
|
|
5005
5534
|
});
|
|
5006
5535
|
}
|
|
@@ -5190,7 +5719,7 @@ async function readContextRecord(integrationManager2, connectorId, tool, args) {
|
|
|
5190
5719
|
if (record === void 0) return { status: "unavailable", reason: "connector_error" };
|
|
5191
5720
|
return { status: "ok", record };
|
|
5192
5721
|
}
|
|
5193
|
-
if (read.status === "not_found") return { status: "not_found" };
|
|
5722
|
+
if (read.status === "not_found" || read.status === "refused") return { status: "not_found" };
|
|
5194
5723
|
return { status: "unavailable", reason: read.reason };
|
|
5195
5724
|
}
|
|
5196
5725
|
function parseExplicitSourceLink(raw) {
|
|
@@ -5466,7 +5995,7 @@ async function resolveDocumentReference(integrationManager2, ref) {
|
|
|
5466
5995
|
}
|
|
5467
5996
|
const args = substitute(docConfig.args, { id: ref.id });
|
|
5468
5997
|
const read = await readPreview(integrationManager2, ref.connector, docConfig.tool, args);
|
|
5469
|
-
if (read.status === "not_found") {
|
|
5998
|
+
if (read.status === "not_found" || read.status === "refused") {
|
|
5470
5999
|
return { ok: false, error: { code: "NOT_FOUND", message: read.message ?? `No document "${ref.kind}"/"${ref.id}" on "${ref.connector}".` } };
|
|
5471
6000
|
}
|
|
5472
6001
|
if (read.status !== "ok") {
|
|
@@ -5548,7 +6077,7 @@ async function resolveInsertValue(integrationManager2, ref) {
|
|
|
5548
6077
|
}
|
|
5549
6078
|
const args = substitute(valueConfig.args, { id: ref.id });
|
|
5550
6079
|
const read = await readPreview(integrationManager2, ref.connector, valueConfig.tool, args);
|
|
5551
|
-
if (read.status === "not_found") {
|
|
6080
|
+
if (read.status === "not_found" || read.status === "refused") {
|
|
5552
6081
|
return { ok: false, error: { code: "NOT_FOUND", message: read.message ?? `No value "${ref.kind}"/"${ref.id}" on "${ref.connector}".` } };
|
|
5553
6082
|
}
|
|
5554
6083
|
if (read.status !== "ok") {
|
|
@@ -6680,6 +7209,24 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
6680
7209
|
return { content: [{ type: "text", text: noMandateRefusal(tool.namespacedName, mandateLabel) }], isError: true };
|
|
6681
7210
|
}
|
|
6682
7211
|
if (matchingAuths.length === 0) return noEligibleMandate(refusals);
|
|
7212
|
+
const blockingFails = await failedBlockingChecks(integrationManager2, tool.integrationId, tool.originalName, args);
|
|
7213
|
+
if (blockingFails.length > 0) {
|
|
7214
|
+
recordBlocked(state2, tool, {
|
|
7215
|
+
kind: "check",
|
|
7216
|
+
// A sentence without content: the check's own label only.
|
|
7217
|
+
detail: `Check failed: ${blockingFails.map((f) => f.label).join("; ")}.`,
|
|
7218
|
+
field: blockingFails[0].key,
|
|
7219
|
+
who: "gateway",
|
|
7220
|
+
code: "CHECK_FAILED"
|
|
7221
|
+
});
|
|
7222
|
+
return {
|
|
7223
|
+
content: [{
|
|
7224
|
+
type: "text",
|
|
7225
|
+
text: `Refused: ${blockingRefusalReason(blockingFails)}. Nothing was proposed and nothing ran. This check reads the system's own data, so an approval cannot override it \u2014 correct the request (or the data in the system) and try again.`
|
|
7226
|
+
}],
|
|
7227
|
+
isError: true
|
|
7228
|
+
};
|
|
7229
|
+
}
|
|
6683
7230
|
const errors = [];
|
|
6684
7231
|
const passers = [];
|
|
6685
7232
|
const structuredRefusals = [];
|