@suveren/gateway 0.7.0 → 0.7.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.
@@ -323,8 +323,14 @@ function serviceRunning() {
323
323
  return p.status === 0 && /state = running/.test(p.stdout || '');
324
324
  }
325
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 || '');
326
+ // NOT schtasks /Query: its LIST output is localized — a German Windows
327
+ // prints "Wird ausgeführt", so matching the English word "Running" reports
328
+ // every non-English machine as stopped, and restart silently falls back to
329
+ // the manual path. Get-ScheduledTask's State is a .NET enum; its name is
330
+ // English on every locale.
331
+ const p = runQuiet('powershell', ['-NoProfile', '-NonInteractive', '-Command',
332
+ `(Get-ScheduledTask -TaskName '${WIN_TASK_NAME}' -ErrorAction Stop).State`]);
333
+ return p.status === 0 && /^Running/m.test((p.stdout || '').trim());
328
334
  }
329
335
  const p = runQuiet('systemctl', ['--user', 'is-active', SYSTEMD_UNIT]);
330
336
  return (p.stdout || '').trim() === 'active';
@@ -344,7 +350,16 @@ function serviceRestart() {
344
350
  if (os === 'darwin') {
345
351
  runQuiet('launchctl', ['kickstart', '-k', `gui/${process.getuid()}/${LAUNCH_AGENT_LABEL}`]);
346
352
  } else if (os === 'win32') {
347
- runQuiet('schtasks', ['/End', '/TN', WIN_TASK_NAME]);
353
+ // NOT schtasks /End: it TerminateProcess()es only the task's ROOT process
354
+ // (node server.js). No signal is delivered, so the shutdown handlers never
355
+ // run and the control-plane / MCP children survive — still holding ports
356
+ // 3400/3430, which makes the relaunched instance die on EADDRINUSE and
357
+ // "restart" quietly become "stop". taskkill /T takes the whole tree.
358
+ const like = SERVER_ENTRY.replace(/'/g, "''").replace(/([\[\]*?])/g, '`$1');
359
+ runQuiet('powershell', ['-NoProfile', '-NonInteractive', '-Command',
360
+ `Get-CimInstance Win32_Process -Filter "Name='node.exe'" | ` +
361
+ `Where-Object { $_.CommandLine -like '*${like}*' } | ` +
362
+ `ForEach-Object { taskkill.exe /PID $_.ProcessId /T /F } | Out-Null`]);
348
363
  runQuiet('schtasks', ['/Run', '/TN', WIN_TASK_NAME]);
349
364
  } else {
350
365
  runQuiet('systemctl', ['--user', 'restart', SYSTEMD_UNIT]);
@@ -573,8 +588,12 @@ async function serviceStatusWindows() {
573
588
  console.log(`Login service: ${installed ? 'installed' : 'not installed'}`);
574
589
  if (installed) {
575
590
  console.log(` Task: ${WIN_TASK_NAME}`);
576
- const state = /Status:\s*(\S+)/.exec(r.stdout || '');
577
- if (state) console.log(` State: ${state[1]}`);
591
+ // Locale-independent state (see serviceRunning): the schtasks LIST value
592
+ // is localized; the PowerShell enum name is not.
593
+ const st = runQuiet('powershell', ['-NoProfile', '-NonInteractive', '-Command',
594
+ `(Get-ScheduledTask -TaskName '${WIN_TASK_NAME}' -ErrorAction Stop).State`]);
595
+ const state = (st.stdout || '').trim();
596
+ if (st.status === 0 && state) console.log(` State: ${state}`);
578
597
  }
579
598
  }
580
599
 
@@ -5,9 +5,9 @@ import {
5
5
 
6
6
  // src/index.ts
7
7
  import { randomBytes as randomBytes2 } from "crypto";
8
- import { existsSync as existsSync5, readFileSync as readFileSync5, renameSync } from "fs";
9
- import { dirname as dirname4, join as join5 } from "path";
10
- import { homedir as homedir4 } from "os";
8
+ import { existsSync as existsSync6, readFileSync as readFileSync5, renameSync } from "fs";
9
+ import { dirname as dirname5, join as join6 } from "path";
10
+ import { homedir as homedir5 } from "os";
11
11
  import express from "express";
12
12
  import { createProxyMiddleware } from "http-proxy-middleware";
13
13
 
@@ -1540,15 +1540,15 @@ import { promisify } from "util";
1540
1540
  var run = promisify(execFile);
1541
1541
  var SUPPORTED = /* @__PURE__ */ new Set(["darwin", "win32", "linux"]);
1542
1542
  var MODULE_DIR = pathDirname(fileURLToPath(import.meta.url));
1543
- function findCli(dirname5 = MODULE_DIR) {
1543
+ function findCli(dirname6 = MODULE_DIR) {
1544
1544
  const candidates = [
1545
1545
  // Bundled (the shipped layout): dist/control-plane → dist → root → bin
1546
- resolve(dirname5, "..", "..", "bin", "suveren-gateway.js"),
1546
+ resolve(dirname6, "..", "..", "bin", "suveren-gateway.js"),
1547
1547
  // Bundled, were the routes/ directory ever preserved.
1548
- resolve(dirname5, "..", "..", "..", "bin", "suveren-gateway.js"),
1548
+ resolve(dirname6, "..", "..", "..", "bin", "suveren-gateway.js"),
1549
1549
  // Dev, running from source: the repo's own bundle directory.
1550
- resolve(dirname5, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
1551
- resolve(dirname5, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
1550
+ resolve(dirname6, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
1551
+ resolve(dirname6, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
1552
1552
  // Deliberately NO process.cwd() fallback: it makes the result depend on
1553
1553
  // where the process happened to be launched from, so the same install
1554
1554
  // resolves differently between a shell and a login service — and it would
@@ -1633,38 +1633,86 @@ function createAutostartRouter() {
1633
1633
 
1634
1634
  // src/lib/desktop-notify.ts
1635
1635
  import { spawn, spawnSync } from "child_process";
1636
+
1637
+ // src/lib/notifier-icon.ts
1638
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
1639
+ import { homedir as homedir2 } from "os";
1640
+ import { join as join2, dirname as dirname2 } from "path";
1641
+ var ICON_BASE64 = "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";
1642
+ function ensureNotifierIcon(dataDir) {
1643
+ try {
1644
+ const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join2(homedir2(), ".suveren");
1645
+ const path = join2(dir, "notifier-icon.png");
1646
+ if (!existsSync3(path)) {
1647
+ mkdirSync2(dirname2(path), { recursive: true });
1648
+ writeFileSync2(path, Buffer.from(ICON_BASE64, "base64"));
1649
+ }
1650
+ return path;
1651
+ } catch {
1652
+ return null;
1653
+ }
1654
+ }
1655
+
1656
+ // src/lib/desktop-notify.ts
1636
1657
  function osaQuote(value) {
1637
1658
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1638
1659
  }
1639
1660
  function psQuote(value) {
1640
1661
  return `'${value.replace(/'/g, "''")}'`;
1641
1662
  }
1663
+ function xmlEscape(value) {
1664
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1665
+ }
1666
+ var WINDOWS_AUMID = "Suveren.Gateway";
1667
+ function windowsToastScript(title, message, url, iconPath) {
1668
+ const toastXml = `<toast${url ? ` activationType="protocol" launch="${xmlEscape(url)}"` : ""}><visual><binding template="ToastGeneric"><text>${xmlEscape(title)}</text><text>${xmlEscape(message)}</text>` + (iconPath ? `<image placement="appLogoOverride" src="${xmlEscape(iconPath)}"/>` : "") + `</binding></visual><audio src="ms-winsoundevent:Notification.Default"/></toast>`;
1669
+ return `$ErrorActionPreference='SilentlyContinue'; $aumid=${psQuote(WINDOWS_AUMID)}; $reg="HKCU:\\Software\\Classes\\AppUserModelId\\$aumid"; if (-not (Test-Path $reg)) { New-Item -Path $reg -Force | Out-Null }; New-ItemProperty -Path $reg -Name DisplayName -Value 'Suveren' -PropertyType String -Force | Out-Null; ` + (iconPath ? `New-ItemProperty -Path $reg -Name IconUri -Value ${psQuote(iconPath)} -PropertyType String -Force | Out-Null; ` : "") + `[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType=WindowsRuntime] | Out-Null; [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType=WindowsRuntime] | Out-Null; $doc = New-Object Windows.Data.Xml.Dom.XmlDocument; $doc.LoadXml(${psQuote(toastXml)}); $toast = New-Object Windows.UI.Notifications.ToastNotification $doc; [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($aumid).Show($toast)`;
1670
+ }
1671
+ var MAC_SOUND = "Glass";
1642
1672
  function buildNotifyCommand(platform, title, message, options = {}) {
1643
- const { url, hasTerminalNotifier: hasTerminalNotifier2 = false } = options;
1673
+ const { url, hasTerminalNotifier: hasTerminalNotifier2 = false, iconPath } = options;
1644
1674
  switch (platform) {
1645
1675
  case "darwin":
1646
1676
  if (url && hasTerminalNotifier2) {
1647
1677
  return {
1648
1678
  cmd: "terminal-notifier",
1649
- args: ["-title", title, "-message", message, "-open", url, "-group", "ai.suveren.gateway"]
1679
+ args: [
1680
+ "-title",
1681
+ title,
1682
+ "-message",
1683
+ message,
1684
+ "-open",
1685
+ url,
1686
+ "-sound",
1687
+ MAC_SOUND,
1688
+ ...iconPath ? ["-appIcon", iconPath] : [],
1689
+ "-group",
1690
+ "ai.suveren.gateway"
1691
+ ]
1650
1692
  };
1651
1693
  }
1652
1694
  return {
1653
1695
  cmd: "osascript",
1654
- args: ["-e", `display notification ${osaQuote(message)} with title ${osaQuote(title)}`]
1696
+ args: [
1697
+ "-e",
1698
+ `display notification ${osaQuote(message)} with title ${osaQuote(title)} sound name ${osaQuote(MAC_SOUND)}`
1699
+ ]
1655
1700
  };
1656
1701
  case "win32":
1657
1702
  return {
1658
1703
  cmd: "powershell",
1704
+ args: ["-NoProfile", "-NonInteractive", "-Command", windowsToastScript(title, message, url, iconPath)]
1705
+ };
1706
+ case "linux":
1707
+ return {
1708
+ cmd: "notify-send",
1659
1709
  args: [
1660
- "-NoProfile",
1661
- "-NonInteractive",
1662
- "-Command",
1663
- `Add-Type -AssemblyName System.Windows.Forms; $n = New-Object System.Windows.Forms.NotifyIcon; $n.Icon = [System.Drawing.SystemIcons]::Information; $n.BalloonTipTitle = ${psQuote(title)}; $n.BalloonTipText = ${psQuote(message)}; $n.Visible = $true; $n.ShowBalloonTip(15000); Start-Sleep -Seconds 16; $n.Dispose()`
1710
+ "--app-name=Suveren",
1711
+ ...iconPath ? ["-i", iconPath] : [],
1712
+ title,
1713
+ message
1664
1714
  ]
1665
1715
  };
1666
- case "linux":
1667
- return { cmd: "notify-send", args: ["--app-name=Suveren", title, message] };
1668
1716
  default:
1669
1717
  return null;
1670
1718
  }
@@ -1688,15 +1736,32 @@ function hasTerminalNotifier() {
1688
1736
  }
1689
1737
  return terminalNotifierCache;
1690
1738
  }
1739
+ function notifySpawnOptions(platform) {
1740
+ return {
1741
+ detached: platform !== "win32",
1742
+ stdio: ["ignore", "ignore", "pipe"]
1743
+ };
1744
+ }
1691
1745
  function notify(title, message, platform = process.platform, url) {
1692
1746
  const command = buildNotifyCommand(platform, title, message, {
1693
1747
  url,
1694
- // Only probe when a URL could actually be attached.
1695
- hasTerminalNotifier: url !== void 0 && platform === "darwin" ? hasTerminalNotifier() : false
1748
+ hasTerminalNotifier: platform === "darwin" ? hasTerminalNotifier() : false,
1749
+ // Windows and Linux show it on the toast itself; macOS only via
1750
+ // terminal-notifier. Failure to write it must never block the sound.
1751
+ iconPath: ensureNotifierIcon()
1696
1752
  });
1697
1753
  if (!command) return;
1698
1754
  try {
1699
- const child = spawn(command.cmd, command.args, { detached: true, stdio: "ignore" });
1755
+ const child = spawn(command.cmd, command.args, notifySpawnOptions(platform));
1756
+ let stderr = "";
1757
+ child.stderr?.on("data", (d) => {
1758
+ stderr += d.toString().slice(0, 500);
1759
+ });
1760
+ child.on("close", (code) => {
1761
+ if (code !== 0) {
1762
+ console.error(`[notify] ${command.cmd} exited ${code}${stderr ? `: ${stderr.trim()}` : ""}`);
1763
+ }
1764
+ });
1700
1765
  child.on("error", () => {
1701
1766
  });
1702
1767
  child.unref();
@@ -1812,13 +1877,13 @@ function createDecryptIntentRouter(vault2) {
1812
1877
 
1813
1878
  // src/routes/approved-intents.ts
1814
1879
  import { Router as Router9 } from "express";
1815
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
1816
- import { homedir as homedir2 } from "os";
1817
- import { join as join2 } from "path";
1818
- var SUVEREN_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join2(homedir2(), ".suveren");
1819
- var FILE_PATH = join2(SUVEREN_DATA_DIR, "approved-intents.enc.json");
1880
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
1881
+ import { homedir as homedir3 } from "os";
1882
+ import { join as join3 } from "path";
1883
+ var SUVEREN_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join3(homedir3(), ".suveren");
1884
+ var FILE_PATH = join3(SUVEREN_DATA_DIR, "approved-intents.enc.json");
1820
1885
  function readFile() {
1821
- if (!existsSync3(FILE_PATH)) {
1886
+ if (!existsSync4(FILE_PATH)) {
1822
1887
  return { version: 1, entries: {} };
1823
1888
  }
1824
1889
  try {
@@ -1828,8 +1893,8 @@ function readFile() {
1828
1893
  }
1829
1894
  }
1830
1895
  function writeFile(data) {
1831
- mkdirSync2(SUVEREN_DATA_DIR, { recursive: true });
1832
- writeFileSync2(FILE_PATH, JSON.stringify(data, null, 2), "utf-8");
1896
+ mkdirSync3(SUVEREN_DATA_DIR, { recursive: true });
1897
+ writeFileSync3(FILE_PATH, JSON.stringify(data, null, 2), "utf-8");
1833
1898
  }
1834
1899
  function createApprovedIntentsRouter(vault2) {
1835
1900
  const router = Router9();
@@ -1875,7 +1940,7 @@ function createApprovedIntentsRouter(vault2) {
1875
1940
 
1876
1941
  // src/lib/update-checker.ts
1877
1942
  import { execSync } from "child_process";
1878
- import { dirname as dirname2 } from "path";
1943
+ import { dirname as dirname3 } from "path";
1879
1944
  import { fileURLToPath as fileURLToPath2 } from "url";
1880
1945
  var GHCR_IMAGE = "suverenai/suveren-gateway";
1881
1946
  var NPM_PACKAGE = "@suveren/gateway";
@@ -1941,7 +2006,7 @@ function compareSemver(a, b) {
1941
2006
  return 0;
1942
2007
  }
1943
2008
  function checkDev() {
1944
- const cwd = dirname2(fileURLToPath2(import.meta.url));
2009
+ const cwd = dirname3(fileURLToPath2(import.meta.url));
1945
2010
  try {
1946
2011
  execSync("git fetch origin --quiet", { cwd, stdio: "ignore", timeout: 15e3 });
1947
2012
  const out = execSync("git rev-list HEAD..origin/main --count", {
@@ -2012,15 +2077,15 @@ data: null
2012
2077
  import { Router as Router10 } from "express";
2013
2078
 
2014
2079
  // src/lib/gateway-settings.ts
2015
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
2016
- import { homedir as homedir3 } from "os";
2017
- import { join as join3, dirname as dirname3 } from "path";
2080
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
2081
+ import { homedir as homedir4 } from "os";
2082
+ import { join as join4, dirname as dirname4 } from "path";
2018
2083
  var DEFAULT_SETTINGS = {
2019
2084
  desktopNotifications: true
2020
2085
  };
2021
2086
  function settingsPath(dataDir) {
2022
- const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join3(homedir3(), ".suveren");
2023
- return join3(dir, "settings.json");
2087
+ const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join4(homedir4(), ".suveren");
2088
+ return join4(dir, "settings.json");
2024
2089
  }
2025
2090
  function readSettings(dataDir) {
2026
2091
  try {
@@ -2038,8 +2103,8 @@ function writeSettings(patch, dataDir) {
2038
2103
  ...typeof patch.desktopNotifications === "boolean" ? { desktopNotifications: patch.desktopNotifications } : {}
2039
2104
  };
2040
2105
  const path = settingsPath(dataDir);
2041
- mkdirSync3(dirname3(path), { recursive: true });
2042
- writeFileSync3(path, JSON.stringify(next, null, 2) + "\n", "utf8");
2106
+ mkdirSync4(dirname4(path), { recursive: true });
2107
+ writeFileSync4(path, JSON.stringify(next, null, 2) + "\n", "utf8");
2043
2108
  return next;
2044
2109
  }
2045
2110
 
@@ -2067,6 +2132,37 @@ function createGatewaySettingsRouter() {
2067
2132
  return router;
2068
2133
  }
2069
2134
 
2135
+ // src/routes/internal-events.ts
2136
+ import { Router as Router11 } from "express";
2137
+ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
2138
+ var ALLOWED = ["proposal-added", "action-approval-needed"];
2139
+ function secretMatches(provided, expected) {
2140
+ if (!expected) return false;
2141
+ if (!provided) return false;
2142
+ const a = Buffer.from(provided);
2143
+ const b = Buffer.from(expected);
2144
+ if (a.length !== b.length) return false;
2145
+ return timingSafeEqual2(a, b);
2146
+ }
2147
+ function createInternalEventsRouter(getSecret) {
2148
+ const router = Router11();
2149
+ router.post("/event", (req, res) => {
2150
+ if (!secretMatches(req.headers["x-internal-secret"], getSecret())) {
2151
+ res.status(401).json({ error: "Unauthorized" });
2152
+ return;
2153
+ }
2154
+ const type = (req.body ?? {}).type;
2155
+ if (typeof type !== "string" || !ALLOWED.includes(type)) {
2156
+ res.status(400).json({ error: "Unsupported event type" });
2157
+ return;
2158
+ }
2159
+ console.error(`[Control Plane] Internal event received: ${type}`);
2160
+ eventBus.emit(type);
2161
+ res.json({ ok: true });
2162
+ });
2163
+ return router;
2164
+ }
2165
+
2070
2166
  // src/lib/notification-dispatcher.ts
2071
2167
  var TRIGGER_EVENTS = ["proposal-added", "action-approval-needed"];
2072
2168
  var TITLE = "Suveren";
@@ -2132,6 +2228,9 @@ var NotificationDispatcher = class {
2132
2228
  } catch {
2133
2229
  enabled = true;
2134
2230
  }
2231
+ console.error(
2232
+ `[Control Plane] Review pending \u2014 desktop notification ${enabled ? "sent" : "suppressed (turned off)"}`
2233
+ );
2135
2234
  if (enabled) {
2136
2235
  const message = count >= FLOOD_THRESHOLD ? floodMessage(count) : WAITING_MESSAGE;
2137
2236
  this.notifyFn(TITLE, message, process.platform, this.url);
@@ -2150,16 +2249,16 @@ function startNotificationDispatcher(opts = {}) {
2150
2249
  }
2151
2250
 
2152
2251
  // src/lib/denials-reader.ts
2153
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
2154
- import { join as join4 } from "path";
2252
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
2253
+ import { join as join5 } from "path";
2155
2254
  function loadDenials(dataDir, decrypt) {
2156
- const encPath = join4(dataDir, "denials.enc.json");
2157
- const plainPath = join4(dataDir, "denials.json");
2158
- if (existsSync4(encPath)) {
2255
+ const encPath = join5(dataDir, "denials.enc.json");
2256
+ const plainPath = join5(dataDir, "denials.json");
2257
+ if (existsSync5(encPath)) {
2159
2258
  const { blob } = JSON.parse(readFileSync4(encPath, "utf-8"));
2160
2259
  return JSON.parse(decrypt(blob)).records ?? [];
2161
2260
  }
2162
- if (existsSync4(plainPath)) {
2261
+ if (existsSync5(plainPath)) {
2163
2262
  return JSON.parse(readFileSync4(plainPath, "utf-8")).records ?? [];
2164
2263
  }
2165
2264
  return [];
@@ -2175,9 +2274,9 @@ function selectDenials(records, opts = {}) {
2175
2274
  // src/index.ts
2176
2275
  (function migrateDataDir() {
2177
2276
  if (process.env.SUVEREN_DATA_DIR) return;
2178
- const oldDir = join5(homedir4(), ".hap");
2179
- const newDir = join5(homedir4(), ".suveren");
2180
- if (!existsSync5(newDir) && existsSync5(oldDir)) {
2277
+ const oldDir = join6(homedir5(), ".hap");
2278
+ const newDir = join6(homedir5(), ".suveren");
2279
+ if (!existsSync6(newDir) && existsSync6(oldDir)) {
2181
2280
  try {
2182
2281
  renameSync(oldDir, newDir);
2183
2282
  console.error(`[Control Plane] Migrated data directory: ${oldDir} \u2192 ${newDir}`);
@@ -2190,7 +2289,7 @@ function selectDenials(records, opts = {}) {
2190
2289
  var SP_URL2 = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
2191
2290
  var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
2192
2291
  var HAP_MODE = process.env.HAP_MODE ?? "personal";
2193
- var UI_DIST = process.env.HAP_UI_DIST ?? join5(import.meta.dirname ?? __dirname, "../../ui/dist");
2292
+ var UI_DIST = process.env.HAP_UI_DIST ?? join6(import.meta.dirname ?? __dirname, "../../ui/dist");
2194
2293
  var vault = new Vault();
2195
2294
  var internalSecret2 = process.env.SUVEREN_INTERNAL_SECRET ?? randomBytes2(32).toString("hex");
2196
2295
  setInternalSecret(internalSecret2);
@@ -2420,6 +2519,7 @@ app.get("/auth/oauth/:integrationId/health", authGuard, async (req, res) => {
2420
2519
  }
2421
2520
  });
2422
2521
  app.get("/events", requireAllowedHost, requireAuthQueryOrHeader(vault), createEventsHandler());
2522
+ app.use("/internal", jsonParser, createInternalEventsRouter(() => internalSecret2));
2423
2523
  app.use("/vault", jsonParser, authGuard, createVaultRouter(vault));
2424
2524
  app.use("/ai", jsonParser, authGuard, createAIRouter(vault));
2425
2525
  app.use("/ai-prompts", jsonParser, authGuard, createAIPromptsRouter());
@@ -2557,7 +2657,7 @@ app.get("/active-authorizations", authGuard, async (_req, res) => {
2557
2657
  });
2558
2658
  app.get("/denials", authGuard, (req, res) => {
2559
2659
  try {
2560
- const dataDir = process.env.SUVEREN_DATA_DIR ?? join5(homedir4(), ".suveren");
2660
+ const dataDir = process.env.SUVEREN_DATA_DIR ?? join6(homedir5(), ".suveren");
2561
2661
  const all = loadDenials(dataDir, (blob) => vault.decrypt(blob));
2562
2662
  const since = Number(req.query.since);
2563
2663
  const limit = Number(req.query.limit);
@@ -2590,11 +2690,11 @@ app.post("/resync-gates", authGuard, async (_req, res) => {
2590
2690
  }
2591
2691
  });
2592
2692
  var AGENT_CONTEXT_MAX_BYTES = 16 * 1024;
2593
- var SUVEREN_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join5(homedir4(), ".suveren");
2693
+ var SUVEREN_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join6(homedir5(), ".suveren");
2594
2694
  app.get("/agent-brief/context", authGuard, async (_req, res) => {
2595
2695
  try {
2596
2696
  const { readFileSync: readFileSync6, existsSync: fileExists } = await import("fs");
2597
- const filePath = join5(SUVEREN_DATA_DIR2, "context.md");
2697
+ const filePath = join6(SUVEREN_DATA_DIR2, "context.md");
2598
2698
  if (!fileExists(filePath)) {
2599
2699
  res.json({ content: "" });
2600
2700
  return;
@@ -2617,11 +2717,11 @@ app.put("/agent-brief/context", jsonParser, authGuard, async (req, res) => {
2617
2717
  res.status(413).json({ error: `Context exceeds ${AGENT_CONTEXT_MAX_BYTES}-byte cap` });
2618
2718
  return;
2619
2719
  }
2620
- const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync4, renameSync: renameSync2 } = await import("fs");
2621
- mkdirSync4(SUVEREN_DATA_DIR2, { recursive: true });
2622
- const filePath = join5(SUVEREN_DATA_DIR2, "context.md");
2720
+ const { mkdirSync: mkdirSync5, writeFileSync: writeFileSync5, renameSync: renameSync2 } = await import("fs");
2721
+ mkdirSync5(SUVEREN_DATA_DIR2, { recursive: true });
2722
+ const filePath = join6(SUVEREN_DATA_DIR2, "context.md");
2623
2723
  const tmpPath = `${filePath}.tmp`;
2624
- writeFileSync4(tmpPath, content, "utf-8");
2724
+ writeFileSync5(tmpPath, content, "utf-8");
2625
2725
  renameSync2(tmpPath, filePath);
2626
2726
  res.json({ ok: true });
2627
2727
  } catch (err) {
@@ -2671,13 +2771,13 @@ app.use(
2671
2771
  })
2672
2772
  );
2673
2773
  function detectInstallMethod() {
2674
- if (existsSync5("/.dockerenv")) return "docker";
2774
+ if (existsSync6("/.dockerenv")) return "docker";
2675
2775
  const dir = import.meta.dirname ?? __dirname;
2676
2776
  if (dir.includes("/node_modules/@suveren/gateway/")) return "npm";
2677
2777
  let cursor = dir;
2678
2778
  for (let i = 0; i < 8; i++) {
2679
- if (existsSync5(join5(cursor, ".git"))) return "dev";
2680
- const parent = dirname4(cursor);
2779
+ if (existsSync6(join6(cursor, ".git"))) return "dev";
2780
+ const parent = dirname5(cursor);
2681
2781
  if (parent === cursor) break;
2682
2782
  cursor = parent;
2683
2783
  }
@@ -2689,8 +2789,8 @@ function detectRunningVersion() {
2689
2789
  return process.env.HAP_BUILD_SHA ?? "dev";
2690
2790
  }
2691
2791
  const dir = import.meta.dirname ?? __dirname;
2692
- const bundlePkg = join5(dir, "..", "..", "package.json");
2693
- if (existsSync5(bundlePkg)) {
2792
+ const bundlePkg = join6(dir, "..", "..", "package.json");
2793
+ if (existsSync6(bundlePkg)) {
2694
2794
  try {
2695
2795
  const pkg = JSON.parse(readFileSync5(bundlePkg, "utf8"));
2696
2796
  if (pkg.name === "@suveren/gateway" && typeof pkg.version === "string") {
@@ -2720,7 +2820,7 @@ app.get("/health", async (req, res) => {
2720
2820
  }
2721
2821
  });
2722
2822
  });
2723
- if (existsSync5(UI_DIST)) {
2823
+ if (existsSync6(UI_DIST)) {
2724
2824
  app.use(express.static(UI_DIST, {
2725
2825
  setHeaders: (res, filePath) => {
2726
2826
  if (filePath.endsWith("index.html")) {
@@ -2730,7 +2830,7 @@ if (existsSync5(UI_DIST)) {
2730
2830
  }));
2731
2831
  app.get("*", (_req, res) => {
2732
2832
  res.setHeader("Cache-Control", "no-store, must-revalidate");
2733
- res.sendFile(join5(UI_DIST, "index.html"));
2833
+ res.sendFile(join6(UI_DIST, "index.html"));
2734
2834
  });
2735
2835
  } else {
2736
2836
  app.get("/", (_req, res) => {
@@ -6,6 +6,33 @@ import express from "express";
6
6
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
7
7
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
8
8
 
9
+ // src/lib/cp-notify.ts
10
+ var CP_PORT = process.env.SUVEREN_CP_PORT ?? "3402";
11
+ var CP_BASE = process.env.SUVEREN_CP_INTERNAL_URL ?? `http://127.0.0.1:${CP_PORT}`;
12
+ var TIMEOUT_MS = 2e3;
13
+ async function notifyControlPlane(type) {
14
+ const secret = process.env.SUVEREN_INTERNAL_SECRET ?? "";
15
+ if (!secret) return;
16
+ try {
17
+ const controller = new AbortController();
18
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
19
+ try {
20
+ await fetch(`${CP_BASE}/internal/event`, {
21
+ method: "POST",
22
+ headers: {
23
+ "Content-Type": "application/json",
24
+ "X-Internal-Secret": secret
25
+ },
26
+ body: JSON.stringify({ type }),
27
+ signal: controller.signal
28
+ });
29
+ } finally {
30
+ clearTimeout(timer);
31
+ }
32
+ } catch {
33
+ }
34
+ }
35
+
9
36
  // src/lib/sp-client.ts
10
37
  var SPReceiptError = class extends Error {
11
38
  constructor(message, statusCode, body) {
@@ -167,6 +194,7 @@ var SPClient = class {
167
194
  const body = await res.json().catch(() => ({}));
168
195
  throw new Error(body.error ?? `SP proposal submission failed: ${res.status}`);
169
196
  }
197
+ void notifyControlPlane("proposal-added");
170
198
  return res.json();
171
199
  }
172
200
  /**
@@ -8,7 +8,7 @@ This package contains type definitions for node (https://nodejs.org/).
8
8
  Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node.
9
9
 
10
10
  ### Additional Details
11
- * Last updated: Mon, 27 Jul 2026 17:32:10 GMT
11
+ * Last updated: Fri, 07 Aug 2026 17:46:44 GMT
12
12
  * Dependencies: [undici-types](https://npmjs.com/package/undici-types)
13
13
 
14
14
  # Credits
@@ -227,7 +227,6 @@ declare module "node:child_process" {
227
227
  * new process in a shell or with the use of the `shell` option of `ChildProcess`:
228
228
  *
229
229
  * ```js
230
- * 'use strict';
231
230
  * import { spawn } from 'node:child_process';
232
231
  *
233
232
  * const subprocess = spawn(
@@ -2698,12 +2698,12 @@ declare module "node:crypto" {
2698
2698
  */
2699
2699
  function sign(
2700
2700
  algorithm: string | null | undefined,
2701
- data: ArrayBufferLike | NodeJS.ArrayBufferView,
2701
+ data: BinaryLike,
2702
2702
  key: KeyLike | SignKeyObjectInput | SignPrivateKeyInput | SignJsonWebKeyInput,
2703
2703
  ): NonSharedBuffer;
2704
2704
  function sign(
2705
2705
  algorithm: string | null | undefined,
2706
- data: ArrayBufferLike | NodeJS.ArrayBufferView,
2706
+ data: BinaryLike,
2707
2707
  key: KeyLike | SignKeyObjectInput | SignPrivateKeyInput | SignJsonWebKeyInput,
2708
2708
  callback: (error: Error | null, data: NonSharedBuffer) => void,
2709
2709
  ): void;
@@ -2729,15 +2729,15 @@ declare module "node:crypto" {
2729
2729
  */
2730
2730
  function verify(
2731
2731
  algorithm: string | null | undefined,
2732
- data: ArrayBufferLike | NodeJS.ArrayBufferView,
2732
+ data: BinaryLike,
2733
2733
  key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput,
2734
- signature: ArrayBufferLike | NodeJS.ArrayBufferView,
2734
+ signature: BinaryLike,
2735
2735
  ): boolean;
2736
2736
  function verify(
2737
2737
  algorithm: string | null | undefined,
2738
- data: ArrayBufferLike | NodeJS.ArrayBufferView,
2738
+ data: BinaryLike,
2739
2739
  key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput,
2740
- signature: ArrayBufferLike | NodeJS.ArrayBufferView,
2740
+ signature: BinaryLike,
2741
2741
  callback: (error: Error | null, result: boolean) => void,
2742
2742
  ): void;
2743
2743
  /**
@@ -183,7 +183,7 @@ declare module "node:dns/promises" {
183
183
  * refresh: 900,
184
184
  * retry: 900,
185
185
  * expire: 1800,
186
- * minttl: 60 } ]
186
+ * minttl: 60 } ];
187
187
  * ```
188
188
  * @since v10.6.0
189
189
  */
@@ -226,7 +226,7 @@ declare module "node:dns/promises" {
226
226
  * regexp: '',
227
227
  * replacement: '_sip._udp.example.com',
228
228
  * order: 30,
229
- * preference: 100
229
+ * preference: 100,
230
230
  * }
231
231
  * ```
232
232
  * @since v10.6.0
@@ -265,7 +265,7 @@ declare module "node:dns/promises" {
265
265
  * refresh: 10000,
266
266
  * retry: 2400,
267
267
  * expire: 604800,
268
- * minttl: 3600
268
+ * minttl: 3600,
269
269
  * }
270
270
  * ```
271
271
  * @since v10.6.0
@@ -285,7 +285,7 @@ declare module "node:dns/promises" {
285
285
  * priority: 10,
286
286
  * weight: 5,
287
287
  * port: 21223,
288
- * name: 'service.example.com'
288
+ * name: 'service.example.com',
289
289
  * }
290
290
  * ```
291
291
  * @since v10.6.0
@@ -306,7 +306,7 @@ declare module "node:dns/promises" {
306
306
  * certUsage: 3,
307
307
  * selector: 1,
308
308
  * match: 1,
309
- * data: [ArrayBuffer]
309
+ * data: [ArrayBuffer],
310
310
  * }
311
311
  * ```
312
312
  * @since v23.9.0, v22.15.0