@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.
@@ -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 getProfile3 } from "@hap/core";
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
- 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();
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
- const current = read2.body.structured[preview.version.field];
3901
- if (typeof current !== "string" && typeof current !== "number") {
3902
- return ok();
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
- const stale = String(current) !== String(approved);
3905
- return ok({
3906
- version: {
3907
- field: preview.version.field,
3908
- approved,
3909
- current,
3910
- stale,
3911
- ...stale ? { currentBody: read2.body } : {}
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/scope-specificity.ts
4120
- function tokenSet(value) {
4121
- if (value === void 0 || value === null) return /* @__PURE__ */ new Set();
4122
- return new Set(
4123
- String(value).split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
4124
- );
4125
- }
4126
- function subsetOrEqual(a, b) {
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
- for (const ctx of contexts) {
4175
- for (const v of tokenSet(ctx?.[boundField])) out.add(v);
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?.boundType?.kind === "per_transaction" && def.boundType.of === ageExecField) return name;
4961
+ if (def.source === "declared" && def.constraint?.type === "number") out.add(name);
4269
4962
  }
4270
- return null;
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 firstParsableDate(obj, paths) {
4279
- for (const path of Array.isArray(paths) ? paths : [paths]) {
4280
- const parsed = parseMessageTimestamp(getByDottedPath(obj, path));
4281
- if (parsed !== null) return parsed;
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 getProfile2, ContentBindingError, boundActionTypes as boundActionTypes2 } from "@hap/core";
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 = getProfile2(profileId);
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 = getProfile2(profileId)?.boundsSchema?.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.hideUnlessAuthorized || tool.gating.category === "disabled") return false;
4413
- return !isBuiltin(tool.integrationId);
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(getProfile2(pick.profileId)?.scopeSchema?.fields?.[pick.field]) : false;
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 ? getProfile2(matchingAuths[0].profileId) : void 0;
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 = getProfile2(profile);
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 = getProfile2(passers[0].profileId)?.scopeSchema?.keyOrder ?? [];
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 = getProfile3(auth.profileId) ?? getProfile3(shortName2);
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 getProfile4 } from "@hap/core";
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 = getProfile4(auth.profileId) ?? getProfile4(shortName2);
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 = getProfile4(auth.profileId) ?? getProfile4(shortName2);
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
- // A downstream schema otherwise reaches the agent verbatim, so a
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
- approvalView: ext.approvalView
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 getProfile5 } from "@hap/core";
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) => getProfile5(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 getProfile6 } from "@hap/core";
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 = getProfile6(auth.profileId);
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 = getProfile6(first.profileId);
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 = getProfile6(input.trim());
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: getProfile6(s.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;