@suveren/gateway 0.28.0 → 0.29.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/crm.json +37 -3
- package/content/integrations/erp.json +20 -2
- package/content/integrations/gmail.json +14 -0
- package/content/integrations/mail.json +20 -3
- package/dist/control-plane/index.mjs +363 -195
- package/dist/mcp-server/http.mjs +948 -227
- package/dist/ui/assets/index-C3vTF2Ye.js +123 -0
- package/dist/ui/assets/{index-RX5juZkm.css → index-DUIlMVlP.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-I9WCdLMY.js +0 -120
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -3180,7 +3180,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3180
3180
|
import { z as z2 } from "zod";
|
|
3181
3181
|
|
|
3182
3182
|
// src/lib/mandate-brief.ts
|
|
3183
|
-
import { getProfile as
|
|
3183
|
+
import { getProfile as getProfile4 } from "@hap/core";
|
|
3184
3184
|
|
|
3185
3185
|
// src/lib/consumption.ts
|
|
3186
3186
|
import { boundActionTypes } from "@hap/core";
|
|
@@ -3473,6 +3473,7 @@ import { join as join14, resolve } from "path";
|
|
|
3473
3473
|
// src/lib/tool-gating-types.ts
|
|
3474
3474
|
var MAX_PREVIEW_READS = 10;
|
|
3475
3475
|
var MAX_FOREACH_ELEMENTS = 50;
|
|
3476
|
+
var MAX_RECORD_KEY_STEPS = 2;
|
|
3476
3477
|
|
|
3477
3478
|
// src/lib/manifest-loader.ts
|
|
3478
3479
|
var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
@@ -3597,6 +3598,231 @@ function invalidManifestPreviewReason(manifest) {
|
|
|
3597
3598
|
}
|
|
3598
3599
|
return null;
|
|
3599
3600
|
}
|
|
3601
|
+
function isNonEmptyString(v) {
|
|
3602
|
+
return typeof v === "string" && v.length > 0;
|
|
3603
|
+
}
|
|
3604
|
+
var VALID_FIELD_PATH_PARSE = ["address.name", "address.email", "epoch-ms"];
|
|
3605
|
+
function invalidFieldPathReason(label, value) {
|
|
3606
|
+
if (isNonEmptyString(value)) return null;
|
|
3607
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
3608
|
+
return `${label} must be a non-empty string or a field-path object`;
|
|
3609
|
+
}
|
|
3610
|
+
const f = value;
|
|
3611
|
+
if (!isNonEmptyString(f.path)) return `${label}.path must be a non-empty string`;
|
|
3612
|
+
if (f.where !== void 0) {
|
|
3613
|
+
if (f.where === null || typeof f.where !== "object" || Array.isArray(f.where)) {
|
|
3614
|
+
return `${label}.where must be an object`;
|
|
3615
|
+
}
|
|
3616
|
+
for (const [k, v] of Object.entries(f.where)) {
|
|
3617
|
+
if (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean") {
|
|
3618
|
+
return `${label}.where.${k} must be a string, number, or boolean`;
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
if (f.pick !== void 0 && !isNonEmptyString(f.pick)) {
|
|
3623
|
+
return `${label}.pick must be a non-empty string`;
|
|
3624
|
+
}
|
|
3625
|
+
if (f.parse !== void 0 && !VALID_FIELD_PATH_PARSE.includes(f.parse)) {
|
|
3626
|
+
return `${label}.parse must be one of ${VALID_FIELD_PATH_PARSE.join(", ")}`;
|
|
3627
|
+
}
|
|
3628
|
+
return null;
|
|
3629
|
+
}
|
|
3630
|
+
function invalidContextReadReason(label, entry) {
|
|
3631
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
3632
|
+
return `${label} must be an object`;
|
|
3633
|
+
}
|
|
3634
|
+
const r = entry;
|
|
3635
|
+
if (!isNonEmptyString(r.tool)) return `${label}.tool must be a non-empty string`;
|
|
3636
|
+
if (r.args === void 0 || r.args === null || typeof r.args !== "object" || Array.isArray(r.args)) {
|
|
3637
|
+
return `${label}.args must be an object`;
|
|
3638
|
+
}
|
|
3639
|
+
for (const [k, v] of Object.entries(r.args)) {
|
|
3640
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
3641
|
+
return `${label}.args.${k} must be a non-empty string`;
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
return null;
|
|
3645
|
+
}
|
|
3646
|
+
function invalidContextSourceReason(toolName, entry) {
|
|
3647
|
+
const label = `context.sources.${toolName}`;
|
|
3648
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
3649
|
+
return `${label} must be an object`;
|
|
3650
|
+
}
|
|
3651
|
+
const s = entry;
|
|
3652
|
+
const readReason = invalidContextReadReason(`${label}.read`, s.read);
|
|
3653
|
+
if (readReason) return readReason;
|
|
3654
|
+
for (const field of ["title", "author", "at", "body"]) {
|
|
3655
|
+
if (s[field] !== void 0) {
|
|
3656
|
+
const reason = invalidFieldPathReason(`${label}.${field}`, s[field]);
|
|
3657
|
+
if (reason) return reason;
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
if (s.keys !== void 0) {
|
|
3661
|
+
if (s.keys === null || typeof s.keys !== "object" || Array.isArray(s.keys)) {
|
|
3662
|
+
return `${label}.keys must be an object`;
|
|
3663
|
+
}
|
|
3664
|
+
for (const [k, v] of Object.entries(s.keys)) {
|
|
3665
|
+
if (k !== "email") return `${label}.keys.${k}: only "email" is a recognized identity key type`;
|
|
3666
|
+
const reason = invalidFieldPathReason(`${label}.keys.${k}`, v);
|
|
3667
|
+
if (reason) return reason;
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
if (s.chain !== void 0) {
|
|
3671
|
+
if (s.chain === null || typeof s.chain !== "object" || Array.isArray(s.chain)) {
|
|
3672
|
+
return `${label}.chain must be an object`;
|
|
3673
|
+
}
|
|
3674
|
+
const c = s.chain;
|
|
3675
|
+
if (!isNonEmptyString(c.field)) return `${label}.chain.field must be a non-empty string`;
|
|
3676
|
+
if (typeof c.max !== "number" || c.max <= 0) return `${label}.chain.max must be a positive number`;
|
|
3677
|
+
}
|
|
3678
|
+
return null;
|
|
3679
|
+
}
|
|
3680
|
+
function invalidContextHistoryReason(history) {
|
|
3681
|
+
const label = "context.history";
|
|
3682
|
+
if (history === null || typeof history !== "object" || Array.isArray(history)) {
|
|
3683
|
+
return `${label} must be an object`;
|
|
3684
|
+
}
|
|
3685
|
+
const h = history;
|
|
3686
|
+
const isKeyType = (v) => v === "email" || v === "email_domain";
|
|
3687
|
+
if (Array.isArray(h.byKey)) {
|
|
3688
|
+
if (h.byKey.length === 0) return `${label}.byKey must not be an empty array`;
|
|
3689
|
+
for (const [i, v] of h.byKey.entries()) {
|
|
3690
|
+
if (!isKeyType(v)) return `${label}.byKey[${i}] must be "email" or "email_domain"`;
|
|
3691
|
+
}
|
|
3692
|
+
} else if (!isKeyType(h.byKey)) {
|
|
3693
|
+
return `${label}.byKey must be "email" or "email_domain" (or an array of those, in preference order)`;
|
|
3694
|
+
}
|
|
3695
|
+
const readReason = invalidContextReadReason(`${label}.read`, h.read);
|
|
3696
|
+
if (readReason) return readReason;
|
|
3697
|
+
if (typeof h.limit !== "number" || h.limit <= 0) return `${label}.limit must be a positive number`;
|
|
3698
|
+
if (typeof h.maxAgeDays !== "number" || h.maxAgeDays <= 0) return `${label}.maxAgeDays must be a positive number`;
|
|
3699
|
+
if (h.item === null || typeof h.item !== "object" || Array.isArray(h.item)) {
|
|
3700
|
+
return `${label}.item must be an object`;
|
|
3701
|
+
}
|
|
3702
|
+
for (const [k, v] of Object.entries(h.item)) {
|
|
3703
|
+
if (k === "kind") {
|
|
3704
|
+
if (!isNonEmptyString(v)) return `${label}.item.kind must be a non-empty string`;
|
|
3705
|
+
continue;
|
|
3706
|
+
}
|
|
3707
|
+
const reason = invalidFieldPathReason(`${label}.item.${k}`, v);
|
|
3708
|
+
if (reason) return reason;
|
|
3709
|
+
}
|
|
3710
|
+
return null;
|
|
3711
|
+
}
|
|
3712
|
+
function invalidContextRecordKeyReason(name, entry) {
|
|
3713
|
+
const label = `context.recordKeys.${name}`;
|
|
3714
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
3715
|
+
return `${label} must be an object`;
|
|
3716
|
+
}
|
|
3717
|
+
const rk = entry;
|
|
3718
|
+
const readReason = invalidContextReadReason(`${label}.read`, rk.read);
|
|
3719
|
+
if (readReason) return readReason;
|
|
3720
|
+
if (rk.keys !== void 0) {
|
|
3721
|
+
if (rk.keys === null || typeof rk.keys !== "object" || Array.isArray(rk.keys)) {
|
|
3722
|
+
return `${label}.keys must be an object`;
|
|
3723
|
+
}
|
|
3724
|
+
for (const [k, v] of Object.entries(rk.keys)) {
|
|
3725
|
+
if (k !== "email") return `${label}.keys.${k}: only "email" is a recognized identity key type`;
|
|
3726
|
+
const reason = invalidFieldPathReason(`${label}.keys.${k}`, v);
|
|
3727
|
+
if (reason) return reason;
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
if (rk.next !== void 0) {
|
|
3731
|
+
if (rk.next === null || typeof rk.next !== "object" || Array.isArray(rk.next)) {
|
|
3732
|
+
return `${label}.next must be an object`;
|
|
3733
|
+
}
|
|
3734
|
+
const next = rk.next;
|
|
3735
|
+
if (!isNonEmptyString(next.recordKey)) return `${label}.next.recordKey must be a non-empty string`;
|
|
3736
|
+
if (!isNonEmptyString(next.field)) return `${label}.next.field must be a non-empty string`;
|
|
3737
|
+
}
|
|
3738
|
+
if (rk.keys === void 0 && rk.next === void 0) {
|
|
3739
|
+
return `${label} must declare "keys", "next", or both \u2014 an entry with neither learns nothing from the record it reads`;
|
|
3740
|
+
}
|
|
3741
|
+
return null;
|
|
3742
|
+
}
|
|
3743
|
+
function recordKeyChainLength(name, recordKeys, seen) {
|
|
3744
|
+
if (seen.has(name)) return "cycle";
|
|
3745
|
+
const entry = recordKeys[name];
|
|
3746
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return "missing";
|
|
3747
|
+
const next = entry.next;
|
|
3748
|
+
if (!next || typeof next !== "object") return 1;
|
|
3749
|
+
const nextName = next.recordKey;
|
|
3750
|
+
if (typeof nextName !== "string" || !(nextName in recordKeys)) return "missing";
|
|
3751
|
+
const rest = recordKeyChainLength(nextName, recordKeys, /* @__PURE__ */ new Set([...seen, name]));
|
|
3752
|
+
if (rest === "cycle" || rest === "missing") return rest;
|
|
3753
|
+
return 1 + rest;
|
|
3754
|
+
}
|
|
3755
|
+
function invalidManifestContextReason(manifest) {
|
|
3756
|
+
const ctx = manifest.context;
|
|
3757
|
+
const recordKeyNames = new Set(Object.keys(ctx?.recordKeys ?? {}));
|
|
3758
|
+
if (ctx) {
|
|
3759
|
+
if (ctx.sources !== void 0) {
|
|
3760
|
+
if (ctx.sources === null || typeof ctx.sources !== "object" || Array.isArray(ctx.sources)) {
|
|
3761
|
+
return "context.sources must be an object";
|
|
3762
|
+
}
|
|
3763
|
+
for (const [kind, entry] of Object.entries(ctx.sources)) {
|
|
3764
|
+
const reason = invalidContextSourceReason(kind, entry);
|
|
3765
|
+
if (reason) return reason;
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
if (ctx.history !== void 0) {
|
|
3769
|
+
const reason = invalidContextHistoryReason(ctx.history);
|
|
3770
|
+
if (reason) return reason;
|
|
3771
|
+
}
|
|
3772
|
+
if (ctx.recordKeys !== void 0) {
|
|
3773
|
+
if (ctx.recordKeys === null || typeof ctx.recordKeys !== "object" || Array.isArray(ctx.recordKeys)) {
|
|
3774
|
+
return "context.recordKeys must be an object";
|
|
3775
|
+
}
|
|
3776
|
+
for (const [name, entry] of Object.entries(ctx.recordKeys)) {
|
|
3777
|
+
const reason = invalidContextRecordKeyReason(name, entry);
|
|
3778
|
+
if (reason) return reason;
|
|
3779
|
+
}
|
|
3780
|
+
for (const name of Object.keys(ctx.recordKeys)) {
|
|
3781
|
+
const length = recordKeyChainLength(name, ctx.recordKeys, /* @__PURE__ */ new Set());
|
|
3782
|
+
if (length === "cycle") {
|
|
3783
|
+
return `context.recordKeys.${name}.next forms a cycle`;
|
|
3784
|
+
}
|
|
3785
|
+
if (length === "missing") {
|
|
3786
|
+
return `context.recordKeys.${name}.next.recordKey names an entry this manifest does not declare`;
|
|
3787
|
+
}
|
|
3788
|
+
if (length > MAX_RECORD_KEY_STEPS) {
|
|
3789
|
+
return `context.recordKeys.${name}.next chain is ${length} steps long \u2014 max ${MAX_RECORD_KEY_STEPS}`;
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
const sourceKinds = new Set(Object.keys(ctx?.sources ?? {}));
|
|
3795
|
+
const overrides = manifest.toolGating?.overrides ?? {};
|
|
3796
|
+
for (const [toolName, entry] of Object.entries(overrides)) {
|
|
3797
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
3798
|
+
const e = entry;
|
|
3799
|
+
if (e.contextKeys !== void 0) {
|
|
3800
|
+
if (e.contextKeys === null || typeof e.contextKeys !== "object" || Array.isArray(e.contextKeys)) {
|
|
3801
|
+
return `override "${toolName}".contextKeys must be an object`;
|
|
3802
|
+
}
|
|
3803
|
+
for (const [name, argName] of Object.entries(e.contextKeys)) {
|
|
3804
|
+
if (!isNonEmptyString(argName)) {
|
|
3805
|
+
return `override "${toolName}".contextKeys.${name} must be a non-empty string naming an action argument`;
|
|
3806
|
+
}
|
|
3807
|
+
if (name !== "email" && !recordKeyNames.has(name)) {
|
|
3808
|
+
return `override "${toolName}".contextKeys.${name}: "${name}" is neither "email" nor a context.recordKeys entry on this manifest`;
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
if (e.sourceFromArg !== void 0) {
|
|
3813
|
+
if (e.sourceFromArg === null || typeof e.sourceFromArg !== "object" || Array.isArray(e.sourceFromArg)) {
|
|
3814
|
+
return `override "${toolName}".sourceFromArg must be an object`;
|
|
3815
|
+
}
|
|
3816
|
+
const sfa = e.sourceFromArg;
|
|
3817
|
+
if (!isNonEmptyString(sfa.kind)) return `override "${toolName}".sourceFromArg.kind must be a non-empty string`;
|
|
3818
|
+
if (!isNonEmptyString(sfa.arg)) return `override "${toolName}".sourceFromArg.arg must be a non-empty string`;
|
|
3819
|
+
if (!sourceKinds.has(sfa.kind)) {
|
|
3820
|
+
return `override "${toolName}".sourceFromArg.kind "${String(sfa.kind)}" is not declared in this manifest's context.sources`;
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
return null;
|
|
3825
|
+
}
|
|
3600
3826
|
var manifests = /* @__PURE__ */ new Map();
|
|
3601
3827
|
function loadManifests(integrationsDir) {
|
|
3602
3828
|
const dir = resolve(
|
|
@@ -3629,6 +3855,11 @@ function loadManifests(integrationsDir) {
|
|
|
3629
3855
|
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${previewReason}`);
|
|
3630
3856
|
continue;
|
|
3631
3857
|
}
|
|
3858
|
+
const contextReason = invalidManifestContextReason(manifest);
|
|
3859
|
+
if (contextReason) {
|
|
3860
|
+
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${contextReason}`);
|
|
3861
|
+
continue;
|
|
3862
|
+
}
|
|
3632
3863
|
manifests.set(id, manifest);
|
|
3633
3864
|
loaded++;
|
|
3634
3865
|
} catch (err) {
|
|
@@ -3887,30 +4118,629 @@ async function buildInternalPreview(integrationManager2, integrationId, toolName
|
|
|
3887
4118
|
if (!preview.version) {
|
|
3888
4119
|
return ok();
|
|
3889
4120
|
}
|
|
3890
|
-
const approved = actionArgs[preview.version.arg];
|
|
3891
|
-
if (typeof approved !== "string" && typeof approved !== "number") {
|
|
3892
|
-
return ok();
|
|
4121
|
+
const approved = actionArgs[preview.version.arg];
|
|
4122
|
+
if (typeof approved !== "string" && typeof approved !== "number") {
|
|
4123
|
+
return ok();
|
|
4124
|
+
}
|
|
4125
|
+
const { [preview.version.arg]: _omit, ...withoutVersion } = actionArgs;
|
|
4126
|
+
const mappedCurrentArgs = mapPreviewArgs(preview, withoutVersion);
|
|
4127
|
+
const read2 = await readPreview(integrationManager2, integrationId, preview.tool, mappedCurrentArgs);
|
|
4128
|
+
if (read2.status !== "ok" || read2.body.structured === void 0) {
|
|
4129
|
+
return ok();
|
|
4130
|
+
}
|
|
4131
|
+
const current = read2.body.structured[preview.version.field];
|
|
4132
|
+
if (typeof current !== "string" && typeof current !== "number") {
|
|
4133
|
+
return ok();
|
|
4134
|
+
}
|
|
4135
|
+
const stale = String(current) !== String(approved);
|
|
4136
|
+
return ok({
|
|
4137
|
+
version: {
|
|
4138
|
+
field: preview.version.field,
|
|
4139
|
+
approved,
|
|
4140
|
+
current,
|
|
4141
|
+
stale,
|
|
4142
|
+
...stale ? { currentBody: read2.body } : {}
|
|
4143
|
+
}
|
|
4144
|
+
});
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
// src/lib/scope-specificity.ts
|
|
4148
|
+
function tokenSet(value) {
|
|
4149
|
+
if (value === void 0 || value === null) return /* @__PURE__ */ new Set();
|
|
4150
|
+
return new Set(
|
|
4151
|
+
String(value).split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
|
|
4152
|
+
);
|
|
4153
|
+
}
|
|
4154
|
+
function subsetOrEqual(a, b) {
|
|
4155
|
+
if (b.size === 0) return true;
|
|
4156
|
+
if (a.size === 0) return false;
|
|
4157
|
+
for (const x of a) if (!b.has(x)) return false;
|
|
4158
|
+
return true;
|
|
4159
|
+
}
|
|
4160
|
+
function atLeastAsSpecific(keys, a, b) {
|
|
4161
|
+
for (const k of keys) {
|
|
4162
|
+
if (!subsetOrEqual(tokenSet(a[k]), tokenSet(b[k]))) return false;
|
|
4163
|
+
}
|
|
4164
|
+
return true;
|
|
4165
|
+
}
|
|
4166
|
+
function strictlyMoreSpecific(keys, a, b) {
|
|
4167
|
+
return atLeastAsSpecific(keys, a, b) && !atLeastAsSpecific(keys, b, a);
|
|
4168
|
+
}
|
|
4169
|
+
function selectAuthorization(contextKeys, passers) {
|
|
4170
|
+
if (passers.length === 1) {
|
|
4171
|
+
return { chosen: passers[0], superseded: [], reason: "sole" };
|
|
4172
|
+
}
|
|
4173
|
+
const dominators = passers.filter(
|
|
4174
|
+
(a) => passers.every((b) => b === a || strictlyMoreSpecific(contextKeys, a.context, b.context))
|
|
4175
|
+
);
|
|
4176
|
+
if (dominators.length === 1) {
|
|
4177
|
+
const chosen2 = dominators[0];
|
|
4178
|
+
return { chosen: chosen2, superseded: passers.filter((p) => p !== chosen2), reason: "most-specific" };
|
|
4179
|
+
}
|
|
4180
|
+
const approvers = passers.filter((p) => p.requiresApproval);
|
|
4181
|
+
const pool = approvers.length > 0 ? approvers : passers;
|
|
4182
|
+
const chosen = pool.slice().sort((a, b) => a.id.localeCompare(b.id))[0];
|
|
4183
|
+
return {
|
|
4184
|
+
chosen,
|
|
4185
|
+
superseded: passers.filter((p) => p !== chosen),
|
|
4186
|
+
reason: approvers.length > 0 ? "fail-safe-approval" : "fail-safe-tiebreak"
|
|
4187
|
+
};
|
|
4188
|
+
}
|
|
4189
|
+
|
|
4190
|
+
// src/lib/read-gate.ts
|
|
4191
|
+
function boundsSatisfyReadGate(bounds, gate) {
|
|
4192
|
+
if (!gate.boundField) return true;
|
|
4193
|
+
const actual = bounds?.[gate.boundField];
|
|
4194
|
+
return actual !== void 0 && String(actual) === gate.requiredValue;
|
|
4195
|
+
}
|
|
4196
|
+
function readToolIsGoverned(g) {
|
|
4197
|
+
if (!g) return false;
|
|
4198
|
+
return Boolean(g.boundField) || g.read != null || g.readGovernance === "none";
|
|
4199
|
+
}
|
|
4200
|
+
function allowedResources(contexts, boundField) {
|
|
4201
|
+
const out = /* @__PURE__ */ new Set();
|
|
4202
|
+
for (const ctx of contexts) {
|
|
4203
|
+
for (const v of tokenSet(ctx?.[boundField])) out.add(v);
|
|
4204
|
+
}
|
|
4205
|
+
return out;
|
|
4206
|
+
}
|
|
4207
|
+
function deniedResources(requested, allowed) {
|
|
4208
|
+
return requested.filter((r) => !allowed.has(r));
|
|
4209
|
+
}
|
|
4210
|
+
function filterItemsByResource(items, idPath, allowed) {
|
|
4211
|
+
return items.filter((it) => {
|
|
4212
|
+
const id = getByDottedPath(it, idPath);
|
|
4213
|
+
return typeof id === "string" && allowed.has(id);
|
|
4214
|
+
});
|
|
4215
|
+
}
|
|
4216
|
+
function hasBlockedValue(item, valuesPath, blocked) {
|
|
4217
|
+
const v = getByDottedPath(item, valuesPath);
|
|
4218
|
+
const arr = Array.isArray(v) ? v : v != null ? [v] : [];
|
|
4219
|
+
return arr.some((x) => typeof x === "string" && blocked.has(x));
|
|
4220
|
+
}
|
|
4221
|
+
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
4222
|
+
function parseMessageTimestamp(value) {
|
|
4223
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4224
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
4225
|
+
const n = Number(value);
|
|
4226
|
+
if (Number.isFinite(n)) return n;
|
|
4227
|
+
const iso = Date.parse(value);
|
|
4228
|
+
if (Number.isFinite(iso)) return iso;
|
|
4229
|
+
}
|
|
4230
|
+
return null;
|
|
4231
|
+
}
|
|
4232
|
+
function ageInDays(timestampMs, nowMs) {
|
|
4233
|
+
return Math.floor((nowMs - timestampMs) / MS_PER_DAY);
|
|
4234
|
+
}
|
|
4235
|
+
function maxReadAgeDays(boundsList, field) {
|
|
4236
|
+
let max = null;
|
|
4237
|
+
for (const b of boundsList) {
|
|
4238
|
+
const raw = b?.[field];
|
|
4239
|
+
const n = typeof raw === "number" ? raw : raw !== void 0 ? Number(raw) : NaN;
|
|
4240
|
+
if (Number.isFinite(n)) max = max === null ? n : Math.max(max, n);
|
|
4241
|
+
}
|
|
4242
|
+
return max;
|
|
4243
|
+
}
|
|
4244
|
+
function isOlderThanMaxAge(timestampMs, maxAgeDays, nowMs) {
|
|
4245
|
+
if (timestampMs === null) return true;
|
|
4246
|
+
return ageInDays(timestampMs, nowMs) > maxAgeDays;
|
|
4247
|
+
}
|
|
4248
|
+
var TRAILING_OPERATOR = /(?:^|\s)(?:OR|AND|NOT)$|[-+|&:]$/i;
|
|
4249
|
+
function parensBalanced(s) {
|
|
4250
|
+
let depth = 0;
|
|
4251
|
+
let inQuote = false;
|
|
4252
|
+
for (const c of s) {
|
|
4253
|
+
if (c === '"') {
|
|
4254
|
+
inQuote = !inQuote;
|
|
4255
|
+
continue;
|
|
4256
|
+
}
|
|
4257
|
+
if (inQuote) continue;
|
|
4258
|
+
if (c === "(") depth++;
|
|
4259
|
+
else if (c === ")" && --depth < 0) return false;
|
|
4260
|
+
}
|
|
4261
|
+
return !inQuote && depth === 0;
|
|
4262
|
+
}
|
|
4263
|
+
function composeReadQuery(agentQuery, clauses) {
|
|
4264
|
+
const injected = clauses.filter((c) => typeof c === "string" && c.trim() !== "").map((c) => c.trim());
|
|
4265
|
+
const base = (agentQuery ?? "").trim();
|
|
4266
|
+
if (base === "") return { ok: true, query: injected.join(" ") };
|
|
4267
|
+
if (!parensBalanced(base)) {
|
|
4268
|
+
return { ok: false, reason: "the requested search query has unbalanced parentheses" };
|
|
4269
|
+
}
|
|
4270
|
+
if (TRAILING_OPERATOR.test(base)) {
|
|
4271
|
+
return { ok: false, reason: "the requested search query ends in a dangling operator" };
|
|
4272
|
+
}
|
|
4273
|
+
if (injected.length === 0) return { ok: true, query: base };
|
|
4274
|
+
return { ok: true, query: `(${base}) ${injected.join(" ")}` };
|
|
4275
|
+
}
|
|
4276
|
+
function detectAgeConflict(agentQuery, conflictPattern, maxAgeDays) {
|
|
4277
|
+
if (!agentQuery || !conflictPattern || maxAgeDays === null) return null;
|
|
4278
|
+
let re;
|
|
4279
|
+
try {
|
|
4280
|
+
re = new RegExp(conflictPattern, "gi");
|
|
4281
|
+
} catch {
|
|
4282
|
+
return null;
|
|
4283
|
+
}
|
|
4284
|
+
let worst = null;
|
|
4285
|
+
for (const match of agentQuery.matchAll(re)) {
|
|
4286
|
+
const days = Number(match[1]);
|
|
4287
|
+
if (!Number.isFinite(days)) continue;
|
|
4288
|
+
if (days >= maxAgeDays && (worst === null || days > worst)) worst = days;
|
|
4289
|
+
}
|
|
4290
|
+
return worst;
|
|
4291
|
+
}
|
|
4292
|
+
function resolveAgeBoundField(boundsSchema, ageExecField) {
|
|
4293
|
+
const fields = boundsSchema?.fields;
|
|
4294
|
+
if (!fields) return null;
|
|
4295
|
+
for (const [name, def] of Object.entries(fields)) {
|
|
4296
|
+
if (def?.boundType?.kind === "per_transaction" && def.boundType.of === ageExecField) return name;
|
|
4297
|
+
}
|
|
4298
|
+
return null;
|
|
4299
|
+
}
|
|
4300
|
+
function getByDottedPath(obj, path) {
|
|
4301
|
+
return path.split(".").reduce(
|
|
4302
|
+
(o, k) => o == null ? void 0 : o[k],
|
|
4303
|
+
obj
|
|
4304
|
+
);
|
|
4305
|
+
}
|
|
4306
|
+
function firstParsableDate(obj, paths) {
|
|
4307
|
+
for (const path of Array.isArray(paths) ? paths : [paths]) {
|
|
4308
|
+
const parsed = parseMessageTimestamp(getByDottedPath(obj, path));
|
|
4309
|
+
if (parsed !== null) return parsed;
|
|
4310
|
+
}
|
|
4311
|
+
return null;
|
|
4312
|
+
}
|
|
4313
|
+
function renderAgeConstraint(template, maxAgeDays, nowMs) {
|
|
4314
|
+
const floorIso = new Date(nowMs - maxAgeDays * MS_PER_DAY).toISOString();
|
|
4315
|
+
return template.replace(/\{days\}/g, String(maxAgeDays)).replace(/\{date\}/g, floorIso.slice(0, 10));
|
|
4316
|
+
}
|
|
4317
|
+
function ageFloorValue(maxAgeDays, nowMs, format = "iso") {
|
|
4318
|
+
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
4319
|
+
if (format === "epoch_ms") return floorMs;
|
|
4320
|
+
if (format === "epoch_s") return String(Math.floor(floorMs / 1e3));
|
|
4321
|
+
return new Date(floorMs).toISOString();
|
|
4322
|
+
}
|
|
4323
|
+
function clampAgeFloor(requested, maxAgeDays, nowMs, format = "iso") {
|
|
4324
|
+
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
4325
|
+
const asked = parseMessageTimestamp(requested);
|
|
4326
|
+
if (asked !== null && asked >= floorMs) return requested;
|
|
4327
|
+
return ageFloorValue(maxAgeDays, nowMs, format);
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4330
|
+
// src/lib/field-path.ts
|
|
4331
|
+
function matchesWhere(element, where) {
|
|
4332
|
+
if (element === null || typeof element !== "object" || Array.isArray(element)) return false;
|
|
4333
|
+
const row = element;
|
|
4334
|
+
return Object.entries(where).every(([key, expected]) => {
|
|
4335
|
+
const actual = row[key];
|
|
4336
|
+
if (typeof expected === "string" && typeof actual === "string") {
|
|
4337
|
+
return expected.toLowerCase() === actual.toLowerCase();
|
|
4338
|
+
}
|
|
4339
|
+
return actual === expected;
|
|
4340
|
+
});
|
|
4341
|
+
}
|
|
4342
|
+
function parseAddress(value, which) {
|
|
4343
|
+
const m = value.match(/^(.*?)<([^<>]+)>\s*$/);
|
|
4344
|
+
if (m) {
|
|
4345
|
+
const email = m[2].trim();
|
|
4346
|
+
if (which === "address.email") return email || void 0;
|
|
4347
|
+
const name = m[1].trim().replace(/^"(.*)"$/, "$1").trim();
|
|
4348
|
+
return name || void 0;
|
|
4349
|
+
}
|
|
4350
|
+
if (which === "address.email") return value.trim() || void 0;
|
|
4351
|
+
return void 0;
|
|
4352
|
+
}
|
|
4353
|
+
function parseEpochMs(value) {
|
|
4354
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
4355
|
+
if (!Number.isFinite(n)) return void 0;
|
|
4356
|
+
return new Date(n).toISOString();
|
|
4357
|
+
}
|
|
4358
|
+
function applyParse(value, parse2) {
|
|
4359
|
+
if (value === void 0 || value === null) return void 0;
|
|
4360
|
+
if (parse2 === "epoch-ms") return parseEpochMs(value);
|
|
4361
|
+
if (typeof value !== "string") return void 0;
|
|
4362
|
+
return parseAddress(value, parse2);
|
|
4363
|
+
}
|
|
4364
|
+
function resolveFieldPath(record, fieldPath) {
|
|
4365
|
+
if (fieldPath === void 0) return void 0;
|
|
4366
|
+
if (typeof fieldPath === "string") return getByDottedPath(record, fieldPath);
|
|
4367
|
+
let value = getByDottedPath(record, fieldPath.path);
|
|
4368
|
+
if (fieldPath.where) {
|
|
4369
|
+
if (!Array.isArray(value)) return void 0;
|
|
4370
|
+
value = value.find((el) => matchesWhere(el, fieldPath.where));
|
|
4371
|
+
if (value === void 0) return void 0;
|
|
4372
|
+
}
|
|
4373
|
+
if (fieldPath.pick !== void 0) {
|
|
4374
|
+
value = getByDottedPath(value, fieldPath.pick);
|
|
4375
|
+
}
|
|
4376
|
+
if (fieldPath.parse) {
|
|
4377
|
+
value = applyParse(value, fieldPath.parse);
|
|
4378
|
+
}
|
|
4379
|
+
return value;
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4382
|
+
// src/lib/request-context.ts
|
|
4383
|
+
var SOURCE_LINK_ARG = "_source";
|
|
4384
|
+
function withSourceLink(schema) {
|
|
4385
|
+
const props = schema.properties ?? {};
|
|
4386
|
+
if ("_source" in props) return schema;
|
|
4387
|
+
return {
|
|
4388
|
+
...schema,
|
|
4389
|
+
properties: {
|
|
4390
|
+
...props,
|
|
4391
|
+
_source: {
|
|
4392
|
+
type: "object",
|
|
4393
|
+
description: 'Optional. Link the request you are answering \u2014 the ONE record (e.g. an inbound email) this action responds to, so the human approver can see why it was proposed. Only a reference: {"connector": "<integration id>", "kind": "<declared kind>", "id": "<record id>"}. Example: {"connector":"mail","kind":"message","id":"m-17"}. The gateway reads the record itself \u2014 never write a summary or quote here.',
|
|
4394
|
+
properties: {
|
|
4395
|
+
connector: { type: "string" },
|
|
4396
|
+
kind: { type: "string" },
|
|
4397
|
+
id: { type: "string" }
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
};
|
|
4402
|
+
}
|
|
4403
|
+
function stripSourceLink(args) {
|
|
4404
|
+
if (!(SOURCE_LINK_ARG in args)) return args;
|
|
4405
|
+
const { [SOURCE_LINK_ARG]: _omit, ...rest } = args;
|
|
4406
|
+
return rest;
|
|
4407
|
+
}
|
|
4408
|
+
var FREE_MAIL_DOMAINS = /* @__PURE__ */ new Set([
|
|
4409
|
+
"gmail.com",
|
|
4410
|
+
"googlemail.com",
|
|
4411
|
+
"outlook.com",
|
|
4412
|
+
"hotmail.com",
|
|
4413
|
+
"hotmail.co.uk",
|
|
4414
|
+
"hotmail.fr",
|
|
4415
|
+
"hotmail.de",
|
|
4416
|
+
"live.com",
|
|
4417
|
+
"msn.com",
|
|
4418
|
+
"yahoo.com",
|
|
4419
|
+
"yahoo.co.uk",
|
|
4420
|
+
"yahoo.de",
|
|
4421
|
+
"yahoo.fr",
|
|
4422
|
+
"ymail.com",
|
|
4423
|
+
"rocketmail.com",
|
|
4424
|
+
"gmx.de",
|
|
4425
|
+
"gmx.net",
|
|
4426
|
+
"gmx.at",
|
|
4427
|
+
"gmx.ch",
|
|
4428
|
+
"gmx.com",
|
|
4429
|
+
"web.de",
|
|
4430
|
+
"t-online.de",
|
|
4431
|
+
"icloud.com",
|
|
4432
|
+
"me.com",
|
|
4433
|
+
"mac.com",
|
|
4434
|
+
"aol.com",
|
|
4435
|
+
"protonmail.com",
|
|
4436
|
+
"proton.me",
|
|
4437
|
+
"mail.com",
|
|
4438
|
+
"zoho.com",
|
|
4439
|
+
"yandex.com"
|
|
4440
|
+
]);
|
|
4441
|
+
function isFreeMailDomain(domain) {
|
|
4442
|
+
return FREE_MAIL_DOMAINS.has(domain.toLowerCase());
|
|
4443
|
+
}
|
|
4444
|
+
function deriveIdentityKeysFromEmail(email) {
|
|
4445
|
+
const lower = email.toLowerCase().trim();
|
|
4446
|
+
const at = lower.lastIndexOf("@");
|
|
4447
|
+
if (at <= 0 || at === lower.length - 1) return [];
|
|
4448
|
+
const domain = lower.slice(at + 1);
|
|
4449
|
+
const keys = [{ type: "email", value: lower }];
|
|
4450
|
+
if (!isFreeMailDomain(domain)) keys.push({ type: "email_domain", value: domain });
|
|
4451
|
+
return keys;
|
|
4452
|
+
}
|
|
4453
|
+
function deriveIdentityKeysFromValue(value) {
|
|
4454
|
+
if (typeof value === "string") return deriveIdentityKeysFromEmail(value);
|
|
4455
|
+
if (Array.isArray(value)) {
|
|
4456
|
+
return value.flatMap((v) => typeof v === "string" ? deriveIdentityKeysFromEmail(v) : []);
|
|
4457
|
+
}
|
|
4458
|
+
return [];
|
|
4459
|
+
}
|
|
4460
|
+
function dedupeKeys(keys) {
|
|
4461
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4462
|
+
const out = [];
|
|
4463
|
+
for (const k of keys) {
|
|
4464
|
+
const sig = `${k.type}:${k.value}`;
|
|
4465
|
+
if (seen.has(sig)) continue;
|
|
4466
|
+
seen.add(sig);
|
|
4467
|
+
out.push(k);
|
|
4468
|
+
}
|
|
4469
|
+
return out;
|
|
4470
|
+
}
|
|
4471
|
+
function substitute(template, values) {
|
|
4472
|
+
const out = {};
|
|
4473
|
+
for (const [arg, token] of Object.entries(template)) {
|
|
4474
|
+
if (token.startsWith("$")) {
|
|
4475
|
+
const key = token.slice(1);
|
|
4476
|
+
if (key in values) out[arg] = values[key];
|
|
4477
|
+
} else {
|
|
4478
|
+
out[arg] = token;
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
return out;
|
|
4482
|
+
}
|
|
4483
|
+
function parseContextBody(body) {
|
|
4484
|
+
if (body.structured !== void 0) return body.structured;
|
|
4485
|
+
if (body.text !== void 0) {
|
|
4486
|
+
try {
|
|
4487
|
+
return JSON.parse(body.text);
|
|
4488
|
+
} catch {
|
|
4489
|
+
return void 0;
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4492
|
+
return void 0;
|
|
4493
|
+
}
|
|
4494
|
+
async function readContextRecord(integrationManager2, connectorId, tool, args) {
|
|
4495
|
+
const read = await readPreview(integrationManager2, connectorId, tool, args);
|
|
4496
|
+
if (read.status === "ok") {
|
|
4497
|
+
const record = parseContextBody(read.body);
|
|
4498
|
+
if (record === void 0) return { status: "unavailable", reason: "connector_error" };
|
|
4499
|
+
return { status: "ok", record };
|
|
4500
|
+
}
|
|
4501
|
+
if (read.status === "not_found") return { status: "not_found" };
|
|
4502
|
+
return { status: "unavailable", reason: read.reason };
|
|
4503
|
+
}
|
|
4504
|
+
function parseExplicitSourceLink(raw) {
|
|
4505
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
4506
|
+
if (typeof raw !== "object" || Array.isArray(raw)) return "invalid";
|
|
4507
|
+
const o = raw;
|
|
4508
|
+
if (typeof o.connector !== "string" || !o.connector) return "invalid";
|
|
4509
|
+
if (typeof o.kind !== "string" || !o.kind) return "invalid";
|
|
4510
|
+
if (typeof o.id !== "string" || !o.id) return "invalid";
|
|
4511
|
+
return { connector: o.connector, kind: o.kind, id: o.id };
|
|
4512
|
+
}
|
|
4513
|
+
function resolveSourceLink(toolArgs, connectorId, sourceFromArg) {
|
|
4514
|
+
const explicit = parseExplicitSourceLink(toolArgs._source);
|
|
4515
|
+
if (explicit !== void 0) return explicit;
|
|
4516
|
+
if (sourceFromArg) {
|
|
4517
|
+
const argVal = toolArgs[sourceFromArg.arg];
|
|
4518
|
+
if (typeof argVal === "string" && argVal) {
|
|
4519
|
+
return { connector: connectorId, kind: sourceFromArg.kind, id: argVal };
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
return void 0;
|
|
4523
|
+
}
|
|
4524
|
+
async function readSourceOnce(integrationManager2, link, sourceConfig) {
|
|
4525
|
+
const args = substitute(sourceConfig.read.args, { id: link.id });
|
|
4526
|
+
const read = await readContextRecord(integrationManager2, link.connector, sourceConfig.read.tool, args);
|
|
4527
|
+
if (read.status === "not_found") return { status: "not_found" };
|
|
4528
|
+
if (read.status === "unavailable") return { status: "unreadable", reason: read.reason };
|
|
4529
|
+
const keys = [];
|
|
4530
|
+
if (sourceConfig.keys?.email) {
|
|
4531
|
+
const v = resolveFieldPath(read.record, sourceConfig.keys.email);
|
|
4532
|
+
if (typeof v === "string" && v) keys.push(...deriveIdentityKeysFromEmail(v));
|
|
4533
|
+
}
|
|
4534
|
+
return { record: read.record, keys };
|
|
4535
|
+
}
|
|
4536
|
+
async function resolveRequestContext(integrationManager2, toolArgs, actionConnectorId, sourceFromArg, configuredConnectorIds) {
|
|
4537
|
+
const link = resolveSourceLink(toolArgs, actionConnectorId, sourceFromArg);
|
|
4538
|
+
if (link === void 0) return { result: { status: "none" }, keys: [] };
|
|
4539
|
+
if (link === "invalid") {
|
|
4540
|
+
return { result: { status: "unreadable", reason: "malformed link" }, keys: [] };
|
|
4541
|
+
}
|
|
4542
|
+
if (!configuredConnectorIds.has(link.connector)) {
|
|
4543
|
+
return { result: { status: "unreadable", reason: "connector not installed" }, keys: [] };
|
|
4544
|
+
}
|
|
4545
|
+
const connectorManifest = getManifest(link.connector);
|
|
4546
|
+
if (!connectorManifest) {
|
|
4547
|
+
return { result: { status: "unreadable", reason: "connector not installed" }, keys: [] };
|
|
4548
|
+
}
|
|
4549
|
+
const sourceConfig = connectorManifest.context?.sources?.[link.kind];
|
|
4550
|
+
if (!sourceConfig) {
|
|
4551
|
+
return { result: { status: "unreadable", reason: `connector "${link.connector}" declares no "${link.kind}" source` }, keys: [] };
|
|
4552
|
+
}
|
|
4553
|
+
const primary = await readSourceOnce(integrationManager2, link, sourceConfig);
|
|
4554
|
+
if ("status" in primary) {
|
|
4555
|
+
if (primary.status === "not_found") return { result: { status: "not_found" }, keys: [] };
|
|
4556
|
+
return { result: { status: "unreadable", reason: primary.reason ?? "connector unreachable" }, keys: [] };
|
|
4557
|
+
}
|
|
4558
|
+
const implicit = toolArgs._source === void 0;
|
|
4559
|
+
const base = {
|
|
4560
|
+
connector: link.connector,
|
|
4561
|
+
kind: link.kind,
|
|
4562
|
+
implicit,
|
|
4563
|
+
...sourceConfig.title ? { title: asString(resolveFieldPath(primary.record, sourceConfig.title)) } : {},
|
|
4564
|
+
...sourceConfig.author ? { author: asString(resolveFieldPath(primary.record, sourceConfig.author)) } : {},
|
|
4565
|
+
...sourceConfig.at ? { at: asString(resolveFieldPath(primary.record, sourceConfig.at)) } : {},
|
|
4566
|
+
...sourceConfig.body ? { body: asBody(resolveFieldPath(primary.record, sourceConfig.body)) } : {}
|
|
4567
|
+
};
|
|
4568
|
+
const chain = [];
|
|
4569
|
+
if (sourceConfig.chain) {
|
|
4570
|
+
let currentId = asString(getByDottedPath(primary.record, sourceConfig.chain.field));
|
|
4571
|
+
let hops = 0;
|
|
4572
|
+
const seen = /* @__PURE__ */ new Set([link.id]);
|
|
4573
|
+
while (currentId && hops < sourceConfig.chain.max && !seen.has(currentId)) {
|
|
4574
|
+
seen.add(currentId);
|
|
4575
|
+
const hopLink = { connector: link.connector, kind: link.kind, id: currentId };
|
|
4576
|
+
const hop = await readSourceOnce(integrationManager2, hopLink, sourceConfig);
|
|
4577
|
+
hops++;
|
|
4578
|
+
if ("status" in hop) break;
|
|
4579
|
+
chain.push({
|
|
4580
|
+
...sourceConfig.author ? { author: asString(resolveFieldPath(hop.record, sourceConfig.author)) } : {},
|
|
4581
|
+
...sourceConfig.at ? { at: asString(resolveFieldPath(hop.record, sourceConfig.at)) } : {},
|
|
4582
|
+
...sourceConfig.title ? { title: asString(resolveFieldPath(hop.record, sourceConfig.title)) } : {},
|
|
4583
|
+
...sourceConfig.body ? { body: asBody(resolveFieldPath(hop.record, sourceConfig.body)) } : {}
|
|
4584
|
+
});
|
|
4585
|
+
currentId = sourceConfig.chain ? asString(getByDottedPath(hop.record, sourceConfig.chain.field)) : void 0;
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
return { result: { status: "ok", ...base, chain }, keys: primary.keys };
|
|
4589
|
+
}
|
|
4590
|
+
function asString(v) {
|
|
4591
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
4592
|
+
}
|
|
4593
|
+
var REQUEST_BODY_CAP = 2e4;
|
|
4594
|
+
function asBody(v) {
|
|
4595
|
+
const s = asString(v);
|
|
4596
|
+
return s === void 0 ? void 0 : s.length > REQUEST_BODY_CAP ? s.slice(0, REQUEST_BODY_CAP) : s;
|
|
4597
|
+
}
|
|
4598
|
+
async function resolveRecordKeyChain(integrationManager2, connectorId, manifest, recordKeyName, idValue, stepsLeft) {
|
|
4599
|
+
if (stepsLeft <= 0) return [];
|
|
4600
|
+
const recordKey = manifest.context?.recordKeys?.[recordKeyName];
|
|
4601
|
+
if (!recordKey) return [];
|
|
4602
|
+
const args = substitute(recordKey.read.args, { id: idValue });
|
|
4603
|
+
const read = await readContextRecord(integrationManager2, connectorId, recordKey.read.tool, args);
|
|
4604
|
+
if (read.status !== "ok") return [];
|
|
4605
|
+
const out = [];
|
|
4606
|
+
const emailField = recordKey.keys?.email;
|
|
4607
|
+
if (emailField) {
|
|
4608
|
+
const v = resolveFieldPath(read.record, emailField);
|
|
4609
|
+
if (typeof v === "string" && v) out.push(...deriveIdentityKeysFromEmail(v));
|
|
4610
|
+
}
|
|
4611
|
+
if (recordKey.next) {
|
|
4612
|
+
const nextId = asString(getByDottedPath(read.record, recordKey.next.field));
|
|
4613
|
+
if (nextId) {
|
|
4614
|
+
out.push(
|
|
4615
|
+
...await resolveRecordKeyChain(
|
|
4616
|
+
integrationManager2,
|
|
4617
|
+
connectorId,
|
|
4618
|
+
manifest,
|
|
4619
|
+
recordKey.next.recordKey,
|
|
4620
|
+
nextId,
|
|
4621
|
+
stepsLeft - 1
|
|
4622
|
+
)
|
|
4623
|
+
);
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
return out;
|
|
4627
|
+
}
|
|
4628
|
+
async function gatherContextKeysFromAction(integrationManager2, connectorId, contextKeys, toolArgs) {
|
|
4629
|
+
if (!contextKeys) return [];
|
|
4630
|
+
const manifest = getManifest(connectorId);
|
|
4631
|
+
const out = [];
|
|
4632
|
+
for (const [name, argName] of Object.entries(contextKeys)) {
|
|
4633
|
+
const raw = toolArgs[argName];
|
|
4634
|
+
if (raw === void 0 || raw === null) continue;
|
|
4635
|
+
if (name === "email") {
|
|
4636
|
+
out.push(...deriveIdentityKeysFromValue(raw));
|
|
4637
|
+
continue;
|
|
4638
|
+
}
|
|
4639
|
+
if (!manifest?.context?.recordKeys?.[name] || typeof raw !== "string" || !raw) continue;
|
|
4640
|
+
out.push(...await resolveRecordKeyChain(integrationManager2, connectorId, manifest, name, raw, MAX_RECORD_KEY_STEPS));
|
|
3893
4641
|
}
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
4642
|
+
return out;
|
|
4643
|
+
}
|
|
4644
|
+
function mapHistoryItem(raw, mapping) {
|
|
4645
|
+
return {
|
|
4646
|
+
// `kind` is a literal label, copied as-is — never a path (doc §8.1).
|
|
4647
|
+
...mapping.kind ? { kind: mapping.kind } : {},
|
|
4648
|
+
...mapping.title ? { title: asString(resolveFieldPath(raw, mapping.title)) } : {},
|
|
4649
|
+
...mapping.body ? { body: asBody(resolveFieldPath(raw, mapping.body)) } : {},
|
|
4650
|
+
...mapping.at ? { at: asString(resolveFieldPath(raw, mapping.at)) } : {},
|
|
4651
|
+
...mapping.author ? { author: asString(resolveFieldPath(raw, mapping.author)) } : {},
|
|
4652
|
+
...mapping.type ? { type: asString(resolveFieldPath(raw, mapping.type)) } : {}
|
|
4653
|
+
};
|
|
4654
|
+
}
|
|
4655
|
+
function withinWindow(at, cutoffMs) {
|
|
4656
|
+
if (!at) return true;
|
|
4657
|
+
const ms = Date.parse(at);
|
|
4658
|
+
if (Number.isNaN(ms)) return true;
|
|
4659
|
+
return ms >= cutoffMs;
|
|
4660
|
+
}
|
|
4661
|
+
function chooseHistoryKey(byKey, keys) {
|
|
4662
|
+
const order = Array.isArray(byKey) ? byKey : [byKey];
|
|
4663
|
+
for (const type of order) {
|
|
4664
|
+
const found = keys.find((k) => k.type === type);
|
|
4665
|
+
if (found) return found;
|
|
3899
4666
|
}
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
4667
|
+
return void 0;
|
|
4668
|
+
}
|
|
4669
|
+
function resolveHistoryArgsTemplate(template, keyType) {
|
|
4670
|
+
const out = {};
|
|
4671
|
+
for (const [argName, token] of Object.entries(template)) {
|
|
4672
|
+
out[argName === "$keyType" ? keyType : argName] = token;
|
|
3903
4673
|
}
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
4674
|
+
return out;
|
|
4675
|
+
}
|
|
4676
|
+
async function readConnectorHistory(integrationManager2, connectorId, history, keys, executionJournal) {
|
|
4677
|
+
const key = chooseHistoryKey(history.byKey, keys);
|
|
4678
|
+
if (!key) return { status: "no_match" };
|
|
4679
|
+
const sinceMs = Date.now() - history.maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
4680
|
+
const args = substitute(resolveHistoryArgsTemplate(history.read.args, key.type), {
|
|
4681
|
+
key: key.value,
|
|
4682
|
+
since: new Date(sinceMs).toISOString(),
|
|
4683
|
+
limit: history.limit
|
|
3913
4684
|
});
|
|
4685
|
+
const read = await readContextRecord(integrationManager2, connectorId, history.read.tool, args);
|
|
4686
|
+
if (read.status !== "ok") return { status: "unreachable" };
|
|
4687
|
+
const rows = Array.isArray(read.record) ? read.record : [];
|
|
4688
|
+
const items = [];
|
|
4689
|
+
for (const row of rows.slice(0, history.limit)) {
|
|
4690
|
+
const item = mapHistoryItem(row, history.item);
|
|
4691
|
+
if (!withinWindow(item.at, sinceMs)) continue;
|
|
4692
|
+
if (history.item.ticket) {
|
|
4693
|
+
const ticketId = asString(resolveFieldPath(row, history.item.ticket));
|
|
4694
|
+
if (ticketId) {
|
|
4695
|
+
const entry = executionJournal.get(ticketId);
|
|
4696
|
+
if (entry?.state === "done") {
|
|
4697
|
+
item.aiWritten = { at: entry.finishedAt ? entry.finishedAt * 1e3 : void 0 };
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4701
|
+
items.push(item);
|
|
4702
|
+
}
|
|
4703
|
+
return { status: "ok", match: { connectorId, matchedKeyType: key.type, matchedKeyValue: key.value, items } };
|
|
4704
|
+
}
|
|
4705
|
+
async function buildHistory(integrationManager2, keys, executionJournal, configuredConnectorIds) {
|
|
4706
|
+
if (keys.length === 0) return { status: "no_customer_link" };
|
|
4707
|
+
const connectors = getAllManifests().filter((m) => m.context?.history && configuredConnectorIds.has(m.id));
|
|
4708
|
+
if (connectors.length === 0) return { status: "no_match" };
|
|
4709
|
+
const matches = [];
|
|
4710
|
+
const unreachable = [];
|
|
4711
|
+
let anyMatchedType = false;
|
|
4712
|
+
for (const manifest of connectors) {
|
|
4713
|
+
const history = manifest.context.history;
|
|
4714
|
+
const outcome = await readConnectorHistory(integrationManager2, manifest.id, history, keys, executionJournal);
|
|
4715
|
+
if (outcome.status === "no_match") continue;
|
|
4716
|
+
anyMatchedType = true;
|
|
4717
|
+
if (outcome.status === "unreachable") {
|
|
4718
|
+
unreachable.push(manifest.id);
|
|
4719
|
+
continue;
|
|
4720
|
+
}
|
|
4721
|
+
matches.push(outcome.match);
|
|
4722
|
+
}
|
|
4723
|
+
if (!anyMatchedType) return { status: "no_match" };
|
|
4724
|
+
if (matches.length === 0 && unreachable.length > 0) return { status: "unreachable", connectorIds: unreachable };
|
|
4725
|
+
return { status: "ok", matches, unreachableConnectorIds: unreachable };
|
|
4726
|
+
}
|
|
4727
|
+
async function buildRequestContext(integrationManager2, executionJournal, actionConnectorId, toolArgs, actionGating, configuredConnectorIds) {
|
|
4728
|
+
const { result: request, keys: sourceKeys } = await resolveRequestContext(
|
|
4729
|
+
integrationManager2,
|
|
4730
|
+
toolArgs,
|
|
4731
|
+
actionConnectorId,
|
|
4732
|
+
actionGating?.sourceFromArg,
|
|
4733
|
+
configuredConnectorIds
|
|
4734
|
+
);
|
|
4735
|
+
const actionKeys = await gatherContextKeysFromAction(
|
|
4736
|
+
integrationManager2,
|
|
4737
|
+
actionConnectorId,
|
|
4738
|
+
actionGating?.contextKeys,
|
|
4739
|
+
toolArgs
|
|
4740
|
+
);
|
|
4741
|
+
const keys = dedupeKeys([...actionKeys, ...sourceKeys]);
|
|
4742
|
+
const history = await buildHistory(integrationManager2, keys, executionJournal, configuredConnectorIds);
|
|
4743
|
+
return { request, history };
|
|
3914
4744
|
}
|
|
3915
4745
|
|
|
3916
4746
|
// src/lib/ticket-verify.ts
|
|
@@ -4116,191 +4946,34 @@ function normalizeIncomingArgs(tool, args) {
|
|
|
4116
4946
|
return out ?? args;
|
|
4117
4947
|
}
|
|
4118
4948
|
|
|
4119
|
-
// src/lib/
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
return
|
|
4123
|
-
|
|
4124
|
-
);
|
|
4125
|
-
}
|
|
4126
|
-
function
|
|
4127
|
-
if (b.size === 0) return true;
|
|
4128
|
-
if (a.size === 0) return false;
|
|
4129
|
-
for (const x of a) if (!b.has(x)) return false;
|
|
4130
|
-
return true;
|
|
4131
|
-
}
|
|
4132
|
-
function atLeastAsSpecific(keys, a, b) {
|
|
4133
|
-
for (const k of keys) {
|
|
4134
|
-
if (!subsetOrEqual(tokenSet(a[k]), tokenSet(b[k]))) return false;
|
|
4135
|
-
}
|
|
4136
|
-
return true;
|
|
4137
|
-
}
|
|
4138
|
-
function strictlyMoreSpecific(keys, a, b) {
|
|
4139
|
-
return atLeastAsSpecific(keys, a, b) && !atLeastAsSpecific(keys, b, a);
|
|
4140
|
-
}
|
|
4141
|
-
function selectAuthorization(contextKeys, passers) {
|
|
4142
|
-
if (passers.length === 1) {
|
|
4143
|
-
return { chosen: passers[0], superseded: [], reason: "sole" };
|
|
4144
|
-
}
|
|
4145
|
-
const dominators = passers.filter(
|
|
4146
|
-
(a) => passers.every((b) => b === a || strictlyMoreSpecific(contextKeys, a.context, b.context))
|
|
4147
|
-
);
|
|
4148
|
-
if (dominators.length === 1) {
|
|
4149
|
-
const chosen2 = dominators[0];
|
|
4150
|
-
return { chosen: chosen2, superseded: passers.filter((p) => p !== chosen2), reason: "most-specific" };
|
|
4151
|
-
}
|
|
4152
|
-
const approvers = passers.filter((p) => p.requiresApproval);
|
|
4153
|
-
const pool = approvers.length > 0 ? approvers : passers;
|
|
4154
|
-
const chosen = pool.slice().sort((a, b) => a.id.localeCompare(b.id))[0];
|
|
4155
|
-
return {
|
|
4156
|
-
chosen,
|
|
4157
|
-
superseded: passers.filter((p) => p !== chosen),
|
|
4158
|
-
reason: approvers.length > 0 ? "fail-safe-approval" : "fail-safe-tiebreak"
|
|
4159
|
-
};
|
|
4160
|
-
}
|
|
4161
|
-
|
|
4162
|
-
// src/lib/read-gate.ts
|
|
4163
|
-
function boundsSatisfyReadGate(bounds, gate) {
|
|
4164
|
-
if (!gate.boundField) return true;
|
|
4165
|
-
const actual = bounds?.[gate.boundField];
|
|
4166
|
-
return actual !== void 0 && String(actual) === gate.requiredValue;
|
|
4167
|
-
}
|
|
4168
|
-
function readToolIsGoverned(g) {
|
|
4169
|
-
if (!g) return false;
|
|
4170
|
-
return Boolean(g.boundField) || g.read != null || g.readGovernance === "none";
|
|
4171
|
-
}
|
|
4172
|
-
function allowedResources(contexts, boundField) {
|
|
4949
|
+
// src/lib/numeric-execution.ts
|
|
4950
|
+
import { getProfile as getProfile2 } from "@hap/core";
|
|
4951
|
+
function parseNumericString(value) {
|
|
4952
|
+
if (!/^-?\d+(\.\d+)?$/.test(value)) return void 0;
|
|
4953
|
+
const parsed = Number(value);
|
|
4954
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
4955
|
+
}
|
|
4956
|
+
function declaredNumericFields(profile) {
|
|
4173
4957
|
const out = /* @__PURE__ */ new Set();
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
}
|
|
4177
|
-
return out;
|
|
4178
|
-
}
|
|
4179
|
-
function deniedResources(requested, allowed) {
|
|
4180
|
-
return requested.filter((r) => !allowed.has(r));
|
|
4181
|
-
}
|
|
4182
|
-
function filterItemsByResource(items, idPath, allowed) {
|
|
4183
|
-
return items.filter((it) => {
|
|
4184
|
-
const id = getByDottedPath(it, idPath);
|
|
4185
|
-
return typeof id === "string" && allowed.has(id);
|
|
4186
|
-
});
|
|
4187
|
-
}
|
|
4188
|
-
function hasBlockedValue(item, valuesPath, blocked) {
|
|
4189
|
-
const v = getByDottedPath(item, valuesPath);
|
|
4190
|
-
const arr = Array.isArray(v) ? v : v != null ? [v] : [];
|
|
4191
|
-
return arr.some((x) => typeof x === "string" && blocked.has(x));
|
|
4192
|
-
}
|
|
4193
|
-
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
4194
|
-
function parseMessageTimestamp(value) {
|
|
4195
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4196
|
-
if (typeof value === "string" && value.trim() !== "") {
|
|
4197
|
-
const n = Number(value);
|
|
4198
|
-
if (Number.isFinite(n)) return n;
|
|
4199
|
-
const iso = Date.parse(value);
|
|
4200
|
-
if (Number.isFinite(iso)) return iso;
|
|
4201
|
-
}
|
|
4202
|
-
return null;
|
|
4203
|
-
}
|
|
4204
|
-
function ageInDays(timestampMs, nowMs) {
|
|
4205
|
-
return Math.floor((nowMs - timestampMs) / MS_PER_DAY);
|
|
4206
|
-
}
|
|
4207
|
-
function maxReadAgeDays(boundsList, field) {
|
|
4208
|
-
let max = null;
|
|
4209
|
-
for (const b of boundsList) {
|
|
4210
|
-
const raw = b?.[field];
|
|
4211
|
-
const n = typeof raw === "number" ? raw : raw !== void 0 ? Number(raw) : NaN;
|
|
4212
|
-
if (Number.isFinite(n)) max = max === null ? n : Math.max(max, n);
|
|
4213
|
-
}
|
|
4214
|
-
return max;
|
|
4215
|
-
}
|
|
4216
|
-
function isOlderThanMaxAge(timestampMs, maxAgeDays, nowMs) {
|
|
4217
|
-
if (timestampMs === null) return true;
|
|
4218
|
-
return ageInDays(timestampMs, nowMs) > maxAgeDays;
|
|
4219
|
-
}
|
|
4220
|
-
var TRAILING_OPERATOR = /(?:^|\s)(?:OR|AND|NOT)$|[-+|&:]$/i;
|
|
4221
|
-
function parensBalanced(s) {
|
|
4222
|
-
let depth = 0;
|
|
4223
|
-
let inQuote = false;
|
|
4224
|
-
for (const c of s) {
|
|
4225
|
-
if (c === '"') {
|
|
4226
|
-
inQuote = !inQuote;
|
|
4227
|
-
continue;
|
|
4228
|
-
}
|
|
4229
|
-
if (inQuote) continue;
|
|
4230
|
-
if (c === "(") depth++;
|
|
4231
|
-
else if (c === ")" && --depth < 0) return false;
|
|
4232
|
-
}
|
|
4233
|
-
return !inQuote && depth === 0;
|
|
4234
|
-
}
|
|
4235
|
-
function composeReadQuery(agentQuery, clauses) {
|
|
4236
|
-
const injected = clauses.filter((c) => typeof c === "string" && c.trim() !== "").map((c) => c.trim());
|
|
4237
|
-
const base = (agentQuery ?? "").trim();
|
|
4238
|
-
if (base === "") return { ok: true, query: injected.join(" ") };
|
|
4239
|
-
if (!parensBalanced(base)) {
|
|
4240
|
-
return { ok: false, reason: "the requested search query has unbalanced parentheses" };
|
|
4241
|
-
}
|
|
4242
|
-
if (TRAILING_OPERATOR.test(base)) {
|
|
4243
|
-
return { ok: false, reason: "the requested search query ends in a dangling operator" };
|
|
4244
|
-
}
|
|
4245
|
-
if (injected.length === 0) return { ok: true, query: base };
|
|
4246
|
-
return { ok: true, query: `(${base}) ${injected.join(" ")}` };
|
|
4247
|
-
}
|
|
4248
|
-
function detectAgeConflict(agentQuery, conflictPattern, maxAgeDays) {
|
|
4249
|
-
if (!agentQuery || !conflictPattern || maxAgeDays === null) return null;
|
|
4250
|
-
let re;
|
|
4251
|
-
try {
|
|
4252
|
-
re = new RegExp(conflictPattern, "gi");
|
|
4253
|
-
} catch {
|
|
4254
|
-
return null;
|
|
4255
|
-
}
|
|
4256
|
-
let worst = null;
|
|
4257
|
-
for (const match of agentQuery.matchAll(re)) {
|
|
4258
|
-
const days = Number(match[1]);
|
|
4259
|
-
if (!Number.isFinite(days)) continue;
|
|
4260
|
-
if (days >= maxAgeDays && (worst === null || days > worst)) worst = days;
|
|
4261
|
-
}
|
|
4262
|
-
return worst;
|
|
4263
|
-
}
|
|
4264
|
-
function resolveAgeBoundField(boundsSchema, ageExecField) {
|
|
4265
|
-
const fields = boundsSchema?.fields;
|
|
4266
|
-
if (!fields) return null;
|
|
4958
|
+
const fields = profile?.executionContextSchema?.fields;
|
|
4959
|
+
if (!fields) return out;
|
|
4267
4960
|
for (const [name, def] of Object.entries(fields)) {
|
|
4268
|
-
if (def
|
|
4961
|
+
if (def.source === "declared" && def.constraint?.type === "number") out.add(name);
|
|
4269
4962
|
}
|
|
4270
|
-
return
|
|
4271
|
-
}
|
|
4272
|
-
function getByDottedPath(obj, path) {
|
|
4273
|
-
return path.split(".").reduce(
|
|
4274
|
-
(o, k) => o == null ? void 0 : o[k],
|
|
4275
|
-
obj
|
|
4276
|
-
);
|
|
4963
|
+
return out;
|
|
4277
4964
|
}
|
|
4278
|
-
function
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4965
|
+
function coerceDeclaredNumericFields(profileId, execution) {
|
|
4966
|
+
const fields = declaredNumericFields(getProfile2(profileId));
|
|
4967
|
+
for (const field of fields) {
|
|
4968
|
+
const value = execution[field];
|
|
4969
|
+
if (typeof value !== "string") continue;
|
|
4970
|
+
const parsed = parseNumericString(value);
|
|
4971
|
+
if (parsed !== void 0) execution[field] = parsed;
|
|
4282
4972
|
}
|
|
4283
|
-
return null;
|
|
4284
|
-
}
|
|
4285
|
-
function renderAgeConstraint(template, maxAgeDays, nowMs) {
|
|
4286
|
-
const floorIso = new Date(nowMs - maxAgeDays * MS_PER_DAY).toISOString();
|
|
4287
|
-
return template.replace(/\{days\}/g, String(maxAgeDays)).replace(/\{date\}/g, floorIso.slice(0, 10));
|
|
4288
|
-
}
|
|
4289
|
-
function ageFloorValue(maxAgeDays, nowMs, format = "iso") {
|
|
4290
|
-
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
4291
|
-
if (format === "epoch_ms") return floorMs;
|
|
4292
|
-
if (format === "epoch_s") return String(Math.floor(floorMs / 1e3));
|
|
4293
|
-
return new Date(floorMs).toISOString();
|
|
4294
|
-
}
|
|
4295
|
-
function clampAgeFloor(requested, maxAgeDays, nowMs, format = "iso") {
|
|
4296
|
-
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
4297
|
-
const asked = parseMessageTimestamp(requested);
|
|
4298
|
-
if (asked !== null && asked >= floorMs) return requested;
|
|
4299
|
-
return ageFloorValue(maxAgeDays, nowMs, format);
|
|
4300
4973
|
}
|
|
4301
4974
|
|
|
4302
4975
|
// src/lib/tool-proxy.ts
|
|
4303
|
-
import { getProfile as
|
|
4976
|
+
import { getProfile as getProfile3, ContentBindingError, boundActionTypes as boundActionTypes2 } from "@hap/core";
|
|
4304
4977
|
import { readFile } from "fs/promises";
|
|
4305
4978
|
import { extname } from "path";
|
|
4306
4979
|
import { randomUUID } from "crypto";
|
|
@@ -4372,7 +5045,7 @@ function profileMatches(profileId, shortName2) {
|
|
|
4372
5045
|
return profileId === shortName2 || profileId.includes("/" + shortName2 + "@") || profileId.endsWith("/" + shortName2);
|
|
4373
5046
|
}
|
|
4374
5047
|
function wireExecutionContext(profileId, execution) {
|
|
4375
|
-
const profile =
|
|
5048
|
+
const profile = getProfile3(profileId);
|
|
4376
5049
|
const allowed = /* @__PURE__ */ new Set(["action_type"]);
|
|
4377
5050
|
const fields = profile?.boundsSchema?.fields;
|
|
4378
5051
|
if (fields) {
|
|
@@ -4390,7 +5063,7 @@ function wireExecutionContext(profileId, execution) {
|
|
|
4390
5063
|
return out;
|
|
4391
5064
|
}
|
|
4392
5065
|
function countBoundsFor(profileId, actionType) {
|
|
4393
|
-
const fields =
|
|
5066
|
+
const fields = getProfile3(profileId)?.boundsSchema?.fields;
|
|
4394
5067
|
if (!fields) return null;
|
|
4395
5068
|
return Object.entries(fields).filter(([, def]) => def.boundType?.kind === "cumulative_count").filter(([name, def]) => {
|
|
4396
5069
|
const applies = boundActionTypes2(name, def);
|
|
@@ -4409,8 +5082,10 @@ function zeroCappedBound(profileId, bounds, actionType) {
|
|
|
4409
5082
|
}
|
|
4410
5083
|
function toolAlwaysListed(tool, isBuiltin) {
|
|
4411
5084
|
if (tool.internal || !tool.gating?.profile) return false;
|
|
4412
|
-
if (tool.gating.
|
|
4413
|
-
|
|
5085
|
+
if (tool.gating.category === "disabled") return false;
|
|
5086
|
+
if (isBuiltin(tool.integrationId)) return false;
|
|
5087
|
+
if (!tool.gating.hideUnlessAuthorized) return true;
|
|
5088
|
+
return Boolean(tool.gating.simulationOnly) && isSimulationMode();
|
|
4414
5089
|
}
|
|
4415
5090
|
function toolIsAuthorizedForDisplay(tool, matchingAuths) {
|
|
4416
5091
|
if (!tool.gating?.hideUnlessAuthorized) return true;
|
|
@@ -4463,7 +5138,7 @@ function recordBlocked(state2, tool, rec) {
|
|
|
4463
5138
|
function recordGatekeeperRefusal(state2, tool, refusals) {
|
|
4464
5139
|
if (refusals.length === 0) return;
|
|
4465
5140
|
const pick = refusals.find((r) => r.bound !== void 0 && r.actual !== void 0) ?? refusals[0];
|
|
4466
|
-
const scoped = pick.field ? Boolean(
|
|
5141
|
+
const scoped = pick.field ? Boolean(getProfile3(pick.profileId)?.scopeSchema?.fields?.[pick.field]) : false;
|
|
4467
5142
|
const asValueOrString = (v) => typeof v === "number" || typeof v === "string" ? v : void 0;
|
|
4468
5143
|
recordBlocked(state2, tool, {
|
|
4469
5144
|
kind: scoped ? "scope" : "bound",
|
|
@@ -4642,7 +5317,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4642
5317
|
);
|
|
4643
5318
|
}
|
|
4644
5319
|
}
|
|
4645
|
-
const readProfile = readAdapter ?
|
|
5320
|
+
const readProfile = readAdapter ? getProfile3(matchingAuths[0].profileId) : void 0;
|
|
4646
5321
|
const boundsSchema = readProfile?.boundsSchema;
|
|
4647
5322
|
const ageBoundField = readAdapter?.ageField ? resolveAgeBoundField(boundsSchema, readAdapter.ageField) : null;
|
|
4648
5323
|
const hasAgeDimension = Boolean(readAdapter?.ageField);
|
|
@@ -4787,6 +5462,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4787
5462
|
}
|
|
4788
5463
|
}
|
|
4789
5464
|
}
|
|
5465
|
+
coerceDeclaredNumericFields(profile, execution);
|
|
4790
5466
|
const declaredActionType = typeof execution.action_type === "string" && execution.action_type.length > 0 ? execution.action_type : void 0;
|
|
4791
5467
|
if (!declaredActionType) {
|
|
4792
5468
|
recordBlocked(state2, tool, {
|
|
@@ -4805,7 +5481,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4805
5481
|
};
|
|
4806
5482
|
}
|
|
4807
5483
|
{
|
|
4808
|
-
const profileDef =
|
|
5484
|
+
const profileDef = getProfile3(profile);
|
|
4809
5485
|
const registry = profileDef?.boundsSchema?.actionTypes;
|
|
4810
5486
|
if (Array.isArray(registry) && registry.length > 0 && !registry.includes(declaredActionType)) {
|
|
4811
5487
|
recordBlocked(state2, tool, {
|
|
@@ -4826,6 +5502,18 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4826
5502
|
}
|
|
4827
5503
|
}
|
|
4828
5504
|
const { matching: matchingAuths, refusals } = eligibleMandates();
|
|
5505
|
+
if (tool.gating?.simulationOnly && isSimulationMode() && !toolIsAuthorizedForDisplay(tool, matchingAuths)) {
|
|
5506
|
+
const actionType = staticExecution?.action_type;
|
|
5507
|
+
const mandateLabel = typeof actionType === "string" ? actionType : profile;
|
|
5508
|
+
recordBlocked(state2, tool, {
|
|
5509
|
+
kind: "not_authorized",
|
|
5510
|
+
detail: `No active authorization grants "${mandateLabel}" under profile "${profile}".`,
|
|
5511
|
+
field: mandateLabel,
|
|
5512
|
+
who: "gateway",
|
|
5513
|
+
code: "NO_MATCHING_MANDATE"
|
|
5514
|
+
});
|
|
5515
|
+
return { content: [{ type: "text", text: noMandateRefusal(tool.namespacedName, mandateLabel) }], isError: true };
|
|
5516
|
+
}
|
|
4829
5517
|
if (matchingAuths.length === 0) return noEligibleMandate(refusals);
|
|
4830
5518
|
const errors = [];
|
|
4831
5519
|
const passers = [];
|
|
@@ -4878,7 +5566,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4878
5566
|
isError: true
|
|
4879
5567
|
};
|
|
4880
5568
|
}
|
|
4881
|
-
const contextKeys =
|
|
5569
|
+
const contextKeys = getProfile3(passers[0].profileId)?.scopeSchema?.keyOrder ?? [];
|
|
4882
5570
|
return attemptWithCandidates(passers);
|
|
4883
5571
|
async function attemptWithCandidates(candidates) {
|
|
4884
5572
|
const selection = selectAuthorization(
|
|
@@ -5180,6 +5868,7 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.` + a
|
|
|
5180
5868
|
let outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, args, receiptId, auth.subjects?.[0]) : args;
|
|
5181
5869
|
if (receiptId) outgoingArgs = attachTicketId(tool, outgoingArgs, receiptId);
|
|
5182
5870
|
outgoingArgs = encodeOutgoingArgs(tool, outgoingArgs);
|
|
5871
|
+
outgoingArgs = stripSourceLink(outgoingArgs);
|
|
5183
5872
|
if (receiptId) {
|
|
5184
5873
|
const begun = state2.executionJournal.begin({
|
|
5185
5874
|
ticketId: receiptId,
|
|
@@ -5256,7 +5945,8 @@ function agentVisibleAuthorizations(authorizations, integrationManager2) {
|
|
|
5256
5945
|
// src/lib/agent-rules.ts
|
|
5257
5946
|
var AGENT_RULES = [
|
|
5258
5947
|
"Never open or operate the Suveren Gateway's web page \u2014 not in a browser you control, not with computer use. Never ask for, read or type the person's API key. Never approve a proposal: approvals are the person's alone.",
|
|
5259
|
-
"Use only the Suveren tools. No terminal, HTTP or browser calls to the gateway's ports (for example `curl localhost:3500/mcp`)."
|
|
5948
|
+
"Use only the Suveren tools. No terminal, HTTP or browser calls to the gateway's ports (for example `curl localhost:3500/mcp`).",
|
|
5949
|
+
"When a tool offers an optional `_source` argument, set it to link the ONE record (e.g. an inbound email) you are acting on \u2014 just the reference ({connector, kind, id}), never a summary or quote; the gateway reads the record itself for the approver."
|
|
5260
5950
|
];
|
|
5261
5951
|
|
|
5262
5952
|
// src/lib/mandate-brief.ts
|
|
@@ -5336,7 +6026,7 @@ function buildMandateBrief(opts) {
|
|
|
5336
6026
|
const parts = [];
|
|
5337
6027
|
if (boundsDesc) parts.push(boundsDesc);
|
|
5338
6028
|
if (executionLog) {
|
|
5339
|
-
const profile =
|
|
6029
|
+
const profile = getProfile4(auth.profileId) ?? getProfile4(shortName2);
|
|
5340
6030
|
const consumption = getConsumptionState(auth, executionLog, profile);
|
|
5341
6031
|
const compact = formatConsumptionCompact(consumption);
|
|
5342
6032
|
if (compact) parts.push(compact);
|
|
@@ -5374,7 +6064,7 @@ function buildMandateBrief(opts) {
|
|
|
5374
6064
|
}
|
|
5375
6065
|
|
|
5376
6066
|
// src/tools/authorizations.ts
|
|
5377
|
-
import { getProfile as
|
|
6067
|
+
import { getProfile as getProfile5 } from "@hap/core";
|
|
5378
6068
|
function shortProfileName2(profileId) {
|
|
5379
6069
|
const withoutVersion = profileId.replace(/@.*$/, "");
|
|
5380
6070
|
const parts = withoutVersion.split("/");
|
|
@@ -5492,7 +6182,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
5492
6182
|
} catch {
|
|
5493
6183
|
}
|
|
5494
6184
|
const shortName2 = shortProfileName2(auth.profileId);
|
|
5495
|
-
const profile =
|
|
6185
|
+
const profile = getProfile5(auth.profileId) ?? getProfile5(shortName2);
|
|
5496
6186
|
if (profile) {
|
|
5497
6187
|
const consumption = getConsumptionState(auth, state2.executionLog, profile);
|
|
5498
6188
|
const consumptionText = formatConsumptionFull(consumption);
|
|
@@ -5552,7 +6242,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
5552
6242
|
lines.push(" Mode: review (each action requires your approval)");
|
|
5553
6243
|
}
|
|
5554
6244
|
const shortName2 = shortProfileName2(auth.profileId);
|
|
5555
|
-
const profile =
|
|
6245
|
+
const profile = getProfile5(auth.profileId) ?? getProfile5(shortName2);
|
|
5556
6246
|
if (profile) {
|
|
5557
6247
|
const consumption = getConsumptionState(auth, state2.executionLog, profile);
|
|
5558
6248
|
const compact = formatConsumptionCompact(consumption);
|
|
@@ -6006,6 +6696,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
6006
6696
|
outgoingArgs = attachTicketId(discovered, outgoingArgs, receiptId);
|
|
6007
6697
|
outgoingArgs = encodeOutgoingArgs(discovered, outgoingArgs);
|
|
6008
6698
|
}
|
|
6699
|
+
outgoingArgs = stripSourceLink(outgoingArgs);
|
|
6009
6700
|
let result;
|
|
6010
6701
|
try {
|
|
6011
6702
|
result = await integrationManager2.callTool(integrationId, toolName, outgoingArgs);
|
|
@@ -7104,19 +7795,19 @@ var IntegrationManager = class {
|
|
|
7104
7795
|
const profileGating = config.toolGating ?? null;
|
|
7105
7796
|
const tools = (toolsResult.tools ?? []).map((tool) => {
|
|
7106
7797
|
const gating = this.resolveToolGating(config.profile, profileGating, tool.name);
|
|
7798
|
+
let inputSchema = withoutBlockedArgs(
|
|
7799
|
+
tool.inputSchema ?? {},
|
|
7800
|
+
gating?.blockedArgs
|
|
7801
|
+
);
|
|
7802
|
+
if (gating && gating.category !== "read" && gating.category !== "disabled") {
|
|
7803
|
+
inputSchema = withSourceLink(inputSchema);
|
|
7804
|
+
}
|
|
7107
7805
|
return {
|
|
7108
7806
|
originalName: tool.name,
|
|
7109
7807
|
namespacedName: `${config.id}__${tool.name}`,
|
|
7110
7808
|
integrationId: config.id,
|
|
7111
7809
|
description: tool.description ?? "",
|
|
7112
|
-
|
|
7113
|
-
// connector can offer a control-bypassing argument and we would pass it
|
|
7114
|
-
// straight on. Anything the manifest blocks is removed here, before the
|
|
7115
|
-
// agent ever sees it exists.
|
|
7116
|
-
inputSchema: withoutBlockedArgs(
|
|
7117
|
-
tool.inputSchema ?? {},
|
|
7118
|
-
gating?.blockedArgs
|
|
7119
|
-
),
|
|
7810
|
+
inputSchema,
|
|
7120
7811
|
outputSchema: tool.outputSchema,
|
|
7121
7812
|
gating
|
|
7122
7813
|
};
|
|
@@ -7493,6 +8184,7 @@ var IntegrationManager = class {
|
|
|
7493
8184
|
readGovernanceReason: ext.readGovernanceReason,
|
|
7494
8185
|
blockedArgs: ext.blockedArgs,
|
|
7495
8186
|
hideUnlessAuthorized: ext.hideUnlessAuthorized,
|
|
8187
|
+
simulationOnly: ext.simulationOnly,
|
|
7496
8188
|
approvalView: ext.approvalView
|
|
7497
8189
|
};
|
|
7498
8190
|
}
|
|
@@ -7509,7 +8201,10 @@ var IntegrationManager = class {
|
|
|
7509
8201
|
argEncoding: ext.argEncoding,
|
|
7510
8202
|
argNormalization: ext.argNormalization,
|
|
7511
8203
|
hideUnlessAuthorized: ext.hideUnlessAuthorized,
|
|
7512
|
-
|
|
8204
|
+
simulationOnly: ext.simulationOnly,
|
|
8205
|
+
approvalView: ext.approvalView,
|
|
8206
|
+
contextKeys: ext.contextKeys,
|
|
8207
|
+
sourceFromArg: ext.sourceFromArg
|
|
7513
8208
|
};
|
|
7514
8209
|
}
|
|
7515
8210
|
return {
|
|
@@ -7640,7 +8335,7 @@ async function buildTicketDetails(archive, ticketIds) {
|
|
|
7640
8335
|
}
|
|
7641
8336
|
|
|
7642
8337
|
// src/lib/report/window.ts
|
|
7643
|
-
import { getProfile as
|
|
8338
|
+
import { getProfile as getProfile6 } from "@hap/core";
|
|
7644
8339
|
var REPORT_AGE_FIELD = "read_age_days";
|
|
7645
8340
|
var REPORTING_PROFILE = "reporting";
|
|
7646
8341
|
var END_SKEW_SECONDS = 120;
|
|
@@ -7654,7 +8349,7 @@ function windowLabel(start, days, loadedAt) {
|
|
|
7654
8349
|
if (loadedAt !== null && loadedAt === start) return `${since} (when the test data was loaded)`;
|
|
7655
8350
|
return `${since} (the last ${days} ${days === 1 ? "day" : "days"})`;
|
|
7656
8351
|
}
|
|
7657
|
-
var defaultLookup = (id) =>
|
|
8352
|
+
var defaultLookup = (id) => getProfile6(id);
|
|
7658
8353
|
function reportingMandateRefusal(m, lookup = defaultLookup) {
|
|
7659
8354
|
const schema = lookup(m.profileId)?.boundsSchema;
|
|
7660
8355
|
const field = resolveAgeBoundField(schema, REPORT_AGE_FIELD);
|
|
@@ -8392,7 +9087,7 @@ function unifiedLineDiff(before, after) {
|
|
|
8392
9087
|
}
|
|
8393
9088
|
|
|
8394
9089
|
// src/lib/builtins/setup.ts
|
|
8395
|
-
import { getProfile as
|
|
9090
|
+
import { getProfile as getProfile7 } from "@hap/core";
|
|
8396
9091
|
var DELEGATION_PROFILE = "delegation";
|
|
8397
9092
|
var DELEGATION_INTENT_HINT = [
|
|
8398
9093
|
"Why \u2014 With test data only, I want to find out which routine parts of my work an AI could take over, so people gain time \u2014 before anything touches real systems.",
|
|
@@ -8444,7 +9139,7 @@ function delegationItselfRefusal(args) {
|
|
|
8444
9139
|
}
|
|
8445
9140
|
function delegationMandateRefusal(auth) {
|
|
8446
9141
|
if (isSimulationMode()) return void 0;
|
|
8447
|
-
const profile =
|
|
9142
|
+
const profile = getProfile7(auth.profileId);
|
|
8448
9143
|
if (profile?.scopeSchema?.fields?.allowed_profiles) return void 0;
|
|
8449
9144
|
return `your Delegation mandate (${shortName(auth.profileId)} ${versionOf(auth.profileId)}) does not declare which profiles or modes you may propose \u2014 only a Delegation mandate on delegation 0.4 or later works outside simulation mode. Ask the person to give a current Delegation mandate.`;
|
|
8450
9145
|
}
|
|
@@ -8502,7 +9197,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
|
|
|
8502
9197
|
);
|
|
8503
9198
|
const first = governing[0];
|
|
8504
9199
|
if (first) {
|
|
8505
|
-
const delegationProfile =
|
|
9200
|
+
const delegationProfile = getProfile7(first.profileId);
|
|
8506
9201
|
const entry = getConsumptionState(first, deps.state.executionLog, delegationProfile).find((e) => e.field === "mandate_daily_max");
|
|
8507
9202
|
if (entry && entry.limit !== null) {
|
|
8508
9203
|
checks.push(`Proposals today: ${entry.current} of ${entry.limit}`);
|
|
@@ -8512,7 +9207,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
|
|
|
8512
9207
|
}
|
|
8513
9208
|
function resolveTargetProfile(input) {
|
|
8514
9209
|
if (typeof input !== "string" || !input.trim()) return void 0;
|
|
8515
|
-
const profile =
|
|
9210
|
+
const profile = getProfile7(input.trim());
|
|
8516
9211
|
if (!profile) return void 0;
|
|
8517
9212
|
return { profile, shortName: shortName(profile.id) };
|
|
8518
9213
|
}
|
|
@@ -8663,7 +9358,7 @@ Read each topic with get_guide(topic) before doing that step.`);
|
|
|
8663
9358
|
return { ...builtinText(`Unknown topic "${topic}". Topics: ${guides.map((g) => g.topic).join(", ") || "none installed"}.`), isError: true };
|
|
8664
9359
|
}
|
|
8665
9360
|
const body = fillGenerated(guide.body.trim(), {
|
|
8666
|
-
limits: () => limitsSection(systems.map((s) => ({ id: s.id, profile:
|
|
9361
|
+
limits: () => limitsSection(systems.map((s) => ({ id: s.id, profile: getProfile7(s.profile) }))),
|
|
8667
9362
|
package: () => packageSection(
|
|
8668
9363
|
deps.integrationManager.getAllTools().find((t) => !deps.integrationManager.isBuiltin(t.integrationId) && t.originalName === "load_simulation")
|
|
8669
9364
|
)
|
|
@@ -9150,6 +9845,32 @@ app.post("/internal/preview", internalOnly, async (req, res) => {
|
|
|
9150
9845
|
});
|
|
9151
9846
|
res.json(result);
|
|
9152
9847
|
});
|
|
9848
|
+
app.post("/internal/request-context", internalOnly, async (req, res) => {
|
|
9849
|
+
const { tool, toolArgs } = req.body;
|
|
9850
|
+
if (!tool || typeof toolArgs !== "object" || toolArgs === null) {
|
|
9851
|
+
res.status(400).json({ error: "Missing tool or toolArgs" });
|
|
9852
|
+
return;
|
|
9853
|
+
}
|
|
9854
|
+
const sep = tool.indexOf("__");
|
|
9855
|
+
if (sep < 0) {
|
|
9856
|
+
res.status(400).json({ error: `Invalid tool name: ${tool}` });
|
|
9857
|
+
return;
|
|
9858
|
+
}
|
|
9859
|
+
const integrationId = tool.slice(0, sep);
|
|
9860
|
+
const toolName = tool.slice(sep + 2);
|
|
9861
|
+
const manifest = getManifest(integrationId);
|
|
9862
|
+
const actionGating = manifest?.toolGating?.overrides?.[toolName];
|
|
9863
|
+
const configuredConnectorIds = new Set(integrationRegistry.getAll().map((c) => c.id));
|
|
9864
|
+
const result = await buildRequestContext(
|
|
9865
|
+
integrationManager,
|
|
9866
|
+
state.executionJournal,
|
|
9867
|
+
integrationId,
|
|
9868
|
+
toolArgs,
|
|
9869
|
+
actionGating ?? void 0,
|
|
9870
|
+
configuredConnectorIds
|
|
9871
|
+
);
|
|
9872
|
+
res.json(result);
|
|
9873
|
+
});
|
|
9153
9874
|
app.post("/internal/add-integration", internalOnly, async (req, res) => {
|
|
9154
9875
|
try {
|
|
9155
9876
|
const config = req.body;
|