@suveren/gateway 0.31.0 → 0.31.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 +5 -0
- package/content/integrations/mollie.json +5 -0
- package/dist/control-plane/index.mjs +81 -32
- package/dist/mcp-server/http.mjs +43 -6
- package/dist/ui/assets/{index-DyZY5sLI.css → index-CC9U7Luz.css} +1 -1
- package/dist/ui/assets/index-CfPjctPy.js +123 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-B7qy0B9J.js +0 -123
|
@@ -216,6 +216,7 @@
|
|
|
216
216
|
},
|
|
217
217
|
"log_activity": {
|
|
218
218
|
"summary": "Log {type}: {summary|short}",
|
|
219
|
+
"actionLabel": "Activity logged",
|
|
219
220
|
"executionMapping": {
|
|
220
221
|
"contact_type": "contact_type"
|
|
221
222
|
},
|
|
@@ -236,6 +237,7 @@
|
|
|
236
237
|
},
|
|
237
238
|
"create_deal": {
|
|
238
239
|
"summary": "Create deal: {title|short}[ — {value|money}]",
|
|
240
|
+
"actionLabel": "Deal created",
|
|
239
241
|
"executionMapping": {
|
|
240
242
|
"contact_type": "contact_type"
|
|
241
243
|
},
|
|
@@ -256,6 +258,7 @@
|
|
|
256
258
|
},
|
|
257
259
|
"update_deal": {
|
|
258
260
|
"summary": "Update deal[: {title|short}][ — {value|money}]",
|
|
261
|
+
"actionLabel": "Deal updated",
|
|
259
262
|
"executionMapping": {
|
|
260
263
|
"contact_type": "contact_type"
|
|
261
264
|
},
|
|
@@ -285,6 +288,7 @@
|
|
|
285
288
|
},
|
|
286
289
|
"create_task": {
|
|
287
290
|
"summary": "Create task: {title|short}[ (due {due_date|date})]",
|
|
291
|
+
"actionLabel": "Task created",
|
|
288
292
|
"executionMapping": {
|
|
289
293
|
"contact_type": "contact_type"
|
|
290
294
|
},
|
|
@@ -305,6 +309,7 @@
|
|
|
305
309
|
},
|
|
306
310
|
"complete_task": {
|
|
307
311
|
"summary": "Complete a task",
|
|
312
|
+
"actionLabel": "Task completed",
|
|
308
313
|
"executionMapping": {
|
|
309
314
|
"contact_type": "contact_type"
|
|
310
315
|
},
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"overrides": {
|
|
40
40
|
"CreatePayment": {
|
|
41
41
|
"summary": "Payment of {amount|money}: {description|short}",
|
|
42
|
+
"actionLabel": "Payment created",
|
|
42
43
|
"executionMapping": {
|
|
43
44
|
"amount": "amount",
|
|
44
45
|
"currency": "currency"
|
|
@@ -58,6 +59,7 @@
|
|
|
58
59
|
},
|
|
59
60
|
"CreatePaymentLink": {
|
|
60
61
|
"summary": "Payment link[ of {amount|money}]: {description|short}",
|
|
62
|
+
"actionLabel": "Payment link created",
|
|
61
63
|
"executionMapping": {
|
|
62
64
|
"amount": "amount",
|
|
63
65
|
"currency": "currency"
|
|
@@ -92,6 +94,7 @@
|
|
|
92
94
|
},
|
|
93
95
|
"CancelSubscription": {
|
|
94
96
|
"summary": "Cancel a subscription",
|
|
97
|
+
"actionLabel": "Subscription cancelled",
|
|
95
98
|
"executionMapping": {},
|
|
96
99
|
"staticExecution": {
|
|
97
100
|
"action_type": "subscribe"
|
|
@@ -99,6 +102,7 @@
|
|
|
99
102
|
},
|
|
100
103
|
"CreateCustomer": {
|
|
101
104
|
"summary": "Create payment customer[ {name}]",
|
|
105
|
+
"actionLabel": "Payment customer created",
|
|
102
106
|
"executionMapping": {},
|
|
103
107
|
"staticExecution": {
|
|
104
108
|
"action_type": "charge"
|
|
@@ -118,6 +122,7 @@
|
|
|
118
122
|
},
|
|
119
123
|
"RevokeMandate": {
|
|
120
124
|
"summary": "Revoke a payment mandate",
|
|
125
|
+
"actionLabel": "Payment mandate revoked",
|
|
121
126
|
"executionMapping": {},
|
|
122
127
|
"staticExecution": {
|
|
123
128
|
"action_type": "charge"
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
8
|
-
import { existsSync as existsSync13, readFileSync as
|
|
9
|
-
import { dirname as
|
|
10
|
-
import { homedir as
|
|
8
|
+
import { existsSync as existsSync13, readFileSync as readFileSync18, renameSync as renameSync6 } from "fs";
|
|
9
|
+
import { dirname as dirname13, join as join18 } from "path";
|
|
10
|
+
import { homedir as homedir11 } from "os";
|
|
11
11
|
import express from "express";
|
|
12
12
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
13
13
|
|
|
@@ -3384,15 +3384,15 @@ import { promisify } from "util";
|
|
|
3384
3384
|
var run = promisify(execFile);
|
|
3385
3385
|
var SUPPORTED = /* @__PURE__ */ new Set(["darwin", "win32", "linux"]);
|
|
3386
3386
|
var MODULE_DIR = pathDirname(fileURLToPath(import.meta.url));
|
|
3387
|
-
function findCli(
|
|
3387
|
+
function findCli(dirname14 = MODULE_DIR) {
|
|
3388
3388
|
const candidates = [
|
|
3389
3389
|
// Bundled (the shipped layout): dist/control-plane → dist → root → bin
|
|
3390
|
-
resolve(
|
|
3390
|
+
resolve(dirname14, "..", "..", "bin", "suveren-gateway.js"),
|
|
3391
3391
|
// Bundled, were the routes/ directory ever preserved.
|
|
3392
|
-
resolve(
|
|
3392
|
+
resolve(dirname14, "..", "..", "..", "bin", "suveren-gateway.js"),
|
|
3393
3393
|
// Dev, running from source: the repo's own bundle directory.
|
|
3394
|
-
resolve(
|
|
3395
|
-
resolve(
|
|
3394
|
+
resolve(dirname14, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
|
|
3395
|
+
resolve(dirname14, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
|
|
3396
3396
|
// Deliberately NO process.cwd() fallback: it makes the result depend on
|
|
3397
3397
|
// where the process happened to be launched from, so the same install
|
|
3398
3398
|
// resolves differently between a shell and a login service — and it would
|
|
@@ -4724,15 +4724,63 @@ function createTicketsSeenRouter(dataDir) {
|
|
|
4724
4724
|
return router;
|
|
4725
4725
|
}
|
|
4726
4726
|
|
|
4727
|
-
// src/routes/
|
|
4727
|
+
// src/routes/report-seen.ts
|
|
4728
4728
|
import { Router as Router21 } from "express";
|
|
4729
|
+
|
|
4730
|
+
// src/lib/report-seen-store.ts
|
|
4731
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11, renameSync as renameSync5 } from "fs";
|
|
4732
|
+
import { homedir as homedir10 } from "os";
|
|
4733
|
+
import { join as join16, dirname as dirname12 } from "path";
|
|
4734
|
+
function storePath4(dataDir) {
|
|
4735
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join16(homedir10(), ".suveren");
|
|
4736
|
+
return join16(dir, "report-seen.json");
|
|
4737
|
+
}
|
|
4738
|
+
function readLastSeenReportAt(dataDir) {
|
|
4739
|
+
try {
|
|
4740
|
+
const raw = JSON.parse(readFileSync16(storePath4(dataDir), "utf8"));
|
|
4741
|
+
return typeof raw.lastSeenReportAt === "number" && Number.isFinite(raw.lastSeenReportAt) ? raw.lastSeenReportAt : 0;
|
|
4742
|
+
} catch {
|
|
4743
|
+
return 0;
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4746
|
+
function markReportSeen(ts, dataDir) {
|
|
4747
|
+
const safeTs = typeof ts === "number" && Number.isFinite(ts) ? ts : Date.now();
|
|
4748
|
+
const path = storePath4(dataDir);
|
|
4749
|
+
mkdirSync11(dirname12(path), { recursive: true });
|
|
4750
|
+
const tmp = `${path}.tmp`;
|
|
4751
|
+
writeFileSync11(tmp, JSON.stringify({ lastSeenReportAt: safeTs }, null, 2) + "\n", "utf8");
|
|
4752
|
+
renameSync5(tmp, path);
|
|
4753
|
+
return safeTs;
|
|
4754
|
+
}
|
|
4755
|
+
|
|
4756
|
+
// src/routes/report-seen.ts
|
|
4757
|
+
function createReportSeenRouter(dataDir) {
|
|
4758
|
+
const router = Router21();
|
|
4759
|
+
router.get("/", (_req, res) => {
|
|
4760
|
+
res.json({ lastSeenReportAt: readLastSeenReportAt(dataDir) });
|
|
4761
|
+
});
|
|
4762
|
+
router.post("/", (req, res) => {
|
|
4763
|
+
const body = req.body;
|
|
4764
|
+
const ts = typeof body?.ts === "number" && Number.isFinite(body.ts) ? body.ts : Date.now();
|
|
4765
|
+
try {
|
|
4766
|
+
res.json({ lastSeenReportAt: markReportSeen(ts, dataDir) });
|
|
4767
|
+
} catch (err) {
|
|
4768
|
+
res.status(500).json({ error: `Could not save: ${err.message}` });
|
|
4769
|
+
}
|
|
4770
|
+
});
|
|
4771
|
+
return router;
|
|
4772
|
+
}
|
|
4773
|
+
|
|
4774
|
+
// src/routes/internal-events.ts
|
|
4775
|
+
import { Router as Router22 } from "express";
|
|
4729
4776
|
import { timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
4730
4777
|
var ALLOWED = [
|
|
4731
4778
|
"proposal-added",
|
|
4732
4779
|
"action-approval-needed",
|
|
4733
4780
|
"execution-failed",
|
|
4734
4781
|
"denial-recorded",
|
|
4735
|
-
"ticket-recorded"
|
|
4782
|
+
"ticket-recorded",
|
|
4783
|
+
"report-written"
|
|
4736
4784
|
];
|
|
4737
4785
|
var SESSION_EXPIRED = "session-expired";
|
|
4738
4786
|
var AS_KEY_MISMATCH = "as-key-mismatch";
|
|
@@ -4746,7 +4794,7 @@ function secretMatches2(provided, expected) {
|
|
|
4746
4794
|
return timingSafeEqual3(a, b);
|
|
4747
4795
|
}
|
|
4748
4796
|
function createInternalEventsRouter(getSecret, onSessionExpired, onAsKeyMismatch, onAsTlsMismatch) {
|
|
4749
|
-
const router =
|
|
4797
|
+
const router = Router22();
|
|
4750
4798
|
router.post("/event", (req, res) => {
|
|
4751
4799
|
if (!secretMatches2(req.headers["x-internal-secret"], getSecret())) {
|
|
4752
4800
|
res.status(401).json({ error: "Unauthorized" });
|
|
@@ -4780,7 +4828,7 @@ function createInternalEventsRouter(getSecret, onSessionExpired, onAsKeyMismatch
|
|
|
4780
4828
|
}
|
|
4781
4829
|
|
|
4782
4830
|
// src/routes/internal-mandate.ts
|
|
4783
|
-
import { Router as
|
|
4831
|
+
import { Router as Router23 } from "express";
|
|
4784
4832
|
import { timingSafeEqual as timingSafeEqual4, randomUUID } from "crypto";
|
|
4785
4833
|
|
|
4786
4834
|
// src/lib/mandate-ceremony.ts
|
|
@@ -4962,7 +5010,7 @@ function secretMatches3(provided, expected) {
|
|
|
4962
5010
|
return a.length === b.length && timingSafeEqual4(a, b);
|
|
4963
5011
|
}
|
|
4964
5012
|
function createInternalMandateRouter(getSecret, deps) {
|
|
4965
|
-
const router =
|
|
5013
|
+
const router = Router23();
|
|
4966
5014
|
router.post("/mandate", async (req, res) => {
|
|
4967
5015
|
if (!secretMatches3(req.header("X-Internal-Secret") ?? void 0, getSecret())) {
|
|
4968
5016
|
res.status(401).json({ error: "unauthorized" });
|
|
@@ -5165,17 +5213,17 @@ function buildSessionHealth(vault2) {
|
|
|
5165
5213
|
}
|
|
5166
5214
|
|
|
5167
5215
|
// src/lib/denials-reader.ts
|
|
5168
|
-
import { existsSync as existsSync12, readFileSync as
|
|
5169
|
-
import { join as
|
|
5216
|
+
import { existsSync as existsSync12, readFileSync as readFileSync17 } from "fs";
|
|
5217
|
+
import { join as join17 } from "path";
|
|
5170
5218
|
function loadDenials(dataDir, decrypt) {
|
|
5171
|
-
const encPath =
|
|
5172
|
-
const plainPath =
|
|
5219
|
+
const encPath = join17(dataDir, "denials.enc.json");
|
|
5220
|
+
const plainPath = join17(dataDir, "denials.json");
|
|
5173
5221
|
if (existsSync12(encPath)) {
|
|
5174
|
-
const { blob } = JSON.parse(
|
|
5222
|
+
const { blob } = JSON.parse(readFileSync17(encPath, "utf-8"));
|
|
5175
5223
|
return JSON.parse(decrypt(blob)).records ?? [];
|
|
5176
5224
|
}
|
|
5177
5225
|
if (existsSync12(plainPath)) {
|
|
5178
|
-
return JSON.parse(
|
|
5226
|
+
return JSON.parse(readFileSync17(plainPath, "utf-8")).records ?? [];
|
|
5179
5227
|
}
|
|
5180
5228
|
return [];
|
|
5181
5229
|
}
|
|
@@ -5199,11 +5247,11 @@ function isSimulationMode() {
|
|
|
5199
5247
|
// src/index.ts
|
|
5200
5248
|
(function migrateDataDir() {
|
|
5201
5249
|
if (process.env.SUVEREN_DATA_DIR) return;
|
|
5202
|
-
const oldDir =
|
|
5203
|
-
const newDir =
|
|
5250
|
+
const oldDir = join18(homedir11(), ".hap");
|
|
5251
|
+
const newDir = join18(homedir11(), ".suveren");
|
|
5204
5252
|
if (!existsSync13(newDir) && existsSync13(oldDir)) {
|
|
5205
5253
|
try {
|
|
5206
|
-
|
|
5254
|
+
renameSync6(oldDir, newDir);
|
|
5207
5255
|
console.error(`[Control Plane] Migrated data directory: ${oldDir} \u2192 ${newDir}`);
|
|
5208
5256
|
} catch (err) {
|
|
5209
5257
|
console.error(`[Control Plane] FATAL: could not migrate data directory ${oldDir} \u2192 ${newDir}:`, err);
|
|
@@ -5211,7 +5259,7 @@ function isSimulationMode() {
|
|
|
5211
5259
|
}
|
|
5212
5260
|
}
|
|
5213
5261
|
})();
|
|
5214
|
-
var DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
5262
|
+
var DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join18(homedir11(), ".suveren");
|
|
5215
5263
|
var SP_URL = resolveAsUrl(DATA_DIR);
|
|
5216
5264
|
validatePinTlsForUrl(resolvePinTls(DATA_DIR), SP_URL);
|
|
5217
5265
|
var POLICY_LOCKED = [...readPolicy().locked];
|
|
@@ -5219,7 +5267,7 @@ var POLICY_LOCKED_INSTALL_METHOD = readPolicy().policy.installMethod;
|
|
|
5219
5267
|
var POLICY_LOCKED_TEAM = readPolicy().policy.team;
|
|
5220
5268
|
var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
|
|
5221
5269
|
var HAP_MODE = process.env.HAP_MODE ?? "personal";
|
|
5222
|
-
var UI_DIST = process.env.HAP_UI_DIST ??
|
|
5270
|
+
var UI_DIST = process.env.HAP_UI_DIST ?? join18(import.meta.dirname ?? __dirname, "../../ui/dist");
|
|
5223
5271
|
var vault = new Vault(DATA_DIR);
|
|
5224
5272
|
var shownSnapshotStore = new ShownSnapshotStore(DATA_DIR);
|
|
5225
5273
|
(function repairIfAsUrlChanged() {
|
|
@@ -5530,6 +5578,7 @@ app.use("/api/gateway-settings", jsonParser, authGuard, createGatewaySettingsRou
|
|
|
5530
5578
|
app.use("/api/archived-mandates", jsonParser, authGuard, createArchivedMandatesRouter());
|
|
5531
5579
|
app.use("/api/blocked-seen", jsonParser, authGuard, createBlockedSeenRouter());
|
|
5532
5580
|
app.use("/api/tickets-seen", jsonParser, authGuard, createTicketsSeenRouter());
|
|
5581
|
+
app.use("/api/report-seen", jsonParser, authGuard, createReportSeenRouter());
|
|
5533
5582
|
app.use("/api/encrypt-intent", jsonParser, authGuard, createEncryptIntentRouter());
|
|
5534
5583
|
app.use("/api/evidence-export", authGuard, createEvidenceExportRouter(SP_URL, vault, DATA_DIR, lockAsTlsMismatch));
|
|
5535
5584
|
app.use("/proposal-status", jsonParser, authGuard, createProposalStatusRouter(SP_URL, vault, DATA_DIR, lockAsTlsMismatch, shownSnapshotStore));
|
|
@@ -5706,7 +5755,7 @@ app.get("/as-pairing", authGuard, (_req, res) => {
|
|
|
5706
5755
|
});
|
|
5707
5756
|
app.get("/denials", authGuard, (req, res) => {
|
|
5708
5757
|
try {
|
|
5709
|
-
const dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
5758
|
+
const dataDir = process.env.SUVEREN_DATA_DIR ?? join18(homedir11(), ".suveren");
|
|
5710
5759
|
const all = loadDenials(dataDir, (blob) => vault.decrypt(blob));
|
|
5711
5760
|
const since = Number(req.query.since);
|
|
5712
5761
|
const limit = Number(req.query.limit);
|
|
@@ -5765,12 +5814,12 @@ app.put("/agent-brief/context", jsonParser, authGuard, async (req, res) => {
|
|
|
5765
5814
|
res.status(413).json({ error: `Context exceeds ${AGENT_CONTEXT_MAX_BYTES}-byte cap` });
|
|
5766
5815
|
return;
|
|
5767
5816
|
}
|
|
5768
|
-
const { mkdirSync:
|
|
5817
|
+
const { mkdirSync: mkdirSync12, writeFileSync: writeFileSync12, renameSync: renameSync7 } = await import("fs");
|
|
5769
5818
|
const filePath = agentBriefPath();
|
|
5770
|
-
|
|
5819
|
+
mkdirSync12(dirname13(filePath), { recursive: true });
|
|
5771
5820
|
const tmpPath = `${filePath}.tmp`;
|
|
5772
|
-
|
|
5773
|
-
|
|
5821
|
+
writeFileSync12(tmpPath, content, "utf-8");
|
|
5822
|
+
renameSync7(tmpPath, filePath);
|
|
5774
5823
|
res.json({ ok: true });
|
|
5775
5824
|
} catch (err) {
|
|
5776
5825
|
console.error("[Control Plane] agent-brief/context PUT failed:", err);
|
|
@@ -5862,10 +5911,10 @@ function detectRunningVersion() {
|
|
|
5862
5911
|
return process.env.HAP_BUILD_SHA ?? "dev";
|
|
5863
5912
|
}
|
|
5864
5913
|
const dir = import.meta.dirname ?? __dirname;
|
|
5865
|
-
const bundlePkg =
|
|
5914
|
+
const bundlePkg = join18(dir, "..", "..", "package.json");
|
|
5866
5915
|
if (existsSync13(bundlePkg)) {
|
|
5867
5916
|
try {
|
|
5868
|
-
const pkg = JSON.parse(
|
|
5917
|
+
const pkg = JSON.parse(readFileSync18(bundlePkg, "utf8"));
|
|
5869
5918
|
if (pkg.name === "@suveren/gateway" && typeof pkg.version === "string") {
|
|
5870
5919
|
return pkg.version;
|
|
5871
5920
|
}
|
|
@@ -5917,7 +5966,7 @@ if (existsSync13(UI_DIST)) {
|
|
|
5917
5966
|
}));
|
|
5918
5967
|
app.get("*", (_req, res) => {
|
|
5919
5968
|
res.setHeader("Cache-Control", "no-store, must-revalidate");
|
|
5920
|
-
res.sendFile(
|
|
5969
|
+
res.sendFile(join18(UI_DIST, "index.html"));
|
|
5921
5970
|
});
|
|
5922
5971
|
} else {
|
|
5923
5972
|
app.get("/", (_req, res) => {
|
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -3113,6 +3113,7 @@ var ReportStore = class {
|
|
|
3113
3113
|
const result = await verifyReport(html, sources);
|
|
3114
3114
|
this.report = { html: result.html, savedAt: now, checkedAt: now, result };
|
|
3115
3115
|
this.persist();
|
|
3116
|
+
void notifyControlPlane("report-written");
|
|
3116
3117
|
return this.report;
|
|
3117
3118
|
}
|
|
3118
3119
|
/**
|
|
@@ -5373,10 +5374,10 @@ function zeroCappedBound(profileId, bounds, actionType) {
|
|
|
5373
5374
|
}
|
|
5374
5375
|
return null;
|
|
5375
5376
|
}
|
|
5376
|
-
function toolAlwaysListed(tool, isBuiltin) {
|
|
5377
|
+
function toolAlwaysListed(tool, isBuiltin, builtinAlwaysListed = () => false) {
|
|
5377
5378
|
if (tool.internal || !tool.gating?.profile) return false;
|
|
5378
5379
|
if (tool.gating.category === "disabled") return false;
|
|
5379
|
-
if (isBuiltin(tool.integrationId)) return false;
|
|
5380
|
+
if (isBuiltin(tool.integrationId) && !builtinAlwaysListed(tool.integrationId)) return false;
|
|
5380
5381
|
if (!tool.gating.hideUnlessAuthorized) return true;
|
|
5381
5382
|
return Boolean(tool.gating.simulationOnly) && isSimulationMode();
|
|
5382
5383
|
}
|
|
@@ -6441,9 +6442,10 @@ function buildCapabilityMap(profileId, integrationManager2) {
|
|
|
6441
6442
|
function listedWithoutMandate(authorizations, integrationManager2) {
|
|
6442
6443
|
if (!integrationManager2) return [];
|
|
6443
6444
|
const isBuiltin = (id) => integrationManager2.isBuiltin?.(id) ?? false;
|
|
6445
|
+
const builtinAlwaysListed = (id) => integrationManager2.builtinAlwaysListed?.(id) ?? false;
|
|
6444
6446
|
const byProfile = /* @__PURE__ */ new Map();
|
|
6445
6447
|
for (const tool of integrationManager2.getAllTools()) {
|
|
6446
|
-
if (!toolAlwaysListed(tool, isBuiltin)) continue;
|
|
6448
|
+
if (!toolAlwaysListed(tool, isBuiltin, builtinAlwaysListed)) continue;
|
|
6447
6449
|
const profile = tool.gating.profile;
|
|
6448
6450
|
if (authorizations.some((a) => a.complete && profileMatches(a.profileId, profile))) continue;
|
|
6449
6451
|
const short = shortProfileName2(profile);
|
|
@@ -7418,8 +7420,9 @@ function createMcpServer(state2, integrationManager2) {
|
|
|
7418
7420
|
function refreshTools() {
|
|
7419
7421
|
const auths = state2.getEnrichedAuthorizations();
|
|
7420
7422
|
const isBuiltin = (id) => integrationManager2?.isBuiltin?.(id) ?? false;
|
|
7423
|
+
const builtinAlwaysListed = (id) => integrationManager2?.builtinAlwaysListed?.(id) ?? false;
|
|
7421
7424
|
for (const [, { tool, registered }] of proxiedTools) {
|
|
7422
|
-
if (toolAlwaysListed(tool, isBuiltin)) {
|
|
7425
|
+
if (toolAlwaysListed(tool, isBuiltin, builtinAlwaysListed)) {
|
|
7423
7426
|
registered.enable();
|
|
7424
7427
|
} else if (tool.gating?.profile) {
|
|
7425
7428
|
const matchingAuths = auths.filter(
|
|
@@ -8268,6 +8271,25 @@ var IntegrationManager = class {
|
|
|
8268
8271
|
isBuiltin(id) {
|
|
8269
8272
|
return this.builtins.has(id);
|
|
8270
8273
|
}
|
|
8274
|
+
/**
|
|
8275
|
+
* Names only, for the UI's labels ("Report written", system "Reports"):
|
|
8276
|
+
* id, display name and per-tool action labels. Kept apart from the
|
|
8277
|
+
* connector manifests so a built-in never shows up as a connector card.
|
|
8278
|
+
*/
|
|
8279
|
+
getBuiltinLabels() {
|
|
8280
|
+
return [...this.builtins.values()].map(({ def }) => {
|
|
8281
|
+
const overrides = {};
|
|
8282
|
+
for (const [tool, o] of Object.entries(def.toolGating?.overrides ?? {})) {
|
|
8283
|
+
const label = o?.actionLabel;
|
|
8284
|
+
if (typeof label === "string") overrides[tool] = { actionLabel: label };
|
|
8285
|
+
}
|
|
8286
|
+
return { id: def.id, name: def.name, toolGating: { overrides } };
|
|
8287
|
+
});
|
|
8288
|
+
}
|
|
8289
|
+
/** A built-in that declares `alwaysListed` (see builtin-integration.ts). */
|
|
8290
|
+
builtinAlwaysListed(id) {
|
|
8291
|
+
return this.builtins.get(id)?.def.alwaysListed === true;
|
|
8292
|
+
}
|
|
8271
8293
|
/**
|
|
8272
8294
|
* Whether `id` may run while simulation mode is on: a connector whose manifest
|
|
8273
8295
|
* carries the `simulation` marker, or a built-in that declares `simulation: true`.
|
|
@@ -9191,6 +9213,14 @@ function writeReportRefusal(args) {
|
|
|
9191
9213
|
if (bytes > MAX_REPORT_HTML_BYTES) {
|
|
9192
9214
|
return `the report is ${bytes} bytes, over the ${MAX_REPORT_HTML_BYTES}-byte limit. Shorten it (the gateway draws the verifiable elements for you \u2014 you do not need to inline their data) and write again.`;
|
|
9193
9215
|
}
|
|
9216
|
+
const { html: kept, notes } = sanitizeReport(html);
|
|
9217
|
+
if (!/<sv-(ai|ticket|approval|mandate|record|case|metric|row)\b/i.test(kept)) {
|
|
9218
|
+
const lost = [];
|
|
9219
|
+
if (notes.droppedBlocks > 0) lost.push(`${notes.droppedBlocks} block(s) outside sv-ai`);
|
|
9220
|
+
if (notes.droppedSvInsideAi > 0) lost.push(`${notes.droppedSvInsideAi} sv-* element(s) inside sv-ai`);
|
|
9221
|
+
if (notes.droppedStyles > 0) lost.push(`${notes.droppedStyles} <style> block(s)`);
|
|
9222
|
+
return `nothing in this report would be shown${lost.length ? ` (dropped: ${lost.join(", ")})` : ""}. A report is a flat sequence of top-level blocks: your own content inside <sv-ai>\u2026</sv-ai>, gateway elements (sv-ticket, sv-approval, sv-mandate, sv-record, sv-case, sv-metric, optionally in sv-row) and at most one sv-glossary. Write it again in that form; nothing was stored and no ticket was used.`;
|
|
9223
|
+
}
|
|
9194
9224
|
return void 0;
|
|
9195
9225
|
}
|
|
9196
9226
|
function writeReportTool(deps) {
|
|
@@ -9244,6 +9274,11 @@ var reportBuiltin = (deps) => ({
|
|
|
9244
9274
|
// read-only export CLI; writes only the local, vault-encrypted report file)
|
|
9245
9275
|
// — safe during a simulation-mode test.
|
|
9246
9276
|
simulation: true,
|
|
9277
|
+
// Listed without a mandate, like connector tools: an AI client that ignores
|
|
9278
|
+
// tools/list_changed (ChatGPT) otherwise never sees report__* after the
|
|
9279
|
+
// reporting mandate is signed mid-chat (simulation finding, 2026-10-16).
|
|
9280
|
+
// Listing authorizes nothing — the gate still refuses a call without one.
|
|
9281
|
+
alwaysListed: true,
|
|
9247
9282
|
// The same predicate the reporting window uses to refuse a mandate
|
|
9248
9283
|
// (reporting@0.1, or no window set): the gate never selects such a mandate
|
|
9249
9284
|
// for ANY report tool — so write_report is never ticketed under (and never
|
|
@@ -9255,7 +9290,7 @@ var reportBuiltin = (deps) => ({
|
|
|
9255
9290
|
get_ticket: READ_GATE,
|
|
9256
9291
|
list_cases: READ_GATE,
|
|
9257
9292
|
get_records: READ_GATE,
|
|
9258
|
-
write_report: { executionMapping: {}, staticExecution: { action_type: "report" } }
|
|
9293
|
+
write_report: { executionMapping: {}, staticExecution: { action_type: "report" }, actionLabel: "Report written" }
|
|
9259
9294
|
}
|
|
9260
9295
|
},
|
|
9261
9296
|
tools: [
|
|
@@ -9654,6 +9689,7 @@ function setupBuiltin(deps) {
|
|
|
9654
9689
|
set_agent_brief: {
|
|
9655
9690
|
executionMapping: {},
|
|
9656
9691
|
staticExecution: { action_type: "brief" },
|
|
9692
|
+
actionLabel: "Agent brief set",
|
|
9657
9693
|
hideUnlessAuthorized: true,
|
|
9658
9694
|
approvalView: { content: { label: "New agent brief", kind: "markdown" } },
|
|
9659
9695
|
// AU3 — the approval card's preview of this pending proposal (DL1
|
|
@@ -9668,6 +9704,7 @@ function setupBuiltin(deps) {
|
|
|
9668
9704
|
create_mandate: {
|
|
9669
9705
|
executionMapping: { profile: "allowed_profiles", mode: "allowed_modes" },
|
|
9670
9706
|
staticExecution: { action_type: "mandate" },
|
|
9707
|
+
actionLabel: "Mandate proposed",
|
|
9671
9708
|
hideUnlessAuthorized: true,
|
|
9672
9709
|
// Shown like the mandate screen: limits and scope by the profile's own names.
|
|
9673
9710
|
approvalView: {
|
|
@@ -10437,7 +10474,7 @@ app.get("/internal/authorizations", internalOnly, (_req, res) => {
|
|
|
10437
10474
|
res.json({ authorizations });
|
|
10438
10475
|
});
|
|
10439
10476
|
app.get("/internal/manifests", internalOnly, (_req, res) => {
|
|
10440
|
-
res.json({ manifests: getAllManifests() });
|
|
10477
|
+
res.json({ manifests: getAllManifests(), builtins: integrationManager?.getBuiltinLabels?.() ?? [] });
|
|
10441
10478
|
});
|
|
10442
10479
|
app.get("/internal/skipped-commitments", internalOnly, async (_req, res) => {
|
|
10443
10480
|
try {
|