@suveren/gateway 0.27.1 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/integrations/crm.json +11 -1
- package/content/integrations/erp.json +40 -2
- package/dist/mcp-server/http.mjs +298 -61
- package/dist/ui/assets/index-I9WCdLMY.js +120 -0
- package/dist/ui/assets/index-RX5juZkm.css +1 -0
- package/dist/ui/index.html +2 -2
- package/node_modules/@hono/node-server/README.md +2 -0
- package/node_modules/@hono/node-server/dist/index.cjs +1 -1
- package/node_modules/@hono/node-server/dist/index.mjs +1 -1
- package/node_modules/@hono/node-server/dist/serve-static.cjs +2 -1
- package/node_modules/@hono/node-server/dist/serve-static.mjs +2 -1
- package/node_modules/@hono/node-server/package.json +1 -1
- package/node_modules/postcss/lib/processor.js +1 -1
- package/node_modules/postcss/lib/stringify.js +4 -2
- package/node_modules/postcss/package.json +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-DKOr7_IY.js +0 -120
- package/dist/ui/assets/index-DeEzJCqI.css +0 -1
|
@@ -54,7 +54,17 @@
|
|
|
54
54
|
"staticExecution": {
|
|
55
55
|
"action_type": "write"
|
|
56
56
|
},
|
|
57
|
-
"actionLabel": "Contact created"
|
|
57
|
+
"actionLabel": "Contact created",
|
|
58
|
+
"preview": [
|
|
59
|
+
{ "tool": "find_contacts", "args": { "query": "email" } }
|
|
60
|
+
],
|
|
61
|
+
"display": {
|
|
62
|
+
"order": ["name", "email", "company", "role", "type", "stage", "tags", "notes"],
|
|
63
|
+
"labels": {
|
|
64
|
+
"name": "Name", "email": "Email", "company": "Company", "role": "Role",
|
|
65
|
+
"type": "Type", "stage": "Stage", "tags": "Tags", "notes": "Notes"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
58
68
|
},
|
|
59
69
|
"update_contact": {
|
|
60
70
|
"executionMapping": {
|
|
@@ -95,7 +95,29 @@
|
|
|
95
95
|
"staticExecution": {
|
|
96
96
|
"action_type": "quote"
|
|
97
97
|
},
|
|
98
|
-
"actionLabel": "Quote drafted"
|
|
98
|
+
"actionLabel": "Quote drafted",
|
|
99
|
+
"preview": [
|
|
100
|
+
{ "tool": "get_customer", "args": { "id": "customer_id" }, "attach": "customer_id" },
|
|
101
|
+
{ "tool": "get_item", "forEach": "lines", "args": { "id": "item_id" }, "attach": "item" }
|
|
102
|
+
],
|
|
103
|
+
"display": {
|
|
104
|
+
"order": ["customer_id", "lines", "discount_pct", "value", "valid_until", "notes"],
|
|
105
|
+
"money": ["value", "customer_id.credit_limit", "customer_id.open_balance", "customer_id.available_credit", "lines[].item.list_price"],
|
|
106
|
+
"percent": ["discount_pct"],
|
|
107
|
+
"currencyField": "currency",
|
|
108
|
+
"labels": {
|
|
109
|
+
"customer_id": "Customer",
|
|
110
|
+
"discount_pct": "Discount",
|
|
111
|
+
"value": "Total (net)",
|
|
112
|
+
"customer_id.credit_limit": "Credit limit",
|
|
113
|
+
"customer_id.open_balance": "Open balance",
|
|
114
|
+
"customer_id.available_credit": "Available credit",
|
|
115
|
+
"customer_id.payment_terms": "Payment terms",
|
|
116
|
+
"lines[].item": "Item",
|
|
117
|
+
"lines[].item.list_price": "List price",
|
|
118
|
+
"lines[].item.stock": "In stock"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
99
121
|
},
|
|
100
122
|
"update_quote": {
|
|
101
123
|
"executionMapping": {
|
|
@@ -106,7 +128,23 @@
|
|
|
106
128
|
"staticExecution": {
|
|
107
129
|
"action_type": "quote"
|
|
108
130
|
},
|
|
109
|
-
"actionLabel": "Quote updated"
|
|
131
|
+
"actionLabel": "Quote updated",
|
|
132
|
+
"preview": [
|
|
133
|
+
{ "tool": "get_item", "forEach": "lines", "args": { "id": "item_id" }, "attach": "item" }
|
|
134
|
+
],
|
|
135
|
+
"display": {
|
|
136
|
+
"order": ["lines", "discount_pct", "value", "valid_until", "notes"],
|
|
137
|
+
"money": ["value", "lines[].item.list_price"],
|
|
138
|
+
"percent": ["discount_pct"],
|
|
139
|
+
"currencyField": "currency",
|
|
140
|
+
"labels": {
|
|
141
|
+
"discount_pct": "Discount",
|
|
142
|
+
"value": "Total (net)",
|
|
143
|
+
"lines[].item": "Item",
|
|
144
|
+
"lines[].item.list_price": "List price",
|
|
145
|
+
"lines[].item.stock": "In stock"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
110
148
|
},
|
|
111
149
|
"send_quote": {
|
|
112
150
|
"executionMapping": {
|
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -3469,6 +3469,12 @@ import { computeContentHash as computeContentHash2 } from "@hap/core";
|
|
|
3469
3469
|
// src/lib/manifest-loader.ts
|
|
3470
3470
|
import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
3471
3471
|
import { join as join14, resolve } from "path";
|
|
3472
|
+
|
|
3473
|
+
// src/lib/tool-gating-types.ts
|
|
3474
|
+
var MAX_PREVIEW_READS = 10;
|
|
3475
|
+
var MAX_FOREACH_ELEMENTS = 50;
|
|
3476
|
+
|
|
3477
|
+
// src/lib/manifest-loader.ts
|
|
3472
3478
|
var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
3473
3479
|
function isExactSemver(value) {
|
|
3474
3480
|
return typeof value === "string" && EXACT_SEMVER_RE.test(value);
|
|
@@ -3483,44 +3489,101 @@ function invalidNpmPinReason(manifest) {
|
|
|
3483
3489
|
}
|
|
3484
3490
|
return null;
|
|
3485
3491
|
}
|
|
3486
|
-
function
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
if (preview === null || typeof preview !== "object" || Array.isArray(preview)) {
|
|
3491
|
-
return `override "${toolName}".preview must be an object`;
|
|
3492
|
+
function invalidReadConfigReason(toolName, idx, entry) {
|
|
3493
|
+
const label = idx === null ? `override "${toolName}".preview` : `override "${toolName}".preview[${idx}]`;
|
|
3494
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
3495
|
+
return `${label} must be an object`;
|
|
3492
3496
|
}
|
|
3493
|
-
const p =
|
|
3497
|
+
const p = entry;
|
|
3494
3498
|
if (typeof p.tool !== "string" || p.tool.length === 0) {
|
|
3495
|
-
return
|
|
3499
|
+
return `${label}.tool must be a non-empty string`;
|
|
3496
3500
|
}
|
|
3497
3501
|
if (p.args === void 0 || p.args === null || typeof p.args !== "object" || Array.isArray(p.args)) {
|
|
3498
|
-
return
|
|
3502
|
+
return `${label}.args must be an object mapping preview-tool arg names to action arg names`;
|
|
3499
3503
|
}
|
|
3500
3504
|
for (const [argName, source] of Object.entries(p.args)) {
|
|
3501
3505
|
if (typeof source !== "string" || source.length === 0) {
|
|
3502
|
-
return
|
|
3506
|
+
return `${label}.args.${argName} must name a non-empty source argument (string)`;
|
|
3503
3507
|
}
|
|
3504
3508
|
}
|
|
3509
|
+
if (p.forEach !== void 0 && (typeof p.forEach !== "string" || p.forEach.length === 0)) {
|
|
3510
|
+
return `${label}.forEach must be a non-empty string naming an action argument`;
|
|
3511
|
+
}
|
|
3512
|
+
if (p.attach !== void 0 && (typeof p.attach !== "string" || p.attach.length === 0)) {
|
|
3513
|
+
return `${label}.attach must be a non-empty string`;
|
|
3514
|
+
}
|
|
3505
3515
|
if (p.version !== void 0) {
|
|
3506
3516
|
if (p.version === null || typeof p.version !== "object" || Array.isArray(p.version)) {
|
|
3507
|
-
return
|
|
3517
|
+
return `${label}.version must be an object`;
|
|
3508
3518
|
}
|
|
3509
3519
|
const v = p.version;
|
|
3510
3520
|
if (typeof v.arg !== "string" || v.arg.length === 0) {
|
|
3511
|
-
return
|
|
3521
|
+
return `${label}.version.arg must be a non-empty string`;
|
|
3512
3522
|
}
|
|
3513
3523
|
if (typeof v.field !== "string" || v.field.length === 0) {
|
|
3514
|
-
return
|
|
3524
|
+
return `${label}.version.field must be a non-empty string`;
|
|
3515
3525
|
}
|
|
3516
3526
|
}
|
|
3517
3527
|
if (p.fields !== void 0) {
|
|
3518
3528
|
if (!Array.isArray(p.fields)) {
|
|
3519
|
-
return
|
|
3529
|
+
return `${label}.fields must be an array of field names`;
|
|
3520
3530
|
}
|
|
3521
3531
|
for (const [i, f] of p.fields.entries()) {
|
|
3522
3532
|
if (typeof f !== "string" || f.length === 0) {
|
|
3523
|
-
return
|
|
3533
|
+
return `${label}.fields[${i}] must be a non-empty string`;
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
return null;
|
|
3538
|
+
}
|
|
3539
|
+
function invalidPreviewReason(toolName, entry) {
|
|
3540
|
+
if (entry === null || typeof entry !== "object") return null;
|
|
3541
|
+
const preview = entry.preview;
|
|
3542
|
+
if (preview === void 0) return null;
|
|
3543
|
+
if (Array.isArray(preview)) {
|
|
3544
|
+
if (preview.length === 0) {
|
|
3545
|
+
return `override "${toolName}".preview must not be an empty list \u2014 omit it, or use the single-object form`;
|
|
3546
|
+
}
|
|
3547
|
+
if (preview.length > MAX_PREVIEW_READS) {
|
|
3548
|
+
return `override "${toolName}".preview declares ${preview.length} reads \u2014 max ${MAX_PREVIEW_READS}`;
|
|
3549
|
+
}
|
|
3550
|
+
for (const [i, read] of preview.entries()) {
|
|
3551
|
+
const reason = invalidReadConfigReason(toolName, i, read);
|
|
3552
|
+
if (reason) return reason;
|
|
3553
|
+
}
|
|
3554
|
+
return null;
|
|
3555
|
+
}
|
|
3556
|
+
return invalidReadConfigReason(toolName, null, preview);
|
|
3557
|
+
}
|
|
3558
|
+
function invalidDisplayHintsReason(toolName, entry) {
|
|
3559
|
+
if (entry === null || typeof entry !== "object") return null;
|
|
3560
|
+
const display = entry.display;
|
|
3561
|
+
if (display === void 0) return null;
|
|
3562
|
+
if (display === null || typeof display !== "object" || Array.isArray(display)) {
|
|
3563
|
+
return `override "${toolName}".display must be an object`;
|
|
3564
|
+
}
|
|
3565
|
+
const d = display;
|
|
3566
|
+
for (const key of ["order", "money", "percent", "hidden"]) {
|
|
3567
|
+
if (d[key] === void 0) continue;
|
|
3568
|
+
if (!Array.isArray(d[key])) {
|
|
3569
|
+
return `override "${toolName}".display.${key} must be an array of field paths`;
|
|
3570
|
+
}
|
|
3571
|
+
for (const [i, v] of d[key].entries()) {
|
|
3572
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
3573
|
+
return `override "${toolName}".display.${key}[${i}] must be a non-empty string`;
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
if (d.currencyField !== void 0 && (typeof d.currencyField !== "string" || d.currencyField.length === 0)) {
|
|
3578
|
+
return `override "${toolName}".display.currencyField must be a non-empty string`;
|
|
3579
|
+
}
|
|
3580
|
+
if (d.labels !== void 0) {
|
|
3581
|
+
if (d.labels === null || typeof d.labels !== "object" || Array.isArray(d.labels)) {
|
|
3582
|
+
return `override "${toolName}".display.labels must be an object mapping field paths to labels`;
|
|
3583
|
+
}
|
|
3584
|
+
for (const [path, label] of Object.entries(d.labels)) {
|
|
3585
|
+
if (typeof label !== "string" || label.length === 0) {
|
|
3586
|
+
return `override "${toolName}".display.labels.${path} must be a non-empty string`;
|
|
3524
3587
|
}
|
|
3525
3588
|
}
|
|
3526
3589
|
}
|
|
@@ -3529,7 +3592,7 @@ function invalidPreviewReason(toolName, entry) {
|
|
|
3529
3592
|
function invalidManifestPreviewReason(manifest) {
|
|
3530
3593
|
const overrides = manifest.toolGating?.overrides ?? {};
|
|
3531
3594
|
for (const [toolName, entry] of Object.entries(overrides)) {
|
|
3532
|
-
const reason = invalidPreviewReason(toolName, entry);
|
|
3595
|
+
const reason = invalidPreviewReason(toolName, entry) ?? invalidDisplayHintsReason(toolName, entry);
|
|
3533
3596
|
if (reason) return reason;
|
|
3534
3597
|
}
|
|
3535
3598
|
return null;
|
|
@@ -3598,6 +3661,13 @@ function getToolPreviewConfig(integrationManager2, integrationId, toolName) {
|
|
|
3598
3661
|
const entry = overrides[toolName];
|
|
3599
3662
|
return entry?.preview;
|
|
3600
3663
|
}
|
|
3664
|
+
function getToolDisplayHints(integrationManager2, integrationId, toolName) {
|
|
3665
|
+
if (integrationManager2.isBuiltin(integrationId)) return void 0;
|
|
3666
|
+
const manifest = getManifest(integrationId);
|
|
3667
|
+
const overrides = manifest?.toolGating?.overrides ?? {};
|
|
3668
|
+
const entry = overrides[toolName];
|
|
3669
|
+
return entry?.display;
|
|
3670
|
+
}
|
|
3601
3671
|
function mapPreviewArgs(preview, actionArgs) {
|
|
3602
3672
|
const mapped = {};
|
|
3603
3673
|
for (const [targetArg, sourceArg] of Object.entries(preview.args)) {
|
|
@@ -3656,10 +3726,105 @@ function hashPreviewBody(body) {
|
|
|
3656
3726
|
}
|
|
3657
3727
|
return computeContentHash2({ kind: "text", version: "1" }, body.text ?? "");
|
|
3658
3728
|
}
|
|
3729
|
+
function isPlainRecord(v) {
|
|
3730
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3731
|
+
}
|
|
3732
|
+
function planPreviewReads(declaration, actionArgs) {
|
|
3733
|
+
const reads = Array.isArray(declaration) ? declaration : [declaration];
|
|
3734
|
+
return reads.map((config) => {
|
|
3735
|
+
const base = {
|
|
3736
|
+
tool: config.tool,
|
|
3737
|
+
attach: config.attach,
|
|
3738
|
+
fields: config.fields,
|
|
3739
|
+
version: config.version,
|
|
3740
|
+
argsMapping: config.args
|
|
3741
|
+
};
|
|
3742
|
+
if (config.forEach) {
|
|
3743
|
+
const source = actionArgs[config.forEach];
|
|
3744
|
+
const list = Array.isArray(source) ? source : [];
|
|
3745
|
+
const capped = list.slice(0, MAX_FOREACH_ELEMENTS);
|
|
3746
|
+
const elements = capped.filter(isPlainRecord).map((el) => ({ args: mapPreviewArgs(config, el) }));
|
|
3747
|
+
return {
|
|
3748
|
+
...base,
|
|
3749
|
+
forEach: config.forEach,
|
|
3750
|
+
elements,
|
|
3751
|
+
...list.length > MAX_FOREACH_ELEMENTS ? { overflow: { shown: MAX_FOREACH_ELEMENTS, total: list.length } } : {}
|
|
3752
|
+
};
|
|
3753
|
+
}
|
|
3754
|
+
const mapped = mapPreviewArgs(config, actionArgs);
|
|
3755
|
+
return { ...base, args: mapped, rawSource: actionArgs, noTarget: previewHasNoTarget(config, mapped) };
|
|
3756
|
+
});
|
|
3757
|
+
}
|
|
3758
|
+
async function runPreviewReadPlan(integrationManager2, integrationId, plan) {
|
|
3759
|
+
const sections = [];
|
|
3760
|
+
for (const item of plan) {
|
|
3761
|
+
const sourceArgNames = Object.values(item.argsMapping);
|
|
3762
|
+
const base = {
|
|
3763
|
+
tool: item.tool,
|
|
3764
|
+
...item.attach ? { attach: item.attach } : {},
|
|
3765
|
+
...sourceArgNames.length === 1 ? { sourceArg: sourceArgNames[0] } : {},
|
|
3766
|
+
...item.fields ? { fields: item.fields } : {}
|
|
3767
|
+
};
|
|
3768
|
+
if (item.elements !== void 0) {
|
|
3769
|
+
const elements = [];
|
|
3770
|
+
for (const el of item.elements) {
|
|
3771
|
+
elements.push(await readPreview(integrationManager2, integrationId, item.tool, el.args));
|
|
3772
|
+
}
|
|
3773
|
+
sections.push({
|
|
3774
|
+
...base,
|
|
3775
|
+
forEach: item.forEach,
|
|
3776
|
+
elements,
|
|
3777
|
+
...item.overflow ? { overflow: item.overflow } : {}
|
|
3778
|
+
});
|
|
3779
|
+
continue;
|
|
3780
|
+
}
|
|
3781
|
+
if (item.noTarget) {
|
|
3782
|
+
sections.push({ ...base, result: { status: "none", reason: "no_target" } });
|
|
3783
|
+
continue;
|
|
3784
|
+
}
|
|
3785
|
+
const read1 = await readPreview(integrationManager2, integrationId, item.tool, item.args);
|
|
3786
|
+
if (read1.status !== "ok" || !item.version) {
|
|
3787
|
+
sections.push({ ...base, result: read1 });
|
|
3788
|
+
continue;
|
|
3789
|
+
}
|
|
3790
|
+
const approved = item.rawSource?.[item.version.arg];
|
|
3791
|
+
if (typeof approved !== "string" && typeof approved !== "number") {
|
|
3792
|
+
sections.push({ ...base, result: read1 });
|
|
3793
|
+
continue;
|
|
3794
|
+
}
|
|
3795
|
+
const { [item.version.arg]: _omit, ...withoutVersion } = item.rawSource;
|
|
3796
|
+
const mappedCurrentArgs = mapPreviewArgs({ args: item.argsMapping }, withoutVersion);
|
|
3797
|
+
const read2 = await readPreview(integrationManager2, integrationId, item.tool, mappedCurrentArgs);
|
|
3798
|
+
if (read2.status !== "ok" || read2.body.structured === void 0) {
|
|
3799
|
+
sections.push({ ...base, result: read1 });
|
|
3800
|
+
continue;
|
|
3801
|
+
}
|
|
3802
|
+
const current = read2.body.structured[item.version.field];
|
|
3803
|
+
if (typeof current !== "string" && typeof current !== "number") {
|
|
3804
|
+
sections.push({ ...base, result: read1 });
|
|
3805
|
+
continue;
|
|
3806
|
+
}
|
|
3807
|
+
const stale = String(current) !== String(approved);
|
|
3808
|
+
sections.push({
|
|
3809
|
+
...base,
|
|
3810
|
+
result: read1,
|
|
3811
|
+
version: {
|
|
3812
|
+
field: item.version.field,
|
|
3813
|
+
approved,
|
|
3814
|
+
current,
|
|
3815
|
+
stale,
|
|
3816
|
+
...stale ? { currentBody: read2.body } : {}
|
|
3817
|
+
}
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
3820
|
+
return sections;
|
|
3821
|
+
}
|
|
3659
3822
|
async function computeSubmissionPreviewHash(integrationManager2, integrationId, toolName, actionArgs) {
|
|
3660
3823
|
if (integrationManager2.isBuiltin(integrationId)) return void 0;
|
|
3661
3824
|
const preview = getToolPreviewConfig(integrationManager2, integrationId, toolName);
|
|
3662
|
-
if (!preview
|
|
3825
|
+
if (!preview) return void 0;
|
|
3826
|
+
if (Array.isArray(preview)) return combinedPreviewReadsHash(integrationManager2, integrationId, preview, actionArgs);
|
|
3827
|
+
if (preview.version) return void 0;
|
|
3663
3828
|
const mapped = mapPreviewArgs(preview, actionArgs);
|
|
3664
3829
|
if (previewHasNoTarget(preview, mapped)) return void 0;
|
|
3665
3830
|
const read = await readPreview(integrationManager2, integrationId, preview.tool, mapped);
|
|
@@ -3669,14 +3834,44 @@ async function computeSubmissionPreviewHash(integrationManager2, integrationId,
|
|
|
3669
3834
|
async function previewChangedSinceSubmission(integrationManager2, integrationId, toolName, actionArgs, previousHash) {
|
|
3670
3835
|
const preview = getToolPreviewConfig(integrationManager2, integrationId, toolName);
|
|
3671
3836
|
if (!preview) return true;
|
|
3837
|
+
if (Array.isArray(preview)) {
|
|
3838
|
+
const hash = await combinedPreviewReadsHash(integrationManager2, integrationId, preview, actionArgs);
|
|
3839
|
+
if (hash === void 0) return true;
|
|
3840
|
+
return hash !== previousHash;
|
|
3841
|
+
}
|
|
3672
3842
|
const mapped = mapPreviewArgs(preview, actionArgs);
|
|
3673
3843
|
const read = await readPreview(integrationManager2, integrationId, preview.tool, mapped);
|
|
3674
3844
|
if (read.status !== "ok") return true;
|
|
3675
3845
|
return hashPreviewBody(read.body) !== previousHash;
|
|
3676
3846
|
}
|
|
3847
|
+
async function combinedPreviewReadsHash(integrationManager2, integrationId, reads, actionArgs) {
|
|
3848
|
+
const plan = planPreviewReads(reads, actionArgs).filter((item) => !item.version);
|
|
3849
|
+
const hashes = [];
|
|
3850
|
+
for (const item of plan) {
|
|
3851
|
+
if (item.elements !== void 0) {
|
|
3852
|
+
for (const el of item.elements) {
|
|
3853
|
+
const read2 = await readPreview(integrationManager2, integrationId, item.tool, el.args);
|
|
3854
|
+
if (read2.status !== "ok") return void 0;
|
|
3855
|
+
hashes.push(hashPreviewBody(read2.body));
|
|
3856
|
+
}
|
|
3857
|
+
continue;
|
|
3858
|
+
}
|
|
3859
|
+
if (item.noTarget) continue;
|
|
3860
|
+
const read = await readPreview(integrationManager2, integrationId, item.tool, item.args);
|
|
3861
|
+
if (read.status !== "ok") return void 0;
|
|
3862
|
+
hashes.push(hashPreviewBody(read.body));
|
|
3863
|
+
}
|
|
3864
|
+
if (hashes.length === 0) return void 0;
|
|
3865
|
+
return computeContentHash2({ kind: "jcs", version: "1" }, { reads: hashes });
|
|
3866
|
+
}
|
|
3677
3867
|
async function buildInternalPreview(integrationManager2, integrationId, toolName, actionArgs) {
|
|
3678
3868
|
const preview = getToolPreviewConfig(integrationManager2, integrationId, toolName);
|
|
3679
3869
|
if (!preview) return { status: "none" };
|
|
3870
|
+
if (Array.isArray(preview)) {
|
|
3871
|
+
const reads = await runPreviewReadPlan(integrationManager2, integrationId, planPreviewReads(preview, actionArgs));
|
|
3872
|
+
const display = getToolDisplayHints(integrationManager2, integrationId, toolName);
|
|
3873
|
+
return { status: "ok", integration: integrationId, reads, ...display ? { display } : {} };
|
|
3874
|
+
}
|
|
3680
3875
|
const mappedArgs = mapPreviewArgs(preview, actionArgs);
|
|
3681
3876
|
if (previewHasNoTarget(preview, mappedArgs)) return { status: "none", reason: "no_target" };
|
|
3682
3877
|
const read1 = await readPreview(integrationManager2, integrationId, preview.tool, mappedArgs);
|
|
@@ -5616,14 +5811,16 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
5616
5811
|
}
|
|
5617
5812
|
let receiptId;
|
|
5618
5813
|
let replayed = false;
|
|
5814
|
+
let binding;
|
|
5815
|
+
let receipt;
|
|
5619
5816
|
try {
|
|
5620
|
-
|
|
5817
|
+
binding = computeContentBinding(
|
|
5621
5818
|
proposal.profileId,
|
|
5622
5819
|
discovered,
|
|
5623
5820
|
toolArgs,
|
|
5624
5821
|
proposalActionType
|
|
5625
5822
|
);
|
|
5626
|
-
const { receipt, idempotent } = await state2.spClient.postReceipt({
|
|
5823
|
+
const { receipt: r, idempotent } = await state2.spClient.postReceipt({
|
|
5627
5824
|
authorizationId: proposal.authorizationId,
|
|
5628
5825
|
// Optional cross-check — the AS fails closed on a mismatch. Parity
|
|
5629
5826
|
// with the automatic path (tool-proxy.ts).
|
|
@@ -5652,48 +5849,10 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
5652
5849
|
// this machine — see the archive call below.
|
|
5653
5850
|
...binding ? { contentHash: binding.contentHash, contentBinding: binding.contentBinding } : {}
|
|
5654
5851
|
});
|
|
5852
|
+
receipt = r;
|
|
5655
5853
|
receiptId = typeof receipt?.id === "string" ? receipt.id : void 0;
|
|
5656
5854
|
replayed = idempotent;
|
|
5657
|
-
await verifyTicket(state2.cache, receipt, {
|
|
5658
|
-
action: proposal.tool,
|
|
5659
|
-
executionContext: proposal.executionContext,
|
|
5660
|
-
authorizationId: proposal.authorizationId,
|
|
5661
|
-
profileId: proposal.profileId,
|
|
5662
|
-
proposalId: proposal.id,
|
|
5663
|
-
contentHash: binding?.contentHash,
|
|
5664
|
-
contentBinding: binding?.contentBinding
|
|
5665
|
-
});
|
|
5666
|
-
await state2.archiveReceipt(receipt, {
|
|
5667
|
-
authorizationId: proposal.authorizationId,
|
|
5668
|
-
profileId: proposal.profileId,
|
|
5669
|
-
boundsHash: cachedAuth?.boundsHash,
|
|
5670
|
-
contextHash: cachedAuth?.contextHash,
|
|
5671
|
-
bounds: cachedAuth?.bounds ?? cachedAuth?.frame,
|
|
5672
|
-
context: cachedAuth?.context,
|
|
5673
|
-
attestations: cachedAuth?.attestations,
|
|
5674
|
-
// What the human actually approved, kept next to the receipt that cites
|
|
5675
|
-
// it — otherwise `proposalId` is a pointer to a server we may not have.
|
|
5676
|
-
// PE1: the archived copy carries the RESOLVED (decrypted on an
|
|
5677
|
-
// encrypted row) toolArgs, never the sealed `content` — this archive
|
|
5678
|
-
// is this gateway's own local evidence and has always stored it
|
|
5679
|
-
// readable, same as every legacy row.
|
|
5680
|
-
proposal: { ...proposal, toolArgs },
|
|
5681
|
-
boundContent: binding?.boundContent
|
|
5682
|
-
});
|
|
5683
5855
|
} catch (err) {
|
|
5684
|
-
if (err instanceof AsKeyMismatchError || err instanceof TicketBindingMismatchError) {
|
|
5685
|
-
void notifyControlPlane("as-key-mismatch");
|
|
5686
|
-
return {
|
|
5687
|
-
text: `Proposal ${proposal.id}: blocked \u2014 ${err.message}`,
|
|
5688
|
-
isError: true
|
|
5689
|
-
};
|
|
5690
|
-
}
|
|
5691
|
-
if (err instanceof ArchiveWriteError) {
|
|
5692
|
-
return {
|
|
5693
|
-
text: `Proposal ${proposal.id}: blocked \u2014 the ticket could not be archived locally \u2014 ${err.message}. The action was not executed.`,
|
|
5694
|
-
isError: true
|
|
5695
|
-
};
|
|
5696
|
-
}
|
|
5697
5856
|
if (err instanceof ContentBindingError2) {
|
|
5698
5857
|
return {
|
|
5699
5858
|
text: `Proposal ${proposal.id}: blocked \u2014 the approved content cannot be bound to the receipt. ${err.message}`,
|
|
@@ -5709,6 +5868,12 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
5709
5868
|
text: `Proposal ${proposal.id} has already been executed by another request.`
|
|
5710
5869
|
};
|
|
5711
5870
|
}
|
|
5871
|
+
if (code === "PROPOSAL_REFUSED") {
|
|
5872
|
+
return {
|
|
5873
|
+
text: `Proposal ${proposal.id}: the Authority Server refused this proposal's ticket earlier \u2014 ${err.message} Nothing ran. Do not retry the same values; ask the person (e.g. for a higher limit).`,
|
|
5874
|
+
isError: true
|
|
5875
|
+
};
|
|
5876
|
+
}
|
|
5712
5877
|
if (err.statusCode === 409 && code === "VERSION_UNSUPPORTED") {
|
|
5713
5878
|
state2.cache.markNeedsReapproval(proposal.authorizationId);
|
|
5714
5879
|
return {
|
|
@@ -5716,8 +5881,20 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
5716
5881
|
isError: true
|
|
5717
5882
|
};
|
|
5718
5883
|
}
|
|
5884
|
+
if (proposal.id) {
|
|
5885
|
+
const asRefusedTicketId = `as_refused:${proposal.id}`;
|
|
5886
|
+
const begun2 = state2.executionJournal.begin({
|
|
5887
|
+
ticketId: asRefusedTicketId,
|
|
5888
|
+
proposalId: proposal.id,
|
|
5889
|
+
tool: proposal.tool,
|
|
5890
|
+
argsHash: hashToolArgs(toolArgs)
|
|
5891
|
+
});
|
|
5892
|
+
if (begun2.ok) {
|
|
5893
|
+
state2.executionJournal.complete(asRefusedTicketId, "failed", "as_refused", `${code ?? "TICKET_REFUSED"}: ${err.message}`);
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5719
5896
|
return {
|
|
5720
|
-
text: `Proposal ${proposal.id}:
|
|
5897
|
+
text: `Proposal ${proposal.id}: the Authority Server refused its ticket \u2014 ${err.message} Nothing ran. Do not retry the same values; ask the person (e.g. for a higher limit).`,
|
|
5721
5898
|
isError: true
|
|
5722
5899
|
};
|
|
5723
5900
|
}
|
|
@@ -5751,6 +5928,54 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
5751
5928
|
isError: true
|
|
5752
5929
|
};
|
|
5753
5930
|
}
|
|
5931
|
+
try {
|
|
5932
|
+
await verifyTicket(state2.cache, receipt, {
|
|
5933
|
+
action: proposal.tool,
|
|
5934
|
+
executionContext: proposal.executionContext,
|
|
5935
|
+
authorizationId: proposal.authorizationId,
|
|
5936
|
+
profileId: proposal.profileId,
|
|
5937
|
+
proposalId: proposal.id,
|
|
5938
|
+
contentHash: binding?.contentHash,
|
|
5939
|
+
contentBinding: binding?.contentBinding
|
|
5940
|
+
});
|
|
5941
|
+
await state2.archiveReceipt(receipt, {
|
|
5942
|
+
authorizationId: proposal.authorizationId,
|
|
5943
|
+
profileId: proposal.profileId,
|
|
5944
|
+
boundsHash: cachedAuth?.boundsHash,
|
|
5945
|
+
contextHash: cachedAuth?.contextHash,
|
|
5946
|
+
bounds: cachedAuth?.bounds ?? cachedAuth?.frame,
|
|
5947
|
+
context: cachedAuth?.context,
|
|
5948
|
+
attestations: cachedAuth?.attestations,
|
|
5949
|
+
// What the human actually approved, kept next to the receipt that cites
|
|
5950
|
+
// it — otherwise `proposalId` is a pointer to a server we may not have.
|
|
5951
|
+
// PE1: the archived copy carries the RESOLVED (decrypted on an
|
|
5952
|
+
// encrypted row) toolArgs, never the sealed `content` — this archive
|
|
5953
|
+
// is this gateway's own local evidence and has always stored it
|
|
5954
|
+
// readable, same as every legacy row.
|
|
5955
|
+
proposal: { ...proposal, toolArgs },
|
|
5956
|
+
boundContent: binding?.boundContent
|
|
5957
|
+
});
|
|
5958
|
+
} catch (err) {
|
|
5959
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
5960
|
+
state2.executionJournal.complete(receiptId, "failed", void 0, detail);
|
|
5961
|
+
if (err instanceof AsKeyMismatchError || err instanceof TicketBindingMismatchError) {
|
|
5962
|
+
void notifyControlPlane("as-key-mismatch");
|
|
5963
|
+
return {
|
|
5964
|
+
text: `Proposal ${proposal.id}: blocked \u2014 ${err.message}`,
|
|
5965
|
+
isError: true
|
|
5966
|
+
};
|
|
5967
|
+
}
|
|
5968
|
+
if (err instanceof ArchiveWriteError) {
|
|
5969
|
+
return {
|
|
5970
|
+
text: `Proposal ${proposal.id}: blocked \u2014 the ticket could not be archived locally \u2014 ${err.message}. The action was not executed.`,
|
|
5971
|
+
isError: true
|
|
5972
|
+
};
|
|
5973
|
+
}
|
|
5974
|
+
return {
|
|
5975
|
+
text: `Proposal ${proposal.id}: blocked \u2014 ${detail}`,
|
|
5976
|
+
isError: true
|
|
5977
|
+
};
|
|
5978
|
+
}
|
|
5754
5979
|
if (replayed) {
|
|
5755
5980
|
console.error(
|
|
5756
5981
|
`[Suveren MCP] Proposal ${proposal.id}: ticket ${receiptId} was replayed by the AS with no local execution record \u2014 executing once (lost-response recovery).`
|
|
@@ -5765,7 +5990,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
5765
5990
|
submitted.previewHash
|
|
5766
5991
|
);
|
|
5767
5992
|
if (changed) {
|
|
5768
|
-
state2.executionJournal.complete(receiptId, "failed", "changed");
|
|
5993
|
+
state2.executionJournal.complete(receiptId, "failed", "changed", `The record changed in ${integrationId} since it was requested.`);
|
|
5769
5994
|
return {
|
|
5770
5995
|
text: `Proposal ${proposal.id} was approved, but the record changed in ${integrationId} since it was requested \u2014 nothing was done. A new attempt needs a new request and approval.`,
|
|
5771
5996
|
isError: true
|
|
@@ -5819,8 +6044,10 @@ function localOutcomeOfExecuted(proposal, state2) {
|
|
|
5819
6044
|
if (!entry || entry.state === "done") return null;
|
|
5820
6045
|
const integration = proposal.tool.split("__")[0];
|
|
5821
6046
|
if (entry.state === "failed") {
|
|
6047
|
+
const reasonSuffix = entry.detail ? `
|
|
6048
|
+
Reason: ${entry.detail}` : "";
|
|
5822
6049
|
return {
|
|
5823
|
-
text: entry.outcome === "refused" ? `Proposal ${proposal.id} was approved, but ${integration} refused to run it \u2014 nothing was done. Do not call the tool again with the same values; a new attempt needs a new request and approval
|
|
6050
|
+
text: entry.outcome === "as_refused" ? `Proposal ${proposal.id} was approved, but the Authority Server refused its ticket \u2014 nothing ran. Do not retry the same values; ask the person (e.g. for a higher limit).${reasonSuffix}` : entry.outcome === "refused" ? `Proposal ${proposal.id} was approved, but ${integration} refused to run it \u2014 nothing was done. Do not call the tool again with the same values; a new attempt needs a new request and approval.${reasonSuffix}` : entry.outcome === "changed" ? `Proposal ${proposal.id} was approved, but the record changed in ${integration} since it was requested \u2014 nothing was done. A new attempt needs a new request and approval.${reasonSuffix}` : `Proposal ${proposal.id} was approved, but running it failed \u2014 whether anything happened in ${integration} is unknown. Check ${integration} before trying again; a new attempt needs a new request and approval.${reasonSuffix}`,
|
|
5824
6051
|
isError: true
|
|
5825
6052
|
};
|
|
5826
6053
|
}
|
|
@@ -5869,6 +6096,16 @@ Result: ${JSON.stringify(match.executionResult, null, 2)}` : " The action ran; t
|
|
|
5869
6096
|
}]
|
|
5870
6097
|
};
|
|
5871
6098
|
}
|
|
6099
|
+
if (match.status === "refused") {
|
|
6100
|
+
const detail = match.refusal ? match.refusal.message : "a bound was exceeded";
|
|
6101
|
+
return {
|
|
6102
|
+
content: [{
|
|
6103
|
+
type: "text",
|
|
6104
|
+
text: `Proposal ${match.id} was approved, but the Authority Server refused its ticket \u2014 ${detail} Nothing ran. Do not retry the same values; ask the person (e.g. for a higher limit).`
|
|
6105
|
+
}],
|
|
6106
|
+
isError: true
|
|
6107
|
+
};
|
|
6108
|
+
}
|
|
5872
6109
|
if (match.status === "rejected") {
|
|
5873
6110
|
const by = match.approverRejectedBy ?? match.rejectedBy;
|
|
5874
6111
|
const outcome = by?.outcome ?? "rejected";
|