@suveren/gateway 0.23.2 → 0.25.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.
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
3819
|
-
return profileId ===
|
|
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);
|
|
@@ -3854,6 +3858,11 @@ function zeroCappedBound(profileId, bounds, actionType) {
|
|
|
3854
3858
|
}
|
|
3855
3859
|
return null;
|
|
3856
3860
|
}
|
|
3861
|
+
function toolAlwaysListed(tool, isBuiltin) {
|
|
3862
|
+
if (tool.internal || !tool.gating?.profile) return false;
|
|
3863
|
+
if (tool.gating.hideUnlessAuthorized || tool.gating.category === "disabled") return false;
|
|
3864
|
+
return !isBuiltin(tool.integrationId);
|
|
3865
|
+
}
|
|
3857
3866
|
function toolIsAuthorizedForDisplay(tool, matchingAuths) {
|
|
3858
3867
|
if (!tool.gating?.hideUnlessAuthorized) return true;
|
|
3859
3868
|
if (matchingAuths.length === 0) return false;
|
|
@@ -3933,6 +3942,12 @@ function recordAsRefusal(state2, tool, authorizationId, err) {
|
|
|
3933
3942
|
});
|
|
3934
3943
|
}
|
|
3935
3944
|
function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
3945
|
+
if (tool.internal) {
|
|
3946
|
+
return async () => ({
|
|
3947
|
+
content: [{ type: "text", text: `Refused: "${tool.namespacedName}" is internal and cannot be called directly. No ticket was requested.` }],
|
|
3948
|
+
isError: true
|
|
3949
|
+
});
|
|
3950
|
+
}
|
|
3936
3951
|
if (isSimulationMode() && !integrationManager2.isSimulationSafe(tool.integrationId)) {
|
|
3937
3952
|
return async () => {
|
|
3938
3953
|
recordBlocked(state2, tool, {
|
|
@@ -4023,7 +4038,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4023
4038
|
return {
|
|
4024
4039
|
content: [{
|
|
4025
4040
|
type: "text",
|
|
4026
|
-
text: refusals.length > 0 ? `Refused: ${reasons.charAt(0).toUpperCase() + reasons.slice(1)} No ticket was requested.` :
|
|
4041
|
+
text: refusals.length > 0 ? `Refused: ${reasons.charAt(0).toUpperCase() + reasons.slice(1)} No ticket was requested.` : noMandateRefusal(tool.namespacedName, profile)
|
|
4027
4042
|
}],
|
|
4028
4043
|
isError: true
|
|
4029
4044
|
};
|
|
@@ -4635,20 +4650,21 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
|
|
|
4635
4650
|
}
|
|
4636
4651
|
});
|
|
4637
4652
|
}
|
|
4638
|
-
function
|
|
4653
|
+
function noMandateRefusal(toolName, profile) {
|
|
4654
|
+
return `Refused: no mandate covers ${toolName} (${shortProfile(profile)}). Nothing was done. Ask the person to grant a ${shortProfile(profile)} mandate in the gateway, then try again. No ticket was requested.`;
|
|
4655
|
+
}
|
|
4656
|
+
function shortProfile(profileId) {
|
|
4657
|
+
const parts = profileId.replace(/@.*$/, "").split("/");
|
|
4658
|
+
return parts[parts.length - 1];
|
|
4659
|
+
}
|
|
4660
|
+
function buildProxiedToolDescription(tool) {
|
|
4639
4661
|
if (!tool.gating || !tool.gating.profile) {
|
|
4640
4662
|
return `[Suveren: no gating config] ${tool.description}`;
|
|
4641
4663
|
}
|
|
4642
4664
|
const profile = tool.gating.profile;
|
|
4643
|
-
const
|
|
4644
|
-
const hasAuth = auths.some(
|
|
4645
|
-
(a) => a.complete && profileMatches(a.profileId, profile)
|
|
4646
|
-
);
|
|
4647
|
-
if (!hasAuth) {
|
|
4648
|
-
return `[Suveren: ${profile} \u2014 no active authorization] ${tool.description}`;
|
|
4649
|
-
}
|
|
4665
|
+
const condition = `works only under a ${shortProfile(profile)} mandate, check list-authorizations`;
|
|
4650
4666
|
if (tool.gating.category === "read") {
|
|
4651
|
-
return `[Suveren: ${profile} \u2014 read] ${tool.description}`;
|
|
4667
|
+
return `[Suveren: ${profile} \u2014 read; ${condition}] ${tool.description}`;
|
|
4652
4668
|
}
|
|
4653
4669
|
const parts = [];
|
|
4654
4670
|
if (tool.gating.staticExecution?.action_type) {
|
|
@@ -4661,7 +4677,7 @@ function buildProxiedToolDescription(tool, state2) {
|
|
|
4661
4677
|
parts.push(`${mappedFields.join(", ")} checked`);
|
|
4662
4678
|
}
|
|
4663
4679
|
const tag = parts.length > 0 ? parts.join(", ") : "gated";
|
|
4664
|
-
return `[Suveren: ${profile} \u2014 ${tag}] ${tool.description}`;
|
|
4680
|
+
return `[Suveren: ${profile} \u2014 ${tag}; ${condition}] ${tool.description}`;
|
|
4665
4681
|
}
|
|
4666
4682
|
|
|
4667
4683
|
// src/lib/agent-visibility.ts
|
|
@@ -4697,6 +4713,7 @@ function countToolsByGating(profileId, integrationManager2) {
|
|
|
4697
4713
|
let gated = 0;
|
|
4698
4714
|
let readOnly = 0;
|
|
4699
4715
|
for (const tool of allTools) {
|
|
4716
|
+
if (tool.internal) continue;
|
|
4700
4717
|
if (!tool.gating || !tool.gating.profile) {
|
|
4701
4718
|
readOnly++;
|
|
4702
4719
|
} else if (tool.gating.profile === profileId || tool.gating.profile === shortProfileName(profileId)) {
|
|
@@ -4716,7 +4733,8 @@ function buildMandateBrief(opts) {
|
|
|
4716
4733
|
"You are connected to Suveren \u2014 the gateway that gates every privileged tool call you make.",
|
|
4717
4734
|
"Suveren implements the bounded-authority model from the open Human Agency Protocol (HAP).",
|
|
4718
4735
|
"You have bounded authorities granted by human decision owners.",
|
|
4719
|
-
"You MUST stay within these bounds \u2014 the Gatekeeper will reject actions that exceed them."
|
|
4736
|
+
"You MUST stay within these bounds \u2014 the Gatekeeper will reject actions that exceed them.",
|
|
4737
|
+
"Tools are listed even where no mandate covers them yet. If a call is refused because no mandate covers it, tell the person and ask them to grant one \u2014 do not retry it or work around it with another tool."
|
|
4720
4738
|
];
|
|
4721
4739
|
if (opts.asVersionRefusal) {
|
|
4722
4740
|
lines.push("");
|
|
@@ -4753,14 +4771,14 @@ function buildMandateBrief(opts) {
|
|
|
4753
4771
|
for (const auth of active) {
|
|
4754
4772
|
const earliestExpiry = Math.min(...auth.attestations.map((a) => a.expiresAt));
|
|
4755
4773
|
const remainingMin = Math.max(0, Math.round((earliestExpiry - now) / 60));
|
|
4756
|
-
const
|
|
4774
|
+
const shortName2 = shortProfileName(auth.profileId);
|
|
4757
4775
|
const version = auth.profileId.match(/@(.+)$/)?.[1] ?? "";
|
|
4758
|
-
const shortId = version ? `${
|
|
4776
|
+
const shortId = version ? `${shortName2}@${version}` : shortName2;
|
|
4759
4777
|
const boundsDesc = Object.entries(auth.frame).filter(([key]) => key !== "profile" && key !== "path").map(([key, value]) => `${key}:${value}`).join(" \xB7 ");
|
|
4760
4778
|
const parts = [];
|
|
4761
4779
|
if (boundsDesc) parts.push(boundsDesc);
|
|
4762
4780
|
if (executionLog) {
|
|
4763
|
-
const profile = getProfile3(auth.profileId) ?? getProfile3(
|
|
4781
|
+
const profile = getProfile3(auth.profileId) ?? getProfile3(shortName2);
|
|
4764
4782
|
const consumption = getConsumptionState(auth, executionLog, profile);
|
|
4765
4783
|
const compact = formatConsumptionCompact(consumption);
|
|
4766
4784
|
if (compact) parts.push(compact);
|
|
@@ -4769,7 +4787,7 @@ function buildMandateBrief(opts) {
|
|
|
4769
4787
|
if (gated > 0 || readOnly > 0) parts.push(`${gated} gated tools`);
|
|
4770
4788
|
parts.push(`${remainingMin} min remaining`);
|
|
4771
4789
|
lines.push(`[${shortId}] ${parts.join(" \xB7 ")}`);
|
|
4772
|
-
lines.push(` \u2192 list-authorizations(domain: "${
|
|
4790
|
+
lines.push(` \u2192 list-authorizations(domain: "${shortName2}") for intent & details`);
|
|
4773
4791
|
lines.push("");
|
|
4774
4792
|
}
|
|
4775
4793
|
}
|
|
@@ -4807,15 +4825,15 @@ function shortProfileName2(profileId) {
|
|
|
4807
4825
|
function buildCapabilityMap(profileId, integrationManager2) {
|
|
4808
4826
|
if (!integrationManager2) return "";
|
|
4809
4827
|
const allTools = integrationManager2.getAllTools();
|
|
4810
|
-
const
|
|
4828
|
+
const shortName2 = shortProfileName2(profileId);
|
|
4811
4829
|
const gated = [];
|
|
4812
4830
|
const readOnly = [];
|
|
4813
4831
|
const undescribed = [];
|
|
4814
4832
|
for (const tool of allTools) {
|
|
4815
|
-
if (!tool.gating || !tool.gating.profile) {
|
|
4833
|
+
if (tool.internal || !tool.gating || !tool.gating.profile) {
|
|
4816
4834
|
continue;
|
|
4817
4835
|
}
|
|
4818
|
-
if (!profileMatches(tool.gating.profile,
|
|
4836
|
+
if (!profileMatches(tool.gating.profile, shortName2) && tool.gating.profile !== profileId) {
|
|
4819
4837
|
continue;
|
|
4820
4838
|
}
|
|
4821
4839
|
const gating = tool.gating;
|
|
@@ -4853,6 +4871,20 @@ function buildCapabilityMap(profileId, integrationManager2) {
|
|
|
4853
4871
|
}
|
|
4854
4872
|
return lines.join("\n");
|
|
4855
4873
|
}
|
|
4874
|
+
function listedWithoutMandate(authorizations, integrationManager2) {
|
|
4875
|
+
if (!integrationManager2) return [];
|
|
4876
|
+
const isBuiltin = (id) => integrationManager2.isBuiltin?.(id) ?? false;
|
|
4877
|
+
const byProfile = /* @__PURE__ */ new Map();
|
|
4878
|
+
for (const tool of integrationManager2.getAllTools()) {
|
|
4879
|
+
if (!toolAlwaysListed(tool, isBuiltin)) continue;
|
|
4880
|
+
const profile = tool.gating.profile;
|
|
4881
|
+
if (authorizations.some((a) => a.complete && profileMatches(a.profileId, profile))) continue;
|
|
4882
|
+
const short = shortProfileName2(profile);
|
|
4883
|
+
byProfile.set(short, [...byProfile.get(short) ?? [], tool.namespacedName]);
|
|
4884
|
+
}
|
|
4885
|
+
return [...byProfile.entries()].map(([profile, tools]) => ` ${profile}: ${tools.join(", ")}`);
|
|
4886
|
+
}
|
|
4887
|
+
var LISTED_WITHOUT_MANDATE_HEADING = "Listed but not usable yet \u2014 no mandate covers these; every call is refused until the person grants one:";
|
|
4856
4888
|
function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
4857
4889
|
return async (args) => {
|
|
4858
4890
|
if (!state2.spClient.isUnlocked()) {
|
|
@@ -4861,18 +4893,16 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
4861
4893
|
const authorizations = agentVisibleAuthorizations(state2.getEnrichedAuthorizations(), integrationManager2);
|
|
4862
4894
|
const now = Math.floor(Date.now() / 1e3);
|
|
4863
4895
|
const domain = args?.domain;
|
|
4896
|
+
const notYet = listedWithoutMandate(authorizations, integrationManager2);
|
|
4864
4897
|
if (authorizations.length === 0) {
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
text: "No active authorizations. A decision owner must grant authority via the Authority UI."
|
|
4869
|
-
}]
|
|
4870
|
-
};
|
|
4898
|
+
const text = ["No active authorizations \u2014 no tool is usable yet. Ask the person to grant a mandate in the gateway."];
|
|
4899
|
+
if (notYet.length > 0) text.push("", LISTED_WITHOUT_MANDATE_HEADING, ...notYet);
|
|
4900
|
+
return { content: [{ type: "text", text: text.join("\n") }] };
|
|
4871
4901
|
}
|
|
4872
4902
|
if (domain) {
|
|
4873
4903
|
const matching = authorizations.filter((auth) => {
|
|
4874
|
-
const
|
|
4875
|
-
return
|
|
4904
|
+
const shortName2 = shortProfileName2(auth.profileId);
|
|
4905
|
+
return shortName2 === domain || profileMatches(auth.profileId, domain);
|
|
4876
4906
|
});
|
|
4877
4907
|
if (matching.length === 0) {
|
|
4878
4908
|
return {
|
|
@@ -4903,8 +4933,8 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
4903
4933
|
}
|
|
4904
4934
|
} catch {
|
|
4905
4935
|
}
|
|
4906
|
-
const
|
|
4907
|
-
const profile = getProfile4(auth.profileId) ?? getProfile4(
|
|
4936
|
+
const shortName2 = shortProfileName2(auth.profileId);
|
|
4937
|
+
const profile = getProfile4(auth.profileId) ?? getProfile4(shortName2);
|
|
4908
4938
|
if (profile) {
|
|
4909
4939
|
const consumption = getConsumptionState(auth, state2.executionLog, profile);
|
|
4910
4940
|
const consumptionText = formatConsumptionFull(consumption);
|
|
@@ -4963,8 +4993,8 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
4963
4993
|
if ((auth.deferredCommitmentDomains ?? []).length > 0) {
|
|
4964
4994
|
lines.push(" Mode: review (each action requires your approval)");
|
|
4965
4995
|
}
|
|
4966
|
-
const
|
|
4967
|
-
const profile = getProfile4(auth.profileId) ?? getProfile4(
|
|
4996
|
+
const shortName2 = shortProfileName2(auth.profileId);
|
|
4997
|
+
const profile = getProfile4(auth.profileId) ?? getProfile4(shortName2);
|
|
4968
4998
|
if (profile) {
|
|
4969
4999
|
const consumption = getConsumptionState(auth, state2.executionLog, profile);
|
|
4970
5000
|
const compact = formatConsumptionCompact(consumption);
|
|
@@ -4988,7 +5018,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
4988
5018
|
output.push("");
|
|
4989
5019
|
}
|
|
4990
5020
|
if (active.length > 0) {
|
|
4991
|
-
output.push("Active authorizations:");
|
|
5021
|
+
output.push("Active authorizations (usable now):");
|
|
4992
5022
|
output.push(...active);
|
|
4993
5023
|
}
|
|
4994
5024
|
if (pending.length > 0) {
|
|
@@ -4996,6 +5026,11 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
4996
5026
|
output.push("Pending (missing owners):");
|
|
4997
5027
|
output.push(...pending);
|
|
4998
5028
|
}
|
|
5029
|
+
if (notYet.length > 0) {
|
|
5030
|
+
if (output.length > 0) output.push("");
|
|
5031
|
+
output.push(LISTED_WITHOUT_MANDATE_HEADING);
|
|
5032
|
+
output.push(...notYet);
|
|
5033
|
+
}
|
|
4999
5034
|
return {
|
|
5000
5035
|
content: [{
|
|
5001
5036
|
type: "text",
|
|
@@ -5603,6 +5638,7 @@ function createMcpServer(state2, integrationManager2) {
|
|
|
5603
5638
|
}
|
|
5604
5639
|
}
|
|
5605
5640
|
for (const tool of allTools) {
|
|
5641
|
+
if (tool.internal) continue;
|
|
5606
5642
|
const existing = proxiedTools.get(tool.namespacedName);
|
|
5607
5643
|
if (existing) {
|
|
5608
5644
|
if (existing.tool === tool) continue;
|
|
@@ -5611,7 +5647,7 @@ function createMcpServer(state2, integrationManager2) {
|
|
|
5611
5647
|
}
|
|
5612
5648
|
const handler = createGatedToolHandler(tool, integrationManager2, state2);
|
|
5613
5649
|
const zodShape = jsonSchemaToZodShape(tool.inputSchema);
|
|
5614
|
-
const description = buildProxiedToolDescription(tool
|
|
5650
|
+
const description = buildProxiedToolDescription(tool);
|
|
5615
5651
|
try {
|
|
5616
5652
|
const registered = server.registerTool(
|
|
5617
5653
|
tool.namespacedName,
|
|
@@ -5629,10 +5665,11 @@ function createMcpServer(state2, integrationManager2) {
|
|
|
5629
5665
|
}
|
|
5630
5666
|
function refreshTools() {
|
|
5631
5667
|
const auths = state2.getEnrichedAuthorizations();
|
|
5668
|
+
const isBuiltin = (id) => integrationManager2?.isBuiltin?.(id) ?? false;
|
|
5632
5669
|
for (const [, { tool, registered }] of proxiedTools) {
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
if (tool.gating?.profile) {
|
|
5670
|
+
if (toolAlwaysListed(tool, isBuiltin)) {
|
|
5671
|
+
registered.enable();
|
|
5672
|
+
} else if (tool.gating?.profile) {
|
|
5636
5673
|
const matchingAuths = auths.filter(
|
|
5637
5674
|
(a) => a.complete && profileMatches(a.profileId, tool.gating.profile)
|
|
5638
5675
|
);
|
|
@@ -6443,6 +6480,8 @@ var IntegrationManager = class {
|
|
|
6443
6480
|
integrationId: def.id,
|
|
6444
6481
|
description: tool.description,
|
|
6445
6482
|
inputSchema: withoutBlockedArgs(tool.inputSchema, gating?.blockedArgs),
|
|
6483
|
+
...tool.outputSchema ? { outputSchema: tool.outputSchema } : {},
|
|
6484
|
+
...tool.internal ? { internal: true } : {},
|
|
6446
6485
|
gating
|
|
6447
6486
|
};
|
|
6448
6487
|
});
|
|
@@ -6488,19 +6527,39 @@ var IntegrationManager = class {
|
|
|
6488
6527
|
}
|
|
6489
6528
|
/**
|
|
6490
6529
|
* The built-in's own refusal for this call, checked before the gate (no proposal,
|
|
6491
|
-
* no ticket): `
|
|
6492
|
-
*
|
|
6530
|
+
* no ticket): an `internal` tool (defence in depth — `index.ts` already never
|
|
6531
|
+
* registers one with the MCP server, so an agent cannot reach this in the first
|
|
6532
|
+
* place), `simulationOnly` outside simulation mode (group-level OR this one
|
|
6533
|
+
* tool's — DL1: a group may now mix test-setup-only tools with ones that work
|
|
6534
|
+
* outside simulation), then the tool's `validate`. Null for connector tools and
|
|
6535
|
+
* for calls that may proceed.
|
|
6493
6536
|
*/
|
|
6494
6537
|
async precheckBuiltin(tool, args) {
|
|
6495
6538
|
const builtin = this.builtins.get(tool.integrationId);
|
|
6496
6539
|
if (!builtin) return null;
|
|
6497
|
-
|
|
6540
|
+
const def = builtin.def.tools.find((t) => t.name === tool.originalName);
|
|
6541
|
+
if (def?.internal) {
|
|
6542
|
+
return `Refused: "${tool.namespacedName}" is internal and cannot be called directly. No ticket was requested.`;
|
|
6543
|
+
}
|
|
6544
|
+
if ((builtin.def.simulationOnly || def?.simulationOnly) && !isSimulationMode()) {
|
|
6498
6545
|
return `Refused: "${tool.namespacedName}" is not available. No ticket was requested.`;
|
|
6499
6546
|
}
|
|
6500
|
-
const def = builtin.def.tools.find((t) => t.name === tool.originalName);
|
|
6501
6547
|
const refusal = await def?.validate?.(args);
|
|
6502
6548
|
return refusal ? `Refused: ${refusal} No ticket was requested.` : null;
|
|
6503
6549
|
}
|
|
6550
|
+
/**
|
|
6551
|
+
* A built-in's own declared preview (AU3 — `BuiltinTool`/toolGating override's
|
|
6552
|
+
* `preview`, same shape a manifest declares for a connector tool). `preview.ts`'s
|
|
6553
|
+
* `getToolPreviewConfig` reads a connector's declared preview from its loaded
|
|
6554
|
+
* manifest (`getManifest`); a built-in has no manifest, so it reads it from here
|
|
6555
|
+
* instead — same override object, widened the same way `resolveToolGating`
|
|
6556
|
+
* already widens it for `approvalView`/`hideUnlessAuthorized`.
|
|
6557
|
+
*/
|
|
6558
|
+
getBuiltinToolPreview(integrationId, toolName) {
|
|
6559
|
+
const builtin = this.builtins.get(integrationId);
|
|
6560
|
+
const override = builtin?.def.toolGating.overrides?.[toolName];
|
|
6561
|
+
return override?.preview;
|
|
6562
|
+
}
|
|
6504
6563
|
/**
|
|
6505
6564
|
* Why this tool's integration refuses the held mandate `auth`, or null when
|
|
6506
6565
|
* it accepts it (always null for connectors and built-ins that declare no
|
|
@@ -6522,7 +6581,14 @@ var IntegrationManager = class {
|
|
|
6522
6581
|
}
|
|
6523
6582
|
try {
|
|
6524
6583
|
const result = await tool.handler(args);
|
|
6525
|
-
return {
|
|
6584
|
+
return {
|
|
6585
|
+
content: result.content.map((c) => ({ type: c.type, text: c.text })),
|
|
6586
|
+
isError: result.isError,
|
|
6587
|
+
// DL1: a built-in's internal preview tools (describe_mandate_proposal /
|
|
6588
|
+
// describe_brief_proposal) return structuredContent, same as a connector's
|
|
6589
|
+
// declared-output-schema read — see the field's doc comment above.
|
|
6590
|
+
...result.structuredContent ? { structuredContent: result.structuredContent } : {}
|
|
6591
|
+
};
|
|
6526
6592
|
} catch (err) {
|
|
6527
6593
|
const message = err instanceof Error ? err.message : String(err);
|
|
6528
6594
|
return { content: [{ type: "text", text: `Tool call failed: ${message}` }], isError: true };
|
|
@@ -7117,9 +7183,9 @@ function loadProfiles(profilesDir) {
|
|
|
7117
7183
|
try {
|
|
7118
7184
|
const profile = JSON.parse(readFileSync18(profilePath, "utf-8"));
|
|
7119
7185
|
registerProfile(profileId, profile);
|
|
7120
|
-
const
|
|
7121
|
-
if (
|
|
7122
|
-
registerProfile(
|
|
7186
|
+
const shortName2 = profileId.split("/").pop()?.replace(/@.*$/, "");
|
|
7187
|
+
if (shortName2 && shortName2 !== profileId) {
|
|
7188
|
+
registerProfile(shortName2, profile);
|
|
7123
7189
|
}
|
|
7124
7190
|
loaded++;
|
|
7125
7191
|
} catch (err) {
|
|
@@ -7151,6 +7217,9 @@ function warnNoProfiles(reason) {
|
|
|
7151
7217
|
function builtinText(text) {
|
|
7152
7218
|
return { content: [{ type: "text", text }] };
|
|
7153
7219
|
}
|
|
7220
|
+
function builtinStructured(structuredContent) {
|
|
7221
|
+
return { content: [{ type: "text", text: JSON.stringify(structuredContent) }], structuredContent };
|
|
7222
|
+
}
|
|
7154
7223
|
|
|
7155
7224
|
// src/lib/builtins/report.ts
|
|
7156
7225
|
var MAX_REPORT_HTML_BYTES = 512 * 1024;
|
|
@@ -7579,6 +7648,45 @@ function fillGenerated(body, sections) {
|
|
|
7579
7648
|
return body.replace(/<!--\s*generated:\s*([a-z-]+)\s*-->/g, (m, key) => sections[key] ? sections[key]() : m);
|
|
7580
7649
|
}
|
|
7581
7650
|
|
|
7651
|
+
// src/lib/unified-diff.ts
|
|
7652
|
+
function unifiedLineDiff(before, after) {
|
|
7653
|
+
const a = before.split("\n");
|
|
7654
|
+
const b = after.split("\n");
|
|
7655
|
+
const n = a.length;
|
|
7656
|
+
const m = b.length;
|
|
7657
|
+
const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
7658
|
+
for (let i2 = n - 1; i2 >= 0; i2--) {
|
|
7659
|
+
for (let j2 = m - 1; j2 >= 0; j2--) {
|
|
7660
|
+
dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
7661
|
+
}
|
|
7662
|
+
}
|
|
7663
|
+
const lines = [];
|
|
7664
|
+
let i = 0;
|
|
7665
|
+
let j = 0;
|
|
7666
|
+
while (i < n && j < m) {
|
|
7667
|
+
if (a[i] === b[j]) {
|
|
7668
|
+
lines.push(a[i]);
|
|
7669
|
+
i++;
|
|
7670
|
+
j++;
|
|
7671
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
7672
|
+
lines.push(`-${a[i]}`);
|
|
7673
|
+
i++;
|
|
7674
|
+
} else {
|
|
7675
|
+
lines.push(`+${b[j]}`);
|
|
7676
|
+
j++;
|
|
7677
|
+
}
|
|
7678
|
+
}
|
|
7679
|
+
while (i < n) {
|
|
7680
|
+
lines.push(`-${a[i]}`);
|
|
7681
|
+
i++;
|
|
7682
|
+
}
|
|
7683
|
+
while (j < m) {
|
|
7684
|
+
lines.push(`+${b[j]}`);
|
|
7685
|
+
j++;
|
|
7686
|
+
}
|
|
7687
|
+
return lines.join("\n");
|
|
7688
|
+
}
|
|
7689
|
+
|
|
7582
7690
|
// src/lib/builtins/setup.ts
|
|
7583
7691
|
import { getProfile as getProfile6 } from "@hap/core";
|
|
7584
7692
|
var DELEGATION_PROFILE = "delegation";
|
|
@@ -7587,7 +7695,12 @@ var DELEGATION_INTENT_HINT = [
|
|
|
7587
7695
|
"",
|
|
7588
7696
|
"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
7697
|
"",
|
|
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."
|
|
7698
|
+
"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.",
|
|
7699
|
+
"",
|
|
7700
|
+
// DL1 — outside simulation, the same mandate also covers ongoing, real-system
|
|
7701
|
+
// use: the Scope above (which profiles, which mode) is what actually bounds
|
|
7702
|
+
// the AI from here, not simulation mode. Plain words, per the brief.
|
|
7703
|
+
"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
7704
|
].join("\n");
|
|
7592
7705
|
function briefRefusal(args) {
|
|
7593
7706
|
if (typeof args.content !== "string" || args.content.trim() === "") return "`content` must be the complete new brief (markdown text).";
|
|
@@ -7611,6 +7724,149 @@ function connectedSystems(im) {
|
|
|
7611
7724
|
}
|
|
7612
7725
|
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
7613
7726
|
}
|
|
7727
|
+
function shortName(idOrName) {
|
|
7728
|
+
const stripped = idOrName.replace(/@.*$/, "").split("/").pop() ?? idOrName;
|
|
7729
|
+
return stripped.toLowerCase();
|
|
7730
|
+
}
|
|
7731
|
+
function versionOf(id) {
|
|
7732
|
+
return id.split("@")[1] ?? "";
|
|
7733
|
+
}
|
|
7734
|
+
function delegationItselfRefusal(args) {
|
|
7735
|
+
const requested = typeof args.profile === "string" ? args.profile : "";
|
|
7736
|
+
if (shortName(requested) === DELEGATION_PROFILE) {
|
|
7737
|
+
return "Delegation can never be proposed \u2014 the AI cannot widen its own delegation.";
|
|
7738
|
+
}
|
|
7739
|
+
return void 0;
|
|
7740
|
+
}
|
|
7741
|
+
function delegationMandateRefusal(auth) {
|
|
7742
|
+
if (isSimulationMode()) return void 0;
|
|
7743
|
+
const profile = getProfile6(auth.profileId);
|
|
7744
|
+
if (profile?.scopeSchema?.fields?.allowed_profiles) return void 0;
|
|
7745
|
+
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.`;
|
|
7746
|
+
}
|
|
7747
|
+
function formatUnit(unit) {
|
|
7748
|
+
if (!unit || unit === "count") return "";
|
|
7749
|
+
if (unit === "minutes") return "min";
|
|
7750
|
+
if (unit === "hours") return "hr";
|
|
7751
|
+
if (unit === "days") return "days";
|
|
7752
|
+
if (unit === "percent") return "%";
|
|
7753
|
+
if (unit.startsWith("currency:")) return unit.slice("currency:".length);
|
|
7754
|
+
return unit;
|
|
7755
|
+
}
|
|
7756
|
+
function formatBoundValue2(value, unit) {
|
|
7757
|
+
if (typeof value !== "number" && typeof value !== "string") return String(value);
|
|
7758
|
+
const u = formatUnit(unit);
|
|
7759
|
+
if (!u) return String(value);
|
|
7760
|
+
if (unit === "percent") return `${value}${u}`;
|
|
7761
|
+
if (unit?.startsWith("currency:")) return `${u} ${value}`;
|
|
7762
|
+
return `${value} ${u}`;
|
|
7763
|
+
}
|
|
7764
|
+
function labeledRecord(values, fields) {
|
|
7765
|
+
const out = {};
|
|
7766
|
+
if (!values || typeof values !== "object" || Array.isArray(values)) return out;
|
|
7767
|
+
for (const [key, value] of Object.entries(values)) {
|
|
7768
|
+
const def = fields?.[key];
|
|
7769
|
+
out[def?.displayName ?? key] = formatBoundValue2(value, def?.unit);
|
|
7770
|
+
}
|
|
7771
|
+
return out;
|
|
7772
|
+
}
|
|
7773
|
+
function formatValidity(args, profile) {
|
|
7774
|
+
const hours = typeof args.duration_hours === "number" && args.duration_hours > 0 ? args.duration_hours : void 0;
|
|
7775
|
+
const totalSeconds = hours !== void 0 ? Math.round(hours * 3600) : profile.ttl?.default ?? 0;
|
|
7776
|
+
if (totalSeconds <= 0) return "unknown";
|
|
7777
|
+
if (totalSeconds % 86400 === 0) {
|
|
7778
|
+
const days = totalSeconds / 86400;
|
|
7779
|
+
return `${days} day${days === 1 ? "" : "s"}`;
|
|
7780
|
+
}
|
|
7781
|
+
if (totalSeconds % 3600 === 0) {
|
|
7782
|
+
const hrs = totalSeconds / 3600;
|
|
7783
|
+
return `${hrs} hour${hrs === 1 ? "" : "s"}`;
|
|
7784
|
+
}
|
|
7785
|
+
return `${Math.round(totalSeconds / 3600)} hours`;
|
|
7786
|
+
}
|
|
7787
|
+
function delegationChecks(deps, args, targetShortName, targetDisplayName) {
|
|
7788
|
+
const governing = deps.state.getEnrichedAuthorizations().filter((a) => a.complete && shortName(a.profileId) === DELEGATION_PROFILE);
|
|
7789
|
+
const listed = (ctx, field) => typeof ctx?.[field] === "string" ? ctx[field].split(",").map((s) => s.trim().toLowerCase()).filter(Boolean) : [];
|
|
7790
|
+
const profileOk = governing.some((g) => listed(g.context, "allowed_profiles").includes(targetShortName.toLowerCase()));
|
|
7791
|
+
const mode = typeof args.mode === "string" ? args.mode : "";
|
|
7792
|
+
const modeOk = mode !== "" && governing.some((g) => listed(g.context, "allowed_modes").includes(mode.toLowerCase()));
|
|
7793
|
+
const checks = [
|
|
7794
|
+
profileOk ? `${targetDisplayName} is allowed by your Delegation mandate` : `${targetDisplayName} is not confirmed as allowed by an active Delegation mandate`
|
|
7795
|
+
];
|
|
7796
|
+
checks.push(
|
|
7797
|
+
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`
|
|
7798
|
+
);
|
|
7799
|
+
const first = governing[0];
|
|
7800
|
+
if (first) {
|
|
7801
|
+
const delegationProfile = getProfile6(first.profileId);
|
|
7802
|
+
const entry = getConsumptionState(first, deps.state.executionLog, delegationProfile).find((e) => e.field === "mandate_daily_max");
|
|
7803
|
+
if (entry && entry.limit !== null) {
|
|
7804
|
+
checks.push(`Proposals today: ${entry.current} of ${entry.limit}`);
|
|
7805
|
+
}
|
|
7806
|
+
}
|
|
7807
|
+
return checks;
|
|
7808
|
+
}
|
|
7809
|
+
function resolveTargetProfile(input) {
|
|
7810
|
+
if (typeof input !== "string" || !input.trim()) return void 0;
|
|
7811
|
+
const profile = getProfile6(input.trim());
|
|
7812
|
+
if (!profile) return void 0;
|
|
7813
|
+
return { profile, shortName: shortName(profile.id) };
|
|
7814
|
+
}
|
|
7815
|
+
var CREATE_MANDATE_INPUT_SCHEMA = {
|
|
7816
|
+
type: "object",
|
|
7817
|
+
properties: {
|
|
7818
|
+
team: { type: "string", description: 'Team name or id, or "personal". Omit to use the workspace of your Delegation mandate.' },
|
|
7819
|
+
// Short name only (e.g. "sales") — a full profile id is refused here, at input
|
|
7820
|
+
// validation, before the tool ever runs (DL1 Interface contract).
|
|
7821
|
+
profile: { type: "string", pattern: "^[a-z][a-z0-9-]*$", description: 'Profile short name, e.g. "sales" (newest version).' },
|
|
7822
|
+
limits: { type: "object", description: "The limits (bounds) by field name, as the profile defines them." },
|
|
7823
|
+
scope: { type: "object", description: "The scope (context) by field name, as the profile defines it." },
|
|
7824
|
+
intent: { type: "string", description: "Why, goal and watch-outs \u2014 signed with the mandate (at most 2000 characters)." },
|
|
7825
|
+
mode: { type: "string", enum: ["review", "automatic"], description: "review = each action needs approval; automatic = within the limits." },
|
|
7826
|
+
duration_hours: { type: "number", description: "How long the mandate is valid, in hours. Omit for the profile default." },
|
|
7827
|
+
title: { type: "string", description: "A short name for the mandate." },
|
|
7828
|
+
ticket_id: {
|
|
7829
|
+
type: "string",
|
|
7830
|
+
description: "Authorization reference for this call, set by the governing gateway \u2014 agents do not set this."
|
|
7831
|
+
}
|
|
7832
|
+
},
|
|
7833
|
+
required: ["profile", "limits", "intent", "mode"]
|
|
7834
|
+
};
|
|
7835
|
+
var SET_AGENT_BRIEF_INPUT_SCHEMA = {
|
|
7836
|
+
type: "object",
|
|
7837
|
+
properties: {
|
|
7838
|
+
content: { type: "string", description: "The complete new agent brief, in markdown (at most 16 KB)." },
|
|
7839
|
+
ticket_id: {
|
|
7840
|
+
type: "string",
|
|
7841
|
+
description: "Authorization reference for this call, set by the governing gateway \u2014 agents do not set this."
|
|
7842
|
+
}
|
|
7843
|
+
},
|
|
7844
|
+
required: ["content"]
|
|
7845
|
+
};
|
|
7846
|
+
var DESCRIBE_MANDATE_PROPOSAL_OUTPUT_SCHEMA = {
|
|
7847
|
+
type: "object",
|
|
7848
|
+
properties: {
|
|
7849
|
+
proposal: { type: "string", title: "Proposal" },
|
|
7850
|
+
profile_version: { type: "string", title: "Version" },
|
|
7851
|
+
workspace: { type: "string", title: "Workspace" },
|
|
7852
|
+
mode: { type: "string", title: "Mode" },
|
|
7853
|
+
valid: { type: "string", title: "Valid" },
|
|
7854
|
+
limits: { type: "object", title: "Limits" },
|
|
7855
|
+
scope: { type: "object", title: "Scope" },
|
|
7856
|
+
title: { type: ["string", "null"], title: "Title" },
|
|
7857
|
+
intent: { type: "string", title: "Intent", description: "Written by the AI \u2014 becomes your words when you sign" },
|
|
7858
|
+
checks: { type: "array", title: "Checks", items: { type: "string" } }
|
|
7859
|
+
},
|
|
7860
|
+
required: ["proposal", "profile_version", "workspace", "mode", "valid", "limits", "scope", "title", "intent", "checks"]
|
|
7861
|
+
};
|
|
7862
|
+
var DESCRIBE_BRIEF_PROPOSAL_OUTPUT_SCHEMA = {
|
|
7863
|
+
type: "object",
|
|
7864
|
+
properties: {
|
|
7865
|
+
change: { type: "string", title: "Change", contentMediaType: "text/x-diff" },
|
|
7866
|
+
words: { type: "number", title: "Words" }
|
|
7867
|
+
},
|
|
7868
|
+
required: ["change", "words"]
|
|
7869
|
+
};
|
|
7614
7870
|
function setupBuiltin(deps) {
|
|
7615
7871
|
return {
|
|
7616
7872
|
id: "setup",
|
|
@@ -7619,18 +7875,24 @@ function setupBuiltin(deps) {
|
|
|
7619
7875
|
// name they'll see everywhere else (Mandates page, dashboard first-run
|
|
7620
7876
|
// card), not the internal "test setup" framing. Description is unchanged.
|
|
7621
7877
|
name: "Delegation",
|
|
7622
|
-
description: "Your AI
|
|
7878
|
+
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
7879
|
intentHint: DELEGATION_INTENT_HINT,
|
|
7624
7880
|
profile: DELEGATION_PROFILE,
|
|
7625
7881
|
simulation: true,
|
|
7626
|
-
|
|
7882
|
+
// DL1: no longer simulationOnly at the GROUP level — create_mandate and
|
|
7883
|
+
// set_agent_brief now work outside simulation too (decision 1); only
|
|
7884
|
+
// get_guide keeps the per-tool flag below (decision 2).
|
|
7885
|
+
mandateRefusal: delegationMandateRefusal,
|
|
7627
7886
|
toolGating: {
|
|
7628
7887
|
overrides: {
|
|
7629
7888
|
set_agent_brief: {
|
|
7630
7889
|
executionMapping: {},
|
|
7631
7890
|
staticExecution: { action_type: "brief" },
|
|
7632
7891
|
hideUnlessAuthorized: true,
|
|
7633
|
-
approvalView: { content: { label: "New agent brief", kind: "markdown" } }
|
|
7892
|
+
approvalView: { content: { label: "New agent brief", kind: "markdown" } },
|
|
7893
|
+
// AU3 — the approval card's preview of this pending proposal (DL1
|
|
7894
|
+
// Interface contract). Identity arg mapping: same field name either side.
|
|
7895
|
+
preview: { tool: "describe_brief_proposal", args: { content: "content" } }
|
|
7634
7896
|
},
|
|
7635
7897
|
get_guide: {
|
|
7636
7898
|
category: "read",
|
|
@@ -7638,7 +7900,7 @@ function setupBuiltin(deps) {
|
|
|
7638
7900
|
requiredValue: "unlimited"
|
|
7639
7901
|
},
|
|
7640
7902
|
create_mandate: {
|
|
7641
|
-
executionMapping: {},
|
|
7903
|
+
executionMapping: { profile: "allowed_profiles", mode: "allowed_modes" },
|
|
7642
7904
|
staticExecution: { action_type: "mandate" },
|
|
7643
7905
|
hideUnlessAuthorized: true,
|
|
7644
7906
|
// Shown like the mandate screen: limits and scope by the profile's own names.
|
|
@@ -7651,13 +7913,23 @@ function setupBuiltin(deps) {
|
|
|
7651
7913
|
mode: { label: "Mode" },
|
|
7652
7914
|
duration_hours: { label: "Valid for (hours)" },
|
|
7653
7915
|
title: { label: "Title" }
|
|
7654
|
-
}
|
|
7655
|
-
|
|
7916
|
+
},
|
|
7917
|
+
preview: { tool: "describe_mandate_proposal", args: { team: "team", profile: "profile", limits: "limits", scope: "scope", intent: "intent", mode: "mode", duration_hours: "duration_hours", title: "title" } }
|
|
7918
|
+
},
|
|
7919
|
+
// Internal preview tools (DL1): never listed, never callable by an
|
|
7920
|
+
// agent (BuiltinTool.internal — enforced in index.ts and
|
|
7921
|
+
// tool-proxy.ts); explicitly disabled here too, so a direct call
|
|
7922
|
+
// through the normal gate (defence in depth) refuses cleanly instead
|
|
7923
|
+
// of falling through to the "not described in manifest" default.
|
|
7924
|
+
describe_mandate_proposal: { category: "disabled" },
|
|
7925
|
+
describe_brief_proposal: { category: "disabled" }
|
|
7656
7926
|
}
|
|
7657
7927
|
},
|
|
7658
7928
|
tools: [
|
|
7659
7929
|
{
|
|
7660
7930
|
name: "get_guide",
|
|
7931
|
+
// DL1 decision 2: stays simulation-only, per-tool (the group no longer is).
|
|
7932
|
+
simulationOnly: true,
|
|
7661
7933
|
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
7934
|
inputSchema: {
|
|
7663
7935
|
type: "object",
|
|
@@ -7701,21 +7973,10 @@ ${body}`);
|
|
|
7701
7973
|
},
|
|
7702
7974
|
{
|
|
7703
7975
|
name: "set_agent_brief",
|
|
7704
|
-
description: "
|
|
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
|
-
},
|
|
7976
|
+
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.",
|
|
7977
|
+
inputSchema: SET_AGENT_BRIEF_INPUT_SCHEMA,
|
|
7716
7978
|
validate: briefRefusal,
|
|
7717
7979
|
handler: async (args) => {
|
|
7718
|
-
if (!isSimulationMode()) return { ...builtinText("Refused: not available outside simulation mode."), isError: true };
|
|
7719
7980
|
const refusal = briefRefusal(args);
|
|
7720
7981
|
if (refusal) return { ...builtinText(`Refused: ${refusal}`), isError: true };
|
|
7721
7982
|
writeContextFile(args.content);
|
|
@@ -7726,37 +7987,86 @@ ${body}`);
|
|
|
7726
7987
|
},
|
|
7727
7988
|
{
|
|
7728
7989
|
name: "create_mandate",
|
|
7729
|
-
description: "
|
|
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
|
-
},
|
|
7990
|
+
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.",
|
|
7991
|
+
inputSchema: CREATE_MANDATE_INPUT_SCHEMA,
|
|
7748
7992
|
validate: async (args) => {
|
|
7993
|
+
const fixed = delegationItselfRefusal(args);
|
|
7994
|
+
if (fixed) {
|
|
7995
|
+
deps.state.denialLog?.record({
|
|
7996
|
+
ts: Date.now(),
|
|
7997
|
+
tool: "create_mandate",
|
|
7998
|
+
integrationId: "setup",
|
|
7999
|
+
profile: DELEGATION_PROFILE,
|
|
8000
|
+
kind: "bound",
|
|
8001
|
+
detail: fixed,
|
|
8002
|
+
who: "gateway",
|
|
8003
|
+
code: "DELEGATION_NOT_PROPOSABLE"
|
|
8004
|
+
});
|
|
8005
|
+
return fixed;
|
|
8006
|
+
}
|
|
7749
8007
|
const check = await controlPlaneMandate(true, mandateRequest(args));
|
|
7750
8008
|
return check.ok ? void 0 : check.message;
|
|
7751
8009
|
},
|
|
7752
8010
|
handler: async (args) => {
|
|
7753
|
-
|
|
8011
|
+
const fixed = delegationItselfRefusal(args);
|
|
8012
|
+
if (fixed) return { ...builtinText(`Refused: ${fixed}`), isError: true };
|
|
7754
8013
|
const r = await controlPlaneMandate(false, mandateRequest(args));
|
|
7755
8014
|
if (!r.ok) return { ...builtinText(`Refused: ${r.message}`), isError: true };
|
|
7756
8015
|
return builtinText(
|
|
7757
8016
|
`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
8017
|
);
|
|
7759
8018
|
}
|
|
8019
|
+
},
|
|
8020
|
+
{
|
|
8021
|
+
// AU3 internal preview (DL1 Interface contract) — never listed, never
|
|
8022
|
+
// callable by an agent. Builds the "What you would sign" card for a
|
|
8023
|
+
// pending create_mandate proposal from the SAME args, local data only.
|
|
8024
|
+
name: "describe_mandate_proposal",
|
|
8025
|
+
internal: true,
|
|
8026
|
+
description: "Internal \u2014 the approval card's preview of a pending create_mandate proposal. Not for agent use.",
|
|
8027
|
+
inputSchema: CREATE_MANDATE_INPUT_SCHEMA,
|
|
8028
|
+
outputSchema: DESCRIBE_MANDATE_PROPOSAL_OUTPUT_SCHEMA,
|
|
8029
|
+
handler: async (args) => {
|
|
8030
|
+
const resolved = resolveTargetProfile(args.profile);
|
|
8031
|
+
if (!resolved) {
|
|
8032
|
+
return { ...builtinText(`Refused: unknown profile "${String(args.profile)}".`), isError: true };
|
|
8033
|
+
}
|
|
8034
|
+
const { profile, shortName: targetShortName } = resolved;
|
|
8035
|
+
const displayName = profile.name ?? targetShortName;
|
|
8036
|
+
const rawTeam = typeof args.team === "string" ? args.team.trim() : "";
|
|
8037
|
+
const workspace = rawTeam && rawTeam.toLowerCase() !== "personal" ? rawTeam : "Personal";
|
|
8038
|
+
const mode = typeof args.mode === "string" ? args.mode : "";
|
|
8039
|
+
const structured = {
|
|
8040
|
+
proposal: `New mandate \xB7 ${displayName}`,
|
|
8041
|
+
profile_version: `${targetShortName} ${profile.version}`,
|
|
8042
|
+
workspace,
|
|
8043
|
+
mode,
|
|
8044
|
+
valid: formatValidity(args, profile),
|
|
8045
|
+
limits: labeledRecord(args.limits, profile.boundsSchema?.fields),
|
|
8046
|
+
scope: labeledRecord(args.scope, profile.scopeSchema?.fields),
|
|
8047
|
+
title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : null,
|
|
8048
|
+
intent: typeof args.intent === "string" ? args.intent : "",
|
|
8049
|
+
checks: delegationChecks(deps, args, targetShortName, displayName)
|
|
8050
|
+
};
|
|
8051
|
+
return builtinStructured(structured);
|
|
8052
|
+
}
|
|
8053
|
+
},
|
|
8054
|
+
{
|
|
8055
|
+
// AU3 internal preview (DL1 Interface contract) — never listed, never
|
|
8056
|
+
// callable by an agent. Builds the "New agent brief" diff card.
|
|
8057
|
+
name: "describe_brief_proposal",
|
|
8058
|
+
internal: true,
|
|
8059
|
+
description: "Internal \u2014 the approval card's preview of a pending set_agent_brief proposal. Not for agent use.",
|
|
8060
|
+
inputSchema: SET_AGENT_BRIEF_INPUT_SCHEMA,
|
|
8061
|
+
outputSchema: DESCRIBE_BRIEF_PROPOSAL_OUTPUT_SCHEMA,
|
|
8062
|
+
handler: async (args) => {
|
|
8063
|
+
const proposed = typeof args.content === "string" ? args.content : "";
|
|
8064
|
+
const current = readContextFile() ?? "";
|
|
8065
|
+
const change = unifiedLineDiff(current, proposed);
|
|
8066
|
+
const trimmed = proposed.trim();
|
|
8067
|
+
const words = trimmed === "" ? 0 : trimmed.split(/\s+/).length;
|
|
8068
|
+
return builtinStructured({ change, words });
|
|
8069
|
+
}
|
|
7760
8070
|
}
|
|
7761
8071
|
]
|
|
7762
8072
|
};
|