@suveren/gateway 0.28.1 → 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 +33 -1
- package/content/integrations/erp.json +16 -0
- package/content/integrations/gmail.json +14 -0
- package/content/integrations/mail.json +16 -1
- package/dist/control-plane/index.mjs +363 -195
- package/dist/mcp-server/http.mjs +926 -221
- 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) {
|
|
@@ -3891,26 +4122,625 @@ async function buildInternalPreview(integrationManager2, integrationId, toolName
|
|
|
3891
4122
|
if (typeof approved !== "string" && typeof approved !== "number") {
|
|
3892
4123
|
return ok();
|
|
3893
4124
|
}
|
|
3894
|
-
const { [preview.version.arg]: _omit, ...withoutVersion } = actionArgs;
|
|
3895
|
-
const mappedCurrentArgs = mapPreviewArgs(preview, withoutVersion);
|
|
3896
|
-
const read2 = await readPreview(integrationManager2, integrationId, preview.tool, mappedCurrentArgs);
|
|
3897
|
-
if (read2.status !== "ok" || read2.body.structured === void 0) {
|
|
3898
|
-
return ok();
|
|
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
|
+
}
|
|
3899
4587
|
}
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
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
|
+
}
|
|
3903
4625
|
}
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
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;
|
|
3912
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));
|
|
4641
|
+
}
|
|
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;
|
|
4666
|
+
}
|
|
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;
|
|
4673
|
+
}
|
|
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);
|
|
@@ -4465,7 +5138,7 @@ function recordBlocked(state2, tool, rec) {
|
|
|
4465
5138
|
function recordGatekeeperRefusal(state2, tool, refusals) {
|
|
4466
5139
|
if (refusals.length === 0) return;
|
|
4467
5140
|
const pick = refusals.find((r) => r.bound !== void 0 && r.actual !== void 0) ?? refusals[0];
|
|
4468
|
-
const scoped = pick.field ? Boolean(
|
|
5141
|
+
const scoped = pick.field ? Boolean(getProfile3(pick.profileId)?.scopeSchema?.fields?.[pick.field]) : false;
|
|
4469
5142
|
const asValueOrString = (v) => typeof v === "number" || typeof v === "string" ? v : void 0;
|
|
4470
5143
|
recordBlocked(state2, tool, {
|
|
4471
5144
|
kind: scoped ? "scope" : "bound",
|
|
@@ -4644,7 +5317,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4644
5317
|
);
|
|
4645
5318
|
}
|
|
4646
5319
|
}
|
|
4647
|
-
const readProfile = readAdapter ?
|
|
5320
|
+
const readProfile = readAdapter ? getProfile3(matchingAuths[0].profileId) : void 0;
|
|
4648
5321
|
const boundsSchema = readProfile?.boundsSchema;
|
|
4649
5322
|
const ageBoundField = readAdapter?.ageField ? resolveAgeBoundField(boundsSchema, readAdapter.ageField) : null;
|
|
4650
5323
|
const hasAgeDimension = Boolean(readAdapter?.ageField);
|
|
@@ -4789,6 +5462,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4789
5462
|
}
|
|
4790
5463
|
}
|
|
4791
5464
|
}
|
|
5465
|
+
coerceDeclaredNumericFields(profile, execution);
|
|
4792
5466
|
const declaredActionType = typeof execution.action_type === "string" && execution.action_type.length > 0 ? execution.action_type : void 0;
|
|
4793
5467
|
if (!declaredActionType) {
|
|
4794
5468
|
recordBlocked(state2, tool, {
|
|
@@ -4807,7 +5481,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4807
5481
|
};
|
|
4808
5482
|
}
|
|
4809
5483
|
{
|
|
4810
|
-
const profileDef =
|
|
5484
|
+
const profileDef = getProfile3(profile);
|
|
4811
5485
|
const registry = profileDef?.boundsSchema?.actionTypes;
|
|
4812
5486
|
if (Array.isArray(registry) && registry.length > 0 && !registry.includes(declaredActionType)) {
|
|
4813
5487
|
recordBlocked(state2, tool, {
|
|
@@ -4892,7 +5566,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4892
5566
|
isError: true
|
|
4893
5567
|
};
|
|
4894
5568
|
}
|
|
4895
|
-
const contextKeys =
|
|
5569
|
+
const contextKeys = getProfile3(passers[0].profileId)?.scopeSchema?.keyOrder ?? [];
|
|
4896
5570
|
return attemptWithCandidates(passers);
|
|
4897
5571
|
async function attemptWithCandidates(candidates) {
|
|
4898
5572
|
const selection = selectAuthorization(
|
|
@@ -5194,6 +5868,7 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.` + a
|
|
|
5194
5868
|
let outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, args, receiptId, auth.subjects?.[0]) : args;
|
|
5195
5869
|
if (receiptId) outgoingArgs = attachTicketId(tool, outgoingArgs, receiptId);
|
|
5196
5870
|
outgoingArgs = encodeOutgoingArgs(tool, outgoingArgs);
|
|
5871
|
+
outgoingArgs = stripSourceLink(outgoingArgs);
|
|
5197
5872
|
if (receiptId) {
|
|
5198
5873
|
const begun = state2.executionJournal.begin({
|
|
5199
5874
|
ticketId: receiptId,
|
|
@@ -5270,7 +5945,8 @@ function agentVisibleAuthorizations(authorizations, integrationManager2) {
|
|
|
5270
5945
|
// src/lib/agent-rules.ts
|
|
5271
5946
|
var AGENT_RULES = [
|
|
5272
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.",
|
|
5273
|
-
"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."
|
|
5274
5950
|
];
|
|
5275
5951
|
|
|
5276
5952
|
// src/lib/mandate-brief.ts
|
|
@@ -5350,7 +6026,7 @@ function buildMandateBrief(opts) {
|
|
|
5350
6026
|
const parts = [];
|
|
5351
6027
|
if (boundsDesc) parts.push(boundsDesc);
|
|
5352
6028
|
if (executionLog) {
|
|
5353
|
-
const profile =
|
|
6029
|
+
const profile = getProfile4(auth.profileId) ?? getProfile4(shortName2);
|
|
5354
6030
|
const consumption = getConsumptionState(auth, executionLog, profile);
|
|
5355
6031
|
const compact = formatConsumptionCompact(consumption);
|
|
5356
6032
|
if (compact) parts.push(compact);
|
|
@@ -5388,7 +6064,7 @@ function buildMandateBrief(opts) {
|
|
|
5388
6064
|
}
|
|
5389
6065
|
|
|
5390
6066
|
// src/tools/authorizations.ts
|
|
5391
|
-
import { getProfile as
|
|
6067
|
+
import { getProfile as getProfile5 } from "@hap/core";
|
|
5392
6068
|
function shortProfileName2(profileId) {
|
|
5393
6069
|
const withoutVersion = profileId.replace(/@.*$/, "");
|
|
5394
6070
|
const parts = withoutVersion.split("/");
|
|
@@ -5506,7 +6182,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
5506
6182
|
} catch {
|
|
5507
6183
|
}
|
|
5508
6184
|
const shortName2 = shortProfileName2(auth.profileId);
|
|
5509
|
-
const profile =
|
|
6185
|
+
const profile = getProfile5(auth.profileId) ?? getProfile5(shortName2);
|
|
5510
6186
|
if (profile) {
|
|
5511
6187
|
const consumption = getConsumptionState(auth, state2.executionLog, profile);
|
|
5512
6188
|
const consumptionText = formatConsumptionFull(consumption);
|
|
@@ -5566,7 +6242,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
5566
6242
|
lines.push(" Mode: review (each action requires your approval)");
|
|
5567
6243
|
}
|
|
5568
6244
|
const shortName2 = shortProfileName2(auth.profileId);
|
|
5569
|
-
const profile =
|
|
6245
|
+
const profile = getProfile5(auth.profileId) ?? getProfile5(shortName2);
|
|
5570
6246
|
if (profile) {
|
|
5571
6247
|
const consumption = getConsumptionState(auth, state2.executionLog, profile);
|
|
5572
6248
|
const compact = formatConsumptionCompact(consumption);
|
|
@@ -6020,6 +6696,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
6020
6696
|
outgoingArgs = attachTicketId(discovered, outgoingArgs, receiptId);
|
|
6021
6697
|
outgoingArgs = encodeOutgoingArgs(discovered, outgoingArgs);
|
|
6022
6698
|
}
|
|
6699
|
+
outgoingArgs = stripSourceLink(outgoingArgs);
|
|
6023
6700
|
let result;
|
|
6024
6701
|
try {
|
|
6025
6702
|
result = await integrationManager2.callTool(integrationId, toolName, outgoingArgs);
|
|
@@ -7118,19 +7795,19 @@ var IntegrationManager = class {
|
|
|
7118
7795
|
const profileGating = config.toolGating ?? null;
|
|
7119
7796
|
const tools = (toolsResult.tools ?? []).map((tool) => {
|
|
7120
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
|
+
}
|
|
7121
7805
|
return {
|
|
7122
7806
|
originalName: tool.name,
|
|
7123
7807
|
namespacedName: `${config.id}__${tool.name}`,
|
|
7124
7808
|
integrationId: config.id,
|
|
7125
7809
|
description: tool.description ?? "",
|
|
7126
|
-
|
|
7127
|
-
// connector can offer a control-bypassing argument and we would pass it
|
|
7128
|
-
// straight on. Anything the manifest blocks is removed here, before the
|
|
7129
|
-
// agent ever sees it exists.
|
|
7130
|
-
inputSchema: withoutBlockedArgs(
|
|
7131
|
-
tool.inputSchema ?? {},
|
|
7132
|
-
gating?.blockedArgs
|
|
7133
|
-
),
|
|
7810
|
+
inputSchema,
|
|
7134
7811
|
outputSchema: tool.outputSchema,
|
|
7135
7812
|
gating
|
|
7136
7813
|
};
|
|
@@ -7525,7 +8202,9 @@ var IntegrationManager = class {
|
|
|
7525
8202
|
argNormalization: ext.argNormalization,
|
|
7526
8203
|
hideUnlessAuthorized: ext.hideUnlessAuthorized,
|
|
7527
8204
|
simulationOnly: ext.simulationOnly,
|
|
7528
|
-
approvalView: ext.approvalView
|
|
8205
|
+
approvalView: ext.approvalView,
|
|
8206
|
+
contextKeys: ext.contextKeys,
|
|
8207
|
+
sourceFromArg: ext.sourceFromArg
|
|
7529
8208
|
};
|
|
7530
8209
|
}
|
|
7531
8210
|
return {
|
|
@@ -7656,7 +8335,7 @@ async function buildTicketDetails(archive, ticketIds) {
|
|
|
7656
8335
|
}
|
|
7657
8336
|
|
|
7658
8337
|
// src/lib/report/window.ts
|
|
7659
|
-
import { getProfile as
|
|
8338
|
+
import { getProfile as getProfile6 } from "@hap/core";
|
|
7660
8339
|
var REPORT_AGE_FIELD = "read_age_days";
|
|
7661
8340
|
var REPORTING_PROFILE = "reporting";
|
|
7662
8341
|
var END_SKEW_SECONDS = 120;
|
|
@@ -7670,7 +8349,7 @@ function windowLabel(start, days, loadedAt) {
|
|
|
7670
8349
|
if (loadedAt !== null && loadedAt === start) return `${since} (when the test data was loaded)`;
|
|
7671
8350
|
return `${since} (the last ${days} ${days === 1 ? "day" : "days"})`;
|
|
7672
8351
|
}
|
|
7673
|
-
var defaultLookup = (id) =>
|
|
8352
|
+
var defaultLookup = (id) => getProfile6(id);
|
|
7674
8353
|
function reportingMandateRefusal(m, lookup = defaultLookup) {
|
|
7675
8354
|
const schema = lookup(m.profileId)?.boundsSchema;
|
|
7676
8355
|
const field = resolveAgeBoundField(schema, REPORT_AGE_FIELD);
|
|
@@ -8408,7 +9087,7 @@ function unifiedLineDiff(before, after) {
|
|
|
8408
9087
|
}
|
|
8409
9088
|
|
|
8410
9089
|
// src/lib/builtins/setup.ts
|
|
8411
|
-
import { getProfile as
|
|
9090
|
+
import { getProfile as getProfile7 } from "@hap/core";
|
|
8412
9091
|
var DELEGATION_PROFILE = "delegation";
|
|
8413
9092
|
var DELEGATION_INTENT_HINT = [
|
|
8414
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.",
|
|
@@ -8460,7 +9139,7 @@ function delegationItselfRefusal(args) {
|
|
|
8460
9139
|
}
|
|
8461
9140
|
function delegationMandateRefusal(auth) {
|
|
8462
9141
|
if (isSimulationMode()) return void 0;
|
|
8463
|
-
const profile =
|
|
9142
|
+
const profile = getProfile7(auth.profileId);
|
|
8464
9143
|
if (profile?.scopeSchema?.fields?.allowed_profiles) return void 0;
|
|
8465
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.`;
|
|
8466
9145
|
}
|
|
@@ -8518,7 +9197,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
|
|
|
8518
9197
|
);
|
|
8519
9198
|
const first = governing[0];
|
|
8520
9199
|
if (first) {
|
|
8521
|
-
const delegationProfile =
|
|
9200
|
+
const delegationProfile = getProfile7(first.profileId);
|
|
8522
9201
|
const entry = getConsumptionState(first, deps.state.executionLog, delegationProfile).find((e) => e.field === "mandate_daily_max");
|
|
8523
9202
|
if (entry && entry.limit !== null) {
|
|
8524
9203
|
checks.push(`Proposals today: ${entry.current} of ${entry.limit}`);
|
|
@@ -8528,7 +9207,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
|
|
|
8528
9207
|
}
|
|
8529
9208
|
function resolveTargetProfile(input) {
|
|
8530
9209
|
if (typeof input !== "string" || !input.trim()) return void 0;
|
|
8531
|
-
const profile =
|
|
9210
|
+
const profile = getProfile7(input.trim());
|
|
8532
9211
|
if (!profile) return void 0;
|
|
8533
9212
|
return { profile, shortName: shortName(profile.id) };
|
|
8534
9213
|
}
|
|
@@ -8679,7 +9358,7 @@ Read each topic with get_guide(topic) before doing that step.`);
|
|
|
8679
9358
|
return { ...builtinText(`Unknown topic "${topic}". Topics: ${guides.map((g) => g.topic).join(", ") || "none installed"}.`), isError: true };
|
|
8680
9359
|
}
|
|
8681
9360
|
const body = fillGenerated(guide.body.trim(), {
|
|
8682
|
-
limits: () => limitsSection(systems.map((s) => ({ id: s.id, profile:
|
|
9361
|
+
limits: () => limitsSection(systems.map((s) => ({ id: s.id, profile: getProfile7(s.profile) }))),
|
|
8683
9362
|
package: () => packageSection(
|
|
8684
9363
|
deps.integrationManager.getAllTools().find((t) => !deps.integrationManager.isBuiltin(t.integrationId) && t.originalName === "load_simulation")
|
|
8685
9364
|
)
|
|
@@ -9166,6 +9845,32 @@ app.post("/internal/preview", internalOnly, async (req, res) => {
|
|
|
9166
9845
|
});
|
|
9167
9846
|
res.json(result);
|
|
9168
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
|
+
});
|
|
9169
9874
|
app.post("/internal/add-integration", internalOnly, async (req, res) => {
|
|
9170
9875
|
try {
|
|
9171
9876
|
const config = req.body;
|