@suveren/gateway 0.23.2 → 0.24.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.
@@ -3257,7 +3257,10 @@ function capText(text, max = PREVIEW_MESSAGE_CAP) {
3257
3257
  if (text === void 0) return void 0;
3258
3258
  return text.length > max ? text.slice(0, max) : text;
3259
3259
  }
3260
- function getToolPreviewConfig(integrationId, toolName) {
3260
+ function getToolPreviewConfig(integrationManager2, integrationId, toolName) {
3261
+ if (integrationManager2.isBuiltin(integrationId)) {
3262
+ return integrationManager2.getBuiltinToolPreview(integrationId, toolName);
3263
+ }
3261
3264
  const manifest = getManifest(integrationId);
3262
3265
  const overrides = manifest?.toolGating?.overrides ?? {};
3263
3266
  const entry = overrides[toolName];
@@ -3322,7 +3325,8 @@ function hashPreviewBody(body) {
3322
3325
  return computeContentHash2({ kind: "text", version: "1" }, body.text ?? "");
3323
3326
  }
3324
3327
  async function computeSubmissionPreviewHash(integrationManager2, integrationId, toolName, actionArgs) {
3325
- const preview = getToolPreviewConfig(integrationId, toolName);
3328
+ if (integrationManager2.isBuiltin(integrationId)) return void 0;
3329
+ const preview = getToolPreviewConfig(integrationManager2, integrationId, toolName);
3326
3330
  if (!preview || preview.version) return void 0;
3327
3331
  const mapped = mapPreviewArgs(preview, actionArgs);
3328
3332
  if (previewHasNoTarget(preview, mapped)) return void 0;
@@ -3331,7 +3335,7 @@ async function computeSubmissionPreviewHash(integrationManager2, integrationId,
3331
3335
  return hashPreviewBody(read.body);
3332
3336
  }
3333
3337
  async function previewChangedSinceSubmission(integrationManager2, integrationId, toolName, actionArgs, previousHash) {
3334
- const preview = getToolPreviewConfig(integrationId, toolName);
3338
+ const preview = getToolPreviewConfig(integrationManager2, integrationId, toolName);
3335
3339
  if (!preview) return true;
3336
3340
  const mapped = mapPreviewArgs(preview, actionArgs);
3337
3341
  const read = await readPreview(integrationManager2, integrationId, preview.tool, mapped);
@@ -3339,7 +3343,7 @@ async function previewChangedSinceSubmission(integrationManager2, integrationId,
3339
3343
  return hashPreviewBody(read.body) !== previousHash;
3340
3344
  }
3341
3345
  async function buildInternalPreview(integrationManager2, integrationId, toolName, actionArgs) {
3342
- const preview = getToolPreviewConfig(integrationId, toolName);
3346
+ const preview = getToolPreviewConfig(integrationManager2, integrationId, toolName);
3343
3347
  if (!preview) return { status: "none" };
3344
3348
  const mappedArgs = mapPreviewArgs(preview, actionArgs);
3345
3349
  if (previewHasNoTarget(preview, mappedArgs)) return { status: "none", reason: "no_target" };
@@ -3815,8 +3819,8 @@ function applyMapping(m, value, execution) {
3815
3819
  execution[m.field] = typeof value === "number" ? value : String(value);
3816
3820
  }
3817
3821
  }
3818
- function profileMatches(profileId, shortName) {
3819
- return profileId === shortName || profileId.includes("/" + shortName + "@") || profileId.endsWith("/" + shortName);
3822
+ function profileMatches(profileId, shortName2) {
3823
+ return profileId === shortName2 || profileId.includes("/" + shortName2 + "@") || profileId.endsWith("/" + shortName2);
3820
3824
  }
3821
3825
  function wireExecutionContext(profileId, execution) {
3822
3826
  const profile = getProfile2(profileId);
@@ -3933,6 +3937,12 @@ function recordAsRefusal(state2, tool, authorizationId, err) {
3933
3937
  });
3934
3938
  }
3935
3939
  function createGatedToolHandler(tool, integrationManager2, state2) {
3940
+ if (tool.internal) {
3941
+ return async () => ({
3942
+ content: [{ type: "text", text: `Refused: "${tool.namespacedName}" is internal and cannot be called directly. No ticket was requested.` }],
3943
+ isError: true
3944
+ });
3945
+ }
3936
3946
  if (isSimulationMode() && !integrationManager2.isSimulationSafe(tool.integrationId)) {
3937
3947
  return async () => {
3938
3948
  recordBlocked(state2, tool, {
@@ -4697,6 +4707,7 @@ function countToolsByGating(profileId, integrationManager2) {
4697
4707
  let gated = 0;
4698
4708
  let readOnly = 0;
4699
4709
  for (const tool of allTools) {
4710
+ if (tool.internal) continue;
4700
4711
  if (!tool.gating || !tool.gating.profile) {
4701
4712
  readOnly++;
4702
4713
  } else if (tool.gating.profile === profileId || tool.gating.profile === shortProfileName(profileId)) {
@@ -4753,14 +4764,14 @@ function buildMandateBrief(opts) {
4753
4764
  for (const auth of active) {
4754
4765
  const earliestExpiry = Math.min(...auth.attestations.map((a) => a.expiresAt));
4755
4766
  const remainingMin = Math.max(0, Math.round((earliestExpiry - now) / 60));
4756
- const shortName = shortProfileName(auth.profileId);
4767
+ const shortName2 = shortProfileName(auth.profileId);
4757
4768
  const version = auth.profileId.match(/@(.+)$/)?.[1] ?? "";
4758
- const shortId = version ? `${shortName}@${version}` : shortName;
4769
+ const shortId = version ? `${shortName2}@${version}` : shortName2;
4759
4770
  const boundsDesc = Object.entries(auth.frame).filter(([key]) => key !== "profile" && key !== "path").map(([key, value]) => `${key}:${value}`).join(" \xB7 ");
4760
4771
  const parts = [];
4761
4772
  if (boundsDesc) parts.push(boundsDesc);
4762
4773
  if (executionLog) {
4763
- const profile = getProfile3(auth.profileId) ?? getProfile3(shortName);
4774
+ const profile = getProfile3(auth.profileId) ?? getProfile3(shortName2);
4764
4775
  const consumption = getConsumptionState(auth, executionLog, profile);
4765
4776
  const compact = formatConsumptionCompact(consumption);
4766
4777
  if (compact) parts.push(compact);
@@ -4769,7 +4780,7 @@ function buildMandateBrief(opts) {
4769
4780
  if (gated > 0 || readOnly > 0) parts.push(`${gated} gated tools`);
4770
4781
  parts.push(`${remainingMin} min remaining`);
4771
4782
  lines.push(`[${shortId}] ${parts.join(" \xB7 ")}`);
4772
- lines.push(` \u2192 list-authorizations(domain: "${shortName}") for intent & details`);
4783
+ lines.push(` \u2192 list-authorizations(domain: "${shortName2}") for intent & details`);
4773
4784
  lines.push("");
4774
4785
  }
4775
4786
  }
@@ -4807,7 +4818,7 @@ function shortProfileName2(profileId) {
4807
4818
  function buildCapabilityMap(profileId, integrationManager2) {
4808
4819
  if (!integrationManager2) return "";
4809
4820
  const allTools = integrationManager2.getAllTools();
4810
- const shortName = shortProfileName2(profileId);
4821
+ const shortName2 = shortProfileName2(profileId);
4811
4822
  const gated = [];
4812
4823
  const readOnly = [];
4813
4824
  const undescribed = [];
@@ -4815,7 +4826,7 @@ function buildCapabilityMap(profileId, integrationManager2) {
4815
4826
  if (!tool.gating || !tool.gating.profile) {
4816
4827
  continue;
4817
4828
  }
4818
- if (!profileMatches(tool.gating.profile, shortName) && tool.gating.profile !== profileId) {
4829
+ if (!profileMatches(tool.gating.profile, shortName2) && tool.gating.profile !== profileId) {
4819
4830
  continue;
4820
4831
  }
4821
4832
  const gating = tool.gating;
@@ -4871,8 +4882,8 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
4871
4882
  }
4872
4883
  if (domain) {
4873
4884
  const matching = authorizations.filter((auth) => {
4874
- const shortName = shortProfileName2(auth.profileId);
4875
- return shortName === domain || profileMatches(auth.profileId, domain);
4885
+ const shortName2 = shortProfileName2(auth.profileId);
4886
+ return shortName2 === domain || profileMatches(auth.profileId, domain);
4876
4887
  });
4877
4888
  if (matching.length === 0) {
4878
4889
  return {
@@ -4903,8 +4914,8 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
4903
4914
  }
4904
4915
  } catch {
4905
4916
  }
4906
- const shortName = shortProfileName2(auth.profileId);
4907
- const profile = getProfile4(auth.profileId) ?? getProfile4(shortName);
4917
+ const shortName2 = shortProfileName2(auth.profileId);
4918
+ const profile = getProfile4(auth.profileId) ?? getProfile4(shortName2);
4908
4919
  if (profile) {
4909
4920
  const consumption = getConsumptionState(auth, state2.executionLog, profile);
4910
4921
  const consumptionText = formatConsumptionFull(consumption);
@@ -4963,8 +4974,8 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
4963
4974
  if ((auth.deferredCommitmentDomains ?? []).length > 0) {
4964
4975
  lines.push(" Mode: review (each action requires your approval)");
4965
4976
  }
4966
- const shortName = shortProfileName2(auth.profileId);
4967
- const profile = getProfile4(auth.profileId) ?? getProfile4(shortName);
4977
+ const shortName2 = shortProfileName2(auth.profileId);
4978
+ const profile = getProfile4(auth.profileId) ?? getProfile4(shortName2);
4968
4979
  if (profile) {
4969
4980
  const consumption = getConsumptionState(auth, state2.executionLog, profile);
4970
4981
  const compact = formatConsumptionCompact(consumption);
@@ -5603,6 +5614,7 @@ function createMcpServer(state2, integrationManager2) {
5603
5614
  }
5604
5615
  }
5605
5616
  for (const tool of allTools) {
5617
+ if (tool.internal) continue;
5606
5618
  const existing = proxiedTools.get(tool.namespacedName);
5607
5619
  if (existing) {
5608
5620
  if (existing.tool === tool) continue;
@@ -6443,6 +6455,8 @@ var IntegrationManager = class {
6443
6455
  integrationId: def.id,
6444
6456
  description: tool.description,
6445
6457
  inputSchema: withoutBlockedArgs(tool.inputSchema, gating?.blockedArgs),
6458
+ ...tool.outputSchema ? { outputSchema: tool.outputSchema } : {},
6459
+ ...tool.internal ? { internal: true } : {},
6446
6460
  gating
6447
6461
  };
6448
6462
  });
@@ -6488,19 +6502,39 @@ var IntegrationManager = class {
6488
6502
  }
6489
6503
  /**
6490
6504
  * The built-in's own refusal for this call, checked before the gate (no proposal,
6491
- * no ticket): `simulationOnly` outside simulation mode, then the tool's `validate`.
6492
- * Null for connector tools and for calls that may proceed.
6505
+ * no ticket): an `internal` tool (defence in depth — `index.ts` already never
6506
+ * registers one with the MCP server, so an agent cannot reach this in the first
6507
+ * place), `simulationOnly` outside simulation mode (group-level OR this one
6508
+ * tool's — DL1: a group may now mix test-setup-only tools with ones that work
6509
+ * outside simulation), then the tool's `validate`. Null for connector tools and
6510
+ * for calls that may proceed.
6493
6511
  */
6494
6512
  async precheckBuiltin(tool, args) {
6495
6513
  const builtin = this.builtins.get(tool.integrationId);
6496
6514
  if (!builtin) return null;
6497
- if (builtin.def.simulationOnly && !isSimulationMode()) {
6515
+ const def = builtin.def.tools.find((t) => t.name === tool.originalName);
6516
+ if (def?.internal) {
6517
+ return `Refused: "${tool.namespacedName}" is internal and cannot be called directly. No ticket was requested.`;
6518
+ }
6519
+ if ((builtin.def.simulationOnly || def?.simulationOnly) && !isSimulationMode()) {
6498
6520
  return `Refused: "${tool.namespacedName}" is not available. No ticket was requested.`;
6499
6521
  }
6500
- const def = builtin.def.tools.find((t) => t.name === tool.originalName);
6501
6522
  const refusal = await def?.validate?.(args);
6502
6523
  return refusal ? `Refused: ${refusal} No ticket was requested.` : null;
6503
6524
  }
6525
+ /**
6526
+ * A built-in's own declared preview (AU3 — `BuiltinTool`/toolGating override's
6527
+ * `preview`, same shape a manifest declares for a connector tool). `preview.ts`'s
6528
+ * `getToolPreviewConfig` reads a connector's declared preview from its loaded
6529
+ * manifest (`getManifest`); a built-in has no manifest, so it reads it from here
6530
+ * instead — same override object, widened the same way `resolveToolGating`
6531
+ * already widens it for `approvalView`/`hideUnlessAuthorized`.
6532
+ */
6533
+ getBuiltinToolPreview(integrationId, toolName) {
6534
+ const builtin = this.builtins.get(integrationId);
6535
+ const override = builtin?.def.toolGating.overrides?.[toolName];
6536
+ return override?.preview;
6537
+ }
6504
6538
  /**
6505
6539
  * Why this tool's integration refuses the held mandate `auth`, or null when
6506
6540
  * it accepts it (always null for connectors and built-ins that declare no
@@ -6522,7 +6556,14 @@ var IntegrationManager = class {
6522
6556
  }
6523
6557
  try {
6524
6558
  const result = await tool.handler(args);
6525
- return { content: result.content.map((c) => ({ type: c.type, text: c.text })), isError: result.isError };
6559
+ return {
6560
+ content: result.content.map((c) => ({ type: c.type, text: c.text })),
6561
+ isError: result.isError,
6562
+ // DL1: a built-in's internal preview tools (describe_mandate_proposal /
6563
+ // describe_brief_proposal) return structuredContent, same as a connector's
6564
+ // declared-output-schema read — see the field's doc comment above.
6565
+ ...result.structuredContent ? { structuredContent: result.structuredContent } : {}
6566
+ };
6526
6567
  } catch (err) {
6527
6568
  const message = err instanceof Error ? err.message : String(err);
6528
6569
  return { content: [{ type: "text", text: `Tool call failed: ${message}` }], isError: true };
@@ -7117,9 +7158,9 @@ function loadProfiles(profilesDir) {
7117
7158
  try {
7118
7159
  const profile = JSON.parse(readFileSync18(profilePath, "utf-8"));
7119
7160
  registerProfile(profileId, profile);
7120
- const shortName = profileId.split("/").pop()?.replace(/@.*$/, "");
7121
- if (shortName && shortName !== profileId) {
7122
- registerProfile(shortName, profile);
7161
+ const shortName2 = profileId.split("/").pop()?.replace(/@.*$/, "");
7162
+ if (shortName2 && shortName2 !== profileId) {
7163
+ registerProfile(shortName2, profile);
7123
7164
  }
7124
7165
  loaded++;
7125
7166
  } catch (err) {
@@ -7151,6 +7192,9 @@ function warnNoProfiles(reason) {
7151
7192
  function builtinText(text) {
7152
7193
  return { content: [{ type: "text", text }] };
7153
7194
  }
7195
+ function builtinStructured(structuredContent) {
7196
+ return { content: [{ type: "text", text: JSON.stringify(structuredContent) }], structuredContent };
7197
+ }
7154
7198
 
7155
7199
  // src/lib/builtins/report.ts
7156
7200
  var MAX_REPORT_HTML_BYTES = 512 * 1024;
@@ -7579,6 +7623,45 @@ function fillGenerated(body, sections) {
7579
7623
  return body.replace(/<!--\s*generated:\s*([a-z-]+)\s*-->/g, (m, key) => sections[key] ? sections[key]() : m);
7580
7624
  }
7581
7625
 
7626
+ // src/lib/unified-diff.ts
7627
+ function unifiedLineDiff(before, after) {
7628
+ const a = before.split("\n");
7629
+ const b = after.split("\n");
7630
+ const n = a.length;
7631
+ const m = b.length;
7632
+ const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
7633
+ for (let i2 = n - 1; i2 >= 0; i2--) {
7634
+ for (let j2 = m - 1; j2 >= 0; j2--) {
7635
+ dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
7636
+ }
7637
+ }
7638
+ const lines = [];
7639
+ let i = 0;
7640
+ let j = 0;
7641
+ while (i < n && j < m) {
7642
+ if (a[i] === b[j]) {
7643
+ lines.push(a[i]);
7644
+ i++;
7645
+ j++;
7646
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) {
7647
+ lines.push(`-${a[i]}`);
7648
+ i++;
7649
+ } else {
7650
+ lines.push(`+${b[j]}`);
7651
+ j++;
7652
+ }
7653
+ }
7654
+ while (i < n) {
7655
+ lines.push(`-${a[i]}`);
7656
+ i++;
7657
+ }
7658
+ while (j < m) {
7659
+ lines.push(`+${b[j]}`);
7660
+ j++;
7661
+ }
7662
+ return lines.join("\n");
7663
+ }
7664
+
7582
7665
  // src/lib/builtins/setup.ts
7583
7666
  import { getProfile as getProfile6 } from "@hap/core";
7584
7667
  var DELEGATION_PROFILE = "delegation";
@@ -7587,7 +7670,12 @@ var DELEGATION_INTENT_HINT = [
7587
7670
  "",
7588
7671
  "Goal \u2014 Follow the setup guide: interview me about the company, the team and the work; prepare test data from real cases, renamed to .example names; propose the mandates and the working instructions (agent brief) that the AI doing the work will need.",
7589
7672
  "",
7590
- "Watch out \u2014 You only propose; nothing happens until I approve it in the Suveren Gateway. Ask, don't guess: never invent a rule, a limit or a step. One proposal at a time. Steps customers would see start with my review. Never use real names or customer data \u2014 proposals are stored readable on the Suveren server. Never give the working AI a reporting mandate during a test run. Keep the setup and the working AI in separate conversations."
7673
+ "Watch out \u2014 You only propose; nothing happens until I approve it in the Suveren Gateway. Ask, don't guess: never invent a rule, a limit or a step. One proposal at a time. Steps customers would see start with my review. Never use real names or customer data \u2014 proposals are stored readable on the Suveren server. Never give the working AI a reporting mandate during a test run. Keep the setup and the working AI in separate conversations.",
7674
+ "",
7675
+ // DL1 — outside simulation, the same mandate also covers ongoing, real-system
7676
+ // use: the Scope above (which profiles, which mode) is what actually bounds
7677
+ // the AI from here, not simulation mode. Plain words, per the brief.
7678
+ "Outside simulation \u2014 once you have connected real systems: the AI proposes mandates and its own working instructions (agent brief) for the profiles you allow above; you approve each one before it takes effect. Delegation itself can never be proposed."
7591
7679
  ].join("\n");
7592
7680
  function briefRefusal(args) {
7593
7681
  if (typeof args.content !== "string" || args.content.trim() === "") return "`content` must be the complete new brief (markdown text).";
@@ -7611,6 +7699,149 @@ function connectedSystems(im) {
7611
7699
  }
7612
7700
  return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
7613
7701
  }
7702
+ function shortName(idOrName) {
7703
+ const stripped = idOrName.replace(/@.*$/, "").split("/").pop() ?? idOrName;
7704
+ return stripped.toLowerCase();
7705
+ }
7706
+ function versionOf(id) {
7707
+ return id.split("@")[1] ?? "";
7708
+ }
7709
+ function delegationItselfRefusal(args) {
7710
+ const requested = typeof args.profile === "string" ? args.profile : "";
7711
+ if (shortName(requested) === DELEGATION_PROFILE) {
7712
+ return "Delegation can never be proposed \u2014 the AI cannot widen its own delegation.";
7713
+ }
7714
+ return void 0;
7715
+ }
7716
+ function delegationMandateRefusal(auth) {
7717
+ if (isSimulationMode()) return void 0;
7718
+ const profile = getProfile6(auth.profileId);
7719
+ if (profile?.scopeSchema?.fields?.allowed_profiles) return void 0;
7720
+ return `your Delegation mandate (${shortName(auth.profileId)} ${versionOf(auth.profileId)}) does not declare which profiles or modes you may propose \u2014 only a Delegation mandate on delegation 0.4 or later works outside simulation mode. Ask the person to give a current Delegation mandate.`;
7721
+ }
7722
+ function formatUnit(unit) {
7723
+ if (!unit || unit === "count") return "";
7724
+ if (unit === "minutes") return "min";
7725
+ if (unit === "hours") return "hr";
7726
+ if (unit === "days") return "days";
7727
+ if (unit === "percent") return "%";
7728
+ if (unit.startsWith("currency:")) return unit.slice("currency:".length);
7729
+ return unit;
7730
+ }
7731
+ function formatBoundValue2(value, unit) {
7732
+ if (typeof value !== "number" && typeof value !== "string") return String(value);
7733
+ const u = formatUnit(unit);
7734
+ if (!u) return String(value);
7735
+ if (unit === "percent") return `${value}${u}`;
7736
+ if (unit?.startsWith("currency:")) return `${u} ${value}`;
7737
+ return `${value} ${u}`;
7738
+ }
7739
+ function labeledRecord(values, fields) {
7740
+ const out = {};
7741
+ if (!values || typeof values !== "object" || Array.isArray(values)) return out;
7742
+ for (const [key, value] of Object.entries(values)) {
7743
+ const def = fields?.[key];
7744
+ out[def?.displayName ?? key] = formatBoundValue2(value, def?.unit);
7745
+ }
7746
+ return out;
7747
+ }
7748
+ function formatValidity(args, profile) {
7749
+ const hours = typeof args.duration_hours === "number" && args.duration_hours > 0 ? args.duration_hours : void 0;
7750
+ const totalSeconds = hours !== void 0 ? Math.round(hours * 3600) : profile.ttl?.default ?? 0;
7751
+ if (totalSeconds <= 0) return "unknown";
7752
+ if (totalSeconds % 86400 === 0) {
7753
+ const days = totalSeconds / 86400;
7754
+ return `${days} day${days === 1 ? "" : "s"}`;
7755
+ }
7756
+ if (totalSeconds % 3600 === 0) {
7757
+ const hrs = totalSeconds / 3600;
7758
+ return `${hrs} hour${hrs === 1 ? "" : "s"}`;
7759
+ }
7760
+ return `${Math.round(totalSeconds / 3600)} hours`;
7761
+ }
7762
+ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
7763
+ const governing = deps.state.getEnrichedAuthorizations().filter((a) => a.complete && shortName(a.profileId) === DELEGATION_PROFILE);
7764
+ const listed = (ctx, field) => typeof ctx?.[field] === "string" ? ctx[field].split(",").map((s) => s.trim().toLowerCase()).filter(Boolean) : [];
7765
+ const profileOk = governing.some((g) => listed(g.context, "allowed_profiles").includes(targetShortName.toLowerCase()));
7766
+ const mode = typeof args.mode === "string" ? args.mode : "";
7767
+ const modeOk = mode !== "" && governing.some((g) => listed(g.context, "allowed_modes").includes(mode.toLowerCase()));
7768
+ const checks = [
7769
+ profileOk ? `${targetDisplayName} is allowed by your Delegation mandate` : `${targetDisplayName} is not confirmed as allowed by an active Delegation mandate`
7770
+ ];
7771
+ checks.push(
7772
+ mode === "" ? "No mode was given" : modeOk ? `${mode} \u2014 within the modes your Delegation mandate allows` : `${mode} is not among the modes your Delegation mandate allows`
7773
+ );
7774
+ const first = governing[0];
7775
+ if (first) {
7776
+ const delegationProfile = getProfile6(first.profileId);
7777
+ const entry = getConsumptionState(first, deps.state.executionLog, delegationProfile).find((e) => e.field === "mandate_daily_max");
7778
+ if (entry && entry.limit !== null) {
7779
+ checks.push(`Proposals today: ${entry.current} of ${entry.limit}`);
7780
+ }
7781
+ }
7782
+ return checks;
7783
+ }
7784
+ function resolveTargetProfile(input) {
7785
+ if (typeof input !== "string" || !input.trim()) return void 0;
7786
+ const profile = getProfile6(input.trim());
7787
+ if (!profile) return void 0;
7788
+ return { profile, shortName: shortName(profile.id) };
7789
+ }
7790
+ var CREATE_MANDATE_INPUT_SCHEMA = {
7791
+ type: "object",
7792
+ properties: {
7793
+ team: { type: "string", description: 'Team name or id, or "personal". Omit to use the workspace of your Delegation mandate.' },
7794
+ // Short name only (e.g. "sales") — a full profile id is refused here, at input
7795
+ // validation, before the tool ever runs (DL1 Interface contract).
7796
+ profile: { type: "string", pattern: "^[a-z][a-z0-9-]*$", description: 'Profile short name, e.g. "sales" (newest version).' },
7797
+ limits: { type: "object", description: "The limits (bounds) by field name, as the profile defines them." },
7798
+ scope: { type: "object", description: "The scope (context) by field name, as the profile defines it." },
7799
+ intent: { type: "string", description: "Why, goal and watch-outs \u2014 signed with the mandate (at most 2000 characters)." },
7800
+ mode: { type: "string", enum: ["review", "automatic"], description: "review = each action needs approval; automatic = within the limits." },
7801
+ duration_hours: { type: "number", description: "How long the mandate is valid, in hours. Omit for the profile default." },
7802
+ title: { type: "string", description: "A short name for the mandate." },
7803
+ ticket_id: {
7804
+ type: "string",
7805
+ description: "Authorization reference for this call, set by the governing gateway \u2014 agents do not set this."
7806
+ }
7807
+ },
7808
+ required: ["profile", "limits", "intent", "mode"]
7809
+ };
7810
+ var SET_AGENT_BRIEF_INPUT_SCHEMA = {
7811
+ type: "object",
7812
+ properties: {
7813
+ content: { type: "string", description: "The complete new agent brief, in markdown (at most 16 KB)." },
7814
+ ticket_id: {
7815
+ type: "string",
7816
+ description: "Authorization reference for this call, set by the governing gateway \u2014 agents do not set this."
7817
+ }
7818
+ },
7819
+ required: ["content"]
7820
+ };
7821
+ var DESCRIBE_MANDATE_PROPOSAL_OUTPUT_SCHEMA = {
7822
+ type: "object",
7823
+ properties: {
7824
+ proposal: { type: "string", title: "Proposal" },
7825
+ profile_version: { type: "string", title: "Version" },
7826
+ workspace: { type: "string", title: "Workspace" },
7827
+ mode: { type: "string", title: "Mode" },
7828
+ valid: { type: "string", title: "Valid" },
7829
+ limits: { type: "object", title: "Limits" },
7830
+ scope: { type: "object", title: "Scope" },
7831
+ title: { type: ["string", "null"], title: "Title" },
7832
+ intent: { type: "string", title: "Intent", description: "Written by the AI \u2014 becomes your words when you sign" },
7833
+ checks: { type: "array", title: "Checks", items: { type: "string" } }
7834
+ },
7835
+ required: ["proposal", "profile_version", "workspace", "mode", "valid", "limits", "scope", "title", "intent", "checks"]
7836
+ };
7837
+ var DESCRIBE_BRIEF_PROPOSAL_OUTPUT_SCHEMA = {
7838
+ type: "object",
7839
+ properties: {
7840
+ change: { type: "string", title: "Change", contentMediaType: "text/x-diff" },
7841
+ words: { type: "number", title: "Words" }
7842
+ },
7843
+ required: ["change", "words"]
7844
+ };
7614
7845
  function setupBuiltin(deps) {
7615
7846
  return {
7616
7847
  id: "setup",
@@ -7619,18 +7850,24 @@ function setupBuiltin(deps) {
7619
7850
  // name they'll see everywhere else (Mandates page, dashboard first-run
7620
7851
  // card), not the internal "test setup" framing. Description is unchanged.
7621
7852
  name: "Delegation",
7622
- description: "Your AI sets up a test of itself: it reads the setup guides and proposes its agent brief and its mandates. Every proposal waits for your approval. Simulation mode only.",
7853
+ description: "Your AI proposes mandates and its agent brief \u2014 in a test (simulation mode) and, within the profiles and modes you allow, on your real systems too. Every proposal waits for your approval.",
7623
7854
  intentHint: DELEGATION_INTENT_HINT,
7624
7855
  profile: DELEGATION_PROFILE,
7625
7856
  simulation: true,
7626
- simulationOnly: true,
7857
+ // DL1: no longer simulationOnly at the GROUP level — create_mandate and
7858
+ // set_agent_brief now work outside simulation too (decision 1); only
7859
+ // get_guide keeps the per-tool flag below (decision 2).
7860
+ mandateRefusal: delegationMandateRefusal,
7627
7861
  toolGating: {
7628
7862
  overrides: {
7629
7863
  set_agent_brief: {
7630
7864
  executionMapping: {},
7631
7865
  staticExecution: { action_type: "brief" },
7632
7866
  hideUnlessAuthorized: true,
7633
- approvalView: { content: { label: "New agent brief", kind: "markdown" } }
7867
+ approvalView: { content: { label: "New agent brief", kind: "markdown" } },
7868
+ // AU3 — the approval card's preview of this pending proposal (DL1
7869
+ // Interface contract). Identity arg mapping: same field name either side.
7870
+ preview: { tool: "describe_brief_proposal", args: { content: "content" } }
7634
7871
  },
7635
7872
  get_guide: {
7636
7873
  category: "read",
@@ -7638,7 +7875,7 @@ function setupBuiltin(deps) {
7638
7875
  requiredValue: "unlimited"
7639
7876
  },
7640
7877
  create_mandate: {
7641
- executionMapping: {},
7878
+ executionMapping: { profile: "allowed_profiles", mode: "allowed_modes" },
7642
7879
  staticExecution: { action_type: "mandate" },
7643
7880
  hideUnlessAuthorized: true,
7644
7881
  // Shown like the mandate screen: limits and scope by the profile's own names.
@@ -7651,13 +7888,23 @@ function setupBuiltin(deps) {
7651
7888
  mode: { label: "Mode" },
7652
7889
  duration_hours: { label: "Valid for (hours)" },
7653
7890
  title: { label: "Title" }
7654
- }
7655
- }
7891
+ },
7892
+ preview: { tool: "describe_mandate_proposal", args: { team: "team", profile: "profile", limits: "limits", scope: "scope", intent: "intent", mode: "mode", duration_hours: "duration_hours", title: "title" } }
7893
+ },
7894
+ // Internal preview tools (DL1): never listed, never callable by an
7895
+ // agent (BuiltinTool.internal — enforced in index.ts and
7896
+ // tool-proxy.ts); explicitly disabled here too, so a direct call
7897
+ // through the normal gate (defence in depth) refuses cleanly instead
7898
+ // of falling through to the "not described in manifest" default.
7899
+ describe_mandate_proposal: { category: "disabled" },
7900
+ describe_brief_proposal: { category: "disabled" }
7656
7901
  }
7657
7902
  },
7658
7903
  tools: [
7659
7904
  {
7660
7905
  name: "get_guide",
7906
+ // DL1 decision 2: stays simulation-only, per-tool (the group no longer is).
7907
+ simulationOnly: true,
7661
7908
  description: "Start here when the person asks how to begin with Suveren or how to set up their work with it. Simulation mode only: the setup guides \u2014 how to interview the person, build the test data, propose mandates and the agent brief, set up the working AI's process, and what can make a test misleading. Call without `topic` for the list and the order to follow; then read each topic before doing that step.",
7662
7909
  inputSchema: {
7663
7910
  type: "object",
@@ -7701,21 +7948,10 @@ ${body}`);
7701
7948
  },
7702
7949
  {
7703
7950
  name: "set_agent_brief",
7704
- 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.",
7705
- inputSchema: {
7706
- type: "object",
7707
- properties: {
7708
- content: { type: "string", description: "The complete new agent brief, in markdown (at most 16 KB)." },
7709
- ticket_id: {
7710
- type: "string",
7711
- description: "Authorization reference for this call, set by the governing gateway \u2014 agents do not set this."
7712
- }
7713
- },
7714
- required: ["content"]
7715
- },
7951
+ description: "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.",
7952
+ inputSchema: SET_AGENT_BRIEF_INPUT_SCHEMA,
7716
7953
  validate: briefRefusal,
7717
7954
  handler: async (args) => {
7718
- if (!isSimulationMode()) return { ...builtinText("Refused: not available outside simulation mode."), isError: true };
7719
7955
  const refusal = briefRefusal(args);
7720
7956
  if (refusal) return { ...builtinText(`Refused: ${refusal}`), isError: true };
7721
7957
  writeContextFile(args.content);
@@ -7726,37 +7962,86 @@ ${body}`);
7726
7962
  },
7727
7963
  {
7728
7964
  name: "create_mandate",
7729
- description: "Simulation mode only: propose a new mandate for the person running this gateway \u2014 the authority another tool needs (e.g. sales quotes up to a value). A person approves or rejects the proposal; only after approval is the mandate created, exactly as if they had signed it on the mandate screen. Checked against the profile before the proposal: unknown limits, a mode the profile does not allow, or a team where the person may not give this mandate are refused at once. Create only.",
7730
- inputSchema: {
7731
- type: "object",
7732
- properties: {
7733
- team: { type: "string", description: 'Team name or id, or "personal". Omit to use the workspace of your Delegation mandate.' },
7734
- profile: { type: "string", description: 'Profile, e.g. "sales" (newest version) or a full profile id.' },
7735
- limits: { type: "object", description: "The limits (bounds) by field name, as the profile defines them." },
7736
- scope: { type: "object", description: "The scope (context) by field name, as the profile defines it." },
7737
- intent: { type: "string", description: "Why, goal and watch-outs \u2014 signed with the mandate (at most 2000 characters)." },
7738
- mode: { type: "string", enum: ["review", "automatic"], description: "review = each action needs approval; automatic = within the limits." },
7739
- duration_hours: { type: "number", description: "How long the mandate is valid, in hours. Omit for the profile default." },
7740
- title: { type: "string", description: "A short name for the mandate." },
7741
- ticket_id: {
7742
- type: "string",
7743
- description: "Authorization reference for this call, set by the governing gateway \u2014 agents do not set this."
7744
- }
7745
- },
7746
- required: ["profile", "limits", "intent", "mode"]
7747
- },
7965
+ description: "Propose a new mandate for the person running this gateway \u2014 the authority another tool needs (e.g. sales quotes up to a value). A person approves or rejects the proposal; only after approval is the mandate created, exactly as if they had signed it on the mandate screen. Checked against the profile before the proposal: unknown limits, a mode the profile does not allow, or a team where the person may not give this mandate are refused at once. Outside simulation mode, the profile and mode you may propose are themselves limited by your Delegation mandate's scope \u2014 Delegation itself can never be proposed. Create only.",
7966
+ inputSchema: CREATE_MANDATE_INPUT_SCHEMA,
7748
7967
  validate: async (args) => {
7968
+ const fixed = delegationItselfRefusal(args);
7969
+ if (fixed) {
7970
+ deps.state.denialLog?.record({
7971
+ ts: Date.now(),
7972
+ tool: "create_mandate",
7973
+ integrationId: "setup",
7974
+ profile: DELEGATION_PROFILE,
7975
+ kind: "bound",
7976
+ detail: fixed,
7977
+ who: "gateway",
7978
+ code: "DELEGATION_NOT_PROPOSABLE"
7979
+ });
7980
+ return fixed;
7981
+ }
7749
7982
  const check = await controlPlaneMandate(true, mandateRequest(args));
7750
7983
  return check.ok ? void 0 : check.message;
7751
7984
  },
7752
7985
  handler: async (args) => {
7753
- if (!isSimulationMode()) return { ...builtinText("Refused: not available outside simulation mode."), isError: true };
7986
+ const fixed = delegationItselfRefusal(args);
7987
+ if (fixed) return { ...builtinText(`Refused: ${fixed}`), isError: true };
7754
7988
  const r = await controlPlaneMandate(false, mandateRequest(args));
7755
7989
  if (!r.ok) return { ...builtinText(`Refused: ${r.message}`), isError: true };
7756
7990
  return builtinText(
7757
7991
  `Mandate created: ${r.authorizationId} \u2014 profile ${r.profileId}, ${r.groupName}, mode ${r.mode}, valid for ${Math.round((r.ttlSeconds ?? 0) / 3600)} h. It is active now.`
7758
7992
  );
7759
7993
  }
7994
+ },
7995
+ {
7996
+ // AU3 internal preview (DL1 Interface contract) — never listed, never
7997
+ // callable by an agent. Builds the "What you would sign" card for a
7998
+ // pending create_mandate proposal from the SAME args, local data only.
7999
+ name: "describe_mandate_proposal",
8000
+ internal: true,
8001
+ description: "Internal \u2014 the approval card's preview of a pending create_mandate proposal. Not for agent use.",
8002
+ inputSchema: CREATE_MANDATE_INPUT_SCHEMA,
8003
+ outputSchema: DESCRIBE_MANDATE_PROPOSAL_OUTPUT_SCHEMA,
8004
+ handler: async (args) => {
8005
+ const resolved = resolveTargetProfile(args.profile);
8006
+ if (!resolved) {
8007
+ return { ...builtinText(`Refused: unknown profile "${String(args.profile)}".`), isError: true };
8008
+ }
8009
+ const { profile, shortName: targetShortName } = resolved;
8010
+ const displayName = profile.name ?? targetShortName;
8011
+ const rawTeam = typeof args.team === "string" ? args.team.trim() : "";
8012
+ const workspace = rawTeam && rawTeam.toLowerCase() !== "personal" ? rawTeam : "Personal";
8013
+ const mode = typeof args.mode === "string" ? args.mode : "";
8014
+ const structured = {
8015
+ proposal: `New mandate \xB7 ${displayName}`,
8016
+ profile_version: `${targetShortName} ${profile.version}`,
8017
+ workspace,
8018
+ mode,
8019
+ valid: formatValidity(args, profile),
8020
+ limits: labeledRecord(args.limits, profile.boundsSchema?.fields),
8021
+ scope: labeledRecord(args.scope, profile.scopeSchema?.fields),
8022
+ title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : null,
8023
+ intent: typeof args.intent === "string" ? args.intent : "",
8024
+ checks: delegationChecks(deps, args, targetShortName, displayName)
8025
+ };
8026
+ return builtinStructured(structured);
8027
+ }
8028
+ },
8029
+ {
8030
+ // AU3 internal preview (DL1 Interface contract) — never listed, never
8031
+ // callable by an agent. Builds the "New agent brief" diff card.
8032
+ name: "describe_brief_proposal",
8033
+ internal: true,
8034
+ description: "Internal \u2014 the approval card's preview of a pending set_agent_brief proposal. Not for agent use.",
8035
+ inputSchema: SET_AGENT_BRIEF_INPUT_SCHEMA,
8036
+ outputSchema: DESCRIBE_BRIEF_PROPOSAL_OUTPUT_SCHEMA,
8037
+ handler: async (args) => {
8038
+ const proposed = typeof args.content === "string" ? args.content : "";
8039
+ const current = readContextFile() ?? "";
8040
+ const change = unifiedLineDiff(current, proposed);
8041
+ const trimmed = proposed.trim();
8042
+ const words = trimmed === "" ? 0 : trimmed.split(/\s+/).length;
8043
+ return builtinStructured({ change, words });
8044
+ }
7760
8045
  }
7761
8046
  ]
7762
8047
  };