@suveren/gateway 0.15.0 → 0.16.0

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,82 @@ 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
+ /** Whether `id` names a built-in (in-process) integration. */
5242
+ isBuiltin(id) {
5243
+ return this.builtins.has(id);
5244
+ }
5245
+ /**
5246
+ * Whether `id` may run while simulation mode is on: a connector whose manifest
5247
+ * carries the `simulation` marker, or a built-in that declares `simulation: true`.
5248
+ */
5249
+ isSimulationSafe(id) {
5250
+ const builtin = this.builtins.get(id);
5251
+ if (builtin) return builtin.def.simulation === true;
5252
+ return manifestIsSimulated(getManifest(id));
5253
+ }
5254
+ /**
5255
+ * The built-in's own refusal for this call, checked before the gate (no proposal,
5256
+ * no ticket): `simulationOnly` outside simulation mode, then the tool's `validate`.
5257
+ * Null for connector tools and for calls that may proceed.
5258
+ */
5259
+ precheckBuiltin(tool, args) {
5260
+ const builtin = this.builtins.get(tool.integrationId);
5261
+ if (!builtin) return null;
5262
+ if (builtin.def.simulationOnly && !isSimulationMode()) {
5263
+ return `Refused: "${tool.namespacedName}" is not available. No ticket was requested.`;
5264
+ }
5265
+ const def = builtin.def.tools.find((t) => t.name === tool.originalName);
5266
+ const refusal = def?.validate?.(args);
5267
+ return refusal ? `Refused: ${refusal} No ticket was requested.` : null;
5268
+ }
5269
+ /**
5270
+ * Proxy a tool call to a downstream MCP server (or a built-in's handler).
5194
5271
  */
5195
5272
  async callTool(integrationId, toolName, args) {
5273
+ const builtin = this.builtins.get(integrationId);
5274
+ if (builtin) {
5275
+ const tool = builtin.def.tools.find((t) => t.name === toolName);
5276
+ if (!tool) {
5277
+ return { content: [{ type: "text", text: `Tool "${integrationId}__${toolName}" does not exist.` }], isError: true };
5278
+ }
5279
+ try {
5280
+ const result = await tool.handler(args);
5281
+ return { content: result.content.map((c) => ({ type: c.type, text: c.text })), isError: result.isError };
5282
+ } catch (err) {
5283
+ const message = err instanceof Error ? err.message : String(err);
5284
+ return { content: [{ type: "text", text: `Tool call failed: ${message}` }], isError: true };
5285
+ }
5286
+ }
5196
5287
  const entry = this.running.get(integrationId);
5197
5288
  if (!entry) {
5198
5289
  return {
@@ -5223,6 +5314,9 @@ var IntegrationManager = class {
5223
5314
  for (const entry of this.running.values()) {
5224
5315
  tools.push(...entry.tools);
5225
5316
  }
5317
+ for (const entry of this.builtins.values()) {
5318
+ tools.push(...entry.tools);
5319
+ }
5226
5320
  return tools;
5227
5321
  }
5228
5322
  /**
@@ -5497,12 +5591,87 @@ function warnNoProfiles(reason) {
5497
5591
  );
5498
5592
  }
5499
5593
 
5594
+ // src/lib/builtin-integration.ts
5595
+ function builtinText(text) {
5596
+ return { content: [{ type: "text", text }] };
5597
+ }
5598
+
5599
+ // src/lib/builtins/setup.ts
5600
+ var DELEGATION_PROFILE = "github.com/humanagencyprotocol/hap-profiles/delegation@0.1";
5601
+ function briefRefusal(args) {
5602
+ if (typeof args.content !== "string" || args.content.trim() === "") return "`content` must be the complete new brief (markdown text).";
5603
+ const bytes = Buffer.byteLength(args.content, "utf-8");
5604
+ if (bytes > CONTEXT_MAX_BYTES) return `the brief is ${bytes} bytes; the limit is ${CONTEXT_MAX_BYTES}. Shorten it.`;
5605
+ return void 0;
5606
+ }
5607
+ function setupBuiltin(_deps) {
5608
+ return {
5609
+ id: "setup",
5610
+ name: "Test setup",
5611
+ profile: DELEGATION_PROFILE,
5612
+ simulation: true,
5613
+ simulationOnly: true,
5614
+ toolGating: {
5615
+ overrides: {
5616
+ set_agent_brief: {
5617
+ executionMapping: {},
5618
+ staticExecution: { action_type: "brief" },
5619
+ hideUnlessAuthorized: true
5620
+ }
5621
+ }
5622
+ },
5623
+ tools: [
5624
+ {
5625
+ name: "set_agent_brief",
5626
+ 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.",
5627
+ inputSchema: {
5628
+ type: "object",
5629
+ properties: {
5630
+ content: { type: "string", description: "The complete new agent brief, in markdown (at most 16 KB)." },
5631
+ receipt_id: {
5632
+ type: "string",
5633
+ description: "Authorization reference for this call, set by the governing gateway \u2014 agents do not set this."
5634
+ }
5635
+ },
5636
+ required: ["content"]
5637
+ },
5638
+ validate: briefRefusal,
5639
+ handler: async (args) => {
5640
+ if (!isSimulationMode()) return { ...builtinText("Refused: not available outside simulation mode."), isError: true };
5641
+ const refusal = briefRefusal(args);
5642
+ if (refusal) return { ...builtinText(`Refused: ${refusal}`), isError: true };
5643
+ writeContextFile(args.content);
5644
+ return builtinText(
5645
+ `Agent brief replaced (${Buffer.byteLength(args.content, "utf-8")} bytes). It applies from the next agent session.`
5646
+ );
5647
+ }
5648
+ }
5649
+ ]
5650
+ };
5651
+ }
5652
+
5653
+ // src/lib/builtins/index.ts
5654
+ var BUILTIN_FACTORIES = [setupBuiltin];
5655
+ function registerBuiltins(deps, factories = BUILTIN_FACTORIES) {
5656
+ const registered = [];
5657
+ for (const factory of factories) {
5658
+ try {
5659
+ const def = factory(deps);
5660
+ deps.integrationManager.registerBuiltin(def);
5661
+ registered.push(def.id);
5662
+ } catch (err) {
5663
+ console.error(`[Suveren MCP] Built-in tool group not registered: ${err instanceof Error ? err.message : String(err)}`);
5664
+ }
5665
+ }
5666
+ return registered;
5667
+ }
5668
+
5500
5669
  // bin/http.ts
5501
5670
  import { decodeAttestationBlob as decodeAttestationBlob3 } from "@hap/core";
5502
5671
 
5503
5672
  // 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";
5673
+ import { existsSync as existsSync16, mkdirSync as mkdirSync12, readFileSync as readFileSync17, unlinkSync as unlinkSync6, writeFileSync as writeFileSync12 } from "fs";
5674
+ import { dirname as dirname11, join as join16 } from "path";
5506
5675
  var CommittedExecutor = class {
5507
5676
  constructor(run) {
5508
5677
  this.run = run;
@@ -5538,9 +5707,9 @@ var ExecutorLock = class {
5538
5707
  if (current && current.pid !== process.pid && isAlive(current.pid)) {
5539
5708
  return { held: false, holderPid: current.pid };
5540
5709
  }
5541
- mkdirSync11(dirname10(this.path), { recursive: true, mode: 448 });
5710
+ mkdirSync12(dirname11(this.path), { recursive: true, mode: 448 });
5542
5711
  const lock = { pid: process.pid, startedAt: Math.floor(Date.now() / 1e3) };
5543
- writeFileSync11(this.path, JSON.stringify(lock), { mode: 384 });
5712
+ writeFileSync12(this.path, JSON.stringify(lock), { mode: 384 });
5544
5713
  this.held = true;
5545
5714
  return { held: true };
5546
5715
  }
@@ -5585,8 +5754,8 @@ import { homedir as homedir10 } from "os";
5585
5754
  import { join as join18 } from "path";
5586
5755
 
5587
5756
  // 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";
5757
+ import { existsSync as existsSync17, mkdirSync as mkdirSync13, readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "fs";
5758
+ import { dirname as dirname12, join as join17 } from "path";
5590
5759
  function trackerPath(dataDir2) {
5591
5760
  return join17(dataDir2, "mcp-as-pairing.json");
5592
5761
  }
@@ -5602,8 +5771,8 @@ function readMcpPairedAsUrl(dataDir2) {
5602
5771
  }
5603
5772
  function writeMcpPairedAsUrl(dataDir2, asUrl) {
5604
5773
  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 });
5774
+ mkdirSync13(dirname12(path), { recursive: true, mode: 448 });
5775
+ writeFileSync13(path, JSON.stringify({ asUrl }, null, 2), { encoding: "utf-8", mode: 384 });
5607
5776
  }
5608
5777
 
5609
5778
  // bin/http.ts
@@ -6092,6 +6261,7 @@ app.listen(port, "0.0.0.0", () => {
6092
6261
  console.error(`[Suveren MCP] SP server: ${spUrl}`);
6093
6262
  profilesLoaded = loadProfiles();
6094
6263
  loadManifests();
6264
+ registerBuiltins({ state, integrationManager });
6095
6265
  if (process.env.SUVEREN_DISABLE_AUTO_INTEGRATIONS !== "1" && integrationRegistry.getEnabled().length === 0) {
6096
6266
  const personalManifests = getAllManifests().filter((m) => m.personalDefault);
6097
6267
  for (const manifest of personalManifests) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suveren/gateway",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Suveren gateway — local agent gateway built in compliance with the Human Agency Protocol (HAP). Runs the UI, control plane, and MCP server in one Node process.",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -0,0 +1,117 @@
1
+ {
2
+ "id": "github.com/humanagencyprotocol/hap-profiles/delegation@0.1",
3
+ "name": "Delegation",
4
+ "version": "0.1",
5
+ "description": "Delegation authority — lets an agent propose changes to its own setup: the agent brief and new mandates. Every proposal waits for a person's approval; nothing takes effect before it. Also lets the agent read the setup guides.",
6
+ "commitment_modes": [
7
+ "review"
8
+ ],
9
+ "boundsSchema": {
10
+ "actionTypes": [
11
+ "brief",
12
+ "mandate"
13
+ ],
14
+ "keyOrder": [
15
+ "profile",
16
+ "read_access",
17
+ "brief_daily_max",
18
+ "mandate_daily_max"
19
+ ],
20
+ "fields": {
21
+ "profile": {
22
+ "type": "string",
23
+ "required": true
24
+ },
25
+ "read_access": {
26
+ "type": "string",
27
+ "required": true,
28
+ "displayName": "Read setup guides",
29
+ "description": "Allow the agent to read the guides for setting up a test: interview, test data, mandates, agent brief, the agent's own process, risks.",
30
+ "boundType": {
31
+ "kind": "enum",
32
+ "values": [
33
+ "unlimited",
34
+ "none"
35
+ ]
36
+ },
37
+ "toolsDescription": "setup guides",
38
+ "default": "unlimited"
39
+ },
40
+ "brief_daily_max": {
41
+ "type": "number",
42
+ "required": true,
43
+ "displayName": "Brief proposals per day",
44
+ "description": "How many times per day the agent may propose a new agent brief. Each proposal waits for your approval.",
45
+ "unit": "count",
46
+ "boundType": {
47
+ "kind": "cumulative_count",
48
+ "window": "daily"
49
+ },
50
+ "appliesTo": [
51
+ "brief"
52
+ ],
53
+ "default": 0
54
+ },
55
+ "mandate_daily_max": {
56
+ "type": "number",
57
+ "required": true,
58
+ "displayName": "Mandate proposals per day",
59
+ "description": "How many new mandates per day the agent may propose. Each one waits for your approval and is created only then.",
60
+ "unit": "count",
61
+ "boundType": {
62
+ "kind": "cumulative_count",
63
+ "window": "daily"
64
+ },
65
+ "appliesTo": [
66
+ "mandate"
67
+ ],
68
+ "default": 0
69
+ }
70
+ }
71
+ },
72
+ "executionContextSchema": {
73
+ "fields": {
74
+ "brief_count_daily": {
75
+ "source": "cumulative",
76
+ "cumulativeField": "_count",
77
+ "window": "daily",
78
+ "description": "Running daily count of brief proposals",
79
+ "required": true,
80
+ "constraint": {
81
+ "type": "number",
82
+ "enforceable": [
83
+ "max"
84
+ ]
85
+ }
86
+ },
87
+ "mandate_count_daily": {
88
+ "source": "cumulative",
89
+ "cumulativeField": "_count",
90
+ "window": "daily",
91
+ "description": "Running daily count of mandate proposals",
92
+ "required": true,
93
+ "constraint": {
94
+ "type": "number",
95
+ "enforceable": [
96
+ "max"
97
+ ]
98
+ }
99
+ }
100
+ }
101
+ },
102
+ "requiredGates": [
103
+ "bounds",
104
+ "intent",
105
+ "commitment",
106
+ "decision_owner"
107
+ ],
108
+ "ttl": {
109
+ "default": 86400,
110
+ "max": 2592000
111
+ },
112
+ "retention_minimum": 7776000,
113
+ "content_binding": {
114
+ "version": "1",
115
+ "kind": "jcs"
116
+ }
117
+ }
@@ -29,6 +29,7 @@
29
29
  "github.com/humanagencyprotocol/hap-profiles/email@0.7": "email/0.7.profile.json",
30
30
  "github.com/humanagencyprotocol/hap-profiles/sales@0.3": "sales/0.3.profile.json",
31
31
  "github.com/humanagencyprotocol/hap-profiles/purchase@0.6": "purchase/0.6.profile.json",
32
- "github.com/humanagencyprotocol/hap-profiles/reporting@0.1": "reporting/0.1.profile.json"
32
+ "github.com/humanagencyprotocol/hap-profiles/reporting@0.1": "reporting/0.1.profile.json",
33
+ "github.com/humanagencyprotocol/hap-profiles/delegation@0.1": "delegation/0.1.profile.json"
33
34
  }
34
35
  }