@suveren/gateway 0.8.5 → 0.8.7
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/dist/control-plane/index.mjs +340 -56
- package/dist/mcp-server/http.mjs +73 -9
- package/dist/ui/assets/{index-CheBVmNI.css → index-BJqF5rmx.css} +1 -1
- package/dist/ui/assets/index-tzStUGuj.js +110 -0
- package/dist/ui/icons/apple-touch-icon.png +0 -0
- package/dist/ui/icons/icon-192.png +0 -0
- package/dist/ui/icons/icon-512.png +0 -0
- package/dist/ui/icons/icon-maskable-512.png +0 -0
- package/dist/ui/index.html +8 -2
- package/dist/ui/manifest.webmanifest +15 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.d.ts +15 -4
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js +22 -3
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/express.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/express.js +3 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/express.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/requestBody.d.ts +25 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/requestBody.d.ts.map +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/requestBody.js +64 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/requestBody.js.map +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/webStandardStreamableHttp.d.ts +10 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/webStandardStreamableHttp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/webStandardStreamableHttp.js +13 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/webStandardStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.d.ts +15 -4
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js +22 -3
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/express.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/express.js +3 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/express.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.d.ts +25 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.d.ts.map +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.js +58 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.js.map +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.d.ts +10 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js +13 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/package.json +1 -1
- package/node_modules/hono/dist/adapter/aws-lambda/handler.js +6 -3
- package/node_modules/hono/dist/adapter/lambda-edge/handler.js +26 -12
- package/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +6 -3
- package/node_modules/hono/dist/cjs/adapter/lambda-edge/handler.js +26 -12
- package/node_modules/hono/dist/cjs/helper/accepts/accepts.js +7 -5
- package/node_modules/hono/dist/cjs/jsx/components.js +1 -1
- package/node_modules/hono/dist/cjs/jsx/streaming.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/pretty-json/index.js +7 -1
- package/node_modules/hono/dist/cjs/router/linear-router/router.js +3 -3
- package/node_modules/hono/dist/cjs/utils/jwt/jwt.js +7 -6
- package/node_modules/hono/dist/helper/accepts/accepts.js +7 -5
- package/node_modules/hono/dist/jsx/components.js +1 -1
- package/node_modules/hono/dist/jsx/streaming.js +1 -1
- package/node_modules/hono/dist/middleware/pretty-json/index.js +7 -1
- package/node_modules/hono/dist/router/linear-router/router.js +3 -3
- package/node_modules/hono/dist/utils/jwt/jwt.js +7 -6
- package/node_modules/hono/package.json +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/index-IVfLWq4W.js +0 -110
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
8
|
-
import { existsSync as existsSync7, readFileSync as
|
|
9
|
-
import { dirname as
|
|
10
|
-
import { homedir as
|
|
8
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, renameSync as renameSync2 } from "fs";
|
|
9
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
10
|
+
import { homedir as homedir7 } from "os";
|
|
11
11
|
import express from "express";
|
|
12
12
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
13
13
|
|
|
@@ -33,6 +33,15 @@ var Vault = class {
|
|
|
33
33
|
spSessionCookie = null;
|
|
34
34
|
unsealedBy = null;
|
|
35
35
|
dataDir;
|
|
36
|
+
/**
|
|
37
|
+
* When the AS session (captured at login into `spSessionCookie`) is due to
|
|
38
|
+
* end — unix seconds, from the login response's `sessionExpiresAt`. Held in
|
|
39
|
+
* memory only, exactly like the vault key and the cookie: never written to
|
|
40
|
+
* disk, gone the moment the process restarts or the vault locks.
|
|
41
|
+
*/
|
|
42
|
+
spSessionExpiresAt = null;
|
|
43
|
+
/** Set by lockExpired(); cleared by any fresh unseal (a new login). */
|
|
44
|
+
lockedReason = null;
|
|
36
45
|
constructor(dataDir) {
|
|
37
46
|
this.dataDir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join(homedir(), ".suveren");
|
|
38
47
|
}
|
|
@@ -59,6 +68,7 @@ var Vault = class {
|
|
|
59
68
|
async unseal(provider) {
|
|
60
69
|
this.vaultKey = await provider.deriveKey(this.ensureSalt());
|
|
61
70
|
this.unsealedBy = provider.id;
|
|
71
|
+
this.lockedReason = null;
|
|
62
72
|
}
|
|
63
73
|
/**
|
|
64
74
|
* Unlock from the user's API key. Also records the key's hash so subsequent
|
|
@@ -86,6 +96,33 @@ var Vault = class {
|
|
|
86
96
|
this.apiKeyHash = null;
|
|
87
97
|
this.spSessionCookie = null;
|
|
88
98
|
this.unsealedBy = null;
|
|
99
|
+
this.spSessionExpiresAt = null;
|
|
100
|
+
this.lockedReason = null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Lock because the AS session ended — a 401 surfaced it, or the scheduled
|
|
104
|
+
* expiry arrived. Distinct from logout/clearKey() only in that it records
|
|
105
|
+
* WHY, so the next `/health` call and the next locked-tool notice can say
|
|
106
|
+
* something better than "boots locked by design" when that is not what
|
|
107
|
+
* happened.
|
|
108
|
+
*/
|
|
109
|
+
lockExpired() {
|
|
110
|
+
this.clearKey();
|
|
111
|
+
this.lockedReason = "expired";
|
|
112
|
+
}
|
|
113
|
+
/** What locked the vault, if known. `null` covers both "still unlocked"
|
|
114
|
+
* and the default boot-locked case — callers that care about the
|
|
115
|
+
* difference should check `isUnlocked()` first. */
|
|
116
|
+
getLockedReason() {
|
|
117
|
+
return this.lockedReason;
|
|
118
|
+
}
|
|
119
|
+
/** unix seconds the current AS session is due to end, or null (locked, or
|
|
120
|
+
* the AS didn't report one — a pre-30-day-rollout server). Never persisted. */
|
|
121
|
+
setSessionExpiresAt(expiresAt) {
|
|
122
|
+
this.spSessionExpiresAt = expiresAt;
|
|
123
|
+
}
|
|
124
|
+
getSessionExpiresAt() {
|
|
125
|
+
return this.spSessionExpiresAt;
|
|
89
126
|
}
|
|
90
127
|
/**
|
|
91
128
|
* Check if the vault was created by a different API key.
|
|
@@ -263,6 +300,15 @@ async function configure(sessionCookie, vaultKeyHex) {
|
|
|
263
300
|
clearTimeout(timeout);
|
|
264
301
|
}
|
|
265
302
|
}
|
|
303
|
+
async function unconfigureSession() {
|
|
304
|
+
const res = await fetch(`${MCP_BASE}/internal/clear-session`, {
|
|
305
|
+
method: "POST",
|
|
306
|
+
headers: internalHeaders()
|
|
307
|
+
});
|
|
308
|
+
if (!res.ok) {
|
|
309
|
+
throw new Error(`MCP clear-session failed: ${res.status}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
266
312
|
async function pushGateContent(data) {
|
|
267
313
|
const res = await fetch(`${MCP_BASE}/internal/gate-content`, {
|
|
268
314
|
method: "POST",
|
|
@@ -552,9 +598,45 @@ async function loadOrGenerateKeyPair(vault2) {
|
|
|
552
598
|
return kp;
|
|
553
599
|
}
|
|
554
600
|
|
|
601
|
+
// src/lib/client-version.ts
|
|
602
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
603
|
+
import { join as join2 } from "path";
|
|
604
|
+
var UNKNOWN = "unknown";
|
|
605
|
+
function readJson(path) {
|
|
606
|
+
try {
|
|
607
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
608
|
+
} catch {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function detectGatewayVersion() {
|
|
613
|
+
const override = process.env.SUVEREN_GATEWAY_VERSION;
|
|
614
|
+
if (override) return override;
|
|
615
|
+
const start = import.meta.dirname ?? process.cwd();
|
|
616
|
+
let dir = start;
|
|
617
|
+
for (let up = 0; up < 6; up++) {
|
|
618
|
+
const pkg = readJson(join2(dir, "package.json"));
|
|
619
|
+
const name = pkg?.name;
|
|
620
|
+
const version = pkg?.version;
|
|
621
|
+
if (typeof version === "string" && (name === "@suveren/gateway" || name === "suveren-gateway")) {
|
|
622
|
+
return version;
|
|
623
|
+
}
|
|
624
|
+
const parent = join2(dir, "..");
|
|
625
|
+
if (parent === dir) break;
|
|
626
|
+
dir = parent;
|
|
627
|
+
}
|
|
628
|
+
return process.env.HAP_BUILD_SHA ?? UNKNOWN;
|
|
629
|
+
}
|
|
630
|
+
var GATEWAY_VERSION = detectGatewayVersion();
|
|
631
|
+
function clientVersionHeaders() {
|
|
632
|
+
return {
|
|
633
|
+
"x-suveren-gateway-version": GATEWAY_VERSION
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
555
637
|
// src/routes/auth.ts
|
|
556
638
|
var SP_URL = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
|
|
557
|
-
function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
|
|
639
|
+
function createAuthRouter(vault2, logoutAuth, loginRateLimit2, onSessionEstablished) {
|
|
558
640
|
const router = Router();
|
|
559
641
|
router.post("/login", loginRateLimit2, async (req, res) => {
|
|
560
642
|
const apiKey = req.headers["x-api-key"] || req.body?.apiKey;
|
|
@@ -568,7 +650,11 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
|
|
|
568
650
|
method: "POST",
|
|
569
651
|
headers: {
|
|
570
652
|
"Content-Type": "application/json",
|
|
571
|
-
"X-API-Key": apiKey
|
|
653
|
+
"X-API-Key": apiKey,
|
|
654
|
+
// The AS grants a 30-day gateway session ONLY to a login that
|
|
655
|
+
// identifies itself this way; without it, a 24-hour browser
|
|
656
|
+
// session (see doc at the top of this file).
|
|
657
|
+
...clientVersionHeaders()
|
|
572
658
|
}
|
|
573
659
|
});
|
|
574
660
|
if (!spRes.ok) {
|
|
@@ -576,6 +662,7 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
|
|
|
576
662
|
res.status(spRes.status).json(err);
|
|
577
663
|
return;
|
|
578
664
|
}
|
|
665
|
+
const data = await spRes.json();
|
|
579
666
|
const setCookieHeaders = spRes.headers.getSetCookie?.() ?? [];
|
|
580
667
|
const sessionCookie = setCookieHeaders.join("; ");
|
|
581
668
|
vault2.setSpCookie(sessionCookie);
|
|
@@ -605,6 +692,9 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
|
|
|
605
692
|
vault2.wipe();
|
|
606
693
|
await vault2.deriveAndSetKey(apiKey);
|
|
607
694
|
}
|
|
695
|
+
const sessionExpiresAt = typeof data.sessionExpiresAt === "number" ? data.sessionExpiresAt : null;
|
|
696
|
+
vault2.setSessionExpiresAt(sessionExpiresAt);
|
|
697
|
+
onSessionEstablished?.();
|
|
608
698
|
if (sessionCookie) {
|
|
609
699
|
try {
|
|
610
700
|
await configure(sessionCookie, vault2.getVaultKeyHex());
|
|
@@ -612,7 +702,6 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
|
|
|
612
702
|
console.error("[Control Plane] Failed to configure MCP:", err);
|
|
613
703
|
}
|
|
614
704
|
}
|
|
615
|
-
const data = await spRes.json();
|
|
616
705
|
res.json(data);
|
|
617
706
|
(async () => {
|
|
618
707
|
try {
|
|
@@ -684,7 +773,16 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
|
|
|
684
773
|
}
|
|
685
774
|
});
|
|
686
775
|
router.post("/logout", logoutAuth, async (_req, res) => {
|
|
776
|
+
const cookie = vault2.getSpCookie();
|
|
687
777
|
vault2.clearKey();
|
|
778
|
+
await Promise.allSettled([
|
|
779
|
+
unconfigureSession(),
|
|
780
|
+
cookie ? fetch(`${SP_URL}/api/auth/logout`, {
|
|
781
|
+
method: "POST",
|
|
782
|
+
headers: { cookie, ...clientVersionHeaders() },
|
|
783
|
+
redirect: "manual"
|
|
784
|
+
}) : Promise.resolve()
|
|
785
|
+
]);
|
|
688
786
|
res.json({ ok: true });
|
|
689
787
|
});
|
|
690
788
|
return router;
|
|
@@ -1294,18 +1392,18 @@ async function testAIConnectivity(config) {
|
|
|
1294
1392
|
}
|
|
1295
1393
|
|
|
1296
1394
|
// src/lib/agent-brief-store.ts
|
|
1297
|
-
import { existsSync as existsSync2, readFileSync as
|
|
1395
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
1298
1396
|
import { homedir as homedir2 } from "os";
|
|
1299
|
-
import { join as
|
|
1397
|
+
import { join as join3 } from "path";
|
|
1300
1398
|
var AGENT_CONTEXT_MAX_BYTES = 16 * 1024;
|
|
1301
1399
|
function agentBriefPath() {
|
|
1302
|
-
const dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
1303
|
-
return
|
|
1400
|
+
const dataDir = process.env.SUVEREN_DATA_DIR ?? join3(homedir2(), ".suveren");
|
|
1401
|
+
return join3(dataDir, "context.md");
|
|
1304
1402
|
}
|
|
1305
1403
|
function readAgentBrief() {
|
|
1306
1404
|
const filePath = agentBriefPath();
|
|
1307
1405
|
if (!existsSync2(filePath)) return "";
|
|
1308
|
-
return
|
|
1406
|
+
return readFileSync3(filePath, "utf-8");
|
|
1309
1407
|
}
|
|
1310
1408
|
|
|
1311
1409
|
// src/routes/ai.ts
|
|
@@ -1676,15 +1774,15 @@ import { promisify } from "util";
|
|
|
1676
1774
|
var run = promisify(execFile);
|
|
1677
1775
|
var SUPPORTED = /* @__PURE__ */ new Set(["darwin", "win32", "linux"]);
|
|
1678
1776
|
var MODULE_DIR = pathDirname(fileURLToPath(import.meta.url));
|
|
1679
|
-
function findCli(
|
|
1777
|
+
function findCli(dirname7 = MODULE_DIR) {
|
|
1680
1778
|
const candidates = [
|
|
1681
1779
|
// Bundled (the shipped layout): dist/control-plane → dist → root → bin
|
|
1682
|
-
resolve(
|
|
1780
|
+
resolve(dirname7, "..", "..", "bin", "suveren-gateway.js"),
|
|
1683
1781
|
// Bundled, were the routes/ directory ever preserved.
|
|
1684
|
-
resolve(
|
|
1782
|
+
resolve(dirname7, "..", "..", "..", "bin", "suveren-gateway.js"),
|
|
1685
1783
|
// Dev, running from source: the repo's own bundle directory.
|
|
1686
|
-
resolve(
|
|
1687
|
-
resolve(
|
|
1784
|
+
resolve(dirname7, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
|
|
1785
|
+
resolve(dirname7, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
|
|
1688
1786
|
// Deliberately NO process.cwd() fallback: it makes the result depend on
|
|
1689
1787
|
// where the process happened to be launched from, so the same install
|
|
1690
1788
|
// resolves differently between a shell and a login service — and it would
|
|
@@ -1773,12 +1871,12 @@ import { spawn, spawnSync } from "child_process";
|
|
|
1773
1871
|
// src/lib/notifier-icon.ts
|
|
1774
1872
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
1775
1873
|
import { homedir as homedir3 } from "os";
|
|
1776
|
-
import { join as
|
|
1874
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
1777
1875
|
var ICON_BASE64 = "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";
|
|
1778
1876
|
function ensureNotifierIcon(dataDir) {
|
|
1779
1877
|
try {
|
|
1780
|
-
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ??
|
|
1781
|
-
const path =
|
|
1878
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join4(homedir3(), ".suveren");
|
|
1879
|
+
const path = join4(dir, "notifier-icon.png");
|
|
1782
1880
|
if (!existsSync4(path)) {
|
|
1783
1881
|
mkdirSync2(dirname2(path), { recursive: true });
|
|
1784
1882
|
writeFileSync2(path, Buffer.from(ICON_BASE64, "base64"));
|
|
@@ -1861,6 +1959,14 @@ function lockedNotification(port2) {
|
|
|
1861
1959
|
url
|
|
1862
1960
|
};
|
|
1863
1961
|
}
|
|
1962
|
+
function sessionExpiredNotification(port2) {
|
|
1963
|
+
const url = `http://localhost:${port2}`;
|
|
1964
|
+
return {
|
|
1965
|
+
title: "Suveren Gateway locked",
|
|
1966
|
+
message: "Your sign-in has ended. Sign in again so your agents can act.",
|
|
1967
|
+
url
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1864
1970
|
var terminalNotifierCache = null;
|
|
1865
1971
|
function hasTerminalNotifier() {
|
|
1866
1972
|
if (terminalNotifierCache !== null) return terminalNotifierCache;
|
|
@@ -1975,17 +2081,17 @@ import { Router as Router9 } from "express";
|
|
|
1975
2081
|
|
|
1976
2082
|
// src/routes/approved-intents.ts
|
|
1977
2083
|
import { Router as Router8 } from "express";
|
|
1978
|
-
import { readFileSync as
|
|
2084
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
1979
2085
|
import { homedir as homedir4 } from "os";
|
|
1980
|
-
import { join as
|
|
1981
|
-
var SUVEREN_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
1982
|
-
var FILE_PATH =
|
|
2086
|
+
import { join as join5 } from "path";
|
|
2087
|
+
var SUVEREN_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join5(homedir4(), ".suveren");
|
|
2088
|
+
var FILE_PATH = join5(SUVEREN_DATA_DIR, "approved-intents.enc.json");
|
|
1983
2089
|
function readFile() {
|
|
1984
2090
|
if (!existsSync5(FILE_PATH)) {
|
|
1985
2091
|
return { version: 1, entries: {} };
|
|
1986
2092
|
}
|
|
1987
2093
|
try {
|
|
1988
|
-
return JSON.parse(
|
|
2094
|
+
return JSON.parse(readFileSync4(FILE_PATH, "utf-8"));
|
|
1989
2095
|
} catch {
|
|
1990
2096
|
return { version: 1, entries: {} };
|
|
1991
2097
|
}
|
|
@@ -2367,6 +2473,7 @@ function startUpdateChecker(method, version) {
|
|
|
2367
2473
|
|
|
2368
2474
|
// src/routes/events.ts
|
|
2369
2475
|
var KEEPALIVE_INTERVAL_MS = 25e3;
|
|
2476
|
+
var PAYLOAD_ALLOWED = /* @__PURE__ */ new Set(["session-locked"]);
|
|
2370
2477
|
function createEventsHandler() {
|
|
2371
2478
|
return function eventsHandler(_req, res) {
|
|
2372
2479
|
res.setHeader("Content-Type", "text/event-stream");
|
|
@@ -2375,8 +2482,9 @@ function createEventsHandler() {
|
|
|
2375
2482
|
res.setHeader("X-Accel-Buffering", "no");
|
|
2376
2483
|
res.flushHeaders();
|
|
2377
2484
|
const unsubscribe = eventBus.subscribe((event) => {
|
|
2485
|
+
const data = PAYLOAD_ALLOWED.has(event.type) ? JSON.stringify(event.payload ?? null) : "null";
|
|
2378
2486
|
res.write(`event: ${event.type}
|
|
2379
|
-
data:
|
|
2487
|
+
data: ${data}
|
|
2380
2488
|
|
|
2381
2489
|
`);
|
|
2382
2490
|
});
|
|
@@ -2394,19 +2502,19 @@ data: null
|
|
|
2394
2502
|
import { Router as Router12 } from "express";
|
|
2395
2503
|
|
|
2396
2504
|
// src/lib/gateway-settings.ts
|
|
2397
|
-
import { readFileSync as
|
|
2505
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
2398
2506
|
import { homedir as homedir5 } from "os";
|
|
2399
|
-
import { join as
|
|
2507
|
+
import { join as join6, dirname as dirname4 } from "path";
|
|
2400
2508
|
var DEFAULT_SETTINGS = {
|
|
2401
2509
|
desktopNotifications: true
|
|
2402
2510
|
};
|
|
2403
2511
|
function settingsPath(dataDir) {
|
|
2404
|
-
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ??
|
|
2405
|
-
return
|
|
2512
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join6(homedir5(), ".suveren");
|
|
2513
|
+
return join6(dir, "settings.json");
|
|
2406
2514
|
}
|
|
2407
2515
|
function readSettings(dataDir) {
|
|
2408
2516
|
try {
|
|
2409
|
-
const raw = JSON.parse(
|
|
2517
|
+
const raw = JSON.parse(readFileSync5(settingsPath(dataDir), "utf8"));
|
|
2410
2518
|
return {
|
|
2411
2519
|
desktopNotifications: typeof raw.desktopNotifications === "boolean" ? raw.desktopNotifications : DEFAULT_SETTINGS.desktopNotifications
|
|
2412
2520
|
};
|
|
@@ -2449,10 +2557,76 @@ function createGatewaySettingsRouter() {
|
|
|
2449
2557
|
return router;
|
|
2450
2558
|
}
|
|
2451
2559
|
|
|
2452
|
-
// src/routes/
|
|
2560
|
+
// src/routes/archived-mandates.ts
|
|
2453
2561
|
import { Router as Router13 } from "express";
|
|
2562
|
+
|
|
2563
|
+
// src/lib/archived-mandates-store.ts
|
|
2564
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, renameSync } from "fs";
|
|
2565
|
+
import { homedir as homedir6 } from "os";
|
|
2566
|
+
import { join as join7, dirname as dirname5 } from "path";
|
|
2567
|
+
var ID_PATTERN = /^[A-Za-z0-9_.:-]{1,200}$/;
|
|
2568
|
+
function isValidMandateId(id) {
|
|
2569
|
+
return typeof id === "string" && ID_PATTERN.test(id);
|
|
2570
|
+
}
|
|
2571
|
+
function storePath(dataDir) {
|
|
2572
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join7(homedir6(), ".suveren");
|
|
2573
|
+
return join7(dir, "archived-mandates.json");
|
|
2574
|
+
}
|
|
2575
|
+
function readArchived(dataDir) {
|
|
2576
|
+
try {
|
|
2577
|
+
const raw = JSON.parse(readFileSync6(storePath(dataDir), "utf8"));
|
|
2578
|
+
if (!Array.isArray(raw.archived)) return [];
|
|
2579
|
+
return [...new Set(raw.archived.filter(isValidMandateId))];
|
|
2580
|
+
} catch {
|
|
2581
|
+
return [];
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
function writeArchived(ids, dataDir) {
|
|
2585
|
+
const next = [...new Set(ids.filter(isValidMandateId))].sort();
|
|
2586
|
+
const path = storePath(dataDir);
|
|
2587
|
+
mkdirSync5(dirname5(path), { recursive: true });
|
|
2588
|
+
const tmp = `${path}.tmp`;
|
|
2589
|
+
writeFileSync5(tmp, JSON.stringify({ archived: next }, null, 2) + "\n", "utf8");
|
|
2590
|
+
renameSync(tmp, path);
|
|
2591
|
+
return next;
|
|
2592
|
+
}
|
|
2593
|
+
function archiveMandate(id, dataDir) {
|
|
2594
|
+
if (!isValidMandateId(id)) throw new Error("invalid mandate id");
|
|
2595
|
+
return writeArchived([...readArchived(dataDir), id], dataDir);
|
|
2596
|
+
}
|
|
2597
|
+
function unarchiveMandate(id, dataDir) {
|
|
2598
|
+
if (!isValidMandateId(id)) throw new Error("invalid mandate id");
|
|
2599
|
+
return writeArchived(readArchived(dataDir).filter((x) => x !== id), dataDir);
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
// src/routes/archived-mandates.ts
|
|
2603
|
+
function createArchivedMandatesRouter(dataDir) {
|
|
2604
|
+
const router = Router13();
|
|
2605
|
+
router.get("/", (_req, res) => {
|
|
2606
|
+
res.json({ archived: readArchived(dataDir) });
|
|
2607
|
+
});
|
|
2608
|
+
const change = (fn) => (req, res) => {
|
|
2609
|
+
const id = req.params.id;
|
|
2610
|
+
if (!isValidMandateId(id)) {
|
|
2611
|
+
res.status(400).json({ error: "invalid mandate id" });
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
try {
|
|
2615
|
+
res.json({ archived: fn(id, dataDir) });
|
|
2616
|
+
} catch (err) {
|
|
2617
|
+
res.status(500).json({ error: `Could not save: ${err.message}` });
|
|
2618
|
+
}
|
|
2619
|
+
};
|
|
2620
|
+
router.put("/:id", change(archiveMandate));
|
|
2621
|
+
router.delete("/:id", change(unarchiveMandate));
|
|
2622
|
+
return router;
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
// src/routes/internal-events.ts
|
|
2626
|
+
import { Router as Router14 } from "express";
|
|
2454
2627
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
2455
2628
|
var ALLOWED = ["proposal-added", "action-approval-needed"];
|
|
2629
|
+
var SESSION_EXPIRED = "session-expired";
|
|
2456
2630
|
function secretMatches(provided, expected) {
|
|
2457
2631
|
if (!expected) return false;
|
|
2458
2632
|
if (!provided) return false;
|
|
@@ -2461,14 +2635,19 @@ function secretMatches(provided, expected) {
|
|
|
2461
2635
|
if (a.length !== b.length) return false;
|
|
2462
2636
|
return timingSafeEqual2(a, b);
|
|
2463
2637
|
}
|
|
2464
|
-
function createInternalEventsRouter(getSecret) {
|
|
2465
|
-
const router =
|
|
2638
|
+
function createInternalEventsRouter(getSecret, onSessionExpired) {
|
|
2639
|
+
const router = Router14();
|
|
2466
2640
|
router.post("/event", (req, res) => {
|
|
2467
2641
|
if (!secretMatches(req.headers["x-internal-secret"], getSecret())) {
|
|
2468
2642
|
res.status(401).json({ error: "Unauthorized" });
|
|
2469
2643
|
return;
|
|
2470
2644
|
}
|
|
2471
2645
|
const type = (req.body ?? {}).type;
|
|
2646
|
+
if (type === SESSION_EXPIRED) {
|
|
2647
|
+
onSessionExpired?.();
|
|
2648
|
+
res.json({ ok: true });
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2472
2651
|
if (typeof type !== "string" || !ALLOWED.includes(type)) {
|
|
2473
2652
|
res.status(400).json({ error: "Unsupported event type" });
|
|
2474
2653
|
return;
|
|
@@ -2565,23 +2744,112 @@ function startNotificationDispatcher(opts = {}) {
|
|
|
2565
2744
|
return new NotificationDispatcher(opts).start();
|
|
2566
2745
|
}
|
|
2567
2746
|
|
|
2747
|
+
// src/lib/session-lock.ts
|
|
2748
|
+
var SESSION_LOCKED_MESSAGE = "Your sign-in ended after 30 days or was revoked. Sign in again.";
|
|
2749
|
+
function createSessionLock(deps) {
|
|
2750
|
+
const { vault: vault2, port: port2 } = deps;
|
|
2751
|
+
const notifyFn = deps.notifyFn ?? notify;
|
|
2752
|
+
const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
|
|
2753
|
+
const emit = deps.emit ?? eventBus.emit.bind(eventBus);
|
|
2754
|
+
return function lockExpiredSession2() {
|
|
2755
|
+
if (!vault2.isUnlocked()) return;
|
|
2756
|
+
vault2.lockExpired();
|
|
2757
|
+
console.error("[Control Plane] Authority Server session ended \u2014 gateway LOCKED");
|
|
2758
|
+
void unconfigureSessionFn().catch((err) => {
|
|
2759
|
+
console.error("[Control Plane] Failed to push cleared session to MCP:", err);
|
|
2760
|
+
});
|
|
2761
|
+
emit("session-locked", { reason: "expired", message: SESSION_LOCKED_MESSAGE });
|
|
2762
|
+
const { title, message, url } = sessionExpiredNotification(port2);
|
|
2763
|
+
notifyFn(title, message, process.platform, url);
|
|
2764
|
+
};
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
// src/lib/session-expiry-scheduler.ts
|
|
2768
|
+
var MAX_TIMEOUT_MS = 2147483647;
|
|
2769
|
+
var SessionExpiryScheduler = class {
|
|
2770
|
+
constructor(getExpiresAt, onExpired, opts = {}) {
|
|
2771
|
+
this.getExpiresAt = getExpiresAt;
|
|
2772
|
+
this.onExpired = onExpired;
|
|
2773
|
+
this.now = opts.now ?? Date.now;
|
|
2774
|
+
this.setTimeoutFn = opts.setTimeoutFn ?? setTimeout;
|
|
2775
|
+
this.clearTimeoutFn = opts.clearTimeoutFn ?? clearTimeout;
|
|
2776
|
+
this.setIntervalFn = opts.setIntervalFn ?? setInterval;
|
|
2777
|
+
this.clearIntervalFn = opts.clearIntervalFn ?? clearInterval;
|
|
2778
|
+
this.coarseIntervalMs = opts.coarseIntervalMs ?? 5 * 6e4;
|
|
2779
|
+
}
|
|
2780
|
+
preciseTimer = null;
|
|
2781
|
+
coarseTimer = null;
|
|
2782
|
+
now;
|
|
2783
|
+
setTimeoutFn;
|
|
2784
|
+
clearTimeoutFn;
|
|
2785
|
+
setIntervalFn;
|
|
2786
|
+
clearIntervalFn;
|
|
2787
|
+
coarseIntervalMs;
|
|
2788
|
+
/** Start the coarse safety-net interval and arm the precise timer. */
|
|
2789
|
+
start() {
|
|
2790
|
+
if (this.coarseTimer) return;
|
|
2791
|
+
this.coarseTimer = this.setIntervalFn(() => this.check(), this.coarseIntervalMs);
|
|
2792
|
+
this.reschedule();
|
|
2793
|
+
}
|
|
2794
|
+
stop() {
|
|
2795
|
+
if (this.preciseTimer) this.clearTimeoutFn(this.preciseTimer);
|
|
2796
|
+
if (this.coarseTimer) this.clearIntervalFn(this.coarseTimer);
|
|
2797
|
+
this.preciseTimer = null;
|
|
2798
|
+
this.coarseTimer = null;
|
|
2799
|
+
}
|
|
2800
|
+
/** Call whenever `sessionExpiresAt` changes — a login (new value) or a
|
|
2801
|
+
* lock (now null, via getExpiresAt) — to re-arm the precise timer. */
|
|
2802
|
+
reschedule() {
|
|
2803
|
+
if (this.preciseTimer) {
|
|
2804
|
+
this.clearTimeoutFn(this.preciseTimer);
|
|
2805
|
+
this.preciseTimer = null;
|
|
2806
|
+
}
|
|
2807
|
+
const exp = this.getExpiresAt();
|
|
2808
|
+
if (exp === null) return;
|
|
2809
|
+
const delayMs = Math.max(0, exp * 1e3 - this.now());
|
|
2810
|
+
if (delayMs > MAX_TIMEOUT_MS) {
|
|
2811
|
+
this.preciseTimer = this.setTimeoutFn(() => this.reschedule(), MAX_TIMEOUT_MS);
|
|
2812
|
+
return;
|
|
2813
|
+
}
|
|
2814
|
+
this.preciseTimer = this.setTimeoutFn(() => this.check(), delayMs);
|
|
2815
|
+
}
|
|
2816
|
+
/** Idempotent: only fires onExpired when the expiry has actually arrived. */
|
|
2817
|
+
check() {
|
|
2818
|
+
const exp = this.getExpiresAt();
|
|
2819
|
+
if (exp !== null && this.now() >= exp * 1e3) this.onExpired();
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
|
|
2823
|
+
// src/lib/session-health.ts
|
|
2824
|
+
function buildSessionHealth(vault2) {
|
|
2825
|
+
const unlocked = vault2.isUnlocked();
|
|
2826
|
+
const lockedReason = vault2.getLockedReason();
|
|
2827
|
+
return {
|
|
2828
|
+
state: unlocked ? "active" : "locked",
|
|
2829
|
+
expiresAt: unlocked ? vault2.getSessionExpiresAt() : null,
|
|
2830
|
+
...lockedReason ? { lockedReason } : {}
|
|
2831
|
+
};
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2568
2834
|
// src/lib/denials-reader.ts
|
|
2569
|
-
import { existsSync as existsSync6, readFileSync as
|
|
2570
|
-
import { join as
|
|
2835
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
2836
|
+
import { join as join8 } from "path";
|
|
2571
2837
|
function loadDenials(dataDir, decrypt) {
|
|
2572
|
-
const encPath =
|
|
2573
|
-
const plainPath =
|
|
2838
|
+
const encPath = join8(dataDir, "denials.enc.json");
|
|
2839
|
+
const plainPath = join8(dataDir, "denials.json");
|
|
2574
2840
|
if (existsSync6(encPath)) {
|
|
2575
|
-
const { blob } = JSON.parse(
|
|
2841
|
+
const { blob } = JSON.parse(readFileSync7(encPath, "utf-8"));
|
|
2576
2842
|
return JSON.parse(decrypt(blob)).records ?? [];
|
|
2577
2843
|
}
|
|
2578
2844
|
if (existsSync6(plainPath)) {
|
|
2579
|
-
return JSON.parse(
|
|
2845
|
+
return JSON.parse(readFileSync7(plainPath, "utf-8")).records ?? [];
|
|
2580
2846
|
}
|
|
2581
2847
|
return [];
|
|
2582
2848
|
}
|
|
2849
|
+
var DENIAL_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
2583
2850
|
function selectDenials(records, opts = {}) {
|
|
2584
|
-
|
|
2851
|
+
const cutoff = (opts.now ?? Date.now()) - DENIAL_MAX_AGE_MS;
|
|
2852
|
+
let out = records.filter((r) => r.ts >= cutoff).sort((a, b) => b.ts - a.ts);
|
|
2585
2853
|
if (opts.since !== void 0 && Number.isFinite(opts.since)) out = out.filter((r) => r.ts >= opts.since);
|
|
2586
2854
|
const count = out.length;
|
|
2587
2855
|
if (opts.limit !== void 0 && Number.isFinite(opts.limit) && opts.limit > 0) out = out.slice(0, opts.limit);
|
|
@@ -2591,11 +2859,11 @@ function selectDenials(records, opts = {}) {
|
|
|
2591
2859
|
// src/index.ts
|
|
2592
2860
|
(function migrateDataDir() {
|
|
2593
2861
|
if (process.env.SUVEREN_DATA_DIR) return;
|
|
2594
|
-
const oldDir =
|
|
2595
|
-
const newDir =
|
|
2862
|
+
const oldDir = join9(homedir7(), ".hap");
|
|
2863
|
+
const newDir = join9(homedir7(), ".suveren");
|
|
2596
2864
|
if (!existsSync7(newDir) && existsSync7(oldDir)) {
|
|
2597
2865
|
try {
|
|
2598
|
-
|
|
2866
|
+
renameSync2(oldDir, newDir);
|
|
2599
2867
|
console.error(`[Control Plane] Migrated data directory: ${oldDir} \u2192 ${newDir}`);
|
|
2600
2868
|
} catch (err) {
|
|
2601
2869
|
console.error(`[Control Plane] FATAL: could not migrate data directory ${oldDir} \u2192 ${newDir}:`, err);
|
|
@@ -2606,7 +2874,7 @@ function selectDenials(records, opts = {}) {
|
|
|
2606
2874
|
var SP_URL2 = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
|
|
2607
2875
|
var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
|
|
2608
2876
|
var HAP_MODE = process.env.HAP_MODE ?? "personal";
|
|
2609
|
-
var UI_DIST = process.env.HAP_UI_DIST ??
|
|
2877
|
+
var UI_DIST = process.env.HAP_UI_DIST ?? join9(import.meta.dirname ?? __dirname, "../../ui/dist");
|
|
2610
2878
|
var vault = new Vault();
|
|
2611
2879
|
var internalSecret2 = process.env.SUVEREN_INTERNAL_SECRET ?? randomBytes2(32).toString("hex");
|
|
2612
2880
|
setInternalSecret(internalSecret2);
|
|
@@ -2630,7 +2898,17 @@ function loginRateLimit(req, res, next) {
|
|
|
2630
2898
|
}
|
|
2631
2899
|
next();
|
|
2632
2900
|
}
|
|
2633
|
-
|
|
2901
|
+
var lockExpiredSession = createSessionLock({ vault, port });
|
|
2902
|
+
var sessionExpiryScheduler = new SessionExpiryScheduler(
|
|
2903
|
+
() => vault.getSessionExpiresAt(),
|
|
2904
|
+
lockExpiredSession
|
|
2905
|
+
);
|
|
2906
|
+
app.use("/auth", jsonParser, createAuthRouter(
|
|
2907
|
+
vault,
|
|
2908
|
+
requireAuth(vault),
|
|
2909
|
+
loginRateLimit,
|
|
2910
|
+
() => sessionExpiryScheduler.reschedule()
|
|
2911
|
+
));
|
|
2634
2912
|
function resolveOrigin(req) {
|
|
2635
2913
|
const fwdHost = req.get("x-forwarded-host");
|
|
2636
2914
|
if (fwdHost) return `${req.protocol}://${fwdHost}`;
|
|
@@ -2836,13 +3114,14 @@ app.get("/auth/oauth/:integrationId/health", authGuard, async (req, res) => {
|
|
|
2836
3114
|
}
|
|
2837
3115
|
});
|
|
2838
3116
|
app.get("/events", requireAllowedHost, requireAuthQueryOrHeader(vault), createEventsHandler());
|
|
2839
|
-
app.use("/internal", jsonParser, createInternalEventsRouter(() => internalSecret2));
|
|
3117
|
+
app.use("/internal", jsonParser, createInternalEventsRouter(() => internalSecret2, lockExpiredSession));
|
|
2840
3118
|
app.use("/vault", jsonParser, authGuard, createVaultRouter(vault));
|
|
2841
3119
|
app.use("/ai", jsonParser, authGuard, createAIRouter(vault));
|
|
2842
3120
|
app.use("/ai-prompts", jsonParser, authGuard, createAIPromptsRouter());
|
|
2843
3121
|
app.use("/mcp", jsonParser, authGuard, createMCPRouter());
|
|
2844
3122
|
app.use("/autostart", jsonParser, authGuard, createAutostartRouter());
|
|
2845
3123
|
app.use("/api/gateway-settings", jsonParser, authGuard, createGatewaySettingsRouter());
|
|
3124
|
+
app.use("/api/archived-mandates", jsonParser, authGuard, createArchivedMandatesRouter());
|
|
2846
3125
|
app.use("/api/encrypt-intent", jsonParser, authGuard, createEncryptIntentRouter());
|
|
2847
3126
|
app.use("/api/evidence-export", authGuard, createEvidenceExportRouter(SP_URL2, vault));
|
|
2848
3127
|
app.use("/api/evidence", authGuard, createEvidenceRouter());
|
|
@@ -2976,7 +3255,7 @@ app.get("/active-authorizations", authGuard, async (_req, res) => {
|
|
|
2976
3255
|
});
|
|
2977
3256
|
app.get("/denials", authGuard, (req, res) => {
|
|
2978
3257
|
try {
|
|
2979
|
-
const dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
3258
|
+
const dataDir = process.env.SUVEREN_DATA_DIR ?? join9(homedir7(), ".suveren");
|
|
2980
3259
|
const all = loadDenials(dataDir, (blob) => vault.decrypt(blob));
|
|
2981
3260
|
const since = Number(req.query.since);
|
|
2982
3261
|
const limit = Number(req.query.limit);
|
|
@@ -3027,12 +3306,12 @@ app.put("/agent-brief/context", jsonParser, authGuard, async (req, res) => {
|
|
|
3027
3306
|
res.status(413).json({ error: `Context exceeds ${AGENT_CONTEXT_MAX_BYTES}-byte cap` });
|
|
3028
3307
|
return;
|
|
3029
3308
|
}
|
|
3030
|
-
const { mkdirSync:
|
|
3309
|
+
const { mkdirSync: mkdirSync6, writeFileSync: writeFileSync6, renameSync: renameSync3 } = await import("fs");
|
|
3031
3310
|
const filePath = agentBriefPath();
|
|
3032
|
-
|
|
3311
|
+
mkdirSync6(dirname6(filePath), { recursive: true });
|
|
3033
3312
|
const tmpPath = `${filePath}.tmp`;
|
|
3034
|
-
|
|
3035
|
-
|
|
3313
|
+
writeFileSync6(tmpPath, content, "utf-8");
|
|
3314
|
+
renameSync3(tmpPath, filePath);
|
|
3036
3315
|
res.json({ ok: true });
|
|
3037
3316
|
} catch (err) {
|
|
3038
3317
|
console.error("[Control Plane] agent-brief/context PUT failed:", err);
|
|
@@ -3054,6 +3333,9 @@ app.use(
|
|
|
3054
3333
|
},
|
|
3055
3334
|
proxyRes: (proxyRes, req) => {
|
|
3056
3335
|
const status = proxyRes.statusCode ?? 0;
|
|
3336
|
+
if (status === 401 && vault.isUnlocked()) {
|
|
3337
|
+
lockExpiredSession();
|
|
3338
|
+
}
|
|
3057
3339
|
if (status < 200 || status >= 300) return;
|
|
3058
3340
|
const method = req.method?.toUpperCase();
|
|
3059
3341
|
const url = req.url ?? "";
|
|
@@ -3086,8 +3368,8 @@ function detectInstallMethod() {
|
|
|
3086
3368
|
if (dir.includes("/node_modules/@suveren/gateway/")) return "npm";
|
|
3087
3369
|
let cursor = dir;
|
|
3088
3370
|
for (let i = 0; i < 8; i++) {
|
|
3089
|
-
if (existsSync7(
|
|
3090
|
-
const parent =
|
|
3371
|
+
if (existsSync7(join9(cursor, ".git"))) return "dev";
|
|
3372
|
+
const parent = dirname6(cursor);
|
|
3091
3373
|
if (parent === cursor) break;
|
|
3092
3374
|
cursor = parent;
|
|
3093
3375
|
}
|
|
@@ -3099,10 +3381,10 @@ function detectRunningVersion() {
|
|
|
3099
3381
|
return process.env.HAP_BUILD_SHA ?? "dev";
|
|
3100
3382
|
}
|
|
3101
3383
|
const dir = import.meta.dirname ?? __dirname;
|
|
3102
|
-
const bundlePkg =
|
|
3384
|
+
const bundlePkg = join9(dir, "..", "..", "package.json");
|
|
3103
3385
|
if (existsSync7(bundlePkg)) {
|
|
3104
3386
|
try {
|
|
3105
|
-
const pkg = JSON.parse(
|
|
3387
|
+
const pkg = JSON.parse(readFileSync8(bundlePkg, "utf8"));
|
|
3106
3388
|
if (pkg.name === "@suveren/gateway" && typeof pkg.version === "string") {
|
|
3107
3389
|
return pkg.version;
|
|
3108
3390
|
}
|
|
@@ -3125,6 +3407,7 @@ app.get("/health", async (req, res) => {
|
|
|
3125
3407
|
updateAvailable: update.updateAvailable,
|
|
3126
3408
|
installMethod: INSTALL_METHOD,
|
|
3127
3409
|
spUrl: SP_URL2,
|
|
3410
|
+
session: buildSessionHealth(vault),
|
|
3128
3411
|
security: {
|
|
3129
3412
|
note: "Gateway secures tool execution. Agent host isolation is the user's responsibility."
|
|
3130
3413
|
}
|
|
@@ -3140,7 +3423,7 @@ if (existsSync7(UI_DIST)) {
|
|
|
3140
3423
|
}));
|
|
3141
3424
|
app.get("*", (_req, res) => {
|
|
3142
3425
|
res.setHeader("Cache-Control", "no-store, must-revalidate");
|
|
3143
|
-
res.sendFile(
|
|
3426
|
+
res.sendFile(join9(UI_DIST, "index.html"));
|
|
3144
3427
|
});
|
|
3145
3428
|
} else {
|
|
3146
3429
|
app.get("/", (_req, res) => {
|
|
@@ -3157,6 +3440,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
3157
3440
|
notify(title, message, process.platform, url);
|
|
3158
3441
|
}
|
|
3159
3442
|
startNotificationDispatcher({ url: `http://localhost:${port}` });
|
|
3443
|
+
sessionExpiryScheduler.start();
|
|
3160
3444
|
console.error(`[Control Plane] SP proxy: ${SP_URL2}`);
|
|
3161
3445
|
console.error(`[Control Plane] UI dist: ${UI_DIST}`);
|
|
3162
3446
|
console.error(`[Control Plane] Internal secret: configured`);
|