@suveren/gateway 0.34.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,6 +4289,92 @@ function invalidManifestContextReason(manifest) {
4316
4289
  }
4317
4290
  return null;
4318
4291
  }
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;
4333
+ const overrides = manifest.toolGating?.overrides ?? {};
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
+ }
4319
4378
  function overrideProfile(entry) {
4320
4379
  if (entry === null || typeof entry !== "object") return void 0;
4321
4380
  const p = entry.profile;
@@ -4334,8 +4393,8 @@ function invalidManifestOverrideProfilesReason(manifest) {
4334
4393
  for (const [toolName, entry] of Object.entries(overrides)) {
4335
4394
  const profile = overrideProfile(entry);
4336
4395
  if (!profile) continue;
4337
- if (!getProfile2(manifest.profile) && !getProfile2(profile)) continue;
4338
- if (!getProfile2(profile)) {
4396
+ if (!getProfile(manifest.profile) && !getProfile(profile)) continue;
4397
+ if (!getProfile(profile)) {
4339
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"`;
4340
4399
  }
4341
4400
  }
@@ -4344,7 +4403,7 @@ function invalidManifestOverrideProfilesReason(manifest) {
4344
4403
  function invalidManifestReadGateReason(manifest) {
4345
4404
  const allOverrides = manifest.toolGating?.overrides ?? {};
4346
4405
  for (const profileId of distinctProfilesOf(manifest)) {
4347
- const profileDef = getProfile2(profileId);
4406
+ const profileDef = getProfile(profileId);
4348
4407
  if (!profileDef) continue;
4349
4408
  const ownOverrides = {};
4350
4409
  for (const [toolName, entry] of Object.entries(allOverrides)) {
@@ -4388,6 +4447,11 @@ function loadManifests(integrationsDir) {
4388
4447
  console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${previewReason}`);
4389
4448
  continue;
4390
4449
  }
4450
+ const internalReason = invalidManifestInternalReason(manifest);
4451
+ if (internalReason) {
4452
+ console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${internalReason}`);
4453
+ continue;
4454
+ }
4391
4455
  const contextReason = invalidManifestContextReason(manifest);
4392
4456
  if (contextReason) {
4393
4457
  console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${contextReason}`);
@@ -4403,6 +4467,21 @@ function loadManifests(integrationsDir) {
4403
4467
  console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${readGateReason}`);
4404
4468
  continue;
4405
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
+ }
4406
4485
  manifests.set(id, manifest);
4407
4486
  loaded++;
4408
4487
  } catch (err) {
@@ -4419,7 +4498,138 @@ function getAllManifests() {
4419
4498
  return Array.from(manifests.values());
4420
4499
  }
4421
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
+
4422
4631
  // src/lib/preview.ts
4632
+ import { computeContentHash } from "@hap/core";
4423
4633
  var PREVIEW_READ_TIMEOUT_MS = 5e3;
4424
4634
  var PREVIEW_MESSAGE_CAP = 500;
4425
4635
  function capText(text, max = PREVIEW_MESSAGE_CAP) {
@@ -4496,9 +4706,9 @@ async function readPreview(integrationManager2, integrationId, previewTool, args
4496
4706
  }
4497
4707
  function hashPreviewBody(body) {
4498
4708
  if (body.structured !== void 0) {
4499
- return computeContentHash2({ kind: "jcs", version: "1" }, body.structured);
4709
+ return computeContentHash({ kind: "jcs", version: "1" }, body.structured);
4500
4710
  }
4501
- return computeContentHash2({ kind: "text", version: "1" }, body.text ?? "");
4711
+ return computeContentHash({ kind: "text", version: "1" }, body.text ?? "");
4502
4712
  }
4503
4713
  function isPlainRecord(v) {
4504
4714
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -4511,6 +4721,7 @@ function planPreviewReads(declaration, actionArgs) {
4511
4721
  attach: config.attach,
4512
4722
  fields: config.fields,
4513
4723
  version: config.version,
4724
+ kind: config.kind,
4514
4725
  argsMapping: config.args
4515
4726
  };
4516
4727
  if (config.forEach) {
@@ -4537,7 +4748,8 @@ async function runPreviewReadPlan(integrationManager2, integrationId, plan) {
4537
4748
  tool: item.tool,
4538
4749
  ...item.attach ? { attach: item.attach } : {},
4539
4750
  ...sourceArgNames.length === 1 ? { sourceArg: sourceArgNames[0] } : {},
4540
- ...item.fields ? { fields: item.fields } : {}
4751
+ ...item.fields ? { fields: item.fields } : {},
4752
+ ...item.kind ? { kind: item.kind } : {}
4541
4753
  };
4542
4754
  if (item.elements !== void 0) {
4543
4755
  const elements = [];
@@ -4636,7 +4848,7 @@ async function combinedPreviewReadsHash(integrationManager2, integrationId, read
4636
4848
  hashes.push(hashPreviewBody(read.body));
4637
4849
  }
4638
4850
  if (hashes.length === 0) return void 0;
4639
- return computeContentHash2({ kind: "jcs", version: "1" }, { reads: hashes });
4851
+ return computeContentHash({ kind: "jcs", version: "1" }, { reads: hashes });
4640
4852
  }
4641
4853
  async function buildInternalPreview(integrationManager2, integrationId, toolName, actionArgs) {
4642
4854
  const preview = getToolPreviewConfig(integrationManager2, integrationId, toolName);
@@ -4686,6 +4898,20 @@ async function buildInternalPreview(integrationManager2, integrationId, toolName
4686
4898
  }
4687
4899
  });
4688
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";
4689
4915
 
4690
4916
  // src/lib/field-path.ts
4691
4917
  function matchesWhere(element, where) {
@@ -5103,6 +5329,447 @@ async function buildRequestContext(integrationManager2, executionJournal, action
5103
5329
  return { request, history };
5104
5330
  }
5105
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
+
5106
5773
  // src/lib/ticket-verify.ts
5107
5774
  import { verifyTicketSignature } from "@hap/core";
5108
5775
  var TicketBindingMismatchError = class extends Error {
@@ -5200,7 +5867,7 @@ async function resolveWhoLabel(archive, domainKey, userId) {
5200
5867
  }
5201
5868
 
5202
5869
  // src/lib/arg-encoding.ts
5203
- function rfc2047Encode(value) {
5870
+ function rfc2047Encode2(value) {
5204
5871
  if (!/[^\x00-\x7F]/.test(value)) return value;
5205
5872
  const CHARSET = "=?UTF-8?Q?";
5206
5873
  const SUFFIX = "?=";
@@ -5234,7 +5901,7 @@ function rfc2047Encode(value) {
5234
5901
  return words.join(" ");
5235
5902
  }
5236
5903
  var ENCODERS = {
5237
- rfc2047: rfc2047Encode
5904
+ rfc2047: rfc2047Encode2
5238
5905
  };
5239
5906
  function encodeOutgoingArgs(tool, args) {
5240
5907
  const declared = tool?.gating?.argEncoding;
@@ -5307,7 +5974,7 @@ function normalizeIncomingArgs(tool, args) {
5307
5974
  }
5308
5975
 
5309
5976
  // src/lib/numeric-execution.ts
5310
- import { getProfile as getProfile3 } from "@hap/core";
5977
+ import { getProfile as getProfile4 } from "@hap/core";
5311
5978
  function parseNumericString(value) {
5312
5979
  if (!/^-?\d+(\.\d+)?$/.test(value)) return void 0;
5313
5980
  const parsed = Number(value);
@@ -5323,7 +5990,7 @@ function declaredNumericFields(profile) {
5323
5990
  return out;
5324
5991
  }
5325
5992
  function coerceDeclaredNumericFields(profileId, execution) {
5326
- const fields = declaredNumericFields(getProfile3(profileId));
5993
+ const fields = declaredNumericFields(getProfile4(profileId));
5327
5994
  for (const field of fields) {
5328
5995
  const value = execution[field];
5329
5996
  if (typeof value !== "string") continue;
@@ -5333,7 +6000,7 @@ function coerceDeclaredNumericFields(profileId, execution) {
5333
6000
  }
5334
6001
 
5335
6002
  // src/lib/tool-proxy.ts
5336
- import { getProfile as getProfile4, ContentBindingError, boundActionTypes as boundActionTypes2 } from "@hap/core";
6003
+ import { getProfile as getProfile5, ContentBindingError, boundActionTypes as boundActionTypes2 } from "@hap/core";
5337
6004
  import { readFile } from "fs/promises";
5338
6005
  import { extname } from "path";
5339
6006
  import { randomUUID } from "crypto";
@@ -5405,7 +6072,7 @@ function profileMatches(profileId, shortName2) {
5405
6072
  return profileId === shortName2 || profileId.includes("/" + shortName2 + "@") || profileId.endsWith("/" + shortName2);
5406
6073
  }
5407
6074
  function wireExecutionContext(profileId, execution) {
5408
- const profile = getProfile4(profileId);
6075
+ const profile = getProfile5(profileId);
5409
6076
  const allowed = /* @__PURE__ */ new Set(["action_type"]);
5410
6077
  const fields = profile?.boundsSchema?.fields;
5411
6078
  if (fields) {
@@ -5423,7 +6090,7 @@ function wireExecutionContext(profileId, execution) {
5423
6090
  return out;
5424
6091
  }
5425
6092
  function countBoundsFor(profileId, actionType) {
5426
- const fields = getProfile4(profileId)?.boundsSchema?.fields;
6093
+ const fields = getProfile5(profileId)?.boundsSchema?.fields;
5427
6094
  if (!fields) return null;
5428
6095
  return Object.entries(fields).filter(([, def]) => def.boundType?.kind === "cumulative_count").filter(([name, def]) => {
5429
6096
  const applies = boundActionTypes2(name, def);
@@ -5498,7 +6165,7 @@ function recordBlocked(state2, tool, rec) {
5498
6165
  function recordGatekeeperRefusal(state2, tool, refusals) {
5499
6166
  if (refusals.length === 0) return;
5500
6167
  const pick = refusals.find((r) => r.bound !== void 0 && r.actual !== void 0) ?? refusals[0];
5501
- 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;
5502
6169
  const asValueOrString = (v) => typeof v === "number" || typeof v === "string" ? v : void 0;
5503
6170
  recordBlocked(state2, tool, {
5504
6171
  kind: scoped ? "scope" : "bound",
@@ -5677,7 +6344,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
5677
6344
  );
5678
6345
  }
5679
6346
  }
5680
- const readProfile = readAdapter ? getProfile4(matchingAuths[0].profileId) : void 0;
6347
+ const readProfile = readAdapter ? getProfile5(matchingAuths[0].profileId) : void 0;
5681
6348
  const boundsSchema = readProfile?.boundsSchema;
5682
6349
  const ageBoundField = readAdapter?.ageField ? resolveAgeBoundField(boundsSchema, readAdapter.ageField) : null;
5683
6350
  const hasAgeDimension = Boolean(readAdapter?.ageField);
@@ -5808,6 +6475,20 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
5808
6475
  });
5809
6476
  }
5810
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
+ }
5811
6492
  const execution = { ...staticExecution };
5812
6493
  for (const [argName, mapping] of Object.entries(executionMapping)) {
5813
6494
  const value = args[argName];
@@ -5824,6 +6505,9 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
5824
6505
  }
5825
6506
  }
5826
6507
  }
6508
+ if (attachmentSourcesForExecution) {
6509
+ execution[ATTACHMENT_SOURCES_FIELD] = attachmentSourcesForExecution;
6510
+ }
5827
6511
  coerceDeclaredNumericFields(profile, execution);
5828
6512
  const declaredActionType = typeof execution.action_type === "string" && execution.action_type.length > 0 ? execution.action_type : void 0;
5829
6513
  if (!declaredActionType) {
@@ -5843,7 +6527,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
5843
6527
  };
5844
6528
  }
5845
6529
  {
5846
- const profileDef = getProfile4(profile);
6530
+ const profileDef = getProfile5(profile);
5847
6531
  const registry = profileDef?.boundsSchema?.actionTypes;
5848
6532
  if (Array.isArray(registry) && registry.length > 0 && !registry.includes(declaredActionType)) {
5849
6533
  recordBlocked(state2, tool, {
@@ -5947,7 +6631,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
5947
6631
  isError: true
5948
6632
  };
5949
6633
  }
5950
- const contextKeys = getProfile4(passers[0].profileId)?.scopeSchema?.keyOrder ?? [];
6634
+ const contextKeys = getProfile5(passers[0].profileId)?.scopeSchema?.keyOrder ?? [];
5951
6635
  return attemptWithCandidates(passers);
5952
6636
  async function attemptWithCandidates(candidates) {
5953
6637
  const selection = selectAuthorization(
@@ -5975,9 +6659,18 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
5975
6659
  isError: true
5976
6660
  };
5977
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
+ }
5978
6671
  if ((auth.deferredCommitmentDomains ?? []).length > 0) {
5979
6672
  try {
5980
- const enrichedArgs = await attachImagePreview(args);
6673
+ const enrichedArgs = await attachImagePreview(callArgs);
5981
6674
  const wireExecCtx = wireExecutionContext(auth.profileId, execution);
5982
6675
  let recipientUserIds = auth.deferredCommitmentDomains.filter((d) => d !== "owner");
5983
6676
  try {
@@ -6031,10 +6724,18 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
6031
6724
  };
6032
6725
  }
6033
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
+ }
6034
6735
  let receiptId;
6035
6736
  try {
6036
6737
  const actionType = declaredActionType;
6037
- const binding = computeContentBinding(auth.profileId, tool, args, actionType);
6738
+ const binding = computeContentBinding(auth.profileId, tool, finalArgs, actionType);
6038
6739
  const idempotencyKey = randomUUID();
6039
6740
  const wireExecCtx = wireExecutionContext(auth.profileId, execution);
6040
6741
  const { receipt } = await state2.spClient.postReceipt({
@@ -6077,10 +6778,12 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
6077
6778
  // would hold a hash of content it cannot reproduce.
6078
6779
  boundContent: binding?.boundContent,
6079
6780
  // UI spec §14 — what this call sends, exactly the object the
6080
- // journal's argsHash is computed from below (`hashToolArgs(args)`),
6081
- // so the ticket view can show it and check it. Same write, so it
6082
- // is as fail-closed and as encrypted as the rest of the entry.
6083
- 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
6084
6787
  });
6085
6788
  } catch (err) {
6086
6789
  if (err instanceof AsKeyMismatchError || err instanceof TicketBindingMismatchError) {
@@ -6172,7 +6875,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
6172
6875
  const uniqueApprovers = [...new Set(pendingApprovers)];
6173
6876
  const recipientUserIds = ownerId ? [.../* @__PURE__ */ new Set([ownerId, ...uniqueApprovers])] : uniqueApprovers;
6174
6877
  try {
6175
- const enrichedArgs = await attachImagePreview(args);
6878
+ const enrichedArgs = await attachImagePreview(callArgs);
6176
6879
  const wireExecCtx = wireExecutionContext(auth.profileId, execution);
6177
6880
  const { proposal, unreadableFor } = await state2.spClient.submitProposal({
6178
6881
  authorizationId: authzId,
@@ -6256,15 +6959,16 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.` + a
6256
6959
  execution: { ...execution },
6257
6960
  timestamp: Math.floor(Date.now() / 1e3)
6258
6961
  });
6259
- 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;
6260
6963
  if (receiptId) outgoingArgs = attachTicketId(tool, outgoingArgs, receiptId);
6261
6964
  outgoingArgs = encodeOutgoingArgs(tool, outgoingArgs);
6262
6965
  outgoingArgs = stripSourceLink(outgoingArgs);
6966
+ outgoingArgs = stripAttachmentsSealedMeta(outgoingArgs);
6263
6967
  if (receiptId) {
6264
6968
  const begun = state2.executionJournal.begin({
6265
6969
  ticketId: receiptId,
6266
6970
  tool: tool.namespacedName,
6267
- argsHash: hashToolArgs(args)
6971
+ argsHash: hashToolArgs(finalArgs)
6268
6972
  });
6269
6973
  if (!begun.ok) {
6270
6974
  return {
@@ -6433,7 +7137,7 @@ function buildMandateBrief(opts) {
6433
7137
  const parts = [];
6434
7138
  if (boundsDesc) parts.push(boundsDesc);
6435
7139
  if (executionLog) {
6436
- const profile = getProfile5(auth.profileId) ?? getProfile5(shortName2);
7140
+ const profile = getProfile6(auth.profileId) ?? getProfile6(shortName2);
6437
7141
  const consumption = getConsumptionState(auth, executionLog, profile);
6438
7142
  const compact = formatConsumptionCompact(consumption);
6439
7143
  if (compact) parts.push(compact);
@@ -6471,7 +7175,7 @@ function buildMandateBrief(opts) {
6471
7175
  }
6472
7176
 
6473
7177
  // src/tools/authorizations.ts
6474
- import { getProfile as getProfile6 } from "@hap/core";
7178
+ import { getProfile as getProfile7 } from "@hap/core";
6475
7179
  function shortProfileName2(profileId) {
6476
7180
  const withoutVersion = profileId.replace(/@.*$/, "");
6477
7181
  const parts = withoutVersion.split("/");
@@ -6590,7 +7294,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
6590
7294
  } catch {
6591
7295
  }
6592
7296
  const shortName2 = shortProfileName2(auth.profileId);
6593
- const profile = getProfile6(auth.profileId) ?? getProfile6(shortName2);
7297
+ const profile = getProfile7(auth.profileId) ?? getProfile7(shortName2);
6594
7298
  if (profile) {
6595
7299
  const consumption = getConsumptionState(auth, state2.executionLog, profile);
6596
7300
  const consumptionText = formatConsumptionFull(consumption);
@@ -6650,7 +7354,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
6650
7354
  lines.push(" Mode: review (each action requires your approval)");
6651
7355
  }
6652
7356
  const shortName2 = shortProfileName2(auth.profileId);
6653
- const profile = getProfile6(auth.profileId) ?? getProfile6(shortName2);
7357
+ const profile = getProfile7(auth.profileId) ?? getProfile7(shortName2);
6654
7358
  if (profile) {
6655
7359
  const consumption = getConsumptionState(auth, state2.executionLog, profile);
6656
7360
  const compact = formatConsumptionCompact(consumption);
@@ -6793,12 +7497,12 @@ function listIntegrationsHandler(state2, integrationManager2) {
6793
7497
  import { ContentBindingError as ContentBindingError2 } from "@hap/core";
6794
7498
 
6795
7499
  // src/lib/proposal-content.ts
6796
- import { createHash as createHash3 } from "crypto";
7500
+ import { createHash as createHash4 } from "crypto";
6797
7501
  import { canonicalize } from "@hap/core";
6798
7502
  var ProposalContentError = class extends Error {
6799
7503
  };
6800
7504
  function computeProposalContentHash(toolArgs) {
6801
- return `sha256:${createHash3("sha256").update(canonicalize(toolArgs)).digest("hex")}`;
7505
+ return `sha256:${createHash4("sha256").update(canonicalize(toolArgs)).digest("hex")}`;
6802
7506
  }
6803
7507
  async function resolveProposalToolArgs(proposal) {
6804
7508
  if (proposal.toolArgs) return proposal.toolArgs;
@@ -7108,8 +7812,23 @@ async function executeCommitted(proposal, state2, integrationManager2) {
7108
7812
  };
7109
7813
  }
7110
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
+ }
7111
7830
  try {
7112
- let outgoingArgs = toolArgs;
7831
+ let outgoingArgs = finalToolArgs;
7113
7832
  if (discovered && receiptId) {
7114
7833
  if (shouldAttachFooter()) {
7115
7834
  outgoingArgs = appendVerificationFooter(discovered, outgoingArgs, receiptId, cachedAuth?.subjects?.[0]);
@@ -7118,6 +7837,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
7118
7837
  outgoingArgs = encodeOutgoingArgs(discovered, outgoingArgs);
7119
7838
  }
7120
7839
  outgoingArgs = stripSourceLink(outgoingArgs);
7840
+ outgoingArgs = stripAttachmentsSealedMeta(outgoingArgs);
7121
7841
  let result;
7122
7842
  try {
7123
7843
  result = await integrationManager2.callTool(integrationId, toolName, outgoingArgs);
@@ -8254,6 +8974,7 @@ var IntegrationManager = class {
8254
8974
  if (gating && gating.category !== "read" && gating.category !== "disabled") {
8255
8975
  inputSchema = withSourceLink(inputSchema);
8256
8976
  }
8977
+ inputSchema = withAttachmentAndInsertSchema(inputSchema, gating ?? void 0);
8257
8978
  return {
8258
8979
  originalName: tool.name,
8259
8980
  namespacedName: `${config.id}__${tool.name}`,
@@ -8261,6 +8982,13 @@ var IntegrationManager = class {
8261
8982
  description: tool.description ?? "",
8262
8983
  inputSchema,
8263
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 } : {},
8264
8992
  gating
8265
8993
  };
8266
8994
  });
@@ -8361,7 +9089,8 @@ var IntegrationManager = class {
8361
9089
  ...t.gating?.approvalView ? { approvalView: t.gating.approvalView } : {},
8362
9090
  // UI spec §14.1 — which sent values the signed ticket also carries
8363
9091
  // (arg → executionContext field), for the "in the ticket" tag.
8364
- ...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 } : {}
8365
9094
  };
8366
9095
  }
8367
9096
  return out;
@@ -8671,7 +9400,8 @@ var IntegrationManager = class {
8671
9400
  blockedArgs: ext.blockedArgs,
8672
9401
  hideUnlessAuthorized: ext.hideUnlessAuthorized,
8673
9402
  simulationOnly: ext.simulationOnly,
8674
- approvalView: ext.approvalView
9403
+ approvalView: ext.approvalView,
9404
+ internal: ext.internal
8675
9405
  };
8676
9406
  }
8677
9407
  return {
@@ -8701,7 +9431,11 @@ var IntegrationManager = class {
8701
9431
  // this, boundField/requiredValue on a non-"read" override was silently
8702
9432
  // discarded here — the config in the manifest existed but did nothing.
8703
9433
  boundField: ext.boundField,
8704
- requiredValue: ext.requiredValue
9434
+ requiredValue: ext.requiredValue,
9435
+ internal: ext.internal,
9436
+ attachmentsArg: ext.attachmentsArg,
9437
+ insertFields: ext.insertFields,
9438
+ aiTextArg: ext.display?.aiText
8705
9439
  };
8706
9440
  }
8707
9441
  return {
@@ -8832,7 +9566,7 @@ async function buildTicketDetails(archive, ticketIds) {
8832
9566
  }
8833
9567
 
8834
9568
  // src/lib/report/window.ts
8835
- import { getProfile as getProfile7 } from "@hap/core";
9569
+ import { getProfile as getProfile8 } from "@hap/core";
8836
9570
  var REPORT_AGE_FIELD = "read_age_days";
8837
9571
  var REPORTING_PROFILE = "reporting";
8838
9572
  var END_SKEW_SECONDS = 120;
@@ -8846,7 +9580,7 @@ function windowLabel(start, days, loadedAt) {
8846
9580
  if (loadedAt !== null && loadedAt === start) return `${since} (when the test data was loaded)`;
8847
9581
  return `${since} (the last ${days} ${days === 1 ? "day" : "days"})`;
8848
9582
  }
8849
- var defaultLookup = (id) => getProfile7(id);
9583
+ var defaultLookup = (id) => getProfile8(id);
8850
9584
  function reportingMandateRefusal(m, lookup = defaultLookup) {
8851
9585
  const schema = lookup(m.profileId)?.boundsSchema;
8852
9586
  const field = resolveAgeBoundField(schema, REPORT_AGE_FIELD);
@@ -9597,7 +10331,7 @@ function unifiedLineDiff(before, after) {
9597
10331
  }
9598
10332
 
9599
10333
  // src/lib/builtins/setup.ts
9600
- import { getProfile as getProfile8 } from "@hap/core";
10334
+ import { getProfile as getProfile9 } from "@hap/core";
9601
10335
  var DELEGATION_PROFILE = "delegation";
9602
10336
  var DELEGATION_INTENT_HINT = [
9603
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.",
@@ -9649,7 +10383,7 @@ function delegationItselfRefusal(args) {
9649
10383
  }
9650
10384
  function delegationMandateRefusal(auth) {
9651
10385
  if (isSimulationMode()) return void 0;
9652
- const profile = getProfile8(auth.profileId);
10386
+ const profile = getProfile9(auth.profileId);
9653
10387
  if (profile?.scopeSchema?.fields?.allowed_profiles) return void 0;
9654
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.`;
9655
10389
  }
@@ -9707,7 +10441,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
9707
10441
  );
9708
10442
  const first = governing[0];
9709
10443
  if (first) {
9710
- const delegationProfile = getProfile8(first.profileId);
10444
+ const delegationProfile = getProfile9(first.profileId);
9711
10445
  const entry = getConsumptionState(first, deps.state.executionLog, delegationProfile).find((e) => e.field === "mandate_daily_max");
9712
10446
  if (entry && entry.limit !== null) {
9713
10447
  checks.push(`Proposals today: ${entry.current} of ${entry.limit}`);
@@ -9717,7 +10451,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
9717
10451
  }
9718
10452
  function resolveTargetProfile(input) {
9719
10453
  if (typeof input !== "string" || !input.trim()) return void 0;
9720
- const profile = getProfile8(input.trim());
10454
+ const profile = getProfile9(input.trim());
9721
10455
  if (!profile) return void 0;
9722
10456
  return { profile, shortName: shortName(profile.id) };
9723
10457
  }
@@ -9870,7 +10604,7 @@ Read each topic with get_guide(topic) before doing that step.`);
9870
10604
  return { ...builtinText(`Unknown topic "${topic}". Topics: ${guides.map((g) => g.topic).join(", ") || "none installed"}.`), isError: true };
9871
10605
  }
9872
10606
  const body = fillGenerated(guide.body.trim(), {
9873
- 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) }))),
9874
10608
  package: () => packageSection(
9875
10609
  deps.integrationManager.getAllTools().find((t) => !deps.integrationManager.isBuiltin(t.integrationId) && t.originalName === "load_simulation")
9876
10610
  )
@@ -10404,6 +11138,15 @@ app.post("/internal/preview", internalOnly, async (req, res) => {
10404
11138
  });
10405
11139
  res.json(result);
10406
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
+ });
10407
11150
  app.post("/internal/request-context", internalOnly, async (req, res) => {
10408
11151
  const { tool, toolArgs } = req.body;
10409
11152
  if (!tool || typeof toolArgs !== "object" || toolArgs === null) {