@suveren/gateway 0.15.0 → 0.16.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.
@@ -2270,14 +2270,26 @@ function formatConsumptionFull(entries) {
2270
2270
  }
2271
2271
 
2272
2272
  // src/lib/context-loader.ts
2273
- import { readFileSync as readFileSync12, existsSync as existsSync11 } from "fs";
2273
+ import { readFileSync as readFileSync12, existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, renameSync as renameSync3 } from "fs";
2274
2274
  import { homedir as homedir7 } from "os";
2275
- import { join as join11 } from "path";
2275
+ import { join as join11, dirname as dirname9 } from "path";
2276
2276
  var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ?? join11(homedir7(), ".suveren");
2277
+ var CONTEXT_MAX_BYTES = 16 * 1024;
2278
+ function contextFilePath(dataDir2) {
2279
+ return join11(dataDir2 ?? DEFAULT_DIR7, "context.md");
2280
+ }
2281
+ function writeContextFile(content, dataDir2) {
2282
+ const bytes = Buffer.byteLength(content, "utf-8");
2283
+ if (bytes > CONTEXT_MAX_BYTES) throw new Error(`the brief is ${bytes} bytes; the limit is ${CONTEXT_MAX_BYTES}.`);
2284
+ const filePath = contextFilePath(dataDir2);
2285
+ mkdirSync9(dirname9(filePath), { recursive: true });
2286
+ const tmpPath = `${filePath}.tmp`;
2287
+ writeFileSync9(tmpPath, content, "utf-8");
2288
+ renameSync3(tmpPath, filePath);
2289
+ }
2277
2290
  var BRIEF_MAX_CHARS = 16 * 1024;
2278
2291
  function readContextFile(dataDir2) {
2279
- const dir = dataDir2 ?? DEFAULT_DIR7;
2280
- const filePath = join11(dir, "context.md");
2292
+ const filePath = contextFilePath(dataDir2);
2281
2293
  if (!existsSync11(filePath)) return null;
2282
2294
  try {
2283
2295
  const content = readFileSync12(filePath, "utf-8").trim();
@@ -2843,68 +2855,6 @@ import { getProfile as getProfile2, ContentBindingError, boundActionTypes as bou
2843
2855
  import { readFile } from "fs/promises";
2844
2856
  import { extname } from "path";
2845
2857
  import { randomUUID } from "crypto";
2846
-
2847
- // src/lib/manifest-loader.ts
2848
- import { readFileSync as readFileSync13, existsSync as existsSync12 } from "fs";
2849
- import { join as join12, resolve } from "path";
2850
- var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
2851
- function isExactSemver(value) {
2852
- return typeof value === "string" && EXACT_SEMVER_RE.test(value);
2853
- }
2854
- function invalidNpmPinReason(manifest) {
2855
- if (!manifest.npmPackage) return null;
2856
- if (manifest.npmVersion === void 0) {
2857
- return `npmPackage "${manifest.npmPackage}" is set but npmVersion is missing \u2014 every connector manifest must pin an exact tested version`;
2858
- }
2859
- if (!isExactSemver(manifest.npmVersion)) {
2860
- return `npmVersion ${JSON.stringify(manifest.npmVersion)} for "${manifest.npmPackage}" is not an exact semver (e.g. "1.2.3") \u2014 ranges, dist-tags like "latest", git specs, URLs, and file paths are not allowed`;
2861
- }
2862
- return null;
2863
- }
2864
- var manifests = /* @__PURE__ */ new Map();
2865
- function loadManifests(integrationsDir) {
2866
- const dir = resolve(
2867
- integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join12(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
2868
- );
2869
- const indexPath = join12(dir, "index.json");
2870
- if (!existsSync12(indexPath)) {
2871
- console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
2872
- return 0;
2873
- }
2874
- let index;
2875
- try {
2876
- index = JSON.parse(readFileSync13(indexPath, "utf-8"));
2877
- } catch (err) {
2878
- console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
2879
- return 0;
2880
- }
2881
- let loaded = 0;
2882
- for (const [id, relativePath] of Object.entries(index.integrations)) {
2883
- const manifestPath = join12(dir, relativePath);
2884
- try {
2885
- const manifest = JSON.parse(readFileSync13(manifestPath, "utf-8"));
2886
- const pinReason = invalidNpmPinReason(manifest);
2887
- if (pinReason) {
2888
- console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${pinReason}`);
2889
- continue;
2890
- }
2891
- manifests.set(id, manifest);
2892
- loaded++;
2893
- } catch (err) {
2894
- console.error(`[ManifestLoader] Failed to load manifest ${id} from ${manifestPath}:`, err);
2895
- }
2896
- }
2897
- console.error(`[ManifestLoader] Loaded ${loaded} integration manifest(s) from ${dir}`);
2898
- return loaded;
2899
- }
2900
- function getManifest(id) {
2901
- return manifests.get(id);
2902
- }
2903
- function getAllManifests() {
2904
- return Array.from(manifests.values());
2905
- }
2906
-
2907
- // src/lib/tool-proxy.ts
2908
2858
  var IMAGE_MIME = {
2909
2859
  ".jpg": "image/jpeg",
2910
2860
  ".jpeg": "image/jpeg",
@@ -3021,7 +2971,7 @@ function denyRead(state2, tool, reason, detail, target) {
3021
2971
  return { content: [{ type: "text", text: `Read blocked by Gatekeeper: ${detail}` }], isError: true };
3022
2972
  }
3023
2973
  function createGatedToolHandler(tool, integrationManager2, state2) {
3024
- if (isSimulationMode() && !manifestIsSimulated(getManifest(tool.integrationId))) {
2974
+ if (isSimulationMode() && !integrationManager2.isSimulationSafe(tool.integrationId)) {
3025
2975
  return async () => ({
3026
2976
  content: [{
3027
2977
  type: "text",
@@ -3031,7 +2981,13 @@ function createGatedToolHandler(tool, integrationManager2, state2) {
3031
2981
  });
3032
2982
  }
3033
2983
  const gated = createGatedToolHandlerInner(tool, integrationManager2, state2);
3034
- const inner = async (args) => gated(normalizeIncomingArgs(tool, args));
2984
+ const gateable = !!tool.gating?.profile && tool.gating.category !== "disabled";
2985
+ const inner = async (args) => {
2986
+ const normalized = normalizeIncomingArgs(tool, args);
2987
+ const refusal = gateable ? integrationManager2.precheckBuiltin?.(tool, normalized) : null;
2988
+ if (refusal) return { content: [{ type: "text", text: refusal }], isError: true };
2989
+ return gated(normalized);
2990
+ };
3035
2991
  const blocked = tool.gating?.blockedArgs ?? [];
3036
2992
  if (blocked.length === 0) return inner;
3037
2993
  return async (args) => {
@@ -4555,16 +4511,16 @@ function verifyGateContentHashes(content, auth) {
4555
4511
  }
4556
4512
 
4557
4513
  // src/lib/integration-registry.ts
4558
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync13 } from "fs";
4514
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync12 } from "fs";
4559
4515
  import { homedir as homedir8 } from "os";
4560
- import { dirname as dirname9, join as join13 } from "path";
4561
- var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join13(homedir8(), ".suveren");
4516
+ import { dirname as dirname10, join as join12 } from "path";
4517
+ var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join12(homedir8(), ".suveren");
4562
4518
  var IntegrationRegistry = class {
4563
4519
  integrations = /* @__PURE__ */ new Map();
4564
4520
  filePath;
4565
4521
  constructor(dataDir2) {
4566
4522
  const dir = dataDir2 ?? DEFAULT_DATA_DIR;
4567
- this.filePath = join13(dir, "integrations.json");
4523
+ this.filePath = join12(dir, "integrations.json");
4568
4524
  this.load();
4569
4525
  }
4570
4526
  // ─── CRUD ───────────────────────────────────────────────────────────────
@@ -4595,12 +4551,12 @@ var IntegrationRegistry = class {
4595
4551
  }
4596
4552
  // ─── Load / Save ────────────────────────────────────────────────────────
4597
4553
  load() {
4598
- if (!existsSync13(this.filePath)) {
4599
- mkdirSync9(dirname9(this.filePath), { recursive: true });
4554
+ if (!existsSync12(this.filePath)) {
4555
+ mkdirSync10(dirname10(this.filePath), { recursive: true });
4600
4556
  return;
4601
4557
  }
4602
4558
  try {
4603
- const raw = readFileSync14(this.filePath, "utf-8");
4559
+ const raw = readFileSync13(this.filePath, "utf-8");
4604
4560
  const data = JSON.parse(raw);
4605
4561
  const migrated = data.integrations.map((i) => {
4606
4562
  if ("toolGating" in i && !("profile" in i)) {
@@ -4629,20 +4585,80 @@ var IntegrationRegistry = class {
4629
4585
  version: 1,
4630
4586
  integrations: Array.from(this.integrations.values())
4631
4587
  };
4632
- mkdirSync9(dirname9(this.filePath), { recursive: true });
4633
- writeFileSync9(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf-8" });
4588
+ mkdirSync10(dirname10(this.filePath), { recursive: true });
4589
+ writeFileSync10(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf-8" });
4634
4590
  }
4635
4591
  };
4636
4592
 
4637
4593
  // src/lib/integration-manager.ts
4638
4594
  import { homedir as homedir9 } from "os";
4639
4595
  import { join as join14, delimiter } from "path";
4640
- import { existsSync as existsSync14, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync15, rmSync } from "fs";
4596
+ import { existsSync as existsSync14, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync15, rmSync } from "fs";
4641
4597
  import { execFile } from "child_process";
4642
4598
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4643
4599
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4644
4600
  import { getProfile as getProfile5 } from "@hap/core";
4645
4601
 
4602
+ // src/lib/manifest-loader.ts
4603
+ import { readFileSync as readFileSync14, existsSync as existsSync13 } from "fs";
4604
+ import { join as join13, resolve } from "path";
4605
+ var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
4606
+ function isExactSemver(value) {
4607
+ return typeof value === "string" && EXACT_SEMVER_RE.test(value);
4608
+ }
4609
+ function invalidNpmPinReason(manifest) {
4610
+ if (!manifest.npmPackage) return null;
4611
+ if (manifest.npmVersion === void 0) {
4612
+ return `npmPackage "${manifest.npmPackage}" is set but npmVersion is missing \u2014 every connector manifest must pin an exact tested version`;
4613
+ }
4614
+ if (!isExactSemver(manifest.npmVersion)) {
4615
+ return `npmVersion ${JSON.stringify(manifest.npmVersion)} for "${manifest.npmPackage}" is not an exact semver (e.g. "1.2.3") \u2014 ranges, dist-tags like "latest", git specs, URLs, and file paths are not allowed`;
4616
+ }
4617
+ return null;
4618
+ }
4619
+ var manifests = /* @__PURE__ */ new Map();
4620
+ function loadManifests(integrationsDir) {
4621
+ const dir = resolve(
4622
+ integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join13(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
4623
+ );
4624
+ const indexPath = join13(dir, "index.json");
4625
+ if (!existsSync13(indexPath)) {
4626
+ console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
4627
+ return 0;
4628
+ }
4629
+ let index;
4630
+ try {
4631
+ index = JSON.parse(readFileSync14(indexPath, "utf-8"));
4632
+ } catch (err) {
4633
+ console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
4634
+ return 0;
4635
+ }
4636
+ let loaded = 0;
4637
+ for (const [id, relativePath] of Object.entries(index.integrations)) {
4638
+ const manifestPath = join13(dir, relativePath);
4639
+ try {
4640
+ const manifest = JSON.parse(readFileSync14(manifestPath, "utf-8"));
4641
+ const pinReason = invalidNpmPinReason(manifest);
4642
+ if (pinReason) {
4643
+ console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${pinReason}`);
4644
+ continue;
4645
+ }
4646
+ manifests.set(id, manifest);
4647
+ loaded++;
4648
+ } catch (err) {
4649
+ console.error(`[ManifestLoader] Failed to load manifest ${id} from ${manifestPath}:`, err);
4650
+ }
4651
+ }
4652
+ console.error(`[ManifestLoader] Loaded ${loaded} integration manifest(s) from ${dir}`);
4653
+ return loaded;
4654
+ }
4655
+ function getManifest(id) {
4656
+ return manifests.get(id);
4657
+ }
4658
+ function getAllManifests() {
4659
+ return Array.from(manifests.values());
4660
+ }
4661
+
4646
4662
  // src/lib/remote-auth-preflight.ts
4647
4663
  var AUTH_HEADER_RE = /^authorization:\s*(.+)$/i;
4648
4664
  function remotePreflightTarget(command, args) {
@@ -4730,11 +4746,11 @@ function buildPath() {
4730
4746
  }
4731
4747
  function ensureIntegrationsDir() {
4732
4748
  if (!existsSync14(INTEGRATIONS_DIR)) {
4733
- mkdirSync10(INTEGRATIONS_DIR, { recursive: true });
4749
+ mkdirSync11(INTEGRATIONS_DIR, { recursive: true });
4734
4750
  }
4735
4751
  const pkgPath = join14(INTEGRATIONS_DIR, "package.json");
4736
4752
  if (!existsSync14(pkgPath)) {
4737
- writeFileSync10(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
4753
+ writeFileSync11(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
4738
4754
  }
4739
4755
  }
4740
4756
  function readAgeOf(config) {
@@ -4762,6 +4778,8 @@ var IntegrationManager = class {
4762
4778
  this.serviceCredentials = serviceCredentials2;
4763
4779
  }
4764
4780
  running = /* @__PURE__ */ new Map();
4781
+ /** In-process tool groups (see builtin-integration.ts) — kept apart from `running` on purpose. */
4782
+ builtins = /* @__PURE__ */ new Map();
4765
4783
  onToolsChanged = null;
4766
4784
  /**
4767
4785
  * Called when a persisted config was migrated to the manifest's command/args
@@ -5190,9 +5208,92 @@ var IntegrationManager = class {
5190
5208
  this.onToolsChanged?.();
5191
5209
  }
5192
5210
  /**
5193
- * Proxy a tool call to a downstream MCP server.
5211
+ * Register an in-process tool group (see builtin-integration.ts). Its tools are
5212
+ * listed, gated and executed through the same path as a connector's; only the
5213
+ * final call runs `handler` instead of a child MCP client. Refused when the id
5214
+ * is unusable or already taken, so a built-in can never shadow a connector.
5215
+ */
5216
+ registerBuiltin(def) {
5217
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(def.id) || def.id.includes("__")) {
5218
+ throw new Error(`Built-in integration id "${def.id}" must be lowercase letters, digits and '-'.`);
5219
+ }
5220
+ if (this.builtins.has(def.id) || this.running.has(def.id) || getManifest(def.id)) {
5221
+ throw new Error(`Built-in integration id "${def.id}" is already in use.`);
5222
+ }
5223
+ const names = /* @__PURE__ */ new Set();
5224
+ const tools = def.tools.map((tool) => {
5225
+ if (names.has(tool.name)) throw new Error(`Built-in "${def.id}": tool "${tool.name}" is declared twice.`);
5226
+ names.add(tool.name);
5227
+ const gating = this.resolveToolGating(def.profile, def.toolGating, tool.name);
5228
+ return {
5229
+ originalName: tool.name,
5230
+ namespacedName: `${def.id}__${tool.name}`,
5231
+ integrationId: def.id,
5232
+ description: tool.description,
5233
+ inputSchema: withoutBlockedArgs(tool.inputSchema, gating?.blockedArgs),
5234
+ gating
5235
+ };
5236
+ });
5237
+ this.builtins.set(def.id, { def, tools });
5238
+ console.error(`[IntegrationManager] Built-in ${def.id}: ${tools.length} tool(s) under profile ${def.profile}`);
5239
+ this.onToolsChanged?.();
5240
+ }
5241
+ /** The built-in groups, for the mandate picker — never part of the connector status list. */
5242
+ getBuiltins() {
5243
+ return Array.from(this.builtins.values()).map(({ def }) => ({
5244
+ id: def.id,
5245
+ name: def.name,
5246
+ description: def.description ?? "",
5247
+ profile: def.profile,
5248
+ available: !def.simulationOnly || isSimulationMode()
5249
+ }));
5250
+ }
5251
+ /** Whether `id` names a built-in (in-process) integration. */
5252
+ isBuiltin(id) {
5253
+ return this.builtins.has(id);
5254
+ }
5255
+ /**
5256
+ * Whether `id` may run while simulation mode is on: a connector whose manifest
5257
+ * carries the `simulation` marker, or a built-in that declares `simulation: true`.
5258
+ */
5259
+ isSimulationSafe(id) {
5260
+ const builtin = this.builtins.get(id);
5261
+ if (builtin) return builtin.def.simulation === true;
5262
+ return manifestIsSimulated(getManifest(id));
5263
+ }
5264
+ /**
5265
+ * The built-in's own refusal for this call, checked before the gate (no proposal,
5266
+ * no ticket): `simulationOnly` outside simulation mode, then the tool's `validate`.
5267
+ * Null for connector tools and for calls that may proceed.
5268
+ */
5269
+ precheckBuiltin(tool, args) {
5270
+ const builtin = this.builtins.get(tool.integrationId);
5271
+ if (!builtin) return null;
5272
+ if (builtin.def.simulationOnly && !isSimulationMode()) {
5273
+ return `Refused: "${tool.namespacedName}" is not available. No ticket was requested.`;
5274
+ }
5275
+ const def = builtin.def.tools.find((t) => t.name === tool.originalName);
5276
+ const refusal = def?.validate?.(args);
5277
+ return refusal ? `Refused: ${refusal} No ticket was requested.` : null;
5278
+ }
5279
+ /**
5280
+ * Proxy a tool call to a downstream MCP server (or a built-in's handler).
5194
5281
  */
5195
5282
  async callTool(integrationId, toolName, args) {
5283
+ const builtin = this.builtins.get(integrationId);
5284
+ if (builtin) {
5285
+ const tool = builtin.def.tools.find((t) => t.name === toolName);
5286
+ if (!tool) {
5287
+ return { content: [{ type: "text", text: `Tool "${integrationId}__${toolName}" does not exist.` }], isError: true };
5288
+ }
5289
+ try {
5290
+ const result = await tool.handler(args);
5291
+ return { content: result.content.map((c) => ({ type: c.type, text: c.text })), isError: result.isError };
5292
+ } catch (err) {
5293
+ const message = err instanceof Error ? err.message : String(err);
5294
+ return { content: [{ type: "text", text: `Tool call failed: ${message}` }], isError: true };
5295
+ }
5296
+ }
5196
5297
  const entry = this.running.get(integrationId);
5197
5298
  if (!entry) {
5198
5299
  return {
@@ -5223,6 +5324,9 @@ var IntegrationManager = class {
5223
5324
  for (const entry of this.running.values()) {
5224
5325
  tools.push(...entry.tools);
5225
5326
  }
5327
+ for (const entry of this.builtins.values()) {
5328
+ tools.push(...entry.tools);
5329
+ }
5226
5330
  return tools;
5227
5331
  }
5228
5332
  /**
@@ -5497,12 +5601,88 @@ function warnNoProfiles(reason) {
5497
5601
  );
5498
5602
  }
5499
5603
 
5604
+ // src/lib/builtin-integration.ts
5605
+ function builtinText(text) {
5606
+ return { content: [{ type: "text", text }] };
5607
+ }
5608
+
5609
+ // src/lib/builtins/setup.ts
5610
+ var DELEGATION_PROFILE = "github.com/humanagencyprotocol/hap-profiles/delegation@0.1";
5611
+ function briefRefusal(args) {
5612
+ if (typeof args.content !== "string" || args.content.trim() === "") return "`content` must be the complete new brief (markdown text).";
5613
+ const bytes = Buffer.byteLength(args.content, "utf-8");
5614
+ if (bytes > CONTEXT_MAX_BYTES) return `the brief is ${bytes} bytes; the limit is ${CONTEXT_MAX_BYTES}. Shorten it.`;
5615
+ return void 0;
5616
+ }
5617
+ function setupBuiltin(_deps) {
5618
+ return {
5619
+ id: "setup",
5620
+ name: "Test setup",
5621
+ description: "Your AI proposes its own setup for a test \u2014 for now its agent brief. Every proposal waits for your approval. Simulation mode only.",
5622
+ profile: DELEGATION_PROFILE,
5623
+ simulation: true,
5624
+ simulationOnly: true,
5625
+ toolGating: {
5626
+ overrides: {
5627
+ set_agent_brief: {
5628
+ executionMapping: {},
5629
+ staticExecution: { action_type: "brief" },
5630
+ hideUnlessAuthorized: true
5631
+ }
5632
+ }
5633
+ },
5634
+ tools: [
5635
+ {
5636
+ name: "set_agent_brief",
5637
+ description: "Simulation mode only: propose a new agent brief \u2014 the standing instructions every agent session starts with. `content` replaces the whole brief (markdown, at most 16 KB). A person approves or rejects the proposal; only after approval is the brief replaced, and it applies from the next session. Write the complete brief, not a change to the current one.",
5638
+ inputSchema: {
5639
+ type: "object",
5640
+ properties: {
5641
+ content: { type: "string", description: "The complete new agent brief, in markdown (at most 16 KB)." },
5642
+ receipt_id: {
5643
+ type: "string",
5644
+ description: "Authorization reference for this call, set by the governing gateway \u2014 agents do not set this."
5645
+ }
5646
+ },
5647
+ required: ["content"]
5648
+ },
5649
+ validate: briefRefusal,
5650
+ handler: async (args) => {
5651
+ if (!isSimulationMode()) return { ...builtinText("Refused: not available outside simulation mode."), isError: true };
5652
+ const refusal = briefRefusal(args);
5653
+ if (refusal) return { ...builtinText(`Refused: ${refusal}`), isError: true };
5654
+ writeContextFile(args.content);
5655
+ return builtinText(
5656
+ `Agent brief replaced (${Buffer.byteLength(args.content, "utf-8")} bytes). It applies from the next agent session.`
5657
+ );
5658
+ }
5659
+ }
5660
+ ]
5661
+ };
5662
+ }
5663
+
5664
+ // src/lib/builtins/index.ts
5665
+ var BUILTIN_FACTORIES = [setupBuiltin];
5666
+ function registerBuiltins(deps, factories = BUILTIN_FACTORIES) {
5667
+ const registered = [];
5668
+ for (const factory of factories) {
5669
+ try {
5670
+ const def = factory(deps);
5671
+ deps.integrationManager.registerBuiltin(def);
5672
+ registered.push(def.id);
5673
+ } catch (err) {
5674
+ console.error(`[Suveren MCP] Built-in tool group not registered: ${err instanceof Error ? err.message : String(err)}`);
5675
+ }
5676
+ }
5677
+ return registered;
5678
+ }
5679
+
5500
5680
  // bin/http.ts
5501
5681
  import { decodeAttestationBlob as decodeAttestationBlob3 } from "@hap/core";
5502
5682
 
5503
5683
  // src/lib/committed-executor.ts
5504
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync17, unlinkSync as unlinkSync6, writeFileSync as writeFileSync11 } from "fs";
5505
- import { dirname as dirname10, join as join16 } from "path";
5684
+ import { existsSync as existsSync16, mkdirSync as mkdirSync12, readFileSync as readFileSync17, unlinkSync as unlinkSync6, writeFileSync as writeFileSync12 } from "fs";
5685
+ import { dirname as dirname11, join as join16 } from "path";
5506
5686
  var CommittedExecutor = class {
5507
5687
  constructor(run) {
5508
5688
  this.run = run;
@@ -5538,9 +5718,9 @@ var ExecutorLock = class {
5538
5718
  if (current && current.pid !== process.pid && isAlive(current.pid)) {
5539
5719
  return { held: false, holderPid: current.pid };
5540
5720
  }
5541
- mkdirSync11(dirname10(this.path), { recursive: true, mode: 448 });
5721
+ mkdirSync12(dirname11(this.path), { recursive: true, mode: 448 });
5542
5722
  const lock = { pid: process.pid, startedAt: Math.floor(Date.now() / 1e3) };
5543
- writeFileSync11(this.path, JSON.stringify(lock), { mode: 384 });
5723
+ writeFileSync12(this.path, JSON.stringify(lock), { mode: 384 });
5544
5724
  this.held = true;
5545
5725
  return { held: true };
5546
5726
  }
@@ -5585,8 +5765,8 @@ import { homedir as homedir10 } from "os";
5585
5765
  import { join as join18 } from "path";
5586
5766
 
5587
5767
  // src/lib/mcp-as-tracker.ts
5588
- import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "fs";
5589
- import { dirname as dirname11, join as join17 } from "path";
5768
+ import { existsSync as existsSync17, mkdirSync as mkdirSync13, readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "fs";
5769
+ import { dirname as dirname12, join as join17 } from "path";
5590
5770
  function trackerPath(dataDir2) {
5591
5771
  return join17(dataDir2, "mcp-as-pairing.json");
5592
5772
  }
@@ -5602,8 +5782,8 @@ function readMcpPairedAsUrl(dataDir2) {
5602
5782
  }
5603
5783
  function writeMcpPairedAsUrl(dataDir2, asUrl) {
5604
5784
  const path = trackerPath(dataDir2);
5605
- mkdirSync12(dirname11(path), { recursive: true, mode: 448 });
5606
- writeFileSync12(path, JSON.stringify({ asUrl }, null, 2), { encoding: "utf-8", mode: 384 });
5785
+ mkdirSync13(dirname12(path), { recursive: true, mode: 448 });
5786
+ writeFileSync13(path, JSON.stringify({ asUrl }, null, 2), { encoding: "utf-8", mode: 384 });
5607
5787
  }
5608
5788
 
5609
5789
  // bin/http.ts
@@ -5965,7 +6145,10 @@ app.get("/health", (_req, res) => {
5965
6145
  activeSessions: activeSessions.size,
5966
6146
  storedGates: state.gateStore.getAll().length,
5967
6147
  serviceCredentials: Array.from(serviceCredentials.keys()),
5968
- integrations: integrationManager.getStatus(integrationRegistry.getAll())
6148
+ integrations: integrationManager.getStatus(integrationRegistry.getAll()),
6149
+ // The gateway's own tool groups (built-ins) — not connectors; the mandate
6150
+ // picker offers them so a person can give a mandate for them.
6151
+ builtins: integrationManager.getBuiltins()
5969
6152
  });
5970
6153
  });
5971
6154
  app.get("/internal/gate-content", internalOnly, (req, res) => {
@@ -6092,6 +6275,7 @@ app.listen(port, "0.0.0.0", () => {
6092
6275
  console.error(`[Suveren MCP] SP server: ${spUrl}`);
6093
6276
  profilesLoaded = loadProfiles();
6094
6277
  loadManifests();
6278
+ registerBuiltins({ state, integrationManager });
6095
6279
  if (process.env.SUVEREN_DISABLE_AUTO_INTEGRATIONS !== "1" && integrationRegistry.getEnabled().length === 0) {
6096
6280
  const personalManifests = getAllManifests().filter((m) => m.personalDefault);
6097
6281
  for (const manifest of personalManifests) {