@suveren/gateway 0.5.0 → 0.6.1
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 +107 -12
- package/content/integrations/deploy-github.json +164 -0
- package/content/integrations/gmail.json +10 -4
- package/content/integrations/index.json +2 -1
- package/dist/control-plane/index.mjs +31 -7
- package/dist/mcp-server/http.mjs +49 -4
- package/dist/ui/assets/index-DecSrutK.js +105 -0
- package/dist/ui/index.html +1 -1
- package/node_modules/@hap/core/dist/index.d.mts +21 -0
- package/node_modules/@hap/core/dist/index.d.ts +21 -0
- package/node_modules/@hap/core/dist/index.js +17 -0
- package/node_modules/@hap/core/dist/index.mjs +17 -0
- package/node_modules/@hap/core/package.json +1 -1
- package/node_modules/@hap/core/src/gatekeeper.ts +28 -0
- package/node_modules/@hap/core/src/types.ts +21 -0
- package/node_modules/fast-uri/index.js +37 -1
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/fast-uri/test/security.test.js +136 -0
- package/node_modules/hono/dist/cjs/helper/proxy/index.js +4 -0
- package/node_modules/hono/dist/cjs/jsx/base.js +1 -9
- package/node_modules/hono/dist/cjs/jsx/hooks/index.js +14 -11
- package/node_modules/hono/dist/cjs/middleware/cors/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/language/language.js +10 -6
- package/node_modules/hono/dist/cjs/utils/cookie.js +2 -1
- package/node_modules/hono/dist/helper/proxy/index.js +4 -0
- package/node_modules/hono/dist/jsx/base.js +1 -9
- package/node_modules/hono/dist/jsx/hooks/index.js +14 -11
- package/node_modules/hono/dist/middleware/cors/index.js +1 -1
- package/node_modules/hono/dist/middleware/language/language.js +10 -6
- package/node_modules/hono/dist/utils/cookie.js +2 -1
- package/node_modules/hono/package.json +2 -3
- package/node_modules/ip-address/README.md +142 -128
- package/node_modules/ip-address/dist/common.d.ts +6 -0
- package/node_modules/ip-address/dist/common.js +17 -1
- package/node_modules/ip-address/dist/common.js.map +1 -1
- package/node_modules/ip-address/dist/ipv4.js +3 -12
- package/node_modules/ip-address/dist/ipv4.js.map +1 -1
- package/node_modules/ip-address/dist/ipv6.d.ts +9 -2
- package/node_modules/ip-address/dist/ipv6.js +35 -16
- package/node_modules/ip-address/dist/ipv6.js.map +1 -1
- package/node_modules/ip-address/package.json +5 -3
- package/node_modules/jose/dist/types/types.d.ts +7 -9
- package/node_modules/jose/dist/webapi/jwe/compact/decrypt.js +2 -2
- package/node_modules/jose/dist/webapi/jwe/flattened/encrypt.js +12 -12
- package/node_modules/jose/dist/webapi/jwe/general/encrypt.js +50 -51
- package/node_modules/jose/dist/webapi/jwks/local.js +10 -31
- package/node_modules/jose/dist/webapi/jwks/remote.js +17 -26
- package/node_modules/jose/dist/webapi/jws/compact/verify.js +2 -2
- package/node_modules/jose/dist/webapi/jws/flattened/sign.js +1 -9
- package/node_modules/jose/dist/webapi/jws/general/sign.js +12 -21
- package/node_modules/jose/dist/webapi/jws/general/verify.js +13 -11
- package/node_modules/jose/dist/webapi/jwt/decrypt.js +3 -3
- package/node_modules/jose/dist/webapi/jwt/verify.js +4 -4
- package/node_modules/jose/dist/webapi/key/export.js +3 -3
- package/node_modules/jose/dist/webapi/key/generate_key_pair.js +7 -6
- package/node_modules/jose/dist/webapi/lib/asn1.js +29 -62
- package/node_modules/jose/dist/webapi/lib/content_encryption.js +16 -34
- package/node_modules/jose/dist/webapi/lib/crypto_key.js +6 -0
- package/node_modules/jose/dist/webapi/lib/invalid_key_input.js +0 -1
- package/node_modules/jose/dist/webapi/lib/jwe_algorithms.js +44 -71
- package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +46 -41
- package/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +14 -13
- package/node_modules/jose/dist/webapi/lib/jwk_to_key.js +5 -10
- package/node_modules/jose/dist/webapi/lib/jws_algorithms.js +20 -18
- package/node_modules/jose/dist/webapi/lib/jws_sign.js +12 -6
- package/node_modules/jose/dist/webapi/lib/jws_verify.js +26 -27
- package/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +60 -102
- package/node_modules/jose/dist/webapi/lib/key.js +61 -99
- package/node_modules/jose/dist/webapi/lib/key_algorithm.js +2 -8
- package/node_modules/jose/dist/webapi/lib/key_descriptor.js +1 -1
- package/node_modules/jose/dist/webapi/lib/key_management.js +74 -161
- package/node_modules/jose/dist/webapi/lib/options.js +11 -14
- package/node_modules/jose/dist/webapi/lib/signing.js +6 -16
- package/node_modules/jose/dist/webapi/lib/type_checks.js +13 -18
- package/node_modules/jose/dist/webapi/util/base64url.js +4 -3
- package/node_modules/jose/package.json +1 -1
- package/package.json +2 -2
- package/profiles/deploy/0.6.profile.json +168 -0
- package/profiles/deploy/0.7.profile.json +168 -0
- package/profiles/email/0.4.profile.json +7 -1
- package/profiles/index.json +3 -1
- package/dist/ui/assets/index-DedRWpBs.js +0 -105
package/bin/suveren-gateway.js
CHANGED
|
@@ -69,13 +69,22 @@ async function start(args) {
|
|
|
69
69
|
if (await isPortListening(SUVEREN_PORT)) {
|
|
70
70
|
console.error(`Port ${SUVEREN_PORT} is already in use.`);
|
|
71
71
|
console.error(``);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
if (serviceRunning()) {
|
|
73
|
+
// Do not suggest uninstalling autostart: that removes a feature the user
|
|
74
|
+
// asked for in order to solve a problem that needs a restart. After an
|
|
75
|
+
// upgrade the files on disk are new and the running process is not.
|
|
76
|
+
console.error(`The login service is already running the gateway, so there is nothing to start.`);
|
|
77
|
+
console.error(``);
|
|
78
|
+
console.error(` Pick up an update: suveren-gateway restart`);
|
|
79
|
+
console.error(` Check what is running: suveren-gateway status`);
|
|
80
|
+
console.error(` Remove autostart: suveren-gateway service uninstall`);
|
|
81
|
+
} else {
|
|
82
|
+
console.error(`Something is already serving there — most likely a gateway started from`);
|
|
83
|
+
console.error(`another terminal, which leaves no PID file for this CLI to find.`);
|
|
84
|
+
console.error(``);
|
|
85
|
+
console.error(` Check what it is: suveren-gateway status`);
|
|
86
|
+
console.error(` Or use another port: SUVEREN_CP_PORT=3410 suveren-gateway start`);
|
|
87
|
+
}
|
|
79
88
|
process.exit(1);
|
|
80
89
|
}
|
|
81
90
|
|
|
@@ -134,6 +143,16 @@ async function start(args) {
|
|
|
134
143
|
|
|
135
144
|
async function stop() {
|
|
136
145
|
const pid = readPid();
|
|
146
|
+
// A service-managed gateway writes no PID file. Reporting "not running" while
|
|
147
|
+
// it serves requests is the most misleading thing this CLI can say, and it is
|
|
148
|
+
// what sent an upgrade into a port conflict it could not explain.
|
|
149
|
+
if (!pid && serviceRunning()) {
|
|
150
|
+
console.error('suveren-gateway is running under the login service, which this command does not manage.');
|
|
151
|
+
console.error('');
|
|
152
|
+
console.error(' To pick up an update: suveren-gateway restart');
|
|
153
|
+
console.error(' To stop it for good: suveren-gateway service uninstall');
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
137
156
|
if (!pid) {
|
|
138
157
|
console.error('suveren-gateway is not running (no PID file).');
|
|
139
158
|
process.exit(1);
|
|
@@ -242,6 +261,14 @@ async function status() {
|
|
|
242
261
|
}
|
|
243
262
|
|
|
244
263
|
async function restart() {
|
|
264
|
+
// The service manager owns the process when autostart is installed, and it
|
|
265
|
+
// leaves no PID file — so this must be asked FIRST or the checks below all
|
|
266
|
+
// conclude, wrongly, that nothing is running.
|
|
267
|
+
if (serviceRestart()) {
|
|
268
|
+
console.log('suveren-gateway: restarted via the login service.');
|
|
269
|
+
console.log(` The vault is locked after a restart — unlock at http://localhost:${SUVEREN_PORT}`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
245
272
|
if (readPid() && isPidAlive(readPid())) {
|
|
246
273
|
await stop();
|
|
247
274
|
}
|
|
@@ -279,6 +306,52 @@ async function logs(args) {
|
|
|
279
306
|
|
|
280
307
|
const LAUNCH_AGENT_LABEL = 'ai.suveren.gateway';
|
|
281
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Is the login service currently running the gateway?
|
|
311
|
+
*
|
|
312
|
+
* `stop`, `start` and `restart` were all written before autostart existed and
|
|
313
|
+
* all reason from the PID file — which a service-managed gateway never writes.
|
|
314
|
+
* The result was that installing autostart silently broke the update path:
|
|
315
|
+
* `stop` reported "not running", and `start` then hit a port held by a process
|
|
316
|
+
* it could not see. Every command has to know about the service the same CLI
|
|
317
|
+
* installs.
|
|
318
|
+
*/
|
|
319
|
+
function serviceRunning() {
|
|
320
|
+
const os = platform();
|
|
321
|
+
if (os === 'darwin') {
|
|
322
|
+
const p = runQuiet('launchctl', ['print', `gui/${process.getuid()}/${LAUNCH_AGENT_LABEL}`]);
|
|
323
|
+
return p.status === 0 && /state = running/.test(p.stdout || '');
|
|
324
|
+
}
|
|
325
|
+
if (os === 'win32') {
|
|
326
|
+
const p = runQuiet('schtasks', ['/Query', '/TN', WIN_TASK_NAME, '/FO', 'LIST']);
|
|
327
|
+
return p.status === 0 && /Status:\s+Running/i.test(p.stdout || '');
|
|
328
|
+
}
|
|
329
|
+
const p = runQuiet('systemctl', ['--user', 'is-active', SYSTEMD_UNIT]);
|
|
330
|
+
return (p.stdout || '').trim() === 'active';
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Restart through whichever service manager owns the process.
|
|
335
|
+
*
|
|
336
|
+
* Returns false when no service is running, so callers fall back to the manual
|
|
337
|
+
* path. Restarting is what an upgrade needs: npm replaces the files on disk,
|
|
338
|
+
* but the running process loaded them once at start and keeps serving the old
|
|
339
|
+
* code until it is replaced.
|
|
340
|
+
*/
|
|
341
|
+
function serviceRestart() {
|
|
342
|
+
if (!serviceRunning()) return false;
|
|
343
|
+
const os = platform();
|
|
344
|
+
if (os === 'darwin') {
|
|
345
|
+
runQuiet('launchctl', ['kickstart', '-k', `gui/${process.getuid()}/${LAUNCH_AGENT_LABEL}`]);
|
|
346
|
+
} else if (os === 'win32') {
|
|
347
|
+
runQuiet('schtasks', ['/End', '/TN', WIN_TASK_NAME]);
|
|
348
|
+
runQuiet('schtasks', ['/Run', '/TN', WIN_TASK_NAME]);
|
|
349
|
+
} else {
|
|
350
|
+
runQuiet('systemctl', ['--user', 'restart', SYSTEMD_UNIT]);
|
|
351
|
+
}
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
282
355
|
async function service(args) {
|
|
283
356
|
const sub = args[0] ?? 'help';
|
|
284
357
|
const os = platform();
|
|
@@ -350,14 +423,30 @@ async function serviceInstallMac() {
|
|
|
350
423
|
|
|
351
424
|
const uid = process.getuid();
|
|
352
425
|
// Clear any prior "disabled" mark from a previous uninstall, so the agent is
|
|
353
|
-
// eligible again
|
|
354
|
-
// gateway alongside the running one and fight over the port.
|
|
426
|
+
// eligible again.
|
|
355
427
|
runQuiet('launchctl', ['enable', `gui/${uid}/${LAUNCH_AGENT_LABEL}`]);
|
|
356
428
|
|
|
429
|
+
// Bootstrap NOW only when nothing is already serving. With a gateway running,
|
|
430
|
+
// loading the agent starts a second one that loses the port race and then
|
|
431
|
+
// crash-loops under KeepAlive — so in that case the agent waits for the next
|
|
432
|
+
// login, when the manual instance is gone. With nothing running there is no
|
|
433
|
+
// conflict, and waiting would leave the user with a "service" that isn't
|
|
434
|
+
// running and no obvious reason why.
|
|
435
|
+
const running = await isAlreadyRunning();
|
|
436
|
+
let loadedNow = false;
|
|
437
|
+
if (!running) {
|
|
438
|
+
const r = runQuiet('launchctl', ['bootstrap', `gui/${uid}`, plistPath]);
|
|
439
|
+
loadedNow = r.status === 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
357
442
|
console.log(`✓ Suveren gateway installed as a login service.`);
|
|
358
443
|
console.log(``);
|
|
359
|
-
|
|
360
|
-
|
|
444
|
+
if (loadedNow) {
|
|
445
|
+
console.log(` It is running now, starts automatically at login, and restarts if it crashes.`);
|
|
446
|
+
} else {
|
|
447
|
+
console.log(` It starts automatically from your NEXT login, and restarts if it crashes.`);
|
|
448
|
+
if (running) console.log(` Your current gateway keeps running — nothing was interrupted.`);
|
|
449
|
+
}
|
|
361
450
|
console.log(` After a reboot it comes up LOCKED — open http://localhost:${SUVEREN_PORT} and`);
|
|
362
451
|
console.log(` enter your Suveren API key once to unlock it (your key is never stored).`);
|
|
363
452
|
console.log(``);
|
|
@@ -395,7 +484,13 @@ async function serviceStatusMac() {
|
|
|
395
484
|
const state = /state = (\w+)/.exec(p.stdout || '');
|
|
396
485
|
console.log(` launchd: loaded${state ? ` (${state[1]})` : ''}`);
|
|
397
486
|
} else if (installed) {
|
|
398
|
-
|
|
487
|
+
// Do NOT point at `service install` here: install deliberately does not
|
|
488
|
+
// bootstrap while a gateway is already running (two would fight over the
|
|
489
|
+
// port), so re-running it would change nothing and the advice would be a
|
|
490
|
+
// dead end. Say plainly when it becomes active, and give the command that
|
|
491
|
+
// actually loads it now.
|
|
492
|
+
console.log(` launchd: not loaded — starts at your next login`);
|
|
493
|
+
console.log(` to activate now: suveren-gateway stop && launchctl bootstrap gui/$(id -u) "${plistPath}"`);
|
|
399
494
|
}
|
|
400
495
|
console.log('');
|
|
401
496
|
await status(); // process/health/vault line
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "deploy-github",
|
|
3
|
+
"name": "Deploy (GitHub)",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Make an already-built version live, under signed authority",
|
|
6
|
+
"icon": "wrench",
|
|
7
|
+
"profile": "deploy",
|
|
8
|
+
"npmPackage": "@humanagencyp/deploy-mcp",
|
|
9
|
+
"mcp": {
|
|
10
|
+
"command": "npx",
|
|
11
|
+
"args": [
|
|
12
|
+
"-y",
|
|
13
|
+
"@humanagencyp/deploy-mcp@latest"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"credentials": {
|
|
17
|
+
"fields": [
|
|
18
|
+
{
|
|
19
|
+
"key": "githubToken",
|
|
20
|
+
"label": "GitHub token",
|
|
21
|
+
"type": "password",
|
|
22
|
+
"placeholder": "github_pat_..."
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"envMapping": {
|
|
26
|
+
"GITHUB_TOKEN": "githubToken"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"oauth": null,
|
|
30
|
+
"toolGating": {
|
|
31
|
+
"default": {
|
|
32
|
+
"executionMapping": {},
|
|
33
|
+
"staticExecution": {},
|
|
34
|
+
"category": "disabled"
|
|
35
|
+
},
|
|
36
|
+
"overrides": {
|
|
37
|
+
"list_deployments": {
|
|
38
|
+
"category": "read",
|
|
39
|
+
"readGovernance": "none",
|
|
40
|
+
"readGovernanceReason": "Returns build metadata and the host's own deployment URLs. No source, no build output, no user content. Scoped to permitted repositories.",
|
|
41
|
+
"read": {
|
|
42
|
+
"resourceBound": "allowed_repos",
|
|
43
|
+
"resourceArg": "repo"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"get_deployment": {
|
|
47
|
+
"category": "read",
|
|
48
|
+
"readGovernance": "none",
|
|
49
|
+
"readGovernanceReason": "Returns metadata for one build — state, commit, URL. No build output.",
|
|
50
|
+
"read": {
|
|
51
|
+
"resourceBound": "allowed_repos",
|
|
52
|
+
"resourceArg": "repo"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"resolve_ref": {
|
|
56
|
+
"category": "read",
|
|
57
|
+
"readGovernance": "none",
|
|
58
|
+
"readGovernanceReason": "Returns a commit pointer only. Repository metadata, not user content.",
|
|
59
|
+
"read": {
|
|
60
|
+
"resourceBound": "allowed_repos",
|
|
61
|
+
"resourceArg": "repo"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"list_environments": {
|
|
65
|
+
"category": "read",
|
|
66
|
+
"readGovernance": "none",
|
|
67
|
+
"readGovernanceReason": "Returns environment names only. Repository configuration, needed so the owner can pick a target that exists.",
|
|
68
|
+
"read": {
|
|
69
|
+
"resourceBound": "allowed_repos",
|
|
70
|
+
"resourceArg": "repo"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"release": {
|
|
74
|
+
"executionMapping": {
|
|
75
|
+
"repo": "allowed_repos",
|
|
76
|
+
"environment": "allowed_environments",
|
|
77
|
+
"workflow": "allowed_workflows"
|
|
78
|
+
},
|
|
79
|
+
"staticExecution": {
|
|
80
|
+
"action_type": "release"
|
|
81
|
+
},
|
|
82
|
+
"contentField": "deployment_url"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"templates": [
|
|
87
|
+
{
|
|
88
|
+
"name": "Watch deployments",
|
|
89
|
+
"description": "See builds and their URLs. Cannot make anything live.",
|
|
90
|
+
"risk": "low",
|
|
91
|
+
"mode": "automatic",
|
|
92
|
+
"bounds": {
|
|
93
|
+
"release_daily_max": "0",
|
|
94
|
+
"rollback_allowed": "no"
|
|
95
|
+
},
|
|
96
|
+
"context": {
|
|
97
|
+
"allowed_repos": "",
|
|
98
|
+
"allowed_environments": "",
|
|
99
|
+
"allowed_workflows": "",
|
|
100
|
+
"allowed_branches": ""
|
|
101
|
+
},
|
|
102
|
+
"intent": "Let the agent see what has been built and whether it succeeded. It may not make anything live: a daily maximum of zero is a real limit, not an unset one. Fill in allowed_repos with the repositories it may look at.",
|
|
103
|
+
"ttl": 2592000,
|
|
104
|
+
"tags": [
|
|
105
|
+
"read-only",
|
|
106
|
+
"30 days"
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "Release to production, review",
|
|
111
|
+
"description": "Make an existing build live after approval. Three a day, no rollback.",
|
|
112
|
+
"risk": "high",
|
|
113
|
+
"mode": "review",
|
|
114
|
+
"bounds": {
|
|
115
|
+
"release_daily_max": "3",
|
|
116
|
+
"rollback_allowed": "no"
|
|
117
|
+
},
|
|
118
|
+
"context": {
|
|
119
|
+
"allowed_repos": "",
|
|
120
|
+
"allowed_environments": "production",
|
|
121
|
+
"allowed_workflows": "deploy-website.yml",
|
|
122
|
+
"allowed_branches": "main"
|
|
123
|
+
},
|
|
124
|
+
"intent": "The agent may propose making a specific build live; nothing runs until I approve it. Open the build URL in the proposal and look at it before approving — that is the point of reviewing a release rather than a commit. Fill in allowed_repos, and check allowed_environments matches what the host actually calls it.",
|
|
125
|
+
"ttl": 2592000,
|
|
126
|
+
"tags": [
|
|
127
|
+
"review",
|
|
128
|
+
"production",
|
|
129
|
+
"30 days"
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
"setupHint": "Use a fine-grained token, scoped to one repository.",
|
|
134
|
+
"setupGuide": [
|
|
135
|
+
{
|
|
136
|
+
"title": "Create a fine-grained token",
|
|
137
|
+
"description": "GitHub Settings, Developer settings, Personal access tokens, Fine-grained. Not a classic token — those reach everything you own.",
|
|
138
|
+
"link": "https://github.com/settings/personal-access-tokens/new"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"title": "Scope it to one repository",
|
|
142
|
+
"description": "Under Repository access pick Only select repositories, then the repo to deploy."
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"title": "Grant three permissions",
|
|
146
|
+
"description": "Actions: Read and write. Environments: Read. Contents: Read. Not Contents: Write — the agent ships commits, it never writes them."
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"title": "Paste the token below",
|
|
150
|
+
"description": "Encrypted in your local vault. The agent never sees it."
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
"proposalLinks": [
|
|
154
|
+
{
|
|
155
|
+
"label": "Open this build",
|
|
156
|
+
"template": "{deployment_url}",
|
|
157
|
+
"description": "Look at exactly what would go live"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"label": "View repository",
|
|
161
|
+
"template": "https://github.com/{repo}"
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
@@ -83,7 +83,10 @@
|
|
|
83
83
|
},
|
|
84
84
|
"staticExecution": {
|
|
85
85
|
"action_type": "send"
|
|
86
|
-
}
|
|
86
|
+
},
|
|
87
|
+
"blockedArgs": [
|
|
88
|
+
"raw"
|
|
89
|
+
]
|
|
87
90
|
},
|
|
88
91
|
"create_draft": {
|
|
89
92
|
"executionMapping": {
|
|
@@ -104,7 +107,10 @@
|
|
|
104
107
|
},
|
|
105
108
|
"staticExecution": {
|
|
106
109
|
"action_type": "send"
|
|
107
|
-
}
|
|
110
|
+
},
|
|
111
|
+
"blockedArgs": [
|
|
112
|
+
"raw"
|
|
113
|
+
]
|
|
108
114
|
},
|
|
109
115
|
"send_draft": {
|
|
110
116
|
"executionMapping": {},
|
|
@@ -187,7 +193,7 @@
|
|
|
187
193
|
"get_profile": {
|
|
188
194
|
"category": "read",
|
|
189
195
|
"readGovernance": "none",
|
|
190
|
-
"readGovernanceReason": "Returns the account's own address and message counts
|
|
196
|
+
"readGovernanceReason": "Returns the account's own address and message counts \u2014 the connector's own identity, no correspondence content."
|
|
191
197
|
}
|
|
192
198
|
}
|
|
193
199
|
},
|
|
@@ -230,7 +236,7 @@
|
|
|
230
236
|
"allowed_recipients": "",
|
|
231
237
|
"allowed_domains": ""
|
|
232
238
|
},
|
|
233
|
-
"intent": "Read my inbox and compose emails. Every send requires my review
|
|
239
|
+
"intent": "Read my inbox and compose emails. Every send requires my review \u2014 I see the recipient, subject, and body before it goes out.",
|
|
234
240
|
"ttl": 2592000,
|
|
235
241
|
"tags": [
|
|
236
242
|
"5 recipients/email",
|
|
@@ -1600,16 +1600,23 @@ function createAutostartRouter() {
|
|
|
1600
1600
|
}
|
|
1601
1601
|
|
|
1602
1602
|
// src/lib/desktop-notify.ts
|
|
1603
|
-
import { spawn } from "child_process";
|
|
1603
|
+
import { spawn, spawnSync } from "child_process";
|
|
1604
1604
|
function osaQuote(value) {
|
|
1605
1605
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1606
1606
|
}
|
|
1607
1607
|
function psQuote(value) {
|
|
1608
1608
|
return `'${value.replace(/'/g, "''")}'`;
|
|
1609
1609
|
}
|
|
1610
|
-
function buildNotifyCommand(platform, title, message) {
|
|
1610
|
+
function buildNotifyCommand(platform, title, message, options = {}) {
|
|
1611
|
+
const { url, hasTerminalNotifier: hasTerminalNotifier2 = false } = options;
|
|
1611
1612
|
switch (platform) {
|
|
1612
1613
|
case "darwin":
|
|
1614
|
+
if (url && hasTerminalNotifier2) {
|
|
1615
|
+
return {
|
|
1616
|
+
cmd: "terminal-notifier",
|
|
1617
|
+
args: ["-title", title, "-message", message, "-open", url, "-group", "ai.suveren.gateway"]
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1613
1620
|
return {
|
|
1614
1621
|
cmd: "osascript",
|
|
1615
1622
|
args: ["-e", `display notification ${osaQuote(message)} with title ${osaQuote(title)}`]
|
|
@@ -1631,13 +1638,30 @@ function buildNotifyCommand(platform, title, message) {
|
|
|
1631
1638
|
}
|
|
1632
1639
|
}
|
|
1633
1640
|
function lockedNotification(port2) {
|
|
1641
|
+
const url = `http://localhost:${port2}`;
|
|
1634
1642
|
return {
|
|
1635
1643
|
title: "Suveren is running but locked",
|
|
1636
|
-
message: `Your agent has no authority until you unlock it. Open
|
|
1644
|
+
message: `Your agent has no authority until you unlock it. Open ${url} and enter your API key.`,
|
|
1645
|
+
url
|
|
1637
1646
|
};
|
|
1638
1647
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1648
|
+
var terminalNotifierCache = null;
|
|
1649
|
+
function hasTerminalNotifier() {
|
|
1650
|
+
if (terminalNotifierCache !== null) return terminalNotifierCache;
|
|
1651
|
+
try {
|
|
1652
|
+
const probe = spawnSync("terminal-notifier", ["-help"], { stdio: "ignore" });
|
|
1653
|
+
terminalNotifierCache = probe.error === void 0;
|
|
1654
|
+
} catch {
|
|
1655
|
+
terminalNotifierCache = false;
|
|
1656
|
+
}
|
|
1657
|
+
return terminalNotifierCache;
|
|
1658
|
+
}
|
|
1659
|
+
function notify(title, message, platform = process.platform, url) {
|
|
1660
|
+
const command = buildNotifyCommand(platform, title, message, {
|
|
1661
|
+
url,
|
|
1662
|
+
// Only probe when a URL could actually be attached.
|
|
1663
|
+
hasTerminalNotifier: url !== void 0 && platform === "darwin" ? hasTerminalNotifier() : false
|
|
1664
|
+
});
|
|
1641
1665
|
if (!command) return;
|
|
1642
1666
|
try {
|
|
1643
1667
|
const child = spawn(command.cmd, command.args, { detached: true, stdio: "ignore" });
|
|
@@ -2546,8 +2570,8 @@ if (existsSync5(UI_DIST)) {
|
|
|
2546
2570
|
app.listen(port, "0.0.0.0", () => {
|
|
2547
2571
|
console.error(`[Control Plane] Listening on http://0.0.0.0:${port}`);
|
|
2548
2572
|
if (process.env.SUVEREN_AUTOSTART === "1" && !vault.isUnlocked()) {
|
|
2549
|
-
const { title, message } = lockedNotification(port);
|
|
2550
|
-
notify(title, message);
|
|
2573
|
+
const { title, message, url } = lockedNotification(port);
|
|
2574
|
+
notify(title, message, process.platform, url);
|
|
2551
2575
|
}
|
|
2552
2576
|
console.error(`[Control Plane] SP proxy: ${SP_URL2}`);
|
|
2553
2577
|
console.error(`[Control Plane] UI dist: ${UI_DIST}`);
|
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -1157,9 +1157,11 @@ function computeContentBinding(profileId, tool, toolArgs) {
|
|
|
1157
1157
|
if (binding.kind === "jcs") {
|
|
1158
1158
|
contentHash = computeContentHash(binding, toolArgs);
|
|
1159
1159
|
} else {
|
|
1160
|
-
const
|
|
1160
|
+
const declared = tool?.gating?.contentField;
|
|
1161
|
+
const field = declared ?? (tool ? detectContentField(tool) : null);
|
|
1161
1162
|
if (!field) return void 0;
|
|
1162
1163
|
const raw = typeof toolArgs[field] === "string" ? toolArgs[field] : "";
|
|
1164
|
+
if (raw === "") return void 0;
|
|
1163
1165
|
contentHash = computeContentHash(binding, raw);
|
|
1164
1166
|
}
|
|
1165
1167
|
return {
|
|
@@ -1445,6 +1447,22 @@ function denyRead(state2, tool, reason, detail, target) {
|
|
|
1445
1447
|
return { content: [{ type: "text", text: `Read blocked by Gatekeeper: ${detail}` }], isError: true };
|
|
1446
1448
|
}
|
|
1447
1449
|
function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
1450
|
+
const inner = createGatedToolHandlerInner(tool, integrationManager2, state2);
|
|
1451
|
+
const blocked = tool.gating?.blockedArgs ?? [];
|
|
1452
|
+
if (blocked.length === 0) return inner;
|
|
1453
|
+
return async (args) => {
|
|
1454
|
+
const present = blocked.filter((a) => a in (args ?? {}));
|
|
1455
|
+
if (present.length === 0) return inner(args);
|
|
1456
|
+
return {
|
|
1457
|
+
content: [{
|
|
1458
|
+
type: "text",
|
|
1459
|
+
text: `Refused: "${tool.namespacedName}" was called with ${present.map((b) => `"${b}"`).join(", ")}, which the integration manifest blocks. That argument carries the whole request in one opaque value, so the recipients, the content and the limits on this authorization cannot be read from it \u2014 the call could not be shown to stay in scope, and could not be shown to a human for approval in any form they could judge. Use the individual arguments instead.`
|
|
1460
|
+
}],
|
|
1461
|
+
isError: true
|
|
1462
|
+
};
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
1448
1466
|
if (!tool.gating || !tool.gating.profile) {
|
|
1449
1467
|
return async () => {
|
|
1450
1468
|
return {
|
|
@@ -2611,6 +2629,19 @@ function readAgeOf(config) {
|
|
|
2611
2629
|
const days = config.readAgeDays;
|
|
2612
2630
|
return typeof days === "number" && Number.isFinite(days) && days >= 0 ? days : null;
|
|
2613
2631
|
}
|
|
2632
|
+
function withoutBlockedArgs(schema, blocked) {
|
|
2633
|
+
if (!blocked?.length) return schema;
|
|
2634
|
+
const props = schema.properties;
|
|
2635
|
+
if (!props) return schema;
|
|
2636
|
+
const kept = Object.fromEntries(Object.entries(props).filter(([k]) => !blocked.includes(k)));
|
|
2637
|
+
const out = { ...schema, properties: kept };
|
|
2638
|
+
if (Array.isArray(schema.required)) {
|
|
2639
|
+
out.required = schema.required.filter(
|
|
2640
|
+
(r) => typeof r !== "string" || !blocked.includes(r)
|
|
2641
|
+
);
|
|
2642
|
+
}
|
|
2643
|
+
return out;
|
|
2644
|
+
}
|
|
2614
2645
|
var RESPAWN_DELAYS = [2e3, 4e3, 6e3];
|
|
2615
2646
|
var MAX_RESPAWN_ATTEMPTS = 3;
|
|
2616
2647
|
var CONNECT_TIMEOUT_MS = 3e4;
|
|
@@ -2806,7 +2837,14 @@ var IntegrationManager = class {
|
|
|
2806
2837
|
namespacedName: `${config.id}__${tool.name}`,
|
|
2807
2838
|
integrationId: config.id,
|
|
2808
2839
|
description: tool.description ?? "",
|
|
2809
|
-
|
|
2840
|
+
// A downstream schema otherwise reaches the agent verbatim, so a
|
|
2841
|
+
// connector can offer a control-bypassing argument and we would pass it
|
|
2842
|
+
// straight on. Anything the manifest blocks is removed here, before the
|
|
2843
|
+
// agent ever sees it exists.
|
|
2844
|
+
inputSchema: withoutBlockedArgs(
|
|
2845
|
+
tool.inputSchema ?? {},
|
|
2846
|
+
gating?.blockedArgs
|
|
2847
|
+
),
|
|
2810
2848
|
gating
|
|
2811
2849
|
};
|
|
2812
2850
|
});
|
|
@@ -3011,13 +3049,20 @@ var IntegrationManager = class {
|
|
|
3011
3049
|
requiredValue: ext.requiredValue,
|
|
3012
3050
|
read: ext.read,
|
|
3013
3051
|
readGovernance: ext.readGovernance,
|
|
3014
|
-
readGovernanceReason: ext.readGovernanceReason
|
|
3052
|
+
readGovernanceReason: ext.readGovernanceReason,
|
|
3053
|
+
blockedArgs: ext.blockedArgs
|
|
3015
3054
|
};
|
|
3016
3055
|
}
|
|
3017
3056
|
return {
|
|
3018
3057
|
profile: profileId,
|
|
3019
3058
|
executionMapping: override.executionMapping,
|
|
3020
|
-
staticExecution: override.staticExecution
|
|
3059
|
+
staticExecution: override.staticExecution,
|
|
3060
|
+
// Which argument the receipt binds to. This resolver is an explicit
|
|
3061
|
+
// whitelist, so a field added to the manifest and the type but not
|
|
3062
|
+
// copied here is silently dropped — the receipt is still issued and
|
|
3063
|
+
// simply carries no binding, which only surfaces when a verifier asks.
|
|
3064
|
+
contentField: ext.contentField,
|
|
3065
|
+
blockedArgs: ext.blockedArgs
|
|
3021
3066
|
};
|
|
3022
3067
|
}
|
|
3023
3068
|
return {
|