@suveren/gateway 0.17.0 → 0.18.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/content/guides/1-interview.md +31 -0
- package/content/guides/2-package.md +30 -0
- package/content/guides/3-mandates.md +34 -0
- package/content/guides/4-brief.md +30 -0
- package/content/guides/5-agent-process.md +39 -0
- package/content/guides/6-risks.md +14 -0
- package/dist/mcp-server/http.mjs +143 -17
- package/package.json +1 -1
- package/server.js +3 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Interview
|
|
2
|
+
|
|
3
|
+
Learn the company, the team and the work, and collect real cases. Nothing is loaded or proposed in this step.
|
|
4
|
+
|
|
5
|
+
You ask; the person answers and checks. Ask one topic at a time and write down what you learn.
|
|
6
|
+
|
|
7
|
+
## What to ask, in this order
|
|
8
|
+
|
|
9
|
+
1. **Company:** what it sells, to whom, in which currency, and which systems it uses. The systems connected to this gateway are listed in the header above; ask about the ones that matter for the work.
|
|
10
|
+
2. **Department and team:** who does the work today, what a normal day looks like, what must never happen.
|
|
11
|
+
3. **The work to test:** which requests come in (for example quote requests, orders, questions), how many, how fast they are answered today, and what a good answer looks like.
|
|
12
|
+
4. **Order of systems:** whether the team follows a fixed sequence across systems — for example: always look up the customer in the CRM before quoting in the ERP, and log the reply in the CRM after sending. Which step must come first, which may be skipped. This becomes the agent's process and a rule in its brief.
|
|
13
|
+
5. **Real cases:** 5–10 recent examples, each with the request as it arrived and the reply that was actually sent. The person pastes or forwards them.
|
|
14
|
+
6. **Rules that are not written down:** discounts, credit limits, who may promise delivery dates, when to escalate and to whom.
|
|
15
|
+
|
|
16
|
+
## Renaming
|
|
17
|
+
|
|
18
|
+
Before anything else happens, propose new names for every person, company and email domain in the cases. Keep amounts, products and dates realistic. Use `.example` domains (for example `einkauf@huber.example`). The person checks the renamed cases; change them until they agree.
|
|
19
|
+
|
|
20
|
+
## Result of this step
|
|
21
|
+
|
|
22
|
+
- A short written summary: company, team, the work, the order of systems, the rules.
|
|
23
|
+
- The renamed cases.
|
|
24
|
+
|
|
25
|
+
Both confirmed by the person. The summary feeds the agent brief and the mandates; the cases feed the test data.
|
|
26
|
+
|
|
27
|
+
## Ask, don't guess
|
|
28
|
+
|
|
29
|
+
If a rule or a step is unclear, ask the person. Never invent a rule, a limit or a step to fill a gap.
|
|
30
|
+
|
|
31
|
+
Next: **package**.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Package
|
|
2
|
+
|
|
3
|
+
Turn the confirmed cases into test data and load it into the simulated systems.
|
|
4
|
+
|
|
5
|
+
## Build the package
|
|
6
|
+
|
|
7
|
+
One package feeds every simulated system. Its format, and how to build a good one, is described in the `load_simulation` tool of each simulated system — read it there; it is the authority. In short: the company's `name` and `currency`, the `customers`, `products` and `contacts` the cases need, and the `cases` themselves (each request as it arrived and the reply that was actually sent).
|
|
8
|
+
|
|
9
|
+
- Include every customer, product and contact a case mentions, with stock, prices and credit limits that make each case answerable the way it really was.
|
|
10
|
+
- Use only the renamed cases.
|
|
11
|
+
- Show the person the package before loading it; load only after they agree.
|
|
12
|
+
|
|
13
|
+
## Load
|
|
14
|
+
|
|
15
|
+
- Loading needs a setup mandate for each system (see **mandates**). The working AI's mandates must never allow loading.
|
|
16
|
+
- Call `load_simulation` in each simulated system with the same package.
|
|
17
|
+
- A load only works into an empty system. Incoming requests are dated within the hour before the load, in case order.
|
|
18
|
+
|
|
19
|
+
## Run again
|
|
20
|
+
|
|
21
|
+
To run the same cases again under a different setup, or other cases under the same setup:
|
|
22
|
+
|
|
23
|
+
1. `clear_simulation` in each system — this deletes all test data, including the record of what happened.
|
|
24
|
+
2. `load_simulation` again.
|
|
25
|
+
|
|
26
|
+
Clear and load each count against the setup mandate's daily limit, so one re-run costs two per system. If the limit is too small, propose a new setup mandate with a higher limit.
|
|
27
|
+
|
|
28
|
+
The real replies stay inside the simulated email system. They are never shown to the working AI; they exist for comparing afterwards.
|
|
29
|
+
|
|
30
|
+
Next: **mandates**.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Mandates
|
|
2
|
+
|
|
3
|
+
Propose the mandates the setup and the working AI need. You propose with `create_mandate`; the person approves each one on its own; only then is it created.
|
|
4
|
+
|
|
5
|
+
## Ask first
|
|
6
|
+
|
|
7
|
+
Before proposing any mandate, ask the person which steps they want to approve themselves. Their answer wins over everything below.
|
|
8
|
+
|
|
9
|
+
## The standard (when the person has no preference)
|
|
10
|
+
|
|
11
|
+
- **Steps the customer would see** — replies, sent quotes, orders — start in **review** mode: each one waits for the person's approval.
|
|
12
|
+
- **Internal steps** — looking up and noting things in the CRM, drafting quotes, loading test data — run **automatically** within limits.
|
|
13
|
+
|
|
14
|
+
Apply this to whatever systems are connected: the header above lists each system with its action types and tools. Judge from the action type and its tools whether the customer would see the result. When unsure, ask.
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- **One mandate per system.** Split a system into several mandates only when the team has different rules for different tasks. A mode applies to a whole mandate, so different modes for different steps of one system mean separate mandates (for example: drafting quotes automatically, sending quotes in review).
|
|
19
|
+
- **Setup and work are separate.** Setup mandates allow loading and clearing test data and nothing else. The working AI's mandates allow no loading or clearing (setup limit 0).
|
|
20
|
+
- **Start strict.** Where the team has not said otherwise, propose review mode or low caps. The person can loosen a mandate later by replacing it.
|
|
21
|
+
- **Limits come from the interview:** amounts, discounts, daily counts, allowed recipients. Do not invent them; ask.
|
|
22
|
+
- **Intent:** write why, goal and watch-outs in the person's words, in the language the team uses. It is signed with the mandate and the working AI follows it.
|
|
23
|
+
- **Teams:** in a team, the person must be an approver for the profile. `create_mandate` refuses at once if not; tell the person who can fix it (a team admin).
|
|
24
|
+
- **Reporting:** never propose a `reporting` mandate for the working AI during a run (see **risks**).
|
|
25
|
+
|
|
26
|
+
## Example (only an example)
|
|
27
|
+
|
|
28
|
+
- Setup, per system: automatic, setup limit 4–6 per day, everything else 0.
|
|
29
|
+
- CRM: automatic, with daily limits.
|
|
30
|
+
- Sales, drafting quotes: automatic, within value and discount caps.
|
|
31
|
+
- Sales, sending quotes and orders: review.
|
|
32
|
+
- Email replies: review, recipients limited to the customers' domains.
|
|
33
|
+
|
|
34
|
+
Next: **brief**.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Brief
|
|
2
|
+
|
|
3
|
+
Propose the agent brief — the standing instructions the working AI starts every session with. You propose with `set_agent_brief`; the person approves; it applies from the working AI's next session.
|
|
4
|
+
|
|
5
|
+
## What goes in
|
|
6
|
+
|
|
7
|
+
From the interview:
|
|
8
|
+
|
|
9
|
+
- **Who the AI works for:** the company, the team, the person.
|
|
10
|
+
- **The job:** which requests it handles and what a good outcome is.
|
|
11
|
+
- **The process:** the steps in order, including the order of systems from the interview, written as a rule (for example: "Always look up the customer in the CRM before preparing a quote.").
|
|
12
|
+
- **House rules:** tone, language, discounts, credit, what to escalate and to whom.
|
|
13
|
+
- **Hard lines:** what it must never do, even where a mandate would allow it.
|
|
14
|
+
- **When unsure, ask:** a missing rule or an unclear step goes to the person, never to a guess.
|
|
15
|
+
|
|
16
|
+
Write the brief in the language the team uses.
|
|
17
|
+
|
|
18
|
+
## What stays out
|
|
19
|
+
|
|
20
|
+
- Anything that reveals a test: no "simulation", "test case", "expected reply" or similar words.
|
|
21
|
+
- The real replies from the cases.
|
|
22
|
+
- Secrets, passwords, keys.
|
|
23
|
+
|
|
24
|
+
## Limits
|
|
25
|
+
|
|
26
|
+
- At most 16 KB.
|
|
27
|
+
- It replaces the whole brief each time — always write the complete brief, not a change.
|
|
28
|
+
- The person approves every version.
|
|
29
|
+
|
|
30
|
+
Next: **agent-process**.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Agent process
|
|
2
|
+
|
|
3
|
+
Help the person set up the working AI's process in its own client. The process is the working AI's work, not the gateway's: the gateway only grants and checks what it may do.
|
|
4
|
+
|
|
5
|
+
## Write the process as steps
|
|
6
|
+
|
|
7
|
+
From the interview, for example:
|
|
8
|
+
|
|
9
|
+
1. Read new messages in the inbox.
|
|
10
|
+
2. Look up the customer in the CRM.
|
|
11
|
+
3. Check stock and price in the ERP.
|
|
12
|
+
4. Prepare the quote.
|
|
13
|
+
5. Reply to the customer.
|
|
14
|
+
|
|
15
|
+
Keep a fixed order of systems from the interview exactly as given; it is also a rule in the brief. The person confirms the steps.
|
|
16
|
+
|
|
17
|
+
## Where it lives
|
|
18
|
+
|
|
19
|
+
- In the working AI's client, as its standing instructions for this work, connected to this gateway.
|
|
20
|
+
- In a separate conversation or project from this setup conversation.
|
|
21
|
+
|
|
22
|
+
## What starts a run
|
|
23
|
+
|
|
24
|
+
- The person asks (for example: "Work the new requests.").
|
|
25
|
+
- Or a scheduled or triggered run, if the client offers one. Check what the person's client offers today; do not assume.
|
|
26
|
+
|
|
27
|
+
## When a step is unclear
|
|
28
|
+
|
|
29
|
+
The working AI stops and asks the person. It never fills a gap by guessing.
|
|
30
|
+
|
|
31
|
+
## Claude Desktop
|
|
32
|
+
|
|
33
|
+
Help the person connect the client to this gateway (the gateway's MCP address) and put the steps into the client's standing instructions for this work. Check the client's current settings with the person; menus change between versions.
|
|
34
|
+
|
|
35
|
+
## ChatGPT
|
|
36
|
+
|
|
37
|
+
Same as above: connect the gateway if the person's plan and client support custom MCP connectors, and add the steps as instructions. If connecting is not possible, say so — the working AI cannot be tested without the gateway.
|
|
38
|
+
|
|
39
|
+
Next: **risks**, then start a run.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Risks
|
|
2
|
+
|
|
3
|
+
What can make a test say more than it should, and what to do about each. Read this once at the start and again before every run.
|
|
4
|
+
|
|
5
|
+
| Risk | Why it matters | What to do |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| The client remembers earlier runs | A re-run of the same cases is no longer a fresh test | Start every run in a new conversation; turn the client's memory off for the working AI |
|
|
8
|
+
| The working AI learns it is a test | It may behave differently | Never give it a `reporting` mandate during a run — its tools list the cases. Grant `reporting` only after the run, or to a separate AI session that only writes the report. Keep test words out of the brief |
|
|
9
|
+
| Setup and work in the same session | Setup tools and the real replies stay in the working AI's context | Use separate conversations for setup and for work |
|
|
10
|
+
| Mandates too loose | A wrong result can look like success | Start in review mode or with low caps; loosen by replacing the mandate |
|
|
11
|
+
| Proposals are stored readable on the Authority Server | The content of every proposal, including intents, can be read there | Test data and renamed cases only — never real customer data in a setup |
|
|
12
|
+
| Setup limit too low | A clear without the following load leaves empty systems | Use a setup limit of 4–6 per day; clear and load in one go |
|
|
13
|
+
|
|
14
|
+
Next: start a run — the person asks the working AI to work the new requests.
|
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -5641,6 +5641,77 @@ async function controlPlaneMandate(dryRun, request) {
|
|
|
5641
5641
|
}
|
|
5642
5642
|
}
|
|
5643
5643
|
|
|
5644
|
+
// src/lib/guides.ts
|
|
5645
|
+
import { existsSync as existsSync16, readdirSync, readFileSync as readFileSync17 } from "fs";
|
|
5646
|
+
import { dirname as dirname11, join as join16 } from "path";
|
|
5647
|
+
var FILE_RE = /^(?:(\d+)-)?([a-z0-9][a-z0-9-]*)\.md$/;
|
|
5648
|
+
function builtinGuidesDir() {
|
|
5649
|
+
if (process.env.SUVEREN_BUILTIN_GUIDES_DIR) return process.env.SUVEREN_BUILTIN_GUIDES_DIR;
|
|
5650
|
+
let dir = import.meta.dirname ?? __dirname;
|
|
5651
|
+
for (let i = 0; i < 8; i++) {
|
|
5652
|
+
const candidate = join16(dir, "content", "guides");
|
|
5653
|
+
if (existsSync16(candidate)) return candidate;
|
|
5654
|
+
const parent = dirname11(dir);
|
|
5655
|
+
if (parent === dir) break;
|
|
5656
|
+
dir = parent;
|
|
5657
|
+
}
|
|
5658
|
+
return join16(import.meta.dirname ?? __dirname, "content", "guides");
|
|
5659
|
+
}
|
|
5660
|
+
function parse(body) {
|
|
5661
|
+
const lines = body.split(/\r?\n/);
|
|
5662
|
+
const titleLine = lines.find((l) => l.startsWith("# ")) ?? "";
|
|
5663
|
+
const title = titleLine.slice(2).trim();
|
|
5664
|
+
const after = lines.slice(lines.indexOf(titleLine) + 1);
|
|
5665
|
+
const summary = after.find((l) => l.trim() !== "" && !l.startsWith("#"))?.trim() ?? "";
|
|
5666
|
+
return { title, summary };
|
|
5667
|
+
}
|
|
5668
|
+
function readDir(dir, source) {
|
|
5669
|
+
if (!dir || !existsSync16(dir)) return [];
|
|
5670
|
+
const out = [];
|
|
5671
|
+
for (const file of readdirSync(dir)) {
|
|
5672
|
+
const m = FILE_RE.exec(file);
|
|
5673
|
+
if (!m) continue;
|
|
5674
|
+
let body;
|
|
5675
|
+
try {
|
|
5676
|
+
body = readFileSync17(join16(dir, file), "utf8");
|
|
5677
|
+
} catch {
|
|
5678
|
+
continue;
|
|
5679
|
+
}
|
|
5680
|
+
const { title, summary } = parse(body);
|
|
5681
|
+
out.push({ topic: m[2], order: m[1] ? Number(m[1]) : Number.POSITIVE_INFINITY, title: title || m[2], summary, body, source });
|
|
5682
|
+
}
|
|
5683
|
+
return out;
|
|
5684
|
+
}
|
|
5685
|
+
function loadGuides(builtinDir = builtinGuidesDir(), overrideDir = process.env.SUVEREN_GUIDES_DIR) {
|
|
5686
|
+
const byTopic = /* @__PURE__ */ new Map();
|
|
5687
|
+
for (const g of readDir(builtinDir, "default")) byTopic.set(g.topic, g);
|
|
5688
|
+
for (const g of readDir(overrideDir, "override")) {
|
|
5689
|
+
const replaced = byTopic.get(g.topic);
|
|
5690
|
+
byTopic.set(g.topic, { ...g, order: Number.isFinite(g.order) ? g.order : replaced?.order ?? Number.POSITIVE_INFINITY });
|
|
5691
|
+
}
|
|
5692
|
+
return [...byTopic.values()].sort((a, b) => a.order - b.order || a.topic.localeCompare(b.topic));
|
|
5693
|
+
}
|
|
5694
|
+
function guideHeader(systems) {
|
|
5695
|
+
const lines = [
|
|
5696
|
+
"> This guide is in English. Talk to the person in their language, and write the brief and mandate intents in the language the team uses.",
|
|
5697
|
+
"",
|
|
5698
|
+
"**Systems connected to this gateway:**",
|
|
5699
|
+
""
|
|
5700
|
+
];
|
|
5701
|
+
if (systems.length === 0) {
|
|
5702
|
+
lines.push("- none yet \u2014 ask the person to connect the systems the work needs before you continue.");
|
|
5703
|
+
} else {
|
|
5704
|
+
for (const s of systems) {
|
|
5705
|
+
const parts = [`profile \`${s.profile}\``];
|
|
5706
|
+
if (s.actionTypes.length) parts.push(`action types ${s.actionTypes.map((a) => `\`${a}\``).join(", ")}`);
|
|
5707
|
+
if (s.writeTools.length) parts.push(`changes via ${s.writeTools.map((t) => `\`${t}\``).join(", ")}`);
|
|
5708
|
+
if (s.readTools.length) parts.push(`reads via ${s.readTools.map((t) => `\`${t}\``).join(", ")}`);
|
|
5709
|
+
lines.push(`- **${s.id}** \u2014 ${parts.join("; ")}`);
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
return lines.join("\n");
|
|
5713
|
+
}
|
|
5714
|
+
|
|
5644
5715
|
// src/lib/builtins/setup.ts
|
|
5645
5716
|
var DELEGATION_PROFILE = "github.com/humanagencyprotocol/hap-profiles/delegation@0.1";
|
|
5646
5717
|
function briefRefusal(args) {
|
|
@@ -5649,11 +5720,27 @@ function briefRefusal(args) {
|
|
|
5649
5720
|
if (bytes > CONTEXT_MAX_BYTES) return `the brief is ${bytes} bytes; the limit is ${CONTEXT_MAX_BYTES}. Shorten it.`;
|
|
5650
5721
|
return void 0;
|
|
5651
5722
|
}
|
|
5652
|
-
function
|
|
5723
|
+
function connectedSystems(im) {
|
|
5724
|
+
const byId = /* @__PURE__ */ new Map();
|
|
5725
|
+
for (const t of im.getAllTools()) {
|
|
5726
|
+
if (im.isBuiltin(t.integrationId) || !t.gating?.profile || t.gating.category === "disabled") continue;
|
|
5727
|
+
const s = byId.get(t.integrationId) ?? { id: t.integrationId, profile: t.gating.profile, actionTypes: [], writeTools: [], readTools: [] };
|
|
5728
|
+
if (t.gating.category === "read") {
|
|
5729
|
+
s.readTools.push(t.originalName);
|
|
5730
|
+
} else {
|
|
5731
|
+
s.writeTools.push(t.originalName);
|
|
5732
|
+
const at = t.gating.staticExecution?.action_type;
|
|
5733
|
+
if (typeof at === "string" && !s.actionTypes.includes(at)) s.actionTypes.push(at);
|
|
5734
|
+
}
|
|
5735
|
+
byId.set(t.integrationId, s);
|
|
5736
|
+
}
|
|
5737
|
+
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
5738
|
+
}
|
|
5739
|
+
function setupBuiltin(deps) {
|
|
5653
5740
|
return {
|
|
5654
5741
|
id: "setup",
|
|
5655
5742
|
name: "Test setup",
|
|
5656
|
-
description: "Your AI
|
|
5743
|
+
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.",
|
|
5657
5744
|
profile: DELEGATION_PROFILE,
|
|
5658
5745
|
simulation: true,
|
|
5659
5746
|
simulationOnly: true,
|
|
@@ -5665,6 +5752,11 @@ function setupBuiltin(_deps) {
|
|
|
5665
5752
|
hideUnlessAuthorized: true,
|
|
5666
5753
|
approvalView: { content: { label: "New agent brief", kind: "markdown" } }
|
|
5667
5754
|
},
|
|
5755
|
+
get_guide: {
|
|
5756
|
+
category: "read",
|
|
5757
|
+
boundField: "read_access",
|
|
5758
|
+
requiredValue: "unlimited"
|
|
5759
|
+
},
|
|
5668
5760
|
create_mandate: {
|
|
5669
5761
|
executionMapping: {},
|
|
5670
5762
|
staticExecution: { action_type: "mandate" },
|
|
@@ -5684,6 +5776,40 @@ function setupBuiltin(_deps) {
|
|
|
5684
5776
|
}
|
|
5685
5777
|
},
|
|
5686
5778
|
tools: [
|
|
5779
|
+
{
|
|
5780
|
+
name: "get_guide",
|
|
5781
|
+
description: "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.",
|
|
5782
|
+
inputSchema: {
|
|
5783
|
+
type: "object",
|
|
5784
|
+
properties: {
|
|
5785
|
+
topic: { type: "string", description: 'The guide to read, e.g. "interview". Omit for the list of topics.' }
|
|
5786
|
+
},
|
|
5787
|
+
required: []
|
|
5788
|
+
},
|
|
5789
|
+
handler: async (args) => {
|
|
5790
|
+
if (!isSimulationMode()) return { ...builtinText("Refused: not available outside simulation mode."), isError: true };
|
|
5791
|
+
const guides = loadGuides();
|
|
5792
|
+
const header = guideHeader(connectedSystems(deps.integrationManager));
|
|
5793
|
+
const topic = typeof args.topic === "string" ? args.topic.trim().toLowerCase() : "";
|
|
5794
|
+
if (!topic) {
|
|
5795
|
+
const list = guides.map((g, i) => `${i + 1}. **${g.topic}** \u2014 ${g.summary}`).join("\n");
|
|
5796
|
+
return builtinText(`${header}
|
|
5797
|
+
|
|
5798
|
+
**Setup guides, in order:**
|
|
5799
|
+
|
|
5800
|
+
${list || "- none installed"}
|
|
5801
|
+
|
|
5802
|
+
Read each topic with get_guide(topic) before doing that step.`);
|
|
5803
|
+
}
|
|
5804
|
+
const guide = guides.find((g) => g.topic === topic);
|
|
5805
|
+
if (!guide) {
|
|
5806
|
+
return { ...builtinText(`Unknown topic "${topic}". Topics: ${guides.map((g) => g.topic).join(", ") || "none installed"}.`), isError: true };
|
|
5807
|
+
}
|
|
5808
|
+
return builtinText(`${header}
|
|
5809
|
+
|
|
5810
|
+
${guide.body.trim()}`);
|
|
5811
|
+
}
|
|
5812
|
+
},
|
|
5687
5813
|
{
|
|
5688
5814
|
name: "set_agent_brief",
|
|
5689
5815
|
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.",
|
|
@@ -5779,8 +5905,8 @@ function registerBuiltins(deps, factories = BUILTIN_FACTORIES) {
|
|
|
5779
5905
|
import { decodeAttestationBlob as decodeAttestationBlob3 } from "@hap/core";
|
|
5780
5906
|
|
|
5781
5907
|
// src/lib/committed-executor.ts
|
|
5782
|
-
import { existsSync as
|
|
5783
|
-
import { dirname as
|
|
5908
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync18, unlinkSync as unlinkSync6, writeFileSync as writeFileSync12 } from "fs";
|
|
5909
|
+
import { dirname as dirname12, join as join17 } from "path";
|
|
5784
5910
|
var CommittedExecutor = class {
|
|
5785
5911
|
constructor(run) {
|
|
5786
5912
|
this.run = run;
|
|
@@ -5808,7 +5934,7 @@ var ExecutorLock = class {
|
|
|
5808
5934
|
path;
|
|
5809
5935
|
held = false;
|
|
5810
5936
|
constructor(dataDir2) {
|
|
5811
|
-
this.path =
|
|
5937
|
+
this.path = join17(dataDir2, "executor.lock");
|
|
5812
5938
|
}
|
|
5813
5939
|
/** Try to take the lock. Returns the holder's pid when someone else has it. */
|
|
5814
5940
|
acquire() {
|
|
@@ -5816,7 +5942,7 @@ var ExecutorLock = class {
|
|
|
5816
5942
|
if (current && current.pid !== process.pid && isAlive(current.pid)) {
|
|
5817
5943
|
return { held: false, holderPid: current.pid };
|
|
5818
5944
|
}
|
|
5819
|
-
mkdirSync12(
|
|
5945
|
+
mkdirSync12(dirname12(this.path), { recursive: true, mode: 448 });
|
|
5820
5946
|
const lock = { pid: process.pid, startedAt: Math.floor(Date.now() / 1e3) };
|
|
5821
5947
|
writeFileSync12(this.path, JSON.stringify(lock), { mode: 384 });
|
|
5822
5948
|
this.held = true;
|
|
@@ -5840,9 +5966,9 @@ var ExecutorLock = class {
|
|
|
5840
5966
|
}
|
|
5841
5967
|
}
|
|
5842
5968
|
read() {
|
|
5843
|
-
if (!
|
|
5969
|
+
if (!existsSync17(this.path)) return null;
|
|
5844
5970
|
try {
|
|
5845
|
-
const parsed = JSON.parse(
|
|
5971
|
+
const parsed = JSON.parse(readFileSync18(this.path, "utf8"));
|
|
5846
5972
|
return typeof parsed?.pid === "number" ? parsed : null;
|
|
5847
5973
|
} catch {
|
|
5848
5974
|
return null;
|
|
@@ -5860,19 +5986,19 @@ function isAlive(pid) {
|
|
|
5860
5986
|
|
|
5861
5987
|
// bin/http.ts
|
|
5862
5988
|
import { homedir as homedir10 } from "os";
|
|
5863
|
-
import { join as
|
|
5989
|
+
import { join as join19 } from "path";
|
|
5864
5990
|
|
|
5865
5991
|
// src/lib/mcp-as-tracker.ts
|
|
5866
|
-
import { existsSync as
|
|
5867
|
-
import { dirname as
|
|
5992
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync13, readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "fs";
|
|
5993
|
+
import { dirname as dirname13, join as join18 } from "path";
|
|
5868
5994
|
function trackerPath(dataDir2) {
|
|
5869
|
-
return
|
|
5995
|
+
return join18(dataDir2, "mcp-as-pairing.json");
|
|
5870
5996
|
}
|
|
5871
5997
|
function readMcpPairedAsUrl(dataDir2) {
|
|
5872
5998
|
const path = trackerPath(dataDir2);
|
|
5873
|
-
if (!
|
|
5999
|
+
if (!existsSync18(path)) return null;
|
|
5874
6000
|
try {
|
|
5875
|
-
const data = JSON.parse(
|
|
6001
|
+
const data = JSON.parse(readFileSync19(path, "utf-8"));
|
|
5876
6002
|
return typeof data.asUrl === "string" ? data.asUrl : null;
|
|
5877
6003
|
} catch {
|
|
5878
6004
|
return null;
|
|
@@ -5880,12 +6006,12 @@ function readMcpPairedAsUrl(dataDir2) {
|
|
|
5880
6006
|
}
|
|
5881
6007
|
function writeMcpPairedAsUrl(dataDir2, asUrl) {
|
|
5882
6008
|
const path = trackerPath(dataDir2);
|
|
5883
|
-
mkdirSync13(
|
|
6009
|
+
mkdirSync13(dirname13(path), { recursive: true, mode: 448 });
|
|
5884
6010
|
writeFileSync13(path, JSON.stringify({ asUrl }, null, 2), { encoding: "utf-8", mode: 384 });
|
|
5885
6011
|
}
|
|
5886
6012
|
|
|
5887
6013
|
// bin/http.ts
|
|
5888
|
-
var dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
6014
|
+
var dataDir = process.env.SUVEREN_DATA_DIR ?? join19(homedir10(), ".suveren");
|
|
5889
6015
|
var spUrl = resolveAsUrl(dataDir);
|
|
5890
6016
|
validatePinTlsForUrl(resolvePinTls(dataDir), spUrl);
|
|
5891
6017
|
var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
|
|
@@ -6419,7 +6545,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
6419
6545
|
(proposal) => executeCommitted(proposal, state, integrationManager)
|
|
6420
6546
|
);
|
|
6421
6547
|
installCommittedExecutor(committedExecutor);
|
|
6422
|
-
const dataDir2 = process.env.SUVEREN_DATA_DIR ??
|
|
6548
|
+
const dataDir2 = process.env.SUVEREN_DATA_DIR ?? join19(homedir10(), ".suveren");
|
|
6423
6549
|
const executorLock = new ExecutorLock(dataDir2);
|
|
6424
6550
|
const lock = executorLock.acquire();
|
|
6425
6551
|
if (!lock.held) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suveren/gateway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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",
|
package/server.js
CHANGED
|
@@ -168,6 +168,8 @@ const UI_DIST = process.env.HAP_UI_DIST ?? join(__dirname, 'dist', 'ui');
|
|
|
168
168
|
// win for advanced users / Docker.
|
|
169
169
|
const MANIFESTS_DIR = process.env.SUVEREN_MANIFESTS_DIR ?? join(__dirname, 'content', 'integrations');
|
|
170
170
|
const PROFILES_DIR = process.env.SUVEREN_PROFILES_DIR ?? join(__dirname, 'profiles');
|
|
171
|
+
// Default setup guides; SUVEREN_GUIDES_DIR (an override folder) is read on top.
|
|
172
|
+
const BUILTIN_GUIDES_DIR = process.env.SUVEREN_BUILTIN_GUIDES_DIR ?? join(__dirname, 'content', 'guides');
|
|
171
173
|
|
|
172
174
|
const env = {
|
|
173
175
|
...process.env,
|
|
@@ -183,6 +185,7 @@ const env = {
|
|
|
183
185
|
HAP_UI_DIST: UI_DIST,
|
|
184
186
|
SUVEREN_MANIFESTS_DIR: MANIFESTS_DIR,
|
|
185
187
|
SUVEREN_PROFILES_DIR: PROFILES_DIR,
|
|
188
|
+
SUVEREN_BUILTIN_GUIDES_DIR: BUILTIN_GUIDES_DIR,
|
|
186
189
|
// Single shared internal secret so CP↔MCP authenticate the bridge.
|
|
187
190
|
SUVEREN_INTERNAL_SECRET: process.env.SUVEREN_INTERNAL_SECRET ?? randomHex(32),
|
|
188
191
|
// Simulation mode — IT policy (see lib/policy.mjs) wins UNCONDITIONALLY,
|