@suveren/gateway 0.33.0 → 0.35.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.
@@ -3411,7 +3411,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3411
3411
  import { z as z2 } from "zod";
3412
3412
 
3413
3413
  // src/lib/mandate-brief.ts
3414
- import { getProfile as getProfile5 } from "@hap/core";
3414
+ import { getProfile as getProfile6 } from "@hap/core";
3415
3415
 
3416
3416
  // src/lib/consumption.ts
3417
3417
  import { boundActionTypes } from "@hap/core";
@@ -3647,67 +3647,17 @@ function appendVerificationFooter(tool, args, receiptId, subject) {
3647
3647
 
3648
3648
  // src/lib/content-binding.ts
3649
3649
  import {
3650
- computeContentHash,
3650
+ computeContentHash as computeContentHash2,
3651
3651
  selectBoundFields,
3652
3652
  isFieldBinding,
3653
3653
  bindingAppliesTo,
3654
- getProfile
3654
+ getProfile as getProfile3
3655
3655
  } from "@hap/core";
3656
- function getContentBinding(profileId) {
3657
- return getProfile(profileId)?.content_binding;
3658
- }
3659
- function computeContentBinding(profileId, tool, toolArgs, actionType) {
3660
- const binding = getContentBinding(profileId);
3661
- if (!binding) return void 0;
3662
- if (isFieldBinding(binding)) {
3663
- if (!bindingAppliesTo(binding, actionType)) {
3664
- if (!actionType && binding.appliesTo) {
3665
- console.error(
3666
- `[Suveren MCP] Warning: ${tool?.namespacedName ?? profileId} has no action_type in staticExecution, so the ${profileId} content binding (${binding.appliesTo.join(", ")}) does not apply and the receipt will bind no content. Fix the integration manifest.`
3667
- );
3668
- }
3669
- return void 0;
3670
- }
3671
- const bound = selectBoundFields(binding, toolArgs);
3672
- return {
3673
- contentHash: computeContentHash(binding, bound),
3674
- contentBinding: { version: binding.version, kind: binding.kind, fields: binding.fields },
3675
- boundContent: bound
3676
- };
3677
- }
3678
- let contentHash;
3679
- let boundContent;
3680
- if (binding.kind === "jcs") {
3681
- contentHash = computeContentHash(binding, toolArgs);
3682
- boundContent = toolArgs;
3683
- } else {
3684
- const declared = tool?.gating?.contentField;
3685
- const field = declared ?? (tool ? detectContentField(tool) : null);
3686
- if (!field) return void 0;
3687
- const raw = typeof toolArgs[field] === "string" ? toolArgs[field] : "";
3688
- if (raw === "") return void 0;
3689
- contentHash = computeContentHash(binding, raw);
3690
- boundContent = raw;
3691
- }
3692
- return {
3693
- contentHash,
3694
- contentBinding: { version: binding.version, kind: binding.kind },
3695
- boundContent
3696
- };
3697
- }
3698
- function attachTicketId(tool, args, ticketId) {
3699
- const schema = tool.inputSchema;
3700
- if (!schema?.properties || !("ticket_id" in schema.properties)) return args;
3701
- return { ...args, ticket_id: ticketId };
3702
- }
3703
-
3704
- // src/lib/preview.ts
3705
- import { computeContentHash as computeContentHash2 } from "@hap/core";
3706
3656
 
3707
3657
  // src/lib/manifest-loader.ts
3708
3658
  import { readFileSync as readFileSync16, existsSync as existsSync15 } from "fs";
3709
3659
  import { join as join15, resolve } from "path";
3710
- import { getProfile as getProfile2 } from "@hap/core";
3660
+ import { getProfile } from "@hap/core";
3711
3661
 
3712
3662
  // src/lib/tool-gating-types.ts
3713
3663
  var MAX_PREVIEW_READS = 10;
@@ -3765,6 +3715,7 @@ function boundsSatisfyReadGate(bounds, gate) {
3765
3715
  }
3766
3716
  function readToolIsGoverned(g) {
3767
3717
  if (!g) return false;
3718
+ if (g.internal === true) return true;
3768
3719
  return Boolean(g.boundField) || g.read != null || g.readGovernance === "none";
3769
3720
  }
3770
3721
  function allowedResources(contexts, boundField) {
@@ -3888,6 +3839,7 @@ function uncoveredReadSwitchReason(overrides, boundsSchema) {
3888
3839
  if (entry !== null && typeof entry !== "object") continue;
3889
3840
  const e = entry ?? {};
3890
3841
  if (entry !== null && e.category !== "read") continue;
3842
+ if (e.internal === true) continue;
3891
3843
  if (e.readGovernance === "none") {
3892
3844
  if (!e.readGovernanceReason) {
3893
3845
  return `override "${toolName}" declares readGovernance:"none" with no readGovernanceReason \u2014 an exemption from this profile's switch(es) [${anySwitch.join(", ")}] needs a written reason`;
@@ -3994,6 +3946,9 @@ function invalidReadConfigReason(toolName, idx, entry) {
3994
3946
  }
3995
3947
  }
3996
3948
  }
3949
+ if (p.kind !== void 0 && p.kind !== "document") {
3950
+ return `${label}.kind must be "document" (the only recognized value)`;
3951
+ }
3997
3952
  return null;
3998
3953
  }
3999
3954
  function invalidPreviewReason(toolName, entry) {
@@ -4047,6 +4002,9 @@ function invalidDisplayHintsReason(toolName, entry) {
4047
4002
  }
4048
4003
  }
4049
4004
  }
4005
+ if (d.aiText !== void 0 && (typeof d.aiText !== "string" || d.aiText.length === 0)) {
4006
+ return `override "${toolName}".display.aiText must be a non-empty string naming an argument`;
4007
+ }
4050
4008
  return null;
4051
4009
  }
4052
4010
  var SUMMARY_FORMATS = ["money", "percent", "list", "short", "date"];
@@ -4083,6 +4041,21 @@ function invalidSummaryReason(toolName, entry) {
4083
4041
  }
4084
4042
  return null;
4085
4043
  }
4044
+ function invalidInternalReason(toolName, entry) {
4045
+ if (entry === null || typeof entry !== "object") return null;
4046
+ const internal = entry.internal;
4047
+ if (internal === void 0) return null;
4048
+ if (typeof internal !== "boolean") return `override "${toolName}".internal must be a boolean`;
4049
+ return null;
4050
+ }
4051
+ function invalidManifestInternalReason(manifest) {
4052
+ const overrides = manifest.toolGating?.overrides ?? {};
4053
+ for (const [toolName, entry] of Object.entries(overrides)) {
4054
+ const reason = invalidInternalReason(toolName, entry);
4055
+ if (reason) return reason;
4056
+ }
4057
+ return null;
4058
+ }
4086
4059
  function invalidManifestPreviewReason(manifest) {
4087
4060
  const overrides = manifest.toolGating?.overrides ?? {};
4088
4061
  for (const [toolName, entry] of Object.entries(overrides)) {
@@ -4316,11 +4289,131 @@ function invalidManifestContextReason(manifest) {
4316
4289
  }
4317
4290
  return null;
4318
4291
  }
4319
- function invalidManifestReadGateReason(manifest) {
4320
- const profileDef = getProfile2(manifest.profile);
4321
- if (!profileDef) return null;
4292
+ function invalidDocumentSourceReason(kind, entry, overrides) {
4293
+ const label = `documents.${kind}`;
4294
+ const readReason = invalidContextReadReason(label, entry);
4295
+ if (readReason) return readReason;
4296
+ const tool = entry.tool;
4297
+ const toolEntry = overrides[tool];
4298
+ if (toolEntry?.internal !== true) {
4299
+ return `${label}.tool "${tool}" must be marked "internal: true" on this manifest's toolGating.overrides \u2014 a document read must never be directly callable by the AI`;
4300
+ }
4301
+ const docLabel = entry.label;
4302
+ if (docLabel !== void 0 && (typeof docLabel !== "string" || docLabel.length === 0)) {
4303
+ return `${label}.label must be a non-empty string`;
4304
+ }
4305
+ return null;
4306
+ }
4307
+ function invalidAttachmentTransportReason(entry) {
4308
+ const label = "attachments";
4309
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return `${label} must be an object`;
4310
+ const a = entry;
4311
+ if (a.transport !== "field" && a.transport !== "mime-raw") {
4312
+ return `${label}.transport must be "field" or "mime-raw"`;
4313
+ }
4314
+ if (!isNonEmptyString(a.arg)) return `${label}.arg must be a non-empty string`;
4315
+ return null;
4316
+ }
4317
+ function invalidInsertValueSourceReason(kind, entry) {
4318
+ const label = `values.${kind}`;
4319
+ const readReason = invalidContextReadReason(label, entry);
4320
+ if (readReason) return readReason;
4321
+ const v = entry;
4322
+ if (!isNonEmptyString(v.path)) return `${label}.path must be a non-empty string`;
4323
+ if (!Array.isArray(v.allowedHosts) || v.allowedHosts.length === 0) {
4324
+ return `${label}.allowedHosts must be a non-empty array of hostnames`;
4325
+ }
4326
+ for (const [i, h] of v.allowedHosts.entries()) {
4327
+ if (typeof h !== "string" || h.length === 0) return `${label}.allowedHosts[${i}] must be a non-empty string`;
4328
+ }
4329
+ return null;
4330
+ }
4331
+ function invalidManifestDocumentsReason(manifest) {
4332
+ if (!manifest.documents) return null;
4322
4333
  const overrides = manifest.toolGating?.overrides ?? {};
4323
- return uncoveredReadSwitchReason(overrides, profileDef.boundsSchema);
4334
+ for (const [kind, entry] of Object.entries(manifest.documents)) {
4335
+ const reason = invalidDocumentSourceReason(kind, entry, overrides);
4336
+ if (reason) return reason;
4337
+ }
4338
+ return null;
4339
+ }
4340
+ function invalidManifestAttachmentsReason(manifest) {
4341
+ if (manifest.attachments !== void 0) {
4342
+ const reason = invalidAttachmentTransportReason(manifest.attachments);
4343
+ if (reason) return reason;
4344
+ }
4345
+ const overrides = manifest.toolGating?.overrides ?? {};
4346
+ for (const [toolName, entry] of Object.entries(overrides)) {
4347
+ if (entry === null || typeof entry !== "object") continue;
4348
+ const e = entry;
4349
+ if (e.attachmentsArg !== void 0) {
4350
+ if (typeof e.attachmentsArg !== "string" || e.attachmentsArg.length === 0) {
4351
+ return `override "${toolName}".attachmentsArg must be a non-empty string`;
4352
+ }
4353
+ if (!manifest.attachments) {
4354
+ return `override "${toolName}" declares "attachmentsArg" but this manifest declares no top-level "attachments" transport`;
4355
+ }
4356
+ }
4357
+ if (e.insertFields !== void 0) {
4358
+ if (!Array.isArray(e.insertFields) || e.insertFields.length === 0) {
4359
+ return `override "${toolName}".insertFields must be a non-empty array of argument names`;
4360
+ }
4361
+ for (const [i, f] of e.insertFields.entries()) {
4362
+ if (typeof f !== "string" || f.length === 0) {
4363
+ return `override "${toolName}".insertFields[${i}] must be a non-empty string`;
4364
+ }
4365
+ }
4366
+ }
4367
+ }
4368
+ return null;
4369
+ }
4370
+ function invalidManifestValuesReason(manifest) {
4371
+ if (!manifest.values) return null;
4372
+ for (const [kind, entry] of Object.entries(manifest.values)) {
4373
+ const reason = invalidInsertValueSourceReason(kind, entry);
4374
+ if (reason) return reason;
4375
+ }
4376
+ return null;
4377
+ }
4378
+ function overrideProfile(entry) {
4379
+ if (entry === null || typeof entry !== "object") return void 0;
4380
+ const p = entry.profile;
4381
+ return typeof p === "string" ? p : void 0;
4382
+ }
4383
+ function distinctProfilesOf(manifest) {
4384
+ const out = [manifest.profile];
4385
+ for (const entry of Object.values(manifest.toolGating?.overrides ?? {})) {
4386
+ const p = overrideProfile(entry);
4387
+ if (p && !out.includes(p)) out.push(p);
4388
+ }
4389
+ return out;
4390
+ }
4391
+ function invalidManifestOverrideProfilesReason(manifest) {
4392
+ const overrides = manifest.toolGating?.overrides ?? {};
4393
+ for (const [toolName, entry] of Object.entries(overrides)) {
4394
+ const profile = overrideProfile(entry);
4395
+ if (!profile) continue;
4396
+ if (!getProfile(manifest.profile) && !getProfile(profile)) continue;
4397
+ if (!getProfile(profile)) {
4398
+ return `override "${toolName}".profile "${profile}" does not resolve to a known profile (full id or short name) \u2014 area-per-tool overrides must name a real profile, same as the manifest's own "profile"`;
4399
+ }
4400
+ }
4401
+ return null;
4402
+ }
4403
+ function invalidManifestReadGateReason(manifest) {
4404
+ const allOverrides = manifest.toolGating?.overrides ?? {};
4405
+ for (const profileId of distinctProfilesOf(manifest)) {
4406
+ const profileDef = getProfile(profileId);
4407
+ if (!profileDef) continue;
4408
+ const ownOverrides = {};
4409
+ for (const [toolName, entry] of Object.entries(allOverrides)) {
4410
+ const resolved = overrideProfile(entry) ?? manifest.profile;
4411
+ if (resolved === profileId) ownOverrides[toolName] = entry;
4412
+ }
4413
+ const reason = uncoveredReadSwitchReason(ownOverrides, profileDef.boundsSchema);
4414
+ if (reason) return reason;
4415
+ }
4416
+ return null;
4324
4417
  }
4325
4418
  var manifests = /* @__PURE__ */ new Map();
4326
4419
  function loadManifests(integrationsDir) {
@@ -4354,16 +4447,41 @@ function loadManifests(integrationsDir) {
4354
4447
  console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${previewReason}`);
4355
4448
  continue;
4356
4449
  }
4450
+ const internalReason = invalidManifestInternalReason(manifest);
4451
+ if (internalReason) {
4452
+ console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${internalReason}`);
4453
+ continue;
4454
+ }
4357
4455
  const contextReason = invalidManifestContextReason(manifest);
4358
4456
  if (contextReason) {
4359
4457
  console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${contextReason}`);
4360
4458
  continue;
4361
4459
  }
4460
+ const overrideProfilesReason = invalidManifestOverrideProfilesReason(manifest);
4461
+ if (overrideProfilesReason) {
4462
+ console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${overrideProfilesReason}`);
4463
+ continue;
4464
+ }
4362
4465
  const readGateReason = invalidManifestReadGateReason(manifest);
4363
4466
  if (readGateReason) {
4364
4467
  console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${readGateReason}`);
4365
4468
  continue;
4366
4469
  }
4470
+ const documentsReason = invalidManifestDocumentsReason(manifest);
4471
+ if (documentsReason) {
4472
+ console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${documentsReason}`);
4473
+ continue;
4474
+ }
4475
+ const attachmentsReason = invalidManifestAttachmentsReason(manifest);
4476
+ if (attachmentsReason) {
4477
+ console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${attachmentsReason}`);
4478
+ continue;
4479
+ }
4480
+ const valuesReason = invalidManifestValuesReason(manifest);
4481
+ if (valuesReason) {
4482
+ console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${valuesReason}`);
4483
+ continue;
4484
+ }
4367
4485
  manifests.set(id, manifest);
4368
4486
  loaded++;
4369
4487
  } catch (err) {
@@ -4380,7 +4498,138 @@ function getAllManifests() {
4380
4498
  return Array.from(manifests.values());
4381
4499
  }
4382
4500
 
4501
+ // src/lib/attachment-resolution.ts
4502
+ import { getProfile as getProfile2 } from "@hap/core";
4503
+
4504
+ // src/lib/attachment-reference.ts
4505
+ var REF_KEYS = ["connector", "kind", "id"];
4506
+ var INSERT_KEYS = ["placeholder", "connector", "kind", "id"];
4507
+ var ATTACHMENT_REFERENCE_SCHEMA = {
4508
+ type: "object",
4509
+ description: "A reference to a document already known to a connector \u2014 never file bytes. The gateway fetches, shows, and seals the file; it is never carried here.",
4510
+ properties: {
4511
+ connector: { type: "string", description: 'The integration id that can provide this document (e.g. "erp").' },
4512
+ kind: { type: "string", description: 'The document kind that connector declares (e.g. "invoice-pdf").' },
4513
+ id: { type: "string", description: "The id of the specific record (e.g. the invoice id)." }
4514
+ },
4515
+ required: ["connector", "kind", "id"],
4516
+ additionalProperties: false
4517
+ };
4518
+ var INSERT_REFERENCE_SCHEMA = {
4519
+ type: "object",
4520
+ description: "A value read from another system to insert into this text \u2014 never typed here. The gateway reads it, checks its source, and replaces the placeholder before sending.",
4521
+ properties: {
4522
+ placeholder: { type: "string", description: "The exact {{placeholder}} token in the text to replace." },
4523
+ connector: { type: "string", description: 'The integration id that can provide this value (e.g. "mollie").' },
4524
+ kind: { type: "string", description: 'The value kind that connector declares (e.g. "payment-link").' },
4525
+ id: { type: "string", description: "The id of the specific record (e.g. the payment link id)." }
4526
+ },
4527
+ required: ["placeholder", "connector", "kind", "id"],
4528
+ additionalProperties: false
4529
+ };
4530
+ function withAttachmentAndInsertSchema(schema, gating) {
4531
+ if (!gating?.attachmentsArg && !gating?.insertFields?.length) return schema;
4532
+ const props = { ...schema.properties ?? {} };
4533
+ if (gating.attachmentsArg) {
4534
+ props[gating.attachmentsArg] = {
4535
+ type: "array",
4536
+ description: "Attachments by reference only \u2014 see items.",
4537
+ items: ATTACHMENT_REFERENCE_SCHEMA
4538
+ };
4539
+ }
4540
+ if (gating.insertFields?.length) {
4541
+ props.insert = {
4542
+ type: "array",
4543
+ description: "Values to insert into this call\u2019s text by reference only \u2014 see items.",
4544
+ items: INSERT_REFERENCE_SCHEMA
4545
+ };
4546
+ }
4547
+ return { ...schema, properties: props };
4548
+ }
4549
+ function validateOne(value, index, keys) {
4550
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
4551
+ return {
4552
+ ok: false,
4553
+ error: { code: "NOT_AN_OBJECT", index, message: `item ${index} must be an object with exactly ${keys.join(", ")}` }
4554
+ };
4555
+ }
4556
+ const obj = value;
4557
+ const extra = Object.keys(obj).filter((k) => !keys.includes(k));
4558
+ if (extra.length > 0) {
4559
+ return {
4560
+ ok: false,
4561
+ error: {
4562
+ code: "EXTRA_FIELD",
4563
+ index,
4564
+ field: extra[0],
4565
+ message: `item ${index} carries "${extra[0]}", which this reference does not accept. Only ${keys.join(", ")} are allowed \u2014 a reference never carries file bytes or a typed value.`
4566
+ }
4567
+ };
4568
+ }
4569
+ for (const k of keys) {
4570
+ if (typeof obj[k] !== "string" || obj[k].length === 0) {
4571
+ return {
4572
+ ok: false,
4573
+ error: { code: "MISSING_FIELD", index, field: k, message: `item ${index}.${k} must be a non-empty string` }
4574
+ };
4575
+ }
4576
+ }
4577
+ return { ok: true, value: obj };
4578
+ }
4579
+ function validateDocumentReferences(value) {
4580
+ if (value === void 0) return { ok: true, refs: [] };
4581
+ if (!Array.isArray(value)) {
4582
+ return { ok: false, error: { code: "NOT_AN_ARRAY", message: "attachments must be an array of references" } };
4583
+ }
4584
+ const refs = [];
4585
+ for (let i = 0; i < value.length; i++) {
4586
+ const r = validateOne(value[i], i, REF_KEYS);
4587
+ if (!r.ok) return r;
4588
+ refs.push(r.value);
4589
+ }
4590
+ return { ok: true, refs };
4591
+ }
4592
+ function validateInsertReferences(value) {
4593
+ if (value === void 0) return { ok: true, refs: [] };
4594
+ if (!Array.isArray(value)) {
4595
+ return { ok: false, error: { code: "NOT_AN_ARRAY", message: "insert must be an array of references" } };
4596
+ }
4597
+ const refs = [];
4598
+ for (let i = 0; i < value.length; i++) {
4599
+ const r = validateOne(value[i], i, INSERT_KEYS);
4600
+ if (!r.ok) return r;
4601
+ refs.push(r.value);
4602
+ }
4603
+ return { ok: true, refs };
4604
+ }
4605
+
4606
+ // src/lib/attachment-policy.ts
4607
+ var ATTACHMENT_SOURCES_FIELD = "allowed_attachment_sources";
4608
+ function attachmentSourcesFieldDeclared(profile) {
4609
+ return !!profile?.scopeSchema?.fields?.[ATTACHMENT_SOURCES_FIELD];
4610
+ }
4611
+ function isAttachmentSourceAllowed(profileId, profile, allowedSourcesValue, ref) {
4612
+ if (!attachmentSourcesFieldDeclared(profile)) {
4613
+ return {
4614
+ allowed: false,
4615
+ reason: `Profile "${profileId}" does not yet declare "${ATTACHMENT_SOURCES_FIELD}" \u2014 no mandate can authorize an attachment until it does. Refusing by default (suveren-invoicing-plan.md \xA76).`
4616
+ };
4617
+ }
4618
+ const allowedSet = new Set(
4619
+ (allowedSourcesValue ?? "").split(",").map((s) => s.trim()).filter(Boolean)
4620
+ );
4621
+ const key = `${ref.connector}:${ref.kind}`;
4622
+ if (!allowedSet.has(key)) {
4623
+ return {
4624
+ allowed: false,
4625
+ reason: `"${key}" is not in this mandate's "${ATTACHMENT_SOURCES_FIELD}" (${allowedSourcesValue || "(none)"}).`
4626
+ };
4627
+ }
4628
+ return { allowed: true, reason: "" };
4629
+ }
4630
+
4383
4631
  // src/lib/preview.ts
4632
+ import { computeContentHash } from "@hap/core";
4384
4633
  var PREVIEW_READ_TIMEOUT_MS = 5e3;
4385
4634
  var PREVIEW_MESSAGE_CAP = 500;
4386
4635
  function capText(text, max = PREVIEW_MESSAGE_CAP) {
@@ -4457,9 +4706,9 @@ async function readPreview(integrationManager2, integrationId, previewTool, args
4457
4706
  }
4458
4707
  function hashPreviewBody(body) {
4459
4708
  if (body.structured !== void 0) {
4460
- return computeContentHash2({ kind: "jcs", version: "1" }, body.structured);
4709
+ return computeContentHash({ kind: "jcs", version: "1" }, body.structured);
4461
4710
  }
4462
- return computeContentHash2({ kind: "text", version: "1" }, body.text ?? "");
4711
+ return computeContentHash({ kind: "text", version: "1" }, body.text ?? "");
4463
4712
  }
4464
4713
  function isPlainRecord(v) {
4465
4714
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -4472,6 +4721,7 @@ function planPreviewReads(declaration, actionArgs) {
4472
4721
  attach: config.attach,
4473
4722
  fields: config.fields,
4474
4723
  version: config.version,
4724
+ kind: config.kind,
4475
4725
  argsMapping: config.args
4476
4726
  };
4477
4727
  if (config.forEach) {
@@ -4498,7 +4748,8 @@ async function runPreviewReadPlan(integrationManager2, integrationId, plan) {
4498
4748
  tool: item.tool,
4499
4749
  ...item.attach ? { attach: item.attach } : {},
4500
4750
  ...sourceArgNames.length === 1 ? { sourceArg: sourceArgNames[0] } : {},
4501
- ...item.fields ? { fields: item.fields } : {}
4751
+ ...item.fields ? { fields: item.fields } : {},
4752
+ ...item.kind ? { kind: item.kind } : {}
4502
4753
  };
4503
4754
  if (item.elements !== void 0) {
4504
4755
  const elements = [];
@@ -4597,7 +4848,7 @@ async function combinedPreviewReadsHash(integrationManager2, integrationId, read
4597
4848
  hashes.push(hashPreviewBody(read.body));
4598
4849
  }
4599
4850
  if (hashes.length === 0) return void 0;
4600
- return computeContentHash2({ kind: "jcs", version: "1" }, { reads: hashes });
4851
+ return computeContentHash({ kind: "jcs", version: "1" }, { reads: hashes });
4601
4852
  }
4602
4853
  async function buildInternalPreview(integrationManager2, integrationId, toolName, actionArgs) {
4603
4854
  const preview = getToolPreviewConfig(integrationManager2, integrationId, toolName);
@@ -4647,6 +4898,20 @@ async function buildInternalPreview(integrationManager2, integrationId, toolName
4647
4898
  }
4648
4899
  });
4649
4900
  }
4901
+ function structuredOrJsonText(body) {
4902
+ if (body.structured && typeof body.structured === "object") return body.structured;
4903
+ const t = body.text?.trim();
4904
+ if (!t || !t.startsWith("{")) return void 0;
4905
+ try {
4906
+ const parsed = JSON.parse(t);
4907
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
4908
+ } catch {
4909
+ return void 0;
4910
+ }
4911
+ }
4912
+
4913
+ // src/lib/document-source.ts
4914
+ import { createHash as createHash3 } from "crypto";
4650
4915
 
4651
4916
  // src/lib/field-path.ts
4652
4917
  function matchesWhere(element, where) {
@@ -5064,6 +5329,447 @@ async function buildRequestContext(integrationManager2, executionJournal, action
5064
5329
  return { request, history };
5065
5330
  }
5066
5331
 
5332
+ // src/lib/document-source.ts
5333
+ function sha256Hex(buf) {
5334
+ return createHash3("sha256").update(buf).digest("hex");
5335
+ }
5336
+ async function resolveDocumentReference(integrationManager2, ref) {
5337
+ const manifest = getManifest(ref.connector);
5338
+ if (!manifest) {
5339
+ return { ok: false, error: { code: "UNKNOWN_CONNECTOR", message: `"${ref.connector}" is not a known connector.` } };
5340
+ }
5341
+ const docConfig = manifest.documents?.[ref.kind];
5342
+ if (!docConfig) {
5343
+ return {
5344
+ ok: false,
5345
+ error: {
5346
+ code: "UNDECLARED_KIND",
5347
+ message: `Connector "${ref.connector}" declares no document kind "${ref.kind}".`
5348
+ }
5349
+ };
5350
+ }
5351
+ const toolEntry = manifest.toolGating?.overrides?.[docConfig.tool];
5352
+ if (!toolEntry?.internal) {
5353
+ return {
5354
+ ok: false,
5355
+ error: {
5356
+ code: "NOT_INTERNAL",
5357
+ message: `Connector "${ref.connector}"'s document read "${docConfig.tool}" is not marked internal \u2014 refusing to call it for a document reference.`
5358
+ }
5359
+ };
5360
+ }
5361
+ const args = substitute(docConfig.args, { id: ref.id });
5362
+ const read = await readPreview(integrationManager2, ref.connector, docConfig.tool, args);
5363
+ if (read.status === "not_found") {
5364
+ return { ok: false, error: { code: "NOT_FOUND", message: read.message ?? `No document "${ref.kind}"/"${ref.id}" on "${ref.connector}".` } };
5365
+ }
5366
+ if (read.status !== "ok") {
5367
+ return { ok: false, error: { code: "UNAVAILABLE", message: read.message ?? `Connector "${ref.connector}" could not be reached.` } };
5368
+ }
5369
+ const body = structuredOrJsonText(read.body);
5370
+ if (!body || typeof body !== "object") {
5371
+ return { ok: false, error: { code: "MALFORMED_RESULT", message: `Document read for "${ref.connector}:${ref.kind}" returned no structured result.` } };
5372
+ }
5373
+ const { filename, contentType, contentBase64, sha256: claimedSha256 } = body;
5374
+ if (typeof filename !== "string" || !filename) {
5375
+ return { ok: false, error: { code: "MALFORMED_RESULT", message: 'Document result is missing "filename".' } };
5376
+ }
5377
+ if (typeof contentType !== "string" || !contentType) {
5378
+ return { ok: false, error: { code: "MALFORMED_RESULT", message: 'Document result is missing "contentType".' } };
5379
+ }
5380
+ if (typeof contentBase64 !== "string" || !contentBase64) {
5381
+ return { ok: false, error: { code: "MALFORMED_RESULT", message: 'Document result is missing "contentBase64".' } };
5382
+ }
5383
+ let bytes;
5384
+ try {
5385
+ bytes = Buffer.from(contentBase64, "base64");
5386
+ } catch {
5387
+ return { ok: false, error: { code: "MALFORMED_RESULT", message: '"contentBase64" is not valid base64.' } };
5388
+ }
5389
+ const computedSha256 = sha256Hex(bytes);
5390
+ if (typeof claimedSha256 === "string" && claimedSha256.length > 0 && claimedSha256 !== computedSha256) {
5391
+ return {
5392
+ ok: false,
5393
+ error: {
5394
+ code: "HASH_MISMATCH",
5395
+ message: `Connector "${ref.connector}" claimed sha256 ${claimedSha256} for "${ref.kind}:${ref.id}" but the bytes hash to ${computedSha256}.`
5396
+ }
5397
+ };
5398
+ }
5399
+ return { ok: true, doc: { filename, contentType, contentBase64, sha256: computedSha256, bytes: bytes.byteLength } };
5400
+ }
5401
+ async function resolveAttachmentPreview(integrationManager2, ref, sealedSha256) {
5402
+ const result = await resolveDocumentReference(integrationManager2, ref);
5403
+ if (!result.ok) {
5404
+ switch (result.error.code) {
5405
+ case "NOT_FOUND":
5406
+ return { status: "not_found", message: result.error.message };
5407
+ case "UNKNOWN_CONNECTOR":
5408
+ case "UNDECLARED_KIND":
5409
+ case "NOT_INTERNAL":
5410
+ return { status: "error", message: result.error.message };
5411
+ default:
5412
+ return { status: "unavailable", message: result.error.message };
5413
+ }
5414
+ }
5415
+ if (result.doc.sha256 !== sealedSha256) {
5416
+ return {
5417
+ status: "changed",
5418
+ message: `"${result.doc.filename}" (${ref.connector}:${ref.kind}/${ref.id}) changed since it was sealed \u2014 its fingerprint no longer matches.`
5419
+ };
5420
+ }
5421
+ return { status: "ok", ...result.doc };
5422
+ }
5423
+
5424
+ // src/lib/insert-values.ts
5425
+ function hostAllowed(host, allowed) {
5426
+ const h = host.toLowerCase();
5427
+ const a = allowed.toLowerCase();
5428
+ return h === a || h.endsWith(`.${a}`);
5429
+ }
5430
+ async function resolveInsertValue(integrationManager2, ref) {
5431
+ const manifest = getManifest(ref.connector);
5432
+ if (!manifest) {
5433
+ return { ok: false, error: { code: "UNKNOWN_CONNECTOR", message: `"${ref.connector}" is not a known connector.` } };
5434
+ }
5435
+ const valueConfig = manifest.values?.[ref.kind];
5436
+ if (!valueConfig) {
5437
+ return {
5438
+ ok: false,
5439
+ error: { code: "UNDECLARED_KIND", message: `Connector "${ref.connector}" declares no insertable value kind "${ref.kind}".` }
5440
+ };
5441
+ }
5442
+ const args = substitute(valueConfig.args, { id: ref.id });
5443
+ const read = await readPreview(integrationManager2, ref.connector, valueConfig.tool, args);
5444
+ if (read.status === "not_found") {
5445
+ return { ok: false, error: { code: "NOT_FOUND", message: read.message ?? `No value "${ref.kind}"/"${ref.id}" on "${ref.connector}".` } };
5446
+ }
5447
+ if (read.status !== "ok") {
5448
+ return { ok: false, error: { code: "UNAVAILABLE", message: read.message ?? `Connector "${ref.connector}" could not be reached.` } };
5449
+ }
5450
+ const body = structuredOrJsonText(read.body);
5451
+ if (!body || typeof body !== "object") {
5452
+ return { ok: false, error: { code: "MALFORMED_RESULT", message: `Value read for "${ref.connector}:${ref.kind}" returned no structured result.` } };
5453
+ }
5454
+ const raw = getByDottedPath(body, valueConfig.path);
5455
+ if (typeof raw !== "string" || !raw) {
5456
+ return { ok: false, error: { code: "MALFORMED_RESULT", message: `Value read for "${ref.connector}:${ref.kind}" has nothing at "${valueConfig.path}".` } };
5457
+ }
5458
+ let url;
5459
+ try {
5460
+ url = new URL(raw);
5461
+ } catch {
5462
+ return { ok: false, error: { code: "NOT_HTTPS", message: `Value "${raw}" is not a valid URL.` } };
5463
+ }
5464
+ if (url.protocol !== "https:") {
5465
+ return { ok: false, error: { code: "NOT_HTTPS", message: `Value "${raw}" is not an https URL.` } };
5466
+ }
5467
+ if (!valueConfig.allowedHosts.some((h) => hostAllowed(url.hostname, h))) {
5468
+ return {
5469
+ ok: false,
5470
+ error: {
5471
+ code: "DISALLOWED_HOST",
5472
+ message: `Value "${raw}" is on host "${url.hostname}", which "${ref.connector}:${ref.kind}" does not allow (allowed: ${valueConfig.allowedHosts.join(", ")}).`
5473
+ }
5474
+ };
5475
+ }
5476
+ return { ok: true, insert: { placeholder: ref.placeholder, value: raw, connector: ref.connector, kind: ref.kind } };
5477
+ }
5478
+ function placeholdersIn(text) {
5479
+ const found = [];
5480
+ const seen = /* @__PURE__ */ new Set();
5481
+ for (const m of text.matchAll(/\{\{[^{}]+\}\}/g)) {
5482
+ if (!seen.has(m[0])) {
5483
+ seen.add(m[0]);
5484
+ found.push(m[0]);
5485
+ }
5486
+ }
5487
+ return found;
5488
+ }
5489
+ function applyInserts(fields, inserts) {
5490
+ const allPlaceholders = /* @__PURE__ */ new Set();
5491
+ for (const text of Object.values(fields)) {
5492
+ for (const p of placeholdersIn(text)) allPlaceholders.add(p);
5493
+ }
5494
+ const insertByPlaceholder = new Map(inserts.map((i) => [i.placeholder, i]));
5495
+ for (const p of allPlaceholders) {
5496
+ if (!insertByPlaceholder.has(p)) {
5497
+ return {
5498
+ ok: false,
5499
+ error: {
5500
+ code: "UNMATCHED_PLACEHOLDER",
5501
+ placeholder: p,
5502
+ message: `The text contains "${p}" but no "insert" entry names that placeholder.`
5503
+ }
5504
+ };
5505
+ }
5506
+ }
5507
+ for (const insert of inserts) {
5508
+ if (!allPlaceholders.has(insert.placeholder)) {
5509
+ return {
5510
+ ok: false,
5511
+ error: {
5512
+ code: "PLACEHOLDER_NOT_FOUND",
5513
+ placeholder: insert.placeholder,
5514
+ message: `"insert" names placeholder "${insert.placeholder}", which does not appear in the text.`
5515
+ }
5516
+ };
5517
+ }
5518
+ }
5519
+ const out = {};
5520
+ for (const [field, text] of Object.entries(fields)) {
5521
+ let replaced = text;
5522
+ for (const insert of inserts) {
5523
+ replaced = replaced.split(insert.placeholder).join(insert.value);
5524
+ }
5525
+ out[field] = replaced;
5526
+ }
5527
+ return { ok: true, fields: out };
5528
+ }
5529
+
5530
+ // src/lib/mime-message.ts
5531
+ var CRLF = "\r\n";
5532
+ function isAscii(s) {
5533
+ return /^[\x20-\x7e]*$/.test(s);
5534
+ }
5535
+ function rfc2047Encode(value) {
5536
+ if (isAscii(value)) return value;
5537
+ const b64 = Buffer.from(value, "utf8").toString("base64");
5538
+ return `=?UTF-8?B?${b64}?=`;
5539
+ }
5540
+ function wrapBase64(b64) {
5541
+ const lines = [];
5542
+ for (let i = 0; i < b64.length; i += 76) lines.push(b64.slice(i, i + 76));
5543
+ return lines.join(CRLF);
5544
+ }
5545
+ function makeBoundary() {
5546
+ const random = Buffer.from(
5547
+ Array.from({ length: 16 }, () => Math.floor(Math.random() * 256))
5548
+ ).toString("hex");
5549
+ return `----=_SuverenPart_${random}`;
5550
+ }
5551
+ function addressHeader(name, addresses) {
5552
+ if (!addresses?.length) return "";
5553
+ return `${name}: ${addresses.join(", ")}${CRLF}`;
5554
+ }
5555
+ function buildMimeMessage(input) {
5556
+ const boundary = makeBoundary();
5557
+ const headers = `MIME-Version: 1.0${CRLF}` + addressHeader("To", input.to) + addressHeader("Cc", input.cc) + `Subject: ${rfc2047Encode(input.subject)}${CRLF}Content-Type: multipart/mixed; boundary="${boundary}"${CRLF}`;
5558
+ const textPart = `--${boundary}${CRLF}Content-Type: text/plain; charset="UTF-8"${CRLF}Content-Transfer-Encoding: base64${CRLF}${CRLF}` + wrapBase64(Buffer.from(input.bodyText, "utf8").toString("base64")) + CRLF;
5559
+ const attachmentParts = input.attachments.map((att) => {
5560
+ const name = rfc2047Encode(att.filename);
5561
+ return `--${boundary}${CRLF}Content-Type: ${att.contentType}; name="${name}"${CRLF}Content-Disposition: attachment; filename="${name}"${CRLF}Content-Transfer-Encoding: base64${CRLF}${CRLF}` + wrapBase64(att.contentBase64) + CRLF;
5562
+ });
5563
+ return headers + CRLF + [textPart, ...attachmentParts].join("") + `--${boundary}--${CRLF}`;
5564
+ }
5565
+ function toBase64Url(mime) {
5566
+ return Buffer.from(mime, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
5567
+ }
5568
+
5569
+ // src/lib/attachment-resolution.ts
5570
+ async function resolveAttachmentsAndInserts(integrationManager2, tool, profileId, args, allowedSourcesValue) {
5571
+ const attachmentsArg = tool.gating?.attachmentsArg;
5572
+ const insertFields = tool.gating?.insertFields;
5573
+ if (!attachmentsArg && !insertFields?.length) return { ok: true, args };
5574
+ const refsResult = attachmentsArg ? validateDocumentReferences(args[attachmentsArg]) : { ok: true, refs: [] };
5575
+ if (!refsResult.ok) {
5576
+ return { ok: false, message: `Refused: ${tool.namespacedName}'s attachments ${refsResult.error.message}` };
5577
+ }
5578
+ const insertResult = validateInsertReferences(args.insert);
5579
+ if (!insertResult.ok) {
5580
+ return { ok: false, message: `Refused: ${tool.namespacedName}'s insert ${insertResult.error.message}` };
5581
+ }
5582
+ const sealedAttachments = [];
5583
+ if (refsResult.refs.length > 0) {
5584
+ const profile = getProfile2(profileId);
5585
+ for (const ref of refsResult.refs) {
5586
+ const policy = isAttachmentSourceAllowed(profileId, profile, allowedSourcesValue, ref);
5587
+ if (!policy.allowed) {
5588
+ return { ok: false, message: `Refused: attachment "${ref.connector}:${ref.kind}" is not allowed. ${policy.reason}` };
5589
+ }
5590
+ const resolved = await resolveDocumentReference(integrationManager2, ref);
5591
+ if (!resolved.ok) {
5592
+ return { ok: false, message: `Refused: could not resolve attachment "${ref.connector}:${ref.kind}/${ref.id}" \u2014 ${resolved.error.message}` };
5593
+ }
5594
+ sealedAttachments.push({
5595
+ connector: ref.connector,
5596
+ kind: ref.kind,
5597
+ id: ref.id,
5598
+ filename: resolved.doc.filename,
5599
+ contentType: resolved.doc.contentType,
5600
+ sha256: resolved.doc.sha256,
5601
+ bytes: resolved.doc.bytes
5602
+ });
5603
+ }
5604
+ }
5605
+ const sealedInserts = [];
5606
+ const resolvedInserts = [];
5607
+ if (insertResult.refs.length > 0) {
5608
+ for (const ref of insertResult.refs) {
5609
+ const resolved = await resolveInsertValue(integrationManager2, ref);
5610
+ if (!resolved.ok) {
5611
+ return { ok: false, message: `Refused: could not resolve insert "${ref.connector}:${ref.kind}/${ref.id}" for "${ref.placeholder}" \u2014 ${resolved.error.message}` };
5612
+ }
5613
+ resolvedInserts.push(resolved.insert);
5614
+ sealedInserts.push({ placeholder: ref.placeholder, connector: ref.connector, kind: ref.kind, id: ref.id, value: resolved.insert.value });
5615
+ }
5616
+ }
5617
+ let outgoing = { ...args };
5618
+ if (insertFields?.length) {
5619
+ const fields = {};
5620
+ for (const field of insertFields) {
5621
+ fields[field] = typeof args[field] === "string" ? args[field] : "";
5622
+ }
5623
+ const applied = applyInserts(fields, resolvedInserts);
5624
+ if (!applied.ok) {
5625
+ return { ok: false, message: `Refused: ${tool.namespacedName}'s insert \u2014 ${applied.error.message}` };
5626
+ }
5627
+ outgoing = { ...outgoing, ...applied.fields };
5628
+ } else if (insertResult.refs.length > 0) {
5629
+ return { ok: false, message: `Refused: ${tool.namespacedName} does not declare any insertable text fields.` };
5630
+ }
5631
+ if (attachmentsArg) delete outgoing[attachmentsArg];
5632
+ delete outgoing.insert;
5633
+ return {
5634
+ ok: true,
5635
+ args: outgoing,
5636
+ ...sealedAttachments.length > 0 || sealedInserts.length > 0 ? { sealed: { attachments: sealedAttachments, inserts: sealedInserts } } : {}
5637
+ };
5638
+ }
5639
+ function toStringArray(value) {
5640
+ if (Array.isArray(value)) return value.map((v) => String(v));
5641
+ if (typeof value === "string" && value) return [value];
5642
+ return [];
5643
+ }
5644
+ async function finalizeAttachmentsForExecution(integrationManager2, integrationId, args) {
5645
+ const sealed = args[ATTACHMENTS_SEALED_META_KEY];
5646
+ if (!sealed || sealed.attachments.length === 0 && sealed.inserts.length === 0) {
5647
+ return { ok: true, args };
5648
+ }
5649
+ const resolvedDocs = [];
5650
+ for (const a of sealed.attachments) {
5651
+ const resolved = await resolveDocumentReference(integrationManager2, { connector: a.connector, kind: a.kind, id: a.id });
5652
+ if (!resolved.ok) {
5653
+ return {
5654
+ ok: false,
5655
+ message: `Refused: could not re-resolve attachment "${a.filename}" (${a.connector}:${a.kind}/${a.id}) at execution \u2014 ${resolved.error.message}`
5656
+ };
5657
+ }
5658
+ if (resolved.doc.sha256 !== a.sha256) {
5659
+ return {
5660
+ ok: false,
5661
+ message: `Refused: attachment "${a.filename}" (${a.connector}:${a.kind}/${a.id}) changed since this was approved \u2014 its fingerprint no longer matches.`
5662
+ };
5663
+ }
5664
+ resolvedDocs.push(resolved.doc);
5665
+ }
5666
+ for (const i of sealed.inserts) {
5667
+ const resolved = await resolveInsertValue(integrationManager2, { connector: i.connector, kind: i.kind, id: i.id, placeholder: i.placeholder });
5668
+ if (!resolved.ok) {
5669
+ return {
5670
+ ok: false,
5671
+ message: `Refused: could not re-resolve the inserted value for "${i.placeholder}" (${i.connector}:${i.kind}/${i.id}) at execution \u2014 ${resolved.error.message}`
5672
+ };
5673
+ }
5674
+ if (resolved.insert.value !== i.value) {
5675
+ return {
5676
+ ok: false,
5677
+ message: `Refused: the value inserted for "${i.placeholder}" (${i.connector}:${i.kind}/${i.id}) changed since this was approved.`
5678
+ };
5679
+ }
5680
+ }
5681
+ let outgoing = { ...args };
5682
+ if (resolvedDocs.length > 0) {
5683
+ const manifest = getManifest(integrationId);
5684
+ const transport = manifest?.attachments;
5685
+ if (!transport) {
5686
+ return { ok: false, message: `Refused: "${integrationId}" declares no attachments transport.` };
5687
+ }
5688
+ if (transport.transport === "field") {
5689
+ outgoing[transport.arg] = resolvedDocs.map((d) => ({
5690
+ filename: d.filename,
5691
+ contentType: d.contentType,
5692
+ contentBase64: d.contentBase64
5693
+ }));
5694
+ } else {
5695
+ const to = toStringArray(outgoing.to);
5696
+ const cc = toStringArray(outgoing.cc);
5697
+ const subject = typeof outgoing.subject === "string" ? outgoing.subject : "";
5698
+ const bodyText = typeof outgoing.body === "string" ? outgoing.body : "";
5699
+ const mime = buildMimeMessage({
5700
+ to,
5701
+ cc,
5702
+ subject,
5703
+ bodyText,
5704
+ attachments: resolvedDocs.map((d) => ({ filename: d.filename, contentType: d.contentType, contentBase64: d.contentBase64 }))
5705
+ });
5706
+ outgoing[transport.arg] = toBase64Url(mime);
5707
+ }
5708
+ }
5709
+ return { ok: true, args: outgoing };
5710
+ }
5711
+ var ATTACHMENTS_SEALED_META_KEY = "_attachmentsSealed";
5712
+ function stripAttachmentsSealedMeta(args) {
5713
+ if (!(ATTACHMENTS_SEALED_META_KEY in args)) return args;
5714
+ const { [ATTACHMENTS_SEALED_META_KEY]: _omit, ...rest } = args;
5715
+ return rest;
5716
+ }
5717
+
5718
+ // src/lib/content-binding.ts
5719
+ function getContentBinding(profileId) {
5720
+ return getProfile3(profileId)?.content_binding;
5721
+ }
5722
+ function computeContentBinding(profileId, tool, toolArgs, actionType) {
5723
+ const binding = getContentBinding(profileId);
5724
+ if (!binding) return void 0;
5725
+ if (isFieldBinding(binding)) {
5726
+ if (!bindingAppliesTo(binding, actionType)) {
5727
+ if (!actionType && binding.appliesTo) {
5728
+ console.error(
5729
+ `[Suveren MCP] Warning: ${tool?.namespacedName ?? profileId} has no action_type in staticExecution, so the ${profileId} content binding (${binding.appliesTo.join(", ")}) does not apply and the receipt will bind no content. Fix the integration manifest.`
5730
+ );
5731
+ }
5732
+ return void 0;
5733
+ }
5734
+ let effectiveArgs = toolArgs;
5735
+ if (binding.fields?.includes("attachments")) {
5736
+ const sealed = toolArgs[ATTACHMENTS_SEALED_META_KEY];
5737
+ const fingerprints = (sealed?.attachments ?? []).map((a) => ({ filename: a.filename, sha256: a.sha256 }));
5738
+ effectiveArgs = { ...toolArgs, attachments: fingerprints };
5739
+ }
5740
+ const bound = selectBoundFields(binding, effectiveArgs);
5741
+ return {
5742
+ contentHash: computeContentHash2(binding, bound),
5743
+ contentBinding: { version: binding.version, kind: binding.kind, fields: binding.fields },
5744
+ boundContent: bound
5745
+ };
5746
+ }
5747
+ let contentHash;
5748
+ let boundContent;
5749
+ if (binding.kind === "jcs") {
5750
+ contentHash = computeContentHash2(binding, toolArgs);
5751
+ boundContent = toolArgs;
5752
+ } else {
5753
+ const declared = tool?.gating?.contentField;
5754
+ const field = declared ?? (tool ? detectContentField(tool) : null);
5755
+ if (!field) return void 0;
5756
+ const raw = typeof toolArgs[field] === "string" ? toolArgs[field] : "";
5757
+ if (raw === "") return void 0;
5758
+ contentHash = computeContentHash2(binding, raw);
5759
+ boundContent = raw;
5760
+ }
5761
+ return {
5762
+ contentHash,
5763
+ contentBinding: { version: binding.version, kind: binding.kind },
5764
+ boundContent
5765
+ };
5766
+ }
5767
+ function attachTicketId(tool, args, ticketId) {
5768
+ const schema = tool.inputSchema;
5769
+ if (!schema?.properties || !("ticket_id" in schema.properties)) return args;
5770
+ return { ...args, ticket_id: ticketId };
5771
+ }
5772
+
5067
5773
  // src/lib/ticket-verify.ts
5068
5774
  import { verifyTicketSignature } from "@hap/core";
5069
5775
  var TicketBindingMismatchError = class extends Error {
@@ -5161,7 +5867,7 @@ async function resolveWhoLabel(archive, domainKey, userId) {
5161
5867
  }
5162
5868
 
5163
5869
  // src/lib/arg-encoding.ts
5164
- function rfc2047Encode(value) {
5870
+ function rfc2047Encode2(value) {
5165
5871
  if (!/[^\x00-\x7F]/.test(value)) return value;
5166
5872
  const CHARSET = "=?UTF-8?Q?";
5167
5873
  const SUFFIX = "?=";
@@ -5195,7 +5901,7 @@ function rfc2047Encode(value) {
5195
5901
  return words.join(" ");
5196
5902
  }
5197
5903
  var ENCODERS = {
5198
- rfc2047: rfc2047Encode
5904
+ rfc2047: rfc2047Encode2
5199
5905
  };
5200
5906
  function encodeOutgoingArgs(tool, args) {
5201
5907
  const declared = tool?.gating?.argEncoding;
@@ -5268,7 +5974,7 @@ function normalizeIncomingArgs(tool, args) {
5268
5974
  }
5269
5975
 
5270
5976
  // src/lib/numeric-execution.ts
5271
- import { getProfile as getProfile3 } from "@hap/core";
5977
+ import { getProfile as getProfile4 } from "@hap/core";
5272
5978
  function parseNumericString(value) {
5273
5979
  if (!/^-?\d+(\.\d+)?$/.test(value)) return void 0;
5274
5980
  const parsed = Number(value);
@@ -5284,7 +5990,7 @@ function declaredNumericFields(profile) {
5284
5990
  return out;
5285
5991
  }
5286
5992
  function coerceDeclaredNumericFields(profileId, execution) {
5287
- const fields = declaredNumericFields(getProfile3(profileId));
5993
+ const fields = declaredNumericFields(getProfile4(profileId));
5288
5994
  for (const field of fields) {
5289
5995
  const value = execution[field];
5290
5996
  if (typeof value !== "string") continue;
@@ -5294,7 +6000,7 @@ function coerceDeclaredNumericFields(profileId, execution) {
5294
6000
  }
5295
6001
 
5296
6002
  // src/lib/tool-proxy.ts
5297
- import { getProfile as getProfile4, ContentBindingError, boundActionTypes as boundActionTypes2 } from "@hap/core";
6003
+ import { getProfile as getProfile5, ContentBindingError, boundActionTypes as boundActionTypes2 } from "@hap/core";
5298
6004
  import { readFile } from "fs/promises";
5299
6005
  import { extname } from "path";
5300
6006
  import { randomUUID } from "crypto";
@@ -5366,7 +6072,7 @@ function profileMatches(profileId, shortName2) {
5366
6072
  return profileId === shortName2 || profileId.includes("/" + shortName2 + "@") || profileId.endsWith("/" + shortName2);
5367
6073
  }
5368
6074
  function wireExecutionContext(profileId, execution) {
5369
- const profile = getProfile4(profileId);
6075
+ const profile = getProfile5(profileId);
5370
6076
  const allowed = /* @__PURE__ */ new Set(["action_type"]);
5371
6077
  const fields = profile?.boundsSchema?.fields;
5372
6078
  if (fields) {
@@ -5384,7 +6090,7 @@ function wireExecutionContext(profileId, execution) {
5384
6090
  return out;
5385
6091
  }
5386
6092
  function countBoundsFor(profileId, actionType) {
5387
- const fields = getProfile4(profileId)?.boundsSchema?.fields;
6093
+ const fields = getProfile5(profileId)?.boundsSchema?.fields;
5388
6094
  if (!fields) return null;
5389
6095
  return Object.entries(fields).filter(([, def]) => def.boundType?.kind === "cumulative_count").filter(([name, def]) => {
5390
6096
  const applies = boundActionTypes2(name, def);
@@ -5459,7 +6165,7 @@ function recordBlocked(state2, tool, rec) {
5459
6165
  function recordGatekeeperRefusal(state2, tool, refusals) {
5460
6166
  if (refusals.length === 0) return;
5461
6167
  const pick = refusals.find((r) => r.bound !== void 0 && r.actual !== void 0) ?? refusals[0];
5462
- const scoped = pick.field ? Boolean(getProfile4(pick.profileId)?.scopeSchema?.fields?.[pick.field]) : false;
6168
+ const scoped = pick.field ? Boolean(getProfile5(pick.profileId)?.scopeSchema?.fields?.[pick.field]) : false;
5463
6169
  const asValueOrString = (v) => typeof v === "number" || typeof v === "string" ? v : void 0;
5464
6170
  recordBlocked(state2, tool, {
5465
6171
  kind: scoped ? "scope" : "bound",
@@ -5638,7 +6344,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
5638
6344
  );
5639
6345
  }
5640
6346
  }
5641
- const readProfile = readAdapter ? getProfile4(matchingAuths[0].profileId) : void 0;
6347
+ const readProfile = readAdapter ? getProfile5(matchingAuths[0].profileId) : void 0;
5642
6348
  const boundsSchema = readProfile?.boundsSchema;
5643
6349
  const ageBoundField = readAdapter?.ageField ? resolveAgeBoundField(boundsSchema, readAdapter.ageField) : null;
5644
6350
  const hasAgeDimension = Boolean(readAdapter?.ageField);
@@ -5769,6 +6475,20 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
5769
6475
  });
5770
6476
  }
5771
6477
  return lockedGuard(async (args) => {
6478
+ let attachmentSourcesForExecution;
6479
+ if (tool.gating?.attachmentsArg || tool.gating?.insertFields?.length) {
6480
+ const refsResult = tool.gating.attachmentsArg ? validateDocumentReferences(args[tool.gating.attachmentsArg]) : { ok: true, refs: [] };
6481
+ if (!refsResult.ok) {
6482
+ return { content: [{ type: "text", text: `Refused: ${tool.namespacedName}'s attachments ${refsResult.error.message}` }], isError: true };
6483
+ }
6484
+ const insertResult = validateInsertReferences(args.insert);
6485
+ if (!insertResult.ok) {
6486
+ return { content: [{ type: "text", text: `Refused: ${tool.namespacedName}'s insert ${insertResult.error.message}` }], isError: true };
6487
+ }
6488
+ if (refsResult.refs.length > 0) {
6489
+ attachmentSourcesForExecution = [...new Set(refsResult.refs.map((r) => `${r.connector}:${r.kind}`))].join(",");
6490
+ }
6491
+ }
5772
6492
  const execution = { ...staticExecution };
5773
6493
  for (const [argName, mapping] of Object.entries(executionMapping)) {
5774
6494
  const value = args[argName];
@@ -5785,6 +6505,9 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
5785
6505
  }
5786
6506
  }
5787
6507
  }
6508
+ if (attachmentSourcesForExecution) {
6509
+ execution[ATTACHMENT_SOURCES_FIELD] = attachmentSourcesForExecution;
6510
+ }
5788
6511
  coerceDeclaredNumericFields(profile, execution);
5789
6512
  const declaredActionType = typeof execution.action_type === "string" && execution.action_type.length > 0 ? execution.action_type : void 0;
5790
6513
  if (!declaredActionType) {
@@ -5804,7 +6527,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
5804
6527
  };
5805
6528
  }
5806
6529
  {
5807
- const profileDef = getProfile4(profile);
6530
+ const profileDef = getProfile5(profile);
5808
6531
  const registry = profileDef?.boundsSchema?.actionTypes;
5809
6532
  if (Array.isArray(registry) && registry.length > 0 && !registry.includes(declaredActionType)) {
5810
6533
  recordBlocked(state2, tool, {
@@ -5908,7 +6631,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
5908
6631
  isError: true
5909
6632
  };
5910
6633
  }
5911
- const contextKeys = getProfile4(passers[0].profileId)?.scopeSchema?.keyOrder ?? [];
6634
+ const contextKeys = getProfile5(passers[0].profileId)?.scopeSchema?.keyOrder ?? [];
5912
6635
  return attemptWithCandidates(passers);
5913
6636
  async function attemptWithCandidates(candidates) {
5914
6637
  const selection = selectAuthorization(
@@ -5936,9 +6659,18 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
5936
6659
  isError: true
5937
6660
  };
5938
6661
  }
6662
+ let callArgs = args;
6663
+ if (tool.gating?.attachmentsArg || tool.gating?.insertFields?.length) {
6664
+ const allowedSourcesValue = typeof auth.context?.[ATTACHMENT_SOURCES_FIELD] === "string" ? auth.context[ATTACHMENT_SOURCES_FIELD] : void 0;
6665
+ const resolved = await resolveAttachmentsAndInserts(integrationManager2, tool, auth.profileId, args, allowedSourcesValue);
6666
+ if (!resolved.ok) {
6667
+ return { content: [{ type: "text", text: resolved.message }], isError: true };
6668
+ }
6669
+ callArgs = resolved.sealed ? { ...resolved.args, [ATTACHMENTS_SEALED_META_KEY]: resolved.sealed } : resolved.args;
6670
+ }
5939
6671
  if ((auth.deferredCommitmentDomains ?? []).length > 0) {
5940
6672
  try {
5941
- const enrichedArgs = await attachImagePreview(args);
6673
+ const enrichedArgs = await attachImagePreview(callArgs);
5942
6674
  const wireExecCtx = wireExecutionContext(auth.profileId, execution);
5943
6675
  let recipientUserIds = auth.deferredCommitmentDomains.filter((d) => d !== "owner");
5944
6676
  try {
@@ -5992,10 +6724,18 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
5992
6724
  };
5993
6725
  }
5994
6726
  }
6727
+ let finalArgs = callArgs;
6728
+ if (tool.gating?.attachmentsArg || tool.gating?.insertFields?.length) {
6729
+ const finalized = await finalizeAttachmentsForExecution(integrationManager2, tool.integrationId, callArgs);
6730
+ if (!finalized.ok) {
6731
+ return { content: [{ type: "text", text: finalized.message }], isError: true };
6732
+ }
6733
+ finalArgs = finalized.args;
6734
+ }
5995
6735
  let receiptId;
5996
6736
  try {
5997
6737
  const actionType = declaredActionType;
5998
- const binding = computeContentBinding(auth.profileId, tool, args, actionType);
6738
+ const binding = computeContentBinding(auth.profileId, tool, finalArgs, actionType);
5999
6739
  const idempotencyKey = randomUUID();
6000
6740
  const wireExecCtx = wireExecutionContext(auth.profileId, execution);
6001
6741
  const { receipt } = await state2.spClient.postReceipt({
@@ -6038,10 +6778,12 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
6038
6778
  // would hold a hash of content it cannot reproduce.
6039
6779
  boundContent: binding?.boundContent,
6040
6780
  // UI spec §14 — what this call sends, exactly the object the
6041
- // journal's argsHash is computed from below (`hashToolArgs(args)`),
6042
- // so the ticket view can show it and check it. Same write, so it
6043
- // is as fail-closed and as encrypted as the rest of the entry.
6044
- sentArgs: args
6781
+ // journal's argsHash is computed from below
6782
+ // (`hashToolArgs(finalArgs)`), so the ticket view can show it and
6783
+ // check it (incl. any attachments' sealed fingerprints — UI spec
6784
+ // §15.2). Same write, so it is as fail-closed and as encrypted
6785
+ // as the rest of the entry.
6786
+ sentArgs: finalArgs
6045
6787
  });
6046
6788
  } catch (err) {
6047
6789
  if (err instanceof AsKeyMismatchError || err instanceof TicketBindingMismatchError) {
@@ -6133,7 +6875,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
6133
6875
  const uniqueApprovers = [...new Set(pendingApprovers)];
6134
6876
  const recipientUserIds = ownerId ? [.../* @__PURE__ */ new Set([ownerId, ...uniqueApprovers])] : uniqueApprovers;
6135
6877
  try {
6136
- const enrichedArgs = await attachImagePreview(args);
6878
+ const enrichedArgs = await attachImagePreview(callArgs);
6137
6879
  const wireExecCtx = wireExecutionContext(auth.profileId, execution);
6138
6880
  const { proposal, unreadableFor } = await state2.spClient.submitProposal({
6139
6881
  authorizationId: authzId,
@@ -6217,15 +6959,16 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.` + a
6217
6959
  execution: { ...execution },
6218
6960
  timestamp: Math.floor(Date.now() / 1e3)
6219
6961
  });
6220
- let outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, args, receiptId, auth.subjects?.[0]) : args;
6962
+ let outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, finalArgs, receiptId, auth.subjects?.[0]) : finalArgs;
6221
6963
  if (receiptId) outgoingArgs = attachTicketId(tool, outgoingArgs, receiptId);
6222
6964
  outgoingArgs = encodeOutgoingArgs(tool, outgoingArgs);
6223
6965
  outgoingArgs = stripSourceLink(outgoingArgs);
6966
+ outgoingArgs = stripAttachmentsSealedMeta(outgoingArgs);
6224
6967
  if (receiptId) {
6225
6968
  const begun = state2.executionJournal.begin({
6226
6969
  ticketId: receiptId,
6227
6970
  tool: tool.namespacedName,
6228
- argsHash: hashToolArgs(args)
6971
+ argsHash: hashToolArgs(finalArgs)
6229
6972
  });
6230
6973
  if (!begun.ok) {
6231
6974
  return {
@@ -6394,7 +7137,7 @@ function buildMandateBrief(opts) {
6394
7137
  const parts = [];
6395
7138
  if (boundsDesc) parts.push(boundsDesc);
6396
7139
  if (executionLog) {
6397
- const profile = getProfile5(auth.profileId) ?? getProfile5(shortName2);
7140
+ const profile = getProfile6(auth.profileId) ?? getProfile6(shortName2);
6398
7141
  const consumption = getConsumptionState(auth, executionLog, profile);
6399
7142
  const compact = formatConsumptionCompact(consumption);
6400
7143
  if (compact) parts.push(compact);
@@ -6432,7 +7175,7 @@ function buildMandateBrief(opts) {
6432
7175
  }
6433
7176
 
6434
7177
  // src/tools/authorizations.ts
6435
- import { getProfile as getProfile6 } from "@hap/core";
7178
+ import { getProfile as getProfile7 } from "@hap/core";
6436
7179
  function shortProfileName2(profileId) {
6437
7180
  const withoutVersion = profileId.replace(/@.*$/, "");
6438
7181
  const parts = withoutVersion.split("/");
@@ -6551,7 +7294,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
6551
7294
  } catch {
6552
7295
  }
6553
7296
  const shortName2 = shortProfileName2(auth.profileId);
6554
- const profile = getProfile6(auth.profileId) ?? getProfile6(shortName2);
7297
+ const profile = getProfile7(auth.profileId) ?? getProfile7(shortName2);
6555
7298
  if (profile) {
6556
7299
  const consumption = getConsumptionState(auth, state2.executionLog, profile);
6557
7300
  const consumptionText = formatConsumptionFull(consumption);
@@ -6611,7 +7354,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
6611
7354
  lines.push(" Mode: review (each action requires your approval)");
6612
7355
  }
6613
7356
  const shortName2 = shortProfileName2(auth.profileId);
6614
- const profile = getProfile6(auth.profileId) ?? getProfile6(shortName2);
7357
+ const profile = getProfile7(auth.profileId) ?? getProfile7(shortName2);
6615
7358
  if (profile) {
6616
7359
  const consumption = getConsumptionState(auth, state2.executionLog, profile);
6617
7360
  const compact = formatConsumptionCompact(consumption);
@@ -6716,29 +7459,30 @@ function listIntegrationsHandler(state2, integrationManager2) {
6716
7459
  }
6717
7460
  const auths = state2.getEnrichedAuthorizations();
6718
7461
  const statuses = integrationManager2.getStatus();
6719
- const integrations = statuses.filter((s) => s.running).map((s) => {
7462
+ const integrations = statuses.filter((s) => s.running).flatMap((s) => {
6720
7463
  const tools = integrationManager2.getAllTools().filter((t) => t.integrationId === s.id);
6721
- const profile = tools[0]?.gating?.profile ?? null;
6722
- const matchingAuths = profile ? auths.filter((a) => a.complete && profileMatches(a.profileId, profile)) : [];
6723
- let status;
6724
- if (matchingAuths.length > 0) {
6725
- const soonestExpiry = Math.min(
6726
- ...matchingAuths.flatMap(
6727
- (a) => a.attestations.map((att) => att.expiresAt)
6728
- )
6729
- );
6730
- const remainingMs = soonestExpiry * 1e3 - Date.now();
6731
- const remainingMin = Math.ceil(remainingMs / 6e4);
6732
- status = `authorized (${matchingAuths.length} path${matchingAuths.length > 1 ? "s" : ""}, ${remainingMin} min remaining)`;
6733
- } else {
6734
- const allProfileAuths = profile ? auths.filter((a) => profileMatches(a.profileId, profile)) : [];
6735
- status = allProfileAuths.length > 0 ? "expired" : "no authorization";
6736
- }
6737
- return {
6738
- id: s.id,
6739
- profile: profile ? shortProfileName3(profile) : null,
6740
- status
6741
- };
7464
+ const profiles = [...new Set(tools.map((t) => t.gating?.profile).filter((p) => !!p))];
7465
+ if (profiles.length === 0) {
7466
+ return [{ id: s.id, profile: null, status: "no authorization" }];
7467
+ }
7468
+ return profiles.map((profile) => {
7469
+ const matchingAuths = auths.filter((a) => a.complete && profileMatches(a.profileId, profile));
7470
+ let status;
7471
+ if (matchingAuths.length > 0) {
7472
+ const soonestExpiry = Math.min(
7473
+ ...matchingAuths.flatMap(
7474
+ (a) => a.attestations.map((att) => att.expiresAt)
7475
+ )
7476
+ );
7477
+ const remainingMs = soonestExpiry * 1e3 - Date.now();
7478
+ const remainingMin = Math.ceil(remainingMs / 6e4);
7479
+ status = `authorized (${matchingAuths.length} path${matchingAuths.length > 1 ? "s" : ""}, ${remainingMin} min remaining)`;
7480
+ } else {
7481
+ const allProfileAuths = auths.filter((a) => profileMatches(a.profileId, profile));
7482
+ status = allProfileAuths.length > 0 ? "expired" : "no authorization";
7483
+ }
7484
+ return { id: s.id, profile: shortProfileName3(profile), status };
7485
+ });
6742
7486
  });
6743
7487
  const text = integrations.length === 0 ? "No integrations running." : integrations.map(
6744
7488
  (i) => `${i.id} [${i.profile ?? "ungated"}] \u2014 ${i.status}`
@@ -6753,12 +7497,12 @@ function listIntegrationsHandler(state2, integrationManager2) {
6753
7497
  import { ContentBindingError as ContentBindingError2 } from "@hap/core";
6754
7498
 
6755
7499
  // src/lib/proposal-content.ts
6756
- import { createHash as createHash3 } from "crypto";
7500
+ import { createHash as createHash4 } from "crypto";
6757
7501
  import { canonicalize } from "@hap/core";
6758
7502
  var ProposalContentError = class extends Error {
6759
7503
  };
6760
7504
  function computeProposalContentHash(toolArgs) {
6761
- return `sha256:${createHash3("sha256").update(canonicalize(toolArgs)).digest("hex")}`;
7505
+ return `sha256:${createHash4("sha256").update(canonicalize(toolArgs)).digest("hex")}`;
6762
7506
  }
6763
7507
  async function resolveProposalToolArgs(proposal) {
6764
7508
  if (proposal.toolArgs) return proposal.toolArgs;
@@ -7068,8 +7812,23 @@ async function executeCommitted(proposal, state2, integrationManager2) {
7068
7812
  };
7069
7813
  }
7070
7814
  }
7815
+ let finalToolArgs = toolArgs;
7816
+ {
7817
+ const finalized = await finalizeAttachmentsForExecution(integrationManager2, integrationId, toolArgs);
7818
+ if (!finalized.ok) {
7819
+ state2.executionJournal.complete(receiptId, "failed", "changed", finalized.message);
7820
+ recordExecutionOutcome(state2, proposal.id, receiptId, false, finalized.message);
7821
+ logExecutionOutcome(proposal.id, false, "attachment or inserted value changed since approval");
7822
+ void notifyControlPlane("execution-failed");
7823
+ return {
7824
+ text: `Proposal ${proposal.id} was approved, but ${finalized.message} Nothing was sent.`,
7825
+ isError: true
7826
+ };
7827
+ }
7828
+ finalToolArgs = finalized.args;
7829
+ }
7071
7830
  try {
7072
- let outgoingArgs = toolArgs;
7831
+ let outgoingArgs = finalToolArgs;
7073
7832
  if (discovered && receiptId) {
7074
7833
  if (shouldAttachFooter()) {
7075
7834
  outgoingArgs = appendVerificationFooter(discovered, outgoingArgs, receiptId, cachedAuth?.subjects?.[0]);
@@ -7078,6 +7837,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
7078
7837
  outgoingArgs = encodeOutgoingArgs(discovered, outgoingArgs);
7079
7838
  }
7080
7839
  outgoingArgs = stripSourceLink(outgoingArgs);
7840
+ outgoingArgs = stripAttachmentsSealedMeta(outgoingArgs);
7081
7841
  let result;
7082
7842
  try {
7083
7843
  result = await integrationManager2.callTool(integrationId, toolName, outgoingArgs);
@@ -8214,6 +8974,7 @@ var IntegrationManager = class {
8214
8974
  if (gating && gating.category !== "read" && gating.category !== "disabled") {
8215
8975
  inputSchema = withSourceLink(inputSchema);
8216
8976
  }
8977
+ inputSchema = withAttachmentAndInsertSchema(inputSchema, gating ?? void 0);
8217
8978
  return {
8218
8979
  originalName: tool.name,
8219
8980
  namespacedName: `${config.id}__${tool.name}`,
@@ -8221,6 +8982,13 @@ var IntegrationManager = class {
8221
8982
  description: tool.description ?? "",
8222
8983
  inputSchema,
8223
8984
  outputSchema: tool.outputSchema,
8985
+ // A connector's own `internal: true` override (tool-gating-types.ts
8986
+ // `ProfileToolGatingEntry.internal`) — same meaning and enforcement
8987
+ // as a built-in's `BuiltinTool.internal` above: never registered
8988
+ // with the MCP server (index.ts), never callable directly
8989
+ // (tool-proxy.ts); only the gateway's own code (e.g. a document
8990
+ // preview read — preview.ts) calls it, via `callTool` directly.
8991
+ ...gating?.internal ? { internal: true } : {},
8224
8992
  gating
8225
8993
  };
8226
8994
  });
@@ -8321,7 +9089,8 @@ var IntegrationManager = class {
8321
9089
  ...t.gating?.approvalView ? { approvalView: t.gating.approvalView } : {},
8322
9090
  // UI spec §14.1 — which sent values the signed ticket also carries
8323
9091
  // (arg → executionContext field), for the "in the ticket" tag.
8324
- ...mapping && Object.keys(mapping).length > 0 ? { executionMapping: mapping } : {}
9092
+ ...mapping && Object.keys(mapping).length > 0 ? { executionMapping: mapping } : {},
9093
+ ...t.gating?.aiTextArg ? { aiTextArg: t.gating.aiTextArg } : {}
8325
9094
  };
8326
9095
  }
8327
9096
  return out;
@@ -8601,18 +9370,26 @@ var IntegrationManager = class {
8601
9370
  /**
8602
9371
  * Resolve gating config for a tool from the profile's toolGating section.
8603
9372
  * Returns the runtime ToolGatingConfig that tool-proxy.ts consumes.
9373
+ *
9374
+ * Area per tool: an override may declare its OWN `profile`, gating that
9375
+ * one tool under a different area than the rest of the connector (e.g.
9376
+ * an ERP's `create_invoice` under `invoicing` while `create_quote` stays
9377
+ * under the manifest's own `sales`). `resolvedProfile` below is what every
9378
+ * downstream consumer reads as this tool's governing profile — falling
9379
+ * back to the manifest-level `profileId` when the override doesn't say.
8604
9380
  */
8605
9381
  resolveToolGating(profileId, profileGating, toolName) {
8606
9382
  if (!profileId || !profileGating) return null;
8607
9383
  if (profileGating.overrides && toolName in profileGating.overrides) {
8608
9384
  const override = profileGating.overrides[toolName];
8609
9385
  const ext = override ?? {};
9386
+ const resolvedProfile = ext.profile ?? profileId;
8610
9387
  if (ext.category === "disabled") {
8611
- return { profile: profileId, executionMapping: {}, category: "disabled" };
9388
+ return { profile: resolvedProfile, executionMapping: {}, category: "disabled" };
8612
9389
  }
8613
9390
  if (override === null || ext.category === "read") {
8614
9391
  return {
8615
- profile: profileId,
9392
+ profile: resolvedProfile,
8616
9393
  executionMapping: {},
8617
9394
  category: "read",
8618
9395
  boundField: ext.boundField,
@@ -8623,11 +9400,12 @@ var IntegrationManager = class {
8623
9400
  blockedArgs: ext.blockedArgs,
8624
9401
  hideUnlessAuthorized: ext.hideUnlessAuthorized,
8625
9402
  simulationOnly: ext.simulationOnly,
8626
- approvalView: ext.approvalView
9403
+ approvalView: ext.approvalView,
9404
+ internal: ext.internal
8627
9405
  };
8628
9406
  }
8629
9407
  return {
8630
- profile: profileId,
9408
+ profile: resolvedProfile,
8631
9409
  executionMapping: override.executionMapping,
8632
9410
  staticExecution: override.staticExecution,
8633
9411
  // Which argument the receipt binds to. This resolver is an explicit
@@ -8653,7 +9431,11 @@ var IntegrationManager = class {
8653
9431
  // this, boundField/requiredValue on a non-"read" override was silently
8654
9432
  // discarded here — the config in the manifest existed but did nothing.
8655
9433
  boundField: ext.boundField,
8656
- requiredValue: ext.requiredValue
9434
+ requiredValue: ext.requiredValue,
9435
+ internal: ext.internal,
9436
+ attachmentsArg: ext.attachmentsArg,
9437
+ insertFields: ext.insertFields,
9438
+ aiTextArg: ext.display?.aiText
8657
9439
  };
8658
9440
  }
8659
9441
  return {
@@ -8784,7 +9566,7 @@ async function buildTicketDetails(archive, ticketIds) {
8784
9566
  }
8785
9567
 
8786
9568
  // src/lib/report/window.ts
8787
- import { getProfile as getProfile7 } from "@hap/core";
9569
+ import { getProfile as getProfile8 } from "@hap/core";
8788
9570
  var REPORT_AGE_FIELD = "read_age_days";
8789
9571
  var REPORTING_PROFILE = "reporting";
8790
9572
  var END_SKEW_SECONDS = 120;
@@ -8798,7 +9580,7 @@ function windowLabel(start, days, loadedAt) {
8798
9580
  if (loadedAt !== null && loadedAt === start) return `${since} (when the test data was loaded)`;
8799
9581
  return `${since} (the last ${days} ${days === 1 ? "day" : "days"})`;
8800
9582
  }
8801
- var defaultLookup = (id) => getProfile7(id);
9583
+ var defaultLookup = (id) => getProfile8(id);
8802
9584
  function reportingMandateRefusal(m, lookup = defaultLookup) {
8803
9585
  const schema = lookup(m.profileId)?.boundsSchema;
8804
9586
  const field = resolveAgeBoundField(schema, REPORT_AGE_FIELD);
@@ -9549,7 +10331,7 @@ function unifiedLineDiff(before, after) {
9549
10331
  }
9550
10332
 
9551
10333
  // src/lib/builtins/setup.ts
9552
- import { getProfile as getProfile8 } from "@hap/core";
10334
+ import { getProfile as getProfile9 } from "@hap/core";
9553
10335
  var DELEGATION_PROFILE = "delegation";
9554
10336
  var DELEGATION_INTENT_HINT = [
9555
10337
  "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.",
@@ -9601,7 +10383,7 @@ function delegationItselfRefusal(args) {
9601
10383
  }
9602
10384
  function delegationMandateRefusal(auth) {
9603
10385
  if (isSimulationMode()) return void 0;
9604
- const profile = getProfile8(auth.profileId);
10386
+ const profile = getProfile9(auth.profileId);
9605
10387
  if (profile?.scopeSchema?.fields?.allowed_profiles) return void 0;
9606
10388
  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.`;
9607
10389
  }
@@ -9659,7 +10441,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
9659
10441
  );
9660
10442
  const first = governing[0];
9661
10443
  if (first) {
9662
- const delegationProfile = getProfile8(first.profileId);
10444
+ const delegationProfile = getProfile9(first.profileId);
9663
10445
  const entry = getConsumptionState(first, deps.state.executionLog, delegationProfile).find((e) => e.field === "mandate_daily_max");
9664
10446
  if (entry && entry.limit !== null) {
9665
10447
  checks.push(`Proposals today: ${entry.current} of ${entry.limit}`);
@@ -9669,7 +10451,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
9669
10451
  }
9670
10452
  function resolveTargetProfile(input) {
9671
10453
  if (typeof input !== "string" || !input.trim()) return void 0;
9672
- const profile = getProfile8(input.trim());
10454
+ const profile = getProfile9(input.trim());
9673
10455
  if (!profile) return void 0;
9674
10456
  return { profile, shortName: shortName(profile.id) };
9675
10457
  }
@@ -9822,7 +10604,7 @@ Read each topic with get_guide(topic) before doing that step.`);
9822
10604
  return { ...builtinText(`Unknown topic "${topic}". Topics: ${guides.map((g) => g.topic).join(", ") || "none installed"}.`), isError: true };
9823
10605
  }
9824
10606
  const body = fillGenerated(guide.body.trim(), {
9825
- limits: () => limitsSection(systems.map((s) => ({ id: s.id, profile: getProfile8(s.profile) }))),
10607
+ limits: () => limitsSection(systems.map((s) => ({ id: s.id, profile: getProfile9(s.profile) }))),
9826
10608
  package: () => packageSection(
9827
10609
  deps.integrationManager.getAllTools().find((t) => !deps.integrationManager.isBuiltin(t.integrationId) && t.originalName === "load_simulation")
9828
10610
  )
@@ -10356,6 +11138,15 @@ app.post("/internal/preview", internalOnly, async (req, res) => {
10356
11138
  });
10357
11139
  res.json(result);
10358
11140
  });
11141
+ app.post("/internal/document-preview", internalOnly, async (req, res) => {
11142
+ const { connector, kind, id, sha256 } = req.body;
11143
+ if (!connector || !kind || !id || !sha256) {
11144
+ res.status(400).json({ error: "Missing connector, kind, id, or sha256" });
11145
+ return;
11146
+ }
11147
+ const result = await resolveAttachmentPreview(integrationManager, { connector, kind, id }, sha256);
11148
+ res.json(result);
11149
+ });
10359
11150
  app.post("/internal/request-context", internalOnly, async (req, res) => {
10360
11151
  const { tool, toolArgs } = req.body;
10361
11152
  if (!tool || typeof toolArgs !== "object" || toolArgs === null) {