@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.
- package/content/integrations/erp.json +151 -2
- package/content/integrations/gmail.json +8 -1
- package/content/integrations/mail.json +9 -2
- package/content/integrations/mollie.json +344 -80
- package/dist/control-plane/index.mjs +79 -0
- package/dist/mcp-server/http.mjs +915 -124
- package/dist/ui/assets/index-M1sLTJjt.js +123 -0
- package/dist/ui/assets/{index-BiGnI2Tk.css → index-c8cmIEPx.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/profiles/charge/0.7.profile.json +232 -0
- package/profiles/email/0.9.profile.json +252 -0
- package/profiles/index.json +4 -1
- package/profiles/invoicing/0.1.profile.json +259 -0
- package/dist/ui/assets/index-Bev2H2zu.js +0 -123
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
4320
|
-
const
|
|
4321
|
-
|
|
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
|
-
|
|
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
|
|
4709
|
+
return computeContentHash({ kind: "jcs", version: "1" }, body.structured);
|
|
4461
4710
|
}
|
|
4462
|
-
return
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
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(
|
|
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,
|
|
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
|
|
6042
|
-
// so the ticket view can show it and
|
|
6043
|
-
//
|
|
6044
|
-
|
|
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(
|
|
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,
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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).
|
|
7462
|
+
const integrations = statuses.filter((s) => s.running).flatMap((s) => {
|
|
6720
7463
|
const tools = integrationManager2.getAllTools().filter((t) => t.integrationId === s.id);
|
|
6721
|
-
const
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
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
|
|
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:${
|
|
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 =
|
|
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:
|
|
9388
|
+
return { profile: resolvedProfile, executionMapping: {}, category: "disabled" };
|
|
8612
9389
|
}
|
|
8613
9390
|
if (override === null || ext.category === "read") {
|
|
8614
9391
|
return {
|
|
8615
|
-
profile:
|
|
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:
|
|
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
|
|
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) =>
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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) {
|