@suveren/gateway 0.35.1 → 0.36.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/bin/suveren-gateway.js +28 -1
- package/content/integrations/calendar.json +2 -0
- package/content/integrations/erp.json +3 -3
- package/content/integrations/gmail.json +2 -0
- package/content/integrations/linkedin.json +3 -0
- package/content/integrations/mail.json +9 -0
- package/dist/mcp-server/http.mjs +106 -43
- package/dist/ui/assets/index-BQ5Pr6As.js +123 -0
- package/dist/ui/index.html +1 -1
- package/lib/autostart-templates.d.mts +7 -0
- package/lib/autostart-templates.mjs +30 -0
- package/node_modules/@hap/core/dist/index.d.mts +25 -1
- package/node_modules/@hap/core/dist/index.d.ts +25 -1
- package/node_modules/@hap/core/dist/index.js +18 -0
- package/node_modules/@hap/core/dist/index.mjs +16 -0
- package/node_modules/@hap/core/package.json +1 -1
- package/node_modules/@hap/core/src/content-binding.ts +26 -1
- package/node_modules/@hap/core/src/types.ts +17 -0
- package/node_modules/postcss/lib/processor.js +1 -1
- package/node_modules/postcss/lib/stringify.js +30 -8
- package/node_modules/postcss/package.json +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/index-BHxzXFpf.js +0 -123
package/bin/suveren-gateway.js
CHANGED
|
@@ -16,7 +16,7 @@ import { existsSync, mkdirSync, openSync, readFileSync, readdirSync, writeFileSy
|
|
|
16
16
|
import { homedir, platform, userInfo } from 'node:os';
|
|
17
17
|
import { dirname, join, resolve } from 'node:path';
|
|
18
18
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
19
|
-
import { buildLaunchAgentPlist, buildMacLauncher, buildSystemdUnit, buildWindowsTaskXml } from '../lib/autostart-templates.mjs';
|
|
19
|
+
import { buildLaunchAgentPlist, buildMacLauncher, buildSystemdUnit, buildWindowsTaskXml, serviceOwnsInstall } from '../lib/autostart-templates.mjs';
|
|
20
20
|
import { DEFAULT_AS_URL, readConfig, writeConfig, validateAsUrl, validateCaFile, validateProxyUrl, validatePinTls, resolveAsUrl, resolvePinTls, resolvePinTlsExpectedFingerprint, resolveSimulation } from '../lib/config.mjs';
|
|
21
21
|
import { createInterface } from 'node:readline/promises';
|
|
22
22
|
import { readPairing as readAsPairing, recordTlsPin, formatFingerprint, normalizeFingerprint } from '../lib/as-pairing.mjs';
|
|
@@ -1038,7 +1038,27 @@ const LAUNCH_AGENT_LABEL = 'ai.suveren.gateway';
|
|
|
1038
1038
|
* it could not see. Every command has to know about the service the same CLI
|
|
1039
1039
|
* installs.
|
|
1040
1040
|
*/
|
|
1041
|
+
function serviceDefinitionText() {
|
|
1042
|
+
const os = platform();
|
|
1043
|
+
const file = os === 'darwin' ? launchAgentPath() : os === 'linux' ? systemdUnitPath() : null;
|
|
1044
|
+
if (!file) return null;
|
|
1045
|
+
try { return existsSync(file) ? readFileSync(file, 'utf8') : null; } catch { return null; }
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/** The login service runs this CLI's installation — see serviceOwnsInstall. */
|
|
1049
|
+
function serviceIsOurs() {
|
|
1050
|
+
return serviceOwnsInstall({
|
|
1051
|
+
definitionText: serviceDefinitionText(),
|
|
1052
|
+
dataDir: DATA_DIR,
|
|
1053
|
+
envDataDir: process.env.SUVEREN_DATA_DIR,
|
|
1054
|
+
same: (a, b) => resolve(a) === resolve(b),
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1041
1058
|
function serviceRunning() {
|
|
1059
|
+
// Another installation's service (a second gateway under this OS user) is
|
|
1060
|
+
// not "running" for this CLI — never stop, restart or report it as ours.
|
|
1061
|
+
if (!serviceIsOurs()) return false;
|
|
1042
1062
|
const os = platform();
|
|
1043
1063
|
if (os === 'darwin') {
|
|
1044
1064
|
const p = runQuiet('launchctl', ['print', `gui/${process.getuid()}/${LAUNCH_AGENT_LABEL}`]);
|
|
@@ -1104,6 +1124,13 @@ async function service(args) {
|
|
|
1104
1124
|
process.exit(2);
|
|
1105
1125
|
}
|
|
1106
1126
|
|
|
1127
|
+
if ((sub === 'install' || sub === 'uninstall') && !serviceIsOurs()) {
|
|
1128
|
+
console.error('The login service on this account belongs to another Suveren gateway installation');
|
|
1129
|
+
console.error(`(a different data folder than ${DATA_DIR}). Refusing to change it.`);
|
|
1130
|
+
console.error('Run this installation in the background with: suveren-gateway start --detach');
|
|
1131
|
+
process.exit(2);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1107
1134
|
switch (sub) {
|
|
1108
1135
|
case 'install': await impl.install(); break;
|
|
1109
1136
|
case 'uninstall': await impl.uninstall(); break;
|
|
@@ -122,6 +122,7 @@
|
|
|
122
122
|
}
|
|
123
123
|
},
|
|
124
124
|
"create_event": {
|
|
125
|
+
"message": { "text": "description", "verb": "Sent" },
|
|
125
126
|
"summary": "Event: {summary|short}[ on {start.dateTime,start.date|date}]",
|
|
126
127
|
"executionMapping": {
|
|
127
128
|
"duration_minutes": "duration",
|
|
@@ -144,6 +145,7 @@
|
|
|
144
145
|
"actionLabel": "Event created"
|
|
145
146
|
},
|
|
146
147
|
"update_event": {
|
|
148
|
+
"message": { "text": "description", "verb": "Sent" },
|
|
147
149
|
"summary": "Update event[: {summary|short}]",
|
|
148
150
|
"executionMapping": {
|
|
149
151
|
"attendees": [
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"icon": "briefcase",
|
|
7
7
|
"profile": "sales",
|
|
8
8
|
"npmPackage": "@humanagencyp/erp-mcp",
|
|
9
|
-
"npmVersion": "0.9.
|
|
9
|
+
"npmVersion": "0.9.2",
|
|
10
10
|
"mcp": {
|
|
11
11
|
"command": "erp-mcp",
|
|
12
12
|
"args": [],
|
|
@@ -276,7 +276,7 @@
|
|
|
276
276
|
],
|
|
277
277
|
"display": {
|
|
278
278
|
"order": ["customer_id", "order_id", "number", "document_date", "service_period", "lines", "net_total", "currency", "payment_term_days", "memo"],
|
|
279
|
-
"money": ["net_total", "customer_id.credit_limit", "customer_id.open_balance", "customer_id.available_credit"],
|
|
279
|
+
"money": ["net_total", "lines[].unit_price", "customer_id.credit_limit", "customer_id.open_balance", "customer_id.available_credit"],
|
|
280
280
|
"currencyField": "currency",
|
|
281
281
|
"labels": {
|
|
282
282
|
"customer_id": "Customer",
|
|
@@ -311,7 +311,7 @@
|
|
|
311
311
|
},
|
|
312
312
|
"display": {
|
|
313
313
|
"order": ["invoice_id", "number", "document_date", "credit_total", "currency", "reason", "service_period", "lines"],
|
|
314
|
-
"money": ["credit_total"],
|
|
314
|
+
"money": ["credit_total", "lines[].unit_price"],
|
|
315
315
|
"currencyField": "currency",
|
|
316
316
|
"labels": {
|
|
317
317
|
"invoice_id": "Invoice",
|
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
},
|
|
85
85
|
"overrides": {
|
|
86
86
|
"send_message": {
|
|
87
|
+
"message": { "text": "body", "to": "to", "cc": "cc", "subject": "subject", "verb": "Sent" },
|
|
87
88
|
"summary": "Email to {to|list}: {subject|short}",
|
|
88
89
|
"executionMapping": {
|
|
89
90
|
"to": [
|
|
@@ -116,6 +117,7 @@
|
|
|
116
117
|
"display": { "aiText": "body" }
|
|
117
118
|
},
|
|
118
119
|
"create_draft": {
|
|
120
|
+
"message": { "text": "body", "to": "to", "cc": "cc", "subject": "subject", "verb": "Sent" },
|
|
119
121
|
"summary": "Draft email to {to|list}[: {subject|short}]",
|
|
120
122
|
"executionMapping": {
|
|
121
123
|
"to": [
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"overrides": {
|
|
64
64
|
"create_post": {
|
|
65
|
+
"message": { "text": "text", "verb": "Published" },
|
|
65
66
|
"summary": "LinkedIn post: {text|short}",
|
|
66
67
|
"executionMapping": {
|
|
67
68
|
"visibility": "audience"
|
|
@@ -74,6 +75,7 @@
|
|
|
74
75
|
"actionLabel": "Post published"
|
|
75
76
|
},
|
|
76
77
|
"create_image_post": {
|
|
78
|
+
"message": { "text": "text", "verb": "Published" },
|
|
77
79
|
"summary": "LinkedIn image post: {text|short}",
|
|
78
80
|
"executionMapping": {
|
|
79
81
|
"visibility": "audience"
|
|
@@ -86,6 +88,7 @@
|
|
|
86
88
|
"actionLabel": "Image post published"
|
|
87
89
|
},
|
|
88
90
|
"create_article_post": {
|
|
91
|
+
"message": { "text": "text", "verb": "Published" },
|
|
89
92
|
"summary": "LinkedIn article: {title,articleUrl|short}",
|
|
90
93
|
"executionMapping": {
|
|
91
94
|
"visibility": "audience"
|
|
@@ -85,7 +85,16 @@
|
|
|
85
85
|
"resultDatePath": "received_at"
|
|
86
86
|
}
|
|
87
87
|
},
|
|
88
|
+
"get_attachment": {
|
|
89
|
+
"category": "read",
|
|
90
|
+
"boundField": "read_access",
|
|
91
|
+
"requiredValue": "unlimited",
|
|
92
|
+
"read": {
|
|
93
|
+
"ageField": "read_age_days"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
88
96
|
"send_message": {
|
|
97
|
+
"message": { "text": "body", "to": "to", "cc": "cc", "subject": "subject", "verb": "Sent" },
|
|
89
98
|
"summary": "Email to {to|list}: {subject|short}",
|
|
90
99
|
"executionMapping": {
|
|
91
100
|
"to": [
|
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -3601,8 +3601,6 @@ function setAsBaseUrl(url) {
|
|
|
3601
3601
|
}
|
|
3602
3602
|
var OPERATOR_NAME = process.env.SUVEREN_OPERATOR_NAME ?? "Suveren";
|
|
3603
3603
|
var HAP_URL = "https://www.humanagencyprotocol.org/";
|
|
3604
|
-
var FOOTER_PROFILES = /* @__PURE__ */ new Set(["email", "calendar", "publish"]);
|
|
3605
|
-
var CONTENT_FIELD_CANDIDATES = ["body", "text", "description", "content"];
|
|
3606
3604
|
var FOOTER_RE = /\n*(?:—|--) (?:Sent|Published) by (?:an AI agent|[^\n]+?'s AI agent)[\s\S]*$/;
|
|
3607
3605
|
function shouldAttachFooter() {
|
|
3608
3606
|
return true;
|
|
@@ -3610,9 +3608,6 @@ function shouldAttachFooter() {
|
|
|
3610
3608
|
function receiptUrl(receiptId) {
|
|
3611
3609
|
return `${AS_BASE}/t/${receiptId}`;
|
|
3612
3610
|
}
|
|
3613
|
-
function verbForProfile(profile) {
|
|
3614
|
-
return profile === "publish" ? "Published" : "Sent";
|
|
3615
|
-
}
|
|
3616
3611
|
function hapLine() {
|
|
3617
3612
|
return `-- ${OPERATOR_NAME} implements HAP, the open Human Agency Protocol: ${HAP_URL}`;
|
|
3618
3613
|
}
|
|
@@ -3620,32 +3615,22 @@ function footerText(receiptId, verb, subject) {
|
|
|
3620
3615
|
const identity = deriveIdentityLine(subject, { operatorName: OPERATOR_NAME }).replace(/^Sent\b/, verb);
|
|
3621
3616
|
return `
|
|
3622
3617
|
|
|
3623
|
-
-- ${identity}.
|
|
3618
|
+
-- ${identity}. Ticket: ${receiptUrl(receiptId)}
|
|
3624
3619
|
|
|
3625
3620
|
${hapLine()}`;
|
|
3626
3621
|
}
|
|
3627
|
-
function isStringType(t) {
|
|
3628
|
-
return t === "string" || Array.isArray(t) && t.includes("string");
|
|
3629
|
-
}
|
|
3630
3622
|
function detectContentField(tool) {
|
|
3631
|
-
|
|
3632
|
-
const props = schema?.properties ?? {};
|
|
3633
|
-
for (const candidate of CONTENT_FIELD_CANDIDATES) {
|
|
3634
|
-
const prop = props[candidate];
|
|
3635
|
-
if (prop && isStringType(prop.type)) return candidate;
|
|
3636
|
-
}
|
|
3637
|
-
return null;
|
|
3623
|
+
return tool.gating?.message?.text ?? null;
|
|
3638
3624
|
}
|
|
3639
3625
|
function stripFooter(value) {
|
|
3640
3626
|
return value.replace(FOOTER_RE, "").replace(/\n+$/, "");
|
|
3641
3627
|
}
|
|
3642
3628
|
function appendVerificationFooter(tool, args, receiptId, subject) {
|
|
3643
|
-
const
|
|
3644
|
-
if (!
|
|
3645
|
-
const field =
|
|
3646
|
-
if (!field) return args;
|
|
3629
|
+
const message = tool.gating?.message;
|
|
3630
|
+
if (!message) return args;
|
|
3631
|
+
const field = message.text;
|
|
3647
3632
|
const current = typeof args[field] === "string" ? args[field] : "";
|
|
3648
|
-
return { ...args, [field]: stripFooter(current) + footerText(receiptId,
|
|
3633
|
+
return { ...args, [field]: stripFooter(current) + footerText(receiptId, message.verb, subject) };
|
|
3649
3634
|
}
|
|
3650
3635
|
|
|
3651
3636
|
// src/lib/content-binding.ts
|
|
@@ -4059,6 +4044,37 @@ function invalidManifestInternalReason(manifest) {
|
|
|
4059
4044
|
}
|
|
4060
4045
|
return null;
|
|
4061
4046
|
}
|
|
4047
|
+
var VALID_MESSAGE_VERBS = ["Sent", "Published"];
|
|
4048
|
+
function invalidMessageReason(toolName, entry) {
|
|
4049
|
+
if (entry === null || typeof entry !== "object") return null;
|
|
4050
|
+
const message = entry.message;
|
|
4051
|
+
if (message === void 0) return null;
|
|
4052
|
+
if (message === null || typeof message !== "object") {
|
|
4053
|
+
return `override "${toolName}".message must be an object`;
|
|
4054
|
+
}
|
|
4055
|
+
const m = message;
|
|
4056
|
+
const at = `override "${toolName}".message`;
|
|
4057
|
+
if (typeof m.text !== "string" || m.text.length === 0) {
|
|
4058
|
+
return `${at}.text must be a non-empty string`;
|
|
4059
|
+
}
|
|
4060
|
+
if (typeof m.verb !== "string" || !VALID_MESSAGE_VERBS.includes(m.verb)) {
|
|
4061
|
+
return `${at}.verb must be one of ${VALID_MESSAGE_VERBS.join(", ")}`;
|
|
4062
|
+
}
|
|
4063
|
+
for (const key of ["to", "cc", "subject"]) {
|
|
4064
|
+
if (m[key] !== void 0 && (typeof m[key] !== "string" || m[key].length === 0)) {
|
|
4065
|
+
return `${at}.${key} must be a non-empty string`;
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
return null;
|
|
4069
|
+
}
|
|
4070
|
+
function invalidManifestMessageReason(manifest) {
|
|
4071
|
+
const overrides = manifest.toolGating?.overrides ?? {};
|
|
4072
|
+
for (const [toolName, entry] of Object.entries(overrides)) {
|
|
4073
|
+
const reason = invalidMessageReason(toolName, entry);
|
|
4074
|
+
if (reason) return reason;
|
|
4075
|
+
}
|
|
4076
|
+
return null;
|
|
4077
|
+
}
|
|
4062
4078
|
function invalidManifestPreviewReason(manifest) {
|
|
4063
4079
|
const overrides = manifest.toolGating?.overrides ?? {};
|
|
4064
4080
|
for (const [toolName, entry] of Object.entries(overrides)) {
|
|
@@ -4488,6 +4504,11 @@ function loadManifests(integrationsDir) {
|
|
|
4488
4504
|
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${internalReason}`);
|
|
4489
4505
|
continue;
|
|
4490
4506
|
}
|
|
4507
|
+
const messageReason = invalidManifestMessageReason(manifest);
|
|
4508
|
+
if (messageReason) {
|
|
4509
|
+
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${messageReason}`);
|
|
4510
|
+
continue;
|
|
4511
|
+
}
|
|
4491
4512
|
const contextReason = invalidManifestContextReason(manifest);
|
|
4492
4513
|
if (contextReason) {
|
|
4493
4514
|
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${contextReason}`);
|
|
@@ -5698,7 +5719,7 @@ function toStringArray(value) {
|
|
|
5698
5719
|
if (typeof value === "string" && value) return [value];
|
|
5699
5720
|
return [];
|
|
5700
5721
|
}
|
|
5701
|
-
async function finalizeAttachmentsForExecution(integrationManager2, integrationId, args) {
|
|
5722
|
+
async function finalizeAttachmentsForExecution(integrationManager2, integrationId, args, message) {
|
|
5702
5723
|
const sealed = args[ATTACHMENTS_SEALED_META_KEY];
|
|
5703
5724
|
if (!sealed || sealed.attachments.length === 0 && sealed.inserts.length === 0) {
|
|
5704
5725
|
return { ok: true, args };
|
|
@@ -5749,18 +5770,27 @@ async function finalizeAttachmentsForExecution(integrationManager2, integrationI
|
|
|
5749
5770
|
contentBase64: d.contentBase64
|
|
5750
5771
|
}));
|
|
5751
5772
|
} else {
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5773
|
+
if (!message?.to) {
|
|
5774
|
+
return {
|
|
5775
|
+
ok: false,
|
|
5776
|
+
message: `Refused: "${integrationId}" sends attachments as one built message, but this tool declares no message fields (manifest \`message.text\` and \`message.to\`) \u2014 nothing was sent.`
|
|
5777
|
+
};
|
|
5778
|
+
}
|
|
5779
|
+
const decl = message;
|
|
5780
|
+
const arg = transport.arg;
|
|
5781
|
+
const attachments = resolvedDocs.map((d) => ({ filename: d.filename, contentType: d.contentType, contentBase64: d.contentBase64 }));
|
|
5782
|
+
const str = (a, name) => name && typeof a[name] === "string" ? a[name] : "";
|
|
5783
|
+
const withRaw = (a) => ({
|
|
5784
|
+
...a,
|
|
5785
|
+
[arg]: toBase64Url(buildMimeMessage({
|
|
5786
|
+
to: toStringArray(a[decl.to]),
|
|
5787
|
+
cc: decl.cc ? toStringArray(a[decl.cc]) : [],
|
|
5788
|
+
subject: str(a, decl.subject),
|
|
5789
|
+
bodyText: str(a, decl.text),
|
|
5790
|
+
attachments
|
|
5791
|
+
}))
|
|
5762
5792
|
});
|
|
5763
|
-
|
|
5793
|
+
return { ok: true, args: withRaw(outgoing), rebuildTransport: withRaw };
|
|
5764
5794
|
}
|
|
5765
5795
|
}
|
|
5766
5796
|
return { ok: true, args: outgoing };
|
|
@@ -5789,10 +5819,11 @@ function computeContentBinding(profileId, tool, toolArgs, actionType) {
|
|
|
5789
5819
|
return void 0;
|
|
5790
5820
|
}
|
|
5791
5821
|
let effectiveArgs = toolArgs;
|
|
5792
|
-
|
|
5822
|
+
const attachmentsArg = tool?.gating?.attachmentsArg;
|
|
5823
|
+
if (attachmentsArg && binding.fields?.includes(attachmentsArg)) {
|
|
5793
5824
|
const sealed = toolArgs[ATTACHMENTS_SEALED_META_KEY];
|
|
5794
5825
|
const fingerprints = (sealed?.attachments ?? []).map((a) => ({ filename: a.filename, sha256: a.sha256 }));
|
|
5795
|
-
effectiveArgs = { ...toolArgs,
|
|
5826
|
+
effectiveArgs = { ...toolArgs, [attachmentsArg]: fingerprints };
|
|
5796
5827
|
}
|
|
5797
5828
|
const bound = selectBoundFields(binding, effectiveArgs);
|
|
5798
5829
|
return {
|
|
@@ -6795,12 +6826,14 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
6795
6826
|
}
|
|
6796
6827
|
}
|
|
6797
6828
|
let finalArgs = callArgs;
|
|
6829
|
+
let rebuildTransport;
|
|
6798
6830
|
if (tool.gating?.attachmentsArg || tool.gating?.insertFields?.length) {
|
|
6799
|
-
const finalized = await finalizeAttachmentsForExecution(integrationManager2, tool.integrationId, callArgs);
|
|
6831
|
+
const finalized = await finalizeAttachmentsForExecution(integrationManager2, tool.integrationId, callArgs, tool.gating?.message);
|
|
6800
6832
|
if (!finalized.ok) {
|
|
6801
6833
|
return { content: [{ type: "text", text: finalized.message }], isError: true };
|
|
6802
6834
|
}
|
|
6803
6835
|
finalArgs = finalized.args;
|
|
6836
|
+
rebuildTransport = finalized.rebuildTransport;
|
|
6804
6837
|
}
|
|
6805
6838
|
let receiptId;
|
|
6806
6839
|
try {
|
|
@@ -7031,6 +7064,7 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.` + a
|
|
|
7031
7064
|
});
|
|
7032
7065
|
let outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, finalArgs, receiptId, auth.subjects?.[0]) : finalArgs;
|
|
7033
7066
|
if (receiptId) outgoingArgs = attachTicketId(tool, outgoingArgs, receiptId);
|
|
7067
|
+
if (rebuildTransport) outgoingArgs = rebuildTransport(outgoingArgs);
|
|
7034
7068
|
outgoingArgs = encodeOutgoingArgs(tool, outgoingArgs);
|
|
7035
7069
|
outgoingArgs = stripSourceLink(outgoingArgs);
|
|
7036
7070
|
outgoingArgs = stripAttachmentsSealedMeta(outgoingArgs);
|
|
@@ -7883,8 +7917,9 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
7883
7917
|
}
|
|
7884
7918
|
}
|
|
7885
7919
|
let finalToolArgs = toolArgs;
|
|
7920
|
+
let rebuildTransport;
|
|
7886
7921
|
{
|
|
7887
|
-
const finalized = await finalizeAttachmentsForExecution(integrationManager2, integrationId, toolArgs);
|
|
7922
|
+
const finalized = await finalizeAttachmentsForExecution(integrationManager2, integrationId, toolArgs, discovered?.gating?.message);
|
|
7888
7923
|
if (!finalized.ok) {
|
|
7889
7924
|
state2.executionJournal.complete(receiptId, "failed", "changed", finalized.message);
|
|
7890
7925
|
recordExecutionOutcome(state2, proposal.id, receiptId, false, finalized.message);
|
|
@@ -7896,6 +7931,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
7896
7931
|
};
|
|
7897
7932
|
}
|
|
7898
7933
|
finalToolArgs = finalized.args;
|
|
7934
|
+
rebuildTransport = finalized.rebuildTransport;
|
|
7899
7935
|
}
|
|
7900
7936
|
try {
|
|
7901
7937
|
let outgoingArgs = finalToolArgs;
|
|
@@ -7904,6 +7940,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
7904
7940
|
outgoingArgs = appendVerificationFooter(discovered, outgoingArgs, receiptId, cachedAuth?.subjects?.[0]);
|
|
7905
7941
|
}
|
|
7906
7942
|
outgoingArgs = attachTicketId(discovered, outgoingArgs, receiptId);
|
|
7943
|
+
if (rebuildTransport) outgoingArgs = rebuildTransport(outgoingArgs);
|
|
7907
7944
|
outgoingArgs = encodeOutgoingArgs(discovered, outgoingArgs);
|
|
7908
7945
|
}
|
|
7909
7946
|
outgoingArgs = stripSourceLink(outgoingArgs);
|
|
@@ -8654,6 +8691,9 @@ function withoutBlockedArgs(schema, blocked) {
|
|
|
8654
8691
|
}
|
|
8655
8692
|
return out;
|
|
8656
8693
|
}
|
|
8694
|
+
function stableEnvKey(env) {
|
|
8695
|
+
return JSON.stringify(Object.keys(env).sort().map((k) => [k, env[k]]));
|
|
8696
|
+
}
|
|
8657
8697
|
var RESPAWN_DELAYS = [2e3, 4e3, 6e3];
|
|
8658
8698
|
var MAX_RESPAWN_ATTEMPTS = 3;
|
|
8659
8699
|
var CONNECT_TIMEOUT_MS = 3e4;
|
|
@@ -8995,6 +9035,7 @@ var IntegrationManager = class {
|
|
|
8995
9035
|
);
|
|
8996
9036
|
}
|
|
8997
9037
|
const env = this.resolveEnvKeys(config);
|
|
9038
|
+
const credentialEnv = stableEnvKey(env);
|
|
8998
9039
|
if (isSimulationMode()) {
|
|
8999
9040
|
const manifest = getManifest(config.id);
|
|
9000
9041
|
const simDecl = manifest?.simulation;
|
|
@@ -9068,7 +9109,8 @@ var IntegrationManager = class {
|
|
|
9068
9109
|
client,
|
|
9069
9110
|
transport,
|
|
9070
9111
|
tools,
|
|
9071
|
-
respawnAttempts: 0
|
|
9112
|
+
respawnAttempts: 0,
|
|
9113
|
+
credentialEnv
|
|
9072
9114
|
};
|
|
9073
9115
|
this.running.set(config.id, entry);
|
|
9074
9116
|
transport.onclose = () => {
|
|
@@ -9391,6 +9433,21 @@ var IntegrationManager = class {
|
|
|
9391
9433
|
isRunning(id) {
|
|
9392
9434
|
return this.running.has(id);
|
|
9393
9435
|
}
|
|
9436
|
+
/**
|
|
9437
|
+
* True when the integration is running but the vault credentials would now
|
|
9438
|
+
* resolve to a different env than the one its process was started with.
|
|
9439
|
+
*
|
|
9440
|
+
* Why: at boot the gateway starts connectors whose settings are all optional
|
|
9441
|
+
* (ERP, CRM — mode/adapter/company file) BEFORE the vault is unlocked, so they
|
|
9442
|
+
* come up on their defaults (simulation). The settings arrive on unlock, and
|
|
9443
|
+
* without a restart the process keeps running on the wrong mode — the user's
|
|
9444
|
+
* live data silently disappears from view.
|
|
9445
|
+
*/
|
|
9446
|
+
credentialsChangedSinceStart(config) {
|
|
9447
|
+
const entry = this.running.get(config.id);
|
|
9448
|
+
if (!entry) return false;
|
|
9449
|
+
return stableEnvKey(this.resolveEnvKeys(config)) !== entry.credentialEnv;
|
|
9450
|
+
}
|
|
9394
9451
|
/**
|
|
9395
9452
|
* Check if all required env keys for an integration can be resolved.
|
|
9396
9453
|
*/
|
|
@@ -9518,7 +9575,8 @@ var IntegrationManager = class {
|
|
|
9518
9575
|
internal: ext.internal,
|
|
9519
9576
|
attachmentsArg: ext.attachmentsArg,
|
|
9520
9577
|
insertFields: ext.insertFields,
|
|
9521
|
-
aiTextArg: ext.display?.aiText
|
|
9578
|
+
aiTextArg: ext.display?.aiText,
|
|
9579
|
+
message: ext.message
|
|
9522
9580
|
};
|
|
9523
9581
|
}
|
|
9524
9582
|
return {
|
|
@@ -9557,7 +9615,8 @@ var IntegrationManager = class {
|
|
|
9557
9615
|
client: null,
|
|
9558
9616
|
transport: null,
|
|
9559
9617
|
tools: [],
|
|
9560
|
-
respawnAttempts: attempts + 1
|
|
9618
|
+
respawnAttempts: attempts + 1,
|
|
9619
|
+
credentialEnv: ""
|
|
9561
9620
|
};
|
|
9562
9621
|
this.running.set(id, fakeEntry);
|
|
9563
9622
|
this.handleCrash(id);
|
|
@@ -11709,10 +11768,14 @@ async function startIntegrationForService(serviceId) {
|
|
|
11709
11768
|
);
|
|
11710
11769
|
for (const config of candidates) {
|
|
11711
11770
|
await startOneIntegration(config);
|
|
11771
|
+
if (integrationManager.isRunning(config.id) && integrationManager.credentialsChangedSinceStart(config)) {
|
|
11772
|
+
console.error(`[Suveren MCP] ${config.id}: credentials changed since start \u2014 restarting`);
|
|
11773
|
+
await startOneIntegration(config, { restart: true });
|
|
11774
|
+
}
|
|
11712
11775
|
}
|
|
11713
11776
|
}
|
|
11714
|
-
async function startOneIntegration(config) {
|
|
11715
|
-
if (integrationManager.isRunning(config.id)) return;
|
|
11777
|
+
async function startOneIntegration(config, opts = {}) {
|
|
11778
|
+
if (!opts.restart && integrationManager.isRunning(config.id)) return;
|
|
11716
11779
|
const needsCreds = Object.keys(config.envKeys ?? {}).length > 0;
|
|
11717
11780
|
if (needsCreds && !integrationManager.canResolveEnvKeys(config)) {
|
|
11718
11781
|
const missing = [];
|
|
@@ -11731,7 +11794,7 @@ async function startOneIntegration(config) {
|
|
|
11731
11794
|
const manifest = getManifest(config.id);
|
|
11732
11795
|
const effectiveConfig = manifest ? { ...config, toolGating: manifest.toolGating, npmPackage: manifest.npmPackage ?? config.npmPackage } : config;
|
|
11733
11796
|
try {
|
|
11734
|
-
await integrationManager.startIntegration(effectiveConfig, { skipIfRunning:
|
|
11797
|
+
await integrationManager.startIntegration(effectiveConfig, { skipIfRunning: !opts.restart });
|
|
11735
11798
|
} catch (err) {
|
|
11736
11799
|
console.error(`[Suveren MCP] Failed to start integration ${config.id}:`, err);
|
|
11737
11800
|
}
|