@suveren/gateway 0.35.0 → 0.35.2
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 +18 -2
- package/content/integrations/erp.json +9 -1
- package/content/integrations/mail.json +4 -1
- package/dist/mcp-server/http.mjs +150 -36
- package/dist/ui/assets/{index-M1sLTJjt.js → index-BONNmuPw.js} +27 -27
- package/dist/ui/assets/{index-c8cmIEPx.css → index-XiOgY6b5.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"icon": "users",
|
|
7
7
|
"profile": "customers",
|
|
8
8
|
"npmPackage": "@humanagencyp/crm-mcp",
|
|
9
|
-
"npmVersion": "1.
|
|
9
|
+
"npmVersion": "1.6.0",
|
|
10
10
|
"mcp": {
|
|
11
11
|
"command": "crm-mcp",
|
|
12
12
|
"args": []
|
|
@@ -26,17 +26,33 @@
|
|
|
26
26
|
"type": "text",
|
|
27
27
|
"placeholder": "simulation (default) — live needs an adapter for your CRM",
|
|
28
28
|
"optional": true
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"key": "liveAdapter",
|
|
32
|
+
"label": "Live adapter (optional)",
|
|
33
|
+
"type": "text",
|
|
34
|
+
"placeholder": "local — your own company's CRM data. Leave empty unless Mode is \"live\"",
|
|
35
|
+
"optional": true
|
|
29
36
|
}
|
|
30
37
|
],
|
|
31
38
|
"envMapping": {
|
|
32
39
|
"DATABASE_URL": "databaseUrl",
|
|
33
|
-
"CRM_MODE": "mode"
|
|
40
|
+
"CRM_MODE": "mode",
|
|
41
|
+
"CRM_LIVE_ADAPTER": "liveAdapter"
|
|
34
42
|
}
|
|
35
43
|
},
|
|
36
44
|
"simulation": {
|
|
37
45
|
"field": "mode",
|
|
38
46
|
"default": "simulation"
|
|
39
47
|
},
|
|
48
|
+
"modeLabels": {
|
|
49
|
+
"simulation": "Test data",
|
|
50
|
+
"live": { "local": "Local" }
|
|
51
|
+
},
|
|
52
|
+
"modeNotes": {
|
|
53
|
+
"live": { "local": "On this computer — no shared access" }
|
|
54
|
+
},
|
|
55
|
+
"adapterField": "liveAdapter",
|
|
40
56
|
"personalDefault": true,
|
|
41
57
|
"oauth": null,
|
|
42
58
|
"context": {
|
|
@@ -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.1",
|
|
10
10
|
"mcp": {
|
|
11
11
|
"command": "erp-mcp",
|
|
12
12
|
"args": [],
|
|
@@ -62,6 +62,14 @@
|
|
|
62
62
|
"field": "mode",
|
|
63
63
|
"default": "simulation"
|
|
64
64
|
},
|
|
65
|
+
"modeLabels": {
|
|
66
|
+
"simulation": "Test data",
|
|
67
|
+
"live": { "local": "Local" }
|
|
68
|
+
},
|
|
69
|
+
"modeNotes": {
|
|
70
|
+
"live": { "local": "On this computer — no shared access" }
|
|
71
|
+
},
|
|
72
|
+
"adapterField": "liveAdapter",
|
|
65
73
|
"personalDefault": true,
|
|
66
74
|
"oauth": null,
|
|
67
75
|
"documents": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "mail",
|
|
3
|
-
"name": "Email
|
|
3
|
+
"name": "Email",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"description": "Simulated inbox for testing an agent's email replies — nothing leaves the laptop",
|
|
6
6
|
"icon": "mail",
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
"field": "mode",
|
|
41
41
|
"default": "simulation"
|
|
42
42
|
},
|
|
43
|
+
"modeLabels": {
|
|
44
|
+
"simulation": "Test mailbox"
|
|
45
|
+
},
|
|
43
46
|
"personalDefault": false,
|
|
44
47
|
"oauth": null,
|
|
45
48
|
"attachments": {
|
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -3530,8 +3530,11 @@ function isSimulationMode() {
|
|
|
3530
3530
|
if (policySimulation !== void 0) return policySimulation;
|
|
3531
3531
|
return process.env.SUVEREN_SIMULATION === "1";
|
|
3532
3532
|
}
|
|
3533
|
-
function
|
|
3534
|
-
|
|
3533
|
+
function connectorCurrentlySimulated(manifest, fields) {
|
|
3534
|
+
const decl = manifest?.simulation;
|
|
3535
|
+
if (!decl) return false;
|
|
3536
|
+
const value = (fields?.[decl.field] ?? "").trim().toLowerCase() || decl.default.trim().toLowerCase();
|
|
3537
|
+
return value === "simulation";
|
|
3535
3538
|
}
|
|
3536
3539
|
var SIMULATION_BLOCK_REASON = "blocked: simulation mode \u2014 real systems are off";
|
|
3537
3540
|
|
|
@@ -4415,6 +4418,39 @@ function invalidManifestReadGateReason(manifest) {
|
|
|
4415
4418
|
}
|
|
4416
4419
|
return null;
|
|
4417
4420
|
}
|
|
4421
|
+
function invalidModeLabelsReason(manifest) {
|
|
4422
|
+
if (!manifest.modeLabels) return null;
|
|
4423
|
+
if (manifest.modeLabels === null || typeof manifest.modeLabels !== "object" || Array.isArray(manifest.modeLabels)) {
|
|
4424
|
+
return "modeLabels must be an object mapping mode values to labels";
|
|
4425
|
+
}
|
|
4426
|
+
if (!manifest.simulation) {
|
|
4427
|
+
return 'modeLabels is set but this manifest declares no "simulation" marker \u2014 mode labels need a mode field to key off';
|
|
4428
|
+
}
|
|
4429
|
+
const fieldKeys = new Set(manifest.credentials?.fields?.map((f) => f.key) ?? []);
|
|
4430
|
+
let needsAdapterField = false;
|
|
4431
|
+
for (const [mode, label] of Object.entries(manifest.modeLabels)) {
|
|
4432
|
+
if (typeof label === "string") {
|
|
4433
|
+
if (label.length === 0) return `modeLabels.${mode} must be a non-empty string`;
|
|
4434
|
+
continue;
|
|
4435
|
+
}
|
|
4436
|
+
if (label === null || typeof label !== "object" || Array.isArray(label)) {
|
|
4437
|
+
return `modeLabels.${mode} must be a non-empty string or an object keyed by adapter value`;
|
|
4438
|
+
}
|
|
4439
|
+
needsAdapterField = true;
|
|
4440
|
+
for (const [adapter, adapterLabel] of Object.entries(label)) {
|
|
4441
|
+
if (typeof adapterLabel !== "string" || adapterLabel.length === 0) {
|
|
4442
|
+
return `modeLabels.${mode}.${adapter} must be a non-empty string`;
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
if (needsAdapterField && !manifest.adapterField) {
|
|
4447
|
+
return 'modeLabels has an adapter-keyed entry but "adapterField" is missing \u2014 name the credential field holding the adapter';
|
|
4448
|
+
}
|
|
4449
|
+
if (manifest.adapterField && !fieldKeys.has(manifest.adapterField)) {
|
|
4450
|
+
return `adapterField "${manifest.adapterField}" does not name a declared credential field`;
|
|
4451
|
+
}
|
|
4452
|
+
return null;
|
|
4453
|
+
}
|
|
4418
4454
|
var manifests = /* @__PURE__ */ new Map();
|
|
4419
4455
|
function loadManifests(integrationsDir) {
|
|
4420
4456
|
const dir = resolve(
|
|
@@ -4482,6 +4518,11 @@ function loadManifests(integrationsDir) {
|
|
|
4482
4518
|
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${valuesReason}`);
|
|
4483
4519
|
continue;
|
|
4484
4520
|
}
|
|
4521
|
+
const modeLabelsReason = invalidModeLabelsReason(manifest) ?? (manifest.modeNotes ? invalidModeLabelsReason({ ...manifest, modeLabels: manifest.modeNotes })?.replace(/modeLabels/g, "modeNotes") ?? null : null);
|
|
4522
|
+
if (modeLabelsReason) {
|
|
4523
|
+
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${modeLabelsReason}`);
|
|
4524
|
+
continue;
|
|
4525
|
+
}
|
|
4485
4526
|
manifests.set(id, manifest);
|
|
4486
4527
|
loaded++;
|
|
4487
4528
|
} catch (err) {
|
|
@@ -4612,7 +4653,7 @@ function isAttachmentSourceAllowed(profileId, profile, allowedSourcesValue, ref)
|
|
|
4612
4653
|
if (!attachmentSourcesFieldDeclared(profile)) {
|
|
4613
4654
|
return {
|
|
4614
4655
|
allowed: false,
|
|
4615
|
-
reason: `Profile "${profileId}" does not yet declare "${ATTACHMENT_SOURCES_FIELD}" \u2014 no mandate can authorize an attachment
|
|
4656
|
+
reason: `Profile "${profileId}" does not yet declare "${ATTACHMENT_SOURCES_FIELD}" \u2014 no mandate can authorize an attachment \u2014 a mandate on a newer email profile that lists the source is needed.`
|
|
4616
4657
|
};
|
|
4617
4658
|
}
|
|
4618
4659
|
const allowedSet = new Set(
|
|
@@ -4627,6 +4668,22 @@ function isAttachmentSourceAllowed(profileId, profile, allowedSourcesValue, ref)
|
|
|
4627
4668
|
}
|
|
4628
4669
|
return { allowed: true, reason: "" };
|
|
4629
4670
|
}
|
|
4671
|
+
function candidatesAllowingAttachments(candidates, refs, profileOf) {
|
|
4672
|
+
if (refs.length === 0) return { allowed: candidates };
|
|
4673
|
+
let refusal;
|
|
4674
|
+
const allowed = candidates.filter((c) => {
|
|
4675
|
+
const value = typeof c.context?.[ATTACHMENT_SOURCES_FIELD] === "string" ? c.context[ATTACHMENT_SOURCES_FIELD] : void 0;
|
|
4676
|
+
for (const ref of refs) {
|
|
4677
|
+
const check = isAttachmentSourceAllowed(c.profileId, profileOf(c.profileId), value, ref);
|
|
4678
|
+
if (!check.allowed) {
|
|
4679
|
+
refusal ??= check.reason;
|
|
4680
|
+
return false;
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
return true;
|
|
4684
|
+
});
|
|
4685
|
+
return { allowed, refusal: allowed.length ? void 0 : refusal };
|
|
4686
|
+
}
|
|
4630
4687
|
|
|
4631
4688
|
// src/lib/preview.ts
|
|
4632
4689
|
import { computeContentHash } from "@hap/core";
|
|
@@ -5692,18 +5749,19 @@ async function finalizeAttachmentsForExecution(integrationManager2, integrationI
|
|
|
5692
5749
|
contentBase64: d.contentBase64
|
|
5693
5750
|
}));
|
|
5694
5751
|
} else {
|
|
5695
|
-
const
|
|
5696
|
-
const
|
|
5697
|
-
const
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5752
|
+
const arg = transport.arg;
|
|
5753
|
+
const attachments = resolvedDocs.map((d) => ({ filename: d.filename, contentType: d.contentType, contentBase64: d.contentBase64 }));
|
|
5754
|
+
const withRaw = (a) => ({
|
|
5755
|
+
...a,
|
|
5756
|
+
[arg]: toBase64Url(buildMimeMessage({
|
|
5757
|
+
to: toStringArray(a.to),
|
|
5758
|
+
cc: toStringArray(a.cc),
|
|
5759
|
+
subject: typeof a.subject === "string" ? a.subject : "",
|
|
5760
|
+
bodyText: typeof a.body === "string" ? a.body : "",
|
|
5761
|
+
attachments
|
|
5762
|
+
}))
|
|
5705
5763
|
});
|
|
5706
|
-
|
|
5764
|
+
return { ok: true, args: withRaw(outgoing), rebuildTransport: withRaw };
|
|
5707
5765
|
}
|
|
5708
5766
|
}
|
|
5709
5767
|
return { ok: true, args: outgoing };
|
|
@@ -6632,7 +6690,20 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
6632
6690
|
};
|
|
6633
6691
|
}
|
|
6634
6692
|
const contextKeys = getProfile5(passers[0].profileId)?.scopeSchema?.keyOrder ?? [];
|
|
6635
|
-
|
|
6693
|
+
let pool = passers;
|
|
6694
|
+
if (tool.gating?.attachmentsArg) {
|
|
6695
|
+
const raw = args[tool.gating.attachmentsArg];
|
|
6696
|
+
const refs = Array.isArray(raw) ? raw.filter((r) => !!r && typeof r === "object" && typeof r.connector === "string" && typeof r.kind === "string") : [];
|
|
6697
|
+
const narrowed = candidatesAllowingAttachments(passers, refs, (id) => getProfile5(id));
|
|
6698
|
+
if (narrowed.allowed.length === 0) {
|
|
6699
|
+
return {
|
|
6700
|
+
content: [{ type: "text", text: `Refused: attachment not allowed by any ${tool.gating.profile ?? ""} mandate. ${narrowed.refusal ?? ""}`.trim() }],
|
|
6701
|
+
isError: true
|
|
6702
|
+
};
|
|
6703
|
+
}
|
|
6704
|
+
pool = narrowed.allowed;
|
|
6705
|
+
}
|
|
6706
|
+
return attemptWithCandidates(pool);
|
|
6636
6707
|
async function attemptWithCandidates(candidates) {
|
|
6637
6708
|
const selection = selectAuthorization(
|
|
6638
6709
|
contextKeys,
|
|
@@ -6725,12 +6796,14 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
6725
6796
|
}
|
|
6726
6797
|
}
|
|
6727
6798
|
let finalArgs = callArgs;
|
|
6799
|
+
let rebuildTransport;
|
|
6728
6800
|
if (tool.gating?.attachmentsArg || tool.gating?.insertFields?.length) {
|
|
6729
6801
|
const finalized = await finalizeAttachmentsForExecution(integrationManager2, tool.integrationId, callArgs);
|
|
6730
6802
|
if (!finalized.ok) {
|
|
6731
6803
|
return { content: [{ type: "text", text: finalized.message }], isError: true };
|
|
6732
6804
|
}
|
|
6733
6805
|
finalArgs = finalized.args;
|
|
6806
|
+
rebuildTransport = finalized.rebuildTransport;
|
|
6734
6807
|
}
|
|
6735
6808
|
let receiptId;
|
|
6736
6809
|
try {
|
|
@@ -6961,6 +7034,7 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.` + a
|
|
|
6961
7034
|
});
|
|
6962
7035
|
let outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, finalArgs, receiptId, auth.subjects?.[0]) : finalArgs;
|
|
6963
7036
|
if (receiptId) outgoingArgs = attachTicketId(tool, outgoingArgs, receiptId);
|
|
7037
|
+
if (rebuildTransport) outgoingArgs = rebuildTransport(outgoingArgs);
|
|
6964
7038
|
outgoingArgs = encodeOutgoingArgs(tool, outgoingArgs);
|
|
6965
7039
|
outgoingArgs = stripSourceLink(outgoingArgs);
|
|
6966
7040
|
outgoingArgs = stripAttachmentsSealedMeta(outgoingArgs);
|
|
@@ -8584,6 +8658,9 @@ function withoutBlockedArgs(schema, blocked) {
|
|
|
8584
8658
|
}
|
|
8585
8659
|
return out;
|
|
8586
8660
|
}
|
|
8661
|
+
function stableEnvKey(env) {
|
|
8662
|
+
return JSON.stringify(Object.keys(env).sort().map((k) => [k, env[k]]));
|
|
8663
|
+
}
|
|
8587
8664
|
var RESPAWN_DELAYS = [2e3, 4e3, 6e3];
|
|
8588
8665
|
var MAX_RESPAWN_ATTEMPTS = 3;
|
|
8589
8666
|
var CONNECT_TIMEOUT_MS = 3e4;
|
|
@@ -8899,7 +8976,7 @@ var IntegrationManager = class {
|
|
|
8899
8976
|
if (opts.skipIfRunning) return existing.tools;
|
|
8900
8977
|
await this.stopIntegrationLocked(config.id);
|
|
8901
8978
|
}
|
|
8902
|
-
if (isSimulationMode() && !
|
|
8979
|
+
if (isSimulationMode() && !this.isSimulationSafe(config.id)) {
|
|
8903
8980
|
throw new Error(`${config.id}: ${SIMULATION_BLOCK_REASON}`);
|
|
8904
8981
|
}
|
|
8905
8982
|
if (config.npmPackage) {
|
|
@@ -8925,6 +9002,7 @@ var IntegrationManager = class {
|
|
|
8925
9002
|
);
|
|
8926
9003
|
}
|
|
8927
9004
|
const env = this.resolveEnvKeys(config);
|
|
9005
|
+
const credentialEnv = stableEnvKey(env);
|
|
8928
9006
|
if (isSimulationMode()) {
|
|
8929
9007
|
const manifest = getManifest(config.id);
|
|
8930
9008
|
const simDecl = manifest?.simulation;
|
|
@@ -8998,7 +9076,8 @@ var IntegrationManager = class {
|
|
|
8998
9076
|
client,
|
|
8999
9077
|
transport,
|
|
9000
9078
|
tools,
|
|
9001
|
-
respawnAttempts: 0
|
|
9079
|
+
respawnAttempts: 0,
|
|
9080
|
+
credentialEnv
|
|
9002
9081
|
};
|
|
9003
9082
|
this.running.set(config.id, entry);
|
|
9004
9083
|
transport.onclose = () => {
|
|
@@ -9119,13 +9198,24 @@ var IntegrationManager = class {
|
|
|
9119
9198
|
return this.builtins.get(id)?.def.alwaysListed === true;
|
|
9120
9199
|
}
|
|
9121
9200
|
/**
|
|
9122
|
-
* Whether `id` may run while simulation mode is on: a
|
|
9123
|
-
*
|
|
9201
|
+
* Whether `id` may run while simulation mode is on: a built-in that
|
|
9202
|
+
* declares `simulation: true`, or a connector whose CURRENT setting (the
|
|
9203
|
+
* credential/setting field its manifest's `simulation.field` names,
|
|
9204
|
+
* defaulting per `simulation.default` when unset) resolves to simulated —
|
|
9205
|
+
* NOT merely a connector whose manifest supports a simulated mode at all.
|
|
9206
|
+
* A connector set to a real mode (e.g. ERP's `mode=live`) is refused here
|
|
9207
|
+
* exactly like one with no simulation capability at all.
|
|
9208
|
+
*
|
|
9209
|
+
* Reads `serviceCredentials` directly, keyed by `id` — the same convention
|
|
9210
|
+
* `resolveEnvKeys`'s manifest-mapping fallback and `getServiceCredentials`
|
|
9211
|
+
* already rely on: a connector's OWN credential fields (mode, liveAdapter,
|
|
9212
|
+
* …) are pushed to this gateway under its own id (control-plane's
|
|
9213
|
+
* `pushServiceCredentials(id, fields)`), never a shared/other service id.
|
|
9124
9214
|
*/
|
|
9125
9215
|
isSimulationSafe(id) {
|
|
9126
9216
|
const builtin = this.builtins.get(id);
|
|
9127
9217
|
if (builtin) return builtin.def.simulation === true;
|
|
9128
|
-
return
|
|
9218
|
+
return connectorCurrentlySimulated(getManifest(id), this.serviceCredentials.get(id));
|
|
9129
9219
|
}
|
|
9130
9220
|
/**
|
|
9131
9221
|
* The built-in's own refusal for this call, checked before the gate (no proposal,
|
|
@@ -9287,15 +9377,17 @@ var IntegrationManager = class {
|
|
|
9287
9377
|
toolCount: 0,
|
|
9288
9378
|
readAgeDays: readAgeOf(config),
|
|
9289
9379
|
// Computed fresh, not cached: true whenever simulation mode is on
|
|
9290
|
-
// AND this connector
|
|
9291
|
-
//
|
|
9292
|
-
//
|
|
9293
|
-
//
|
|
9294
|
-
//
|
|
9295
|
-
//
|
|
9380
|
+
// AND this connector is not currently simulation-safe (see
|
|
9381
|
+
// `isSimulationSafe` — no capability at all, OR capability
|
|
9382
|
+
// declared but currently SET to a real mode), which is exactly
|
|
9383
|
+
// the condition startIntegrationLocked refuses under — so the UI
|
|
9384
|
+
// always reflects why a real connector never started, without
|
|
9385
|
+
// needing a separate tracking path for this one reason. This is
|
|
9386
|
+
// `paused`, NOT `error`: it is not a problem, it's the mode
|
|
9387
|
+
// working as designed (see IntegrationStatus.paused). Genuine
|
|
9296
9388
|
// start failures still surface via the one-shot `warning` string
|
|
9297
9389
|
// returned by /internal/add-integration — they never land here.
|
|
9298
|
-
paused: isSimulationMode() && !
|
|
9390
|
+
paused: isSimulationMode() && !this.isSimulationSafe(config.id) ? "simulation" : void 0
|
|
9299
9391
|
});
|
|
9300
9392
|
}
|
|
9301
9393
|
}
|
|
@@ -9308,6 +9400,21 @@ var IntegrationManager = class {
|
|
|
9308
9400
|
isRunning(id) {
|
|
9309
9401
|
return this.running.has(id);
|
|
9310
9402
|
}
|
|
9403
|
+
/**
|
|
9404
|
+
* True when the integration is running but the vault credentials would now
|
|
9405
|
+
* resolve to a different env than the one its process was started with.
|
|
9406
|
+
*
|
|
9407
|
+
* Why: at boot the gateway starts connectors whose settings are all optional
|
|
9408
|
+
* (ERP, CRM — mode/adapter/company file) BEFORE the vault is unlocked, so they
|
|
9409
|
+
* come up on their defaults (simulation). The settings arrive on unlock, and
|
|
9410
|
+
* without a restart the process keeps running on the wrong mode — the user's
|
|
9411
|
+
* live data silently disappears from view.
|
|
9412
|
+
*/
|
|
9413
|
+
credentialsChangedSinceStart(config) {
|
|
9414
|
+
const entry = this.running.get(config.id);
|
|
9415
|
+
if (!entry) return false;
|
|
9416
|
+
return stableEnvKey(this.resolveEnvKeys(config)) !== entry.credentialEnv;
|
|
9417
|
+
}
|
|
9311
9418
|
/**
|
|
9312
9419
|
* Check if all required env keys for an integration can be resolved.
|
|
9313
9420
|
*/
|
|
@@ -9474,7 +9581,8 @@ var IntegrationManager = class {
|
|
|
9474
9581
|
client: null,
|
|
9475
9582
|
transport: null,
|
|
9476
9583
|
tools: [],
|
|
9477
|
-
respawnAttempts: attempts + 1
|
|
9584
|
+
respawnAttempts: attempts + 1,
|
|
9585
|
+
credentialEnv: ""
|
|
9478
9586
|
};
|
|
9479
9587
|
this.running.set(id, fakeEntry);
|
|
9480
9588
|
this.handleCrash(id);
|
|
@@ -10354,8 +10462,9 @@ function briefRefusal(args) {
|
|
|
10354
10462
|
function connectedSystems(im) {
|
|
10355
10463
|
const byId = /* @__PURE__ */ new Map();
|
|
10356
10464
|
for (const t of im.getAllTools()) {
|
|
10357
|
-
if (im.isBuiltin(t.integrationId) || !t.gating?.profile || t.gating.category === "disabled") continue;
|
|
10358
|
-
const
|
|
10465
|
+
if (im.isBuiltin(t.integrationId) || t.internal || !t.gating?.profile || t.gating.category === "disabled") continue;
|
|
10466
|
+
const key = `${t.integrationId}|${t.gating.profile}`;
|
|
10467
|
+
const s = byId.get(key) ?? { id: t.integrationId, profile: t.gating.profile, actionTypes: [], writeTools: [], readTools: [] };
|
|
10359
10468
|
if (t.gating.category === "read") {
|
|
10360
10469
|
s.readTools.push(t.originalName);
|
|
10361
10470
|
} else {
|
|
@@ -10363,9 +10472,9 @@ function connectedSystems(im) {
|
|
|
10363
10472
|
const at = t.gating.staticExecution?.action_type;
|
|
10364
10473
|
if (typeof at === "string" && !s.actionTypes.includes(at)) s.actionTypes.push(at);
|
|
10365
10474
|
}
|
|
10366
|
-
byId.set(
|
|
10475
|
+
byId.set(key, s);
|
|
10367
10476
|
}
|
|
10368
|
-
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
10477
|
+
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id) || a.profile.localeCompare(b.profile));
|
|
10369
10478
|
}
|
|
10370
10479
|
function shortName(idOrName) {
|
|
10371
10480
|
const stripped = idOrName.replace(/@.*$/, "").split("/").pop() ?? idOrName;
|
|
@@ -10586,7 +10695,7 @@ function setupBuiltin(deps) {
|
|
|
10586
10695
|
const guides = loadGuides();
|
|
10587
10696
|
const systems = connectedSystems(deps.integrationManager);
|
|
10588
10697
|
const running = new Set(systems.map((s) => s.id));
|
|
10589
|
-
const inactive = getAllManifests().filter((m) =>
|
|
10698
|
+
const inactive = getAllManifests().filter((m) => deps.integrationManager.isSimulationSafe(m.id) && !running.has(m.id)).map((m) => m.name);
|
|
10590
10699
|
const header = guideHeader(systems, inactive);
|
|
10591
10700
|
const topic = typeof args.topic === "string" ? args.topic.trim().toLowerCase() : "";
|
|
10592
10701
|
if (!topic) {
|
|
@@ -11624,11 +11733,16 @@ async function startIntegrationForService(serviceId) {
|
|
|
11624
11733
|
(c) => c.id === serviceId || Object.values(c.envKeys ?? {}).some((ref) => typeof ref === "string" && ref.startsWith(`${serviceId}.`))
|
|
11625
11734
|
);
|
|
11626
11735
|
for (const config of candidates) {
|
|
11736
|
+
if (integrationManager.isRunning(config.id) && integrationManager.credentialsChangedSinceStart(config)) {
|
|
11737
|
+
console.error(`[Suveren MCP] ${config.id}: credentials changed since start \u2014 restarting`);
|
|
11738
|
+
await startOneIntegration(config, { restart: true });
|
|
11739
|
+
continue;
|
|
11740
|
+
}
|
|
11627
11741
|
await startOneIntegration(config);
|
|
11628
11742
|
}
|
|
11629
11743
|
}
|
|
11630
|
-
async function startOneIntegration(config) {
|
|
11631
|
-
if (integrationManager.isRunning(config.id)) return;
|
|
11744
|
+
async function startOneIntegration(config, opts = {}) {
|
|
11745
|
+
if (!opts.restart && integrationManager.isRunning(config.id)) return;
|
|
11632
11746
|
const needsCreds = Object.keys(config.envKeys ?? {}).length > 0;
|
|
11633
11747
|
if (needsCreds && !integrationManager.canResolveEnvKeys(config)) {
|
|
11634
11748
|
const missing = [];
|
|
@@ -11647,7 +11761,7 @@ async function startOneIntegration(config) {
|
|
|
11647
11761
|
const manifest = getManifest(config.id);
|
|
11648
11762
|
const effectiveConfig = manifest ? { ...config, toolGating: manifest.toolGating, npmPackage: manifest.npmPackage ?? config.npmPackage } : config;
|
|
11649
11763
|
try {
|
|
11650
|
-
await integrationManager.startIntegration(effectiveConfig, { skipIfRunning:
|
|
11764
|
+
await integrationManager.startIntegration(effectiveConfig, { skipIfRunning: !opts.restart });
|
|
11651
11765
|
} catch (err) {
|
|
11652
11766
|
console.error(`[Suveren MCP] Failed to start integration ${config.id}:`, err);
|
|
11653
11767
|
}
|