@sanctuary-framework/mcp-server 0.10.6 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +1907 -243
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1909 -245
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1204 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1206 -10
- package/dist/index.js.map +1 -1
- package/package.json +21 -2
package/dist/cli.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var fs = require('fs');
|
|
|
21
21
|
var index_js$1 = require('@modelcontextprotocol/sdk/client/index.js');
|
|
22
22
|
var stdio_js$1 = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
23
23
|
var sse_js = require('@modelcontextprotocol/sdk/client/sse.js');
|
|
24
|
+
var url = require('url');
|
|
24
25
|
var readline = require('readline');
|
|
25
26
|
var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
26
27
|
|
|
@@ -846,7 +847,8 @@ var init_state_store = __esm({
|
|
|
846
847
|
"_handshake",
|
|
847
848
|
"_shr",
|
|
848
849
|
"_sovereignty_profile",
|
|
849
|
-
"_context_gate_policies"
|
|
850
|
+
"_context_gate_policies",
|
|
851
|
+
"_fortress_mode"
|
|
850
852
|
];
|
|
851
853
|
StateStore = class _StateStore {
|
|
852
854
|
storage;
|
|
@@ -1889,7 +1891,8 @@ var init_tools = __esm({
|
|
|
1889
1891
|
"_handshake",
|
|
1890
1892
|
"_shr",
|
|
1891
1893
|
"_sovereignty_profile",
|
|
1892
|
-
"_context_gate_policies"
|
|
1894
|
+
"_context_gate_policies",
|
|
1895
|
+
"_fortress_mode"
|
|
1893
1896
|
];
|
|
1894
1897
|
IdentityManager = class {
|
|
1895
1898
|
storage;
|
|
@@ -4453,8 +4456,12 @@ var init_loader = __esm({
|
|
|
4453
4456
|
// Clears all runtime governance state — always requires approval
|
|
4454
4457
|
"sanctuary_bootstrap",
|
|
4455
4458
|
// Creates new Ed25519 identity + publishes — always requires approval
|
|
4456
|
-
"sanctuary_export_identity_bundle"
|
|
4459
|
+
"sanctuary_export_identity_bundle",
|
|
4457
4460
|
// Exports portable identity — always requires approval
|
|
4461
|
+
// WP-MVP-2 Operator Console: federation-node-join requires explicit
|
|
4462
|
+
// operator confirmation per Key 8. No auto-approve path. The console's
|
|
4463
|
+
// JoinApprover drives this gate via `MeshConsoleClient.makeJoinApprover`.
|
|
4464
|
+
"federation_node_join"
|
|
4458
4465
|
],
|
|
4459
4466
|
tier2_anomaly: DEFAULT_TIER2,
|
|
4460
4467
|
tier3_always_allow: [
|
|
@@ -6513,6 +6520,28 @@ function generateDashboardHTML(options) {
|
|
|
6513
6520
|
</div>
|
|
6514
6521
|
</div>
|
|
6515
6522
|
|
|
6523
|
+
<!--
|
|
6524
|
+
Mesh Health panel (WP-MVP-3 Follow-up #3).
|
|
6525
|
+
Subscribes to the existing /events SSE channel \u2014 no new transport.
|
|
6526
|
+
Render shape: per-node row with presence + flags + rollup.
|
|
6527
|
+
On open alert: list inline with operator-decision CTAs (rollback /
|
|
6528
|
+
split-brain / canonical-audit promotion).
|
|
6529
|
+
On post-recovery prompt: render rotation-prompt overlay with
|
|
6530
|
+
broker-credential list + "rotate now" buttons (rotation flow itself
|
|
6531
|
+
is the existing v0.10.x broker rotation surface).
|
|
6532
|
+
-->
|
|
6533
|
+
<div class="mesh-health-panel" id="mesh-health-panel">
|
|
6534
|
+
<div class="panel-header">
|
|
6535
|
+
<div class="panel-title">Mesh Health</div>
|
|
6536
|
+
<span class="card-value" id="mesh-health-updated-at" style="font-size: 11px; color: var(--text-secondary);">\u2014</span>
|
|
6537
|
+
</div>
|
|
6538
|
+
<div id="mesh-health-rows" class="mesh-health-rows">
|
|
6539
|
+
<div class="empty-state">Waiting for mesh health data\u2026</div>
|
|
6540
|
+
</div>
|
|
6541
|
+
<div id="mesh-health-alerts" class="mesh-health-alerts" style="margin-top: 12px;"></div>
|
|
6542
|
+
<div id="mesh-post-recovery-prompt" class="mesh-post-recovery-prompt" style="margin-top: 12px; display: none;"></div>
|
|
6543
|
+
</div>
|
|
6544
|
+
|
|
6516
6545
|
<!-- Sovereignty Profile Panel -->
|
|
6517
6546
|
<div class="profile-panel" id="sovereignty-profile-panel">
|
|
6518
6547
|
<div class="panel-header">
|
|
@@ -7222,12 +7251,96 @@ function generateDashboardHTML(options) {
|
|
|
7222
7251
|
loadProxyServers();
|
|
7223
7252
|
});
|
|
7224
7253
|
|
|
7254
|
+
// \u2500\u2500 Mesh Health (WP-MVP-3 Follow-up #3) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7255
|
+
// The federation FailureModeDetector pushes per-tick snapshots and
|
|
7256
|
+
// per-detection alerts via the existing /events channel. Renders are
|
|
7257
|
+
// best-effort \u2014 if the panel DOM is absent (older HTML cache), we
|
|
7258
|
+
// silently skip rather than crashing.
|
|
7259
|
+
eventSource.addEventListener('mesh-health', (e) => {
|
|
7260
|
+
try {
|
|
7261
|
+
const snap = JSON.parse(e.data);
|
|
7262
|
+
renderMeshHealth(snap);
|
|
7263
|
+
} catch (err) { console.error('mesh-health render failed', err); }
|
|
7264
|
+
});
|
|
7265
|
+
eventSource.addEventListener('mesh-failure-mode-alert', (e) => {
|
|
7266
|
+
try {
|
|
7267
|
+
const alert = JSON.parse(e.data);
|
|
7268
|
+
appendMeshAlert(alert);
|
|
7269
|
+
} catch (err) { console.error('mesh alert render failed', err); }
|
|
7270
|
+
});
|
|
7271
|
+
eventSource.addEventListener('mesh-post-recovery-prompt', (e) => {
|
|
7272
|
+
try {
|
|
7273
|
+
const prompt = JSON.parse(e.data);
|
|
7274
|
+
renderMeshPostRecoveryPrompt(prompt);
|
|
7275
|
+
} catch (err) { console.error('mesh post-recovery render failed', err); }
|
|
7276
|
+
});
|
|
7277
|
+
|
|
7225
7278
|
eventSource.onerror = () => {
|
|
7226
7279
|
console.error('SSE error');
|
|
7227
7280
|
setTimeout(setupSSE, 5000);
|
|
7228
7281
|
};
|
|
7229
7282
|
}
|
|
7230
7283
|
|
|
7284
|
+
// \u2500\u2500 Mesh Health rendering (WP-MVP-3 Follow-up #3) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7285
|
+
function renderMeshHealth(snap) {
|
|
7286
|
+
const updatedAt = document.getElementById('mesh-health-updated-at');
|
|
7287
|
+
const rows = document.getElementById('mesh-health-rows');
|
|
7288
|
+
if (!rows || !snap) return;
|
|
7289
|
+
if (updatedAt) updatedAt.textContent = formatTime(snap.generated_at);
|
|
7290
|
+
const nodes = Array.isArray(snap.nodes) ? snap.nodes : [];
|
|
7291
|
+
if (nodes.length === 0) {
|
|
7292
|
+
rows.innerHTML = '<div class="empty-state">No mesh nodes seen yet.</div>';
|
|
7293
|
+
return;
|
|
7294
|
+
}
|
|
7295
|
+
rows.innerHTML = nodes.map((n) => {
|
|
7296
|
+
const flags = (n.flags || []).map((f) => '<span class="mesh-flag">' + esc(f) + '</span>').join(' ');
|
|
7297
|
+
return '<div class="mesh-row" data-rollup="' + esc(n.rollup) + '">' +
|
|
7298
|
+
'<span class="mesh-node-id">' + esc(n.node_id) + '</span>' +
|
|
7299
|
+
'<span class="mesh-presence">' + esc(n.presence) + '</span>' +
|
|
7300
|
+
'<span class="mesh-rollup">' + esc(n.rollup) + '</span>' +
|
|
7301
|
+
'<span class="mesh-flags">' + flags + '</span>' +
|
|
7302
|
+
'</div>';
|
|
7303
|
+
}).join('');
|
|
7304
|
+
const alertsBox = document.getElementById('mesh-health-alerts');
|
|
7305
|
+
if (alertsBox && Array.isArray(snap.open_alerts)) {
|
|
7306
|
+
alertsBox.innerHTML = snap.open_alerts.map((a) =>
|
|
7307
|
+
'<div class="mesh-alert" data-mode="' + esc(a.mode) + '">' +
|
|
7308
|
+
'<div class="mesh-alert-mode">' + esc(a.mode) + ' \u2014 ' + esc(a.target_node) + '</div>' +
|
|
7309
|
+
'<div class="mesh-alert-message">' + esc(a.message) + '</div>' +
|
|
7310
|
+
'</div>'
|
|
7311
|
+
).join('');
|
|
7312
|
+
}
|
|
7313
|
+
}
|
|
7314
|
+
|
|
7315
|
+
function appendMeshAlert(alert) {
|
|
7316
|
+
const alertsBox = document.getElementById('mesh-health-alerts');
|
|
7317
|
+
if (!alertsBox || !alert) return;
|
|
7318
|
+
const html = '<div class="mesh-alert" data-mode="' + esc(alert.mode) + '">' +
|
|
7319
|
+
'<div class="mesh-alert-mode">' + esc(alert.mode) + ' \u2014 ' + esc(alert.target_node) + '</div>' +
|
|
7320
|
+
'<div class="mesh-alert-message">' + esc(alert.message) + '</div>' +
|
|
7321
|
+
'</div>';
|
|
7322
|
+
alertsBox.insertAdjacentHTML('afterbegin', html);
|
|
7323
|
+
}
|
|
7324
|
+
|
|
7325
|
+
function renderMeshPostRecoveryPrompt(prompt) {
|
|
7326
|
+
const box = document.getElementById('mesh-post-recovery-prompt');
|
|
7327
|
+
if (!box || !prompt) return;
|
|
7328
|
+
box.style.display = 'block';
|
|
7329
|
+
const creds = Array.isArray(prompt.credentials) ? prompt.credentials : [];
|
|
7330
|
+
box.innerHTML = '<div class="mesh-rotation-banner">' +
|
|
7331
|
+
'<strong>Post-rotation hygiene:</strong> we just rotated your fortress root key. ' +
|
|
7332
|
+
'Rotate externally-scoped broker credentials that third parties may still associate with ' +
|
|
7333
|
+
'the pre-rotation key material.' +
|
|
7334
|
+
'</div>' +
|
|
7335
|
+
'<ul class="mesh-rotation-list">' +
|
|
7336
|
+
creds.map((c) => '<li>' +
|
|
7337
|
+
'<span class="mesh-cred-name">' + esc(c.secret_name) + '</span>' +
|
|
7338
|
+
'<button class="mesh-cred-rotate" data-secret="' + esc(c.secret_name) + '">' +
|
|
7339
|
+
'rotate now</button>' +
|
|
7340
|
+
'</li>').join('') +
|
|
7341
|
+
'</ul>';
|
|
7342
|
+
}
|
|
7343
|
+
|
|
7231
7344
|
// Activity Feed
|
|
7232
7345
|
function addActivityItem(item) {
|
|
7233
7346
|
activityLog.unshift(item);
|
|
@@ -9807,6 +9920,27 @@ data: ${JSON.stringify(data)}
|
|
|
9807
9920
|
broadcastProtectionStatus(data) {
|
|
9808
9921
|
this.broadcastSSE("protection-status", data);
|
|
9809
9922
|
}
|
|
9923
|
+
// ── Mesh-health surface (WP-MVP-3 Follow-up #3) ─────────────────────
|
|
9924
|
+
//
|
|
9925
|
+
// The federation FailureModeDetector pushes per-tick health snapshots and
|
|
9926
|
+
// per-detection alerts here; the existing /events SSE channel transports
|
|
9927
|
+
// them to the browser. No new transport.
|
|
9928
|
+
//
|
|
9929
|
+
// Spec §8 + §9. Spawn-prompt acceptance criterion 7: "Mesh Health dashboard
|
|
9930
|
+
// panel renders via existing SSE /events channel — no new transport. Every
|
|
9931
|
+
// state transition produces an observable SSE event."
|
|
9932
|
+
/** Push a Mesh Health snapshot (full re-render trigger on the client). */
|
|
9933
|
+
broadcastMeshHealth(snapshot) {
|
|
9934
|
+
this.broadcastSSE("mesh-health", snapshot);
|
|
9935
|
+
}
|
|
9936
|
+
/** Push a single failure-mode alert (incremental client update). */
|
|
9937
|
+
broadcastMeshFailureModeAlert(alert) {
|
|
9938
|
+
this.broadcastSSE("mesh-failure-mode-alert", alert);
|
|
9939
|
+
}
|
|
9940
|
+
/** Push a post-recovery prompt update (master rotation hygiene flow). */
|
|
9941
|
+
broadcastMeshPostRecoveryPrompt(prompt) {
|
|
9942
|
+
this.broadcastSSE("mesh-post-recovery-prompt", prompt);
|
|
9943
|
+
}
|
|
9810
9944
|
/**
|
|
9811
9945
|
* Open a URL in the system's default browser.
|
|
9812
9946
|
* Cross-platform: macOS (open), Linux (xdg-open), Windows (start).
|
|
@@ -13203,7 +13337,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
13203
13337
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13204
13338
|
const canonicalBytes = canonicalize(outcome);
|
|
13205
13339
|
const canonicalString = new TextDecoder().decode(canonicalBytes);
|
|
13206
|
-
const
|
|
13340
|
+
const sha2568 = createCommitment(canonicalString);
|
|
13207
13341
|
let pedersenData;
|
|
13208
13342
|
if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
|
|
13209
13343
|
const pedersen = createPedersenCommitment(outcome.rounds);
|
|
@@ -13215,7 +13349,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
13215
13349
|
const commitmentPayload = {
|
|
13216
13350
|
bridge_commitment_id: commitmentId,
|
|
13217
13351
|
session_id: outcome.session_id,
|
|
13218
|
-
sha256_commitment:
|
|
13352
|
+
sha256_commitment: sha2568.commitment,
|
|
13219
13353
|
terms_hash: outcome.terms_hash,
|
|
13220
13354
|
committer_did: identity.did,
|
|
13221
13355
|
committed_at: now,
|
|
@@ -13226,8 +13360,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
13226
13360
|
return {
|
|
13227
13361
|
bridge_commitment_id: commitmentId,
|
|
13228
13362
|
session_id: outcome.session_id,
|
|
13229
|
-
sha256_commitment:
|
|
13230
|
-
blinding_factor:
|
|
13363
|
+
sha256_commitment: sha2568.commitment,
|
|
13364
|
+
blinding_factor: sha2568.blinding_factor,
|
|
13231
13365
|
committer_did: identity.did,
|
|
13232
13366
|
signature: toBase64url(signature),
|
|
13233
13367
|
pedersen_commitment: pedersenData,
|
|
@@ -22072,7 +22206,13 @@ function l3Card(l3) {
|
|
|
22072
22206
|
);
|
|
22073
22207
|
}
|
|
22074
22208
|
function l4Card(l4) {
|
|
22075
|
-
|
|
22209
|
+
let score;
|
|
22210
|
+
if (l4.score != null) {
|
|
22211
|
+
score = `<div class="score-block"><span class="score-value">${escHtml2(l4.score)}</span><span class="score-label">Verascore</span></div>`;
|
|
22212
|
+
} else {
|
|
22213
|
+
const claimText = l4.claim_cta ?? "Claim your profile at verascore.ai";
|
|
22214
|
+
score = `<div class="claim-block"><a class="claim-link" href="${VERASCORE_CLAIM_URL}" target="_blank" rel="noopener noreferrer">${escHtml2(claimText)}</a></div>`;
|
|
22215
|
+
}
|
|
22076
22216
|
return layerCard(
|
|
22077
22217
|
l4,
|
|
22078
22218
|
`<div class="layer-cta">${score}</div>${l4EvidenceBlock(l4)}`
|
|
@@ -22734,225 +22874,1353 @@ details.audit-details .audit-filters { display: flex; gap: 6px; padding: 0 18px
|
|
|
22734
22874
|
</body>
|
|
22735
22875
|
</html>`;
|
|
22736
22876
|
}
|
|
22737
|
-
var HERO_COPY;
|
|
22877
|
+
var HERO_COPY, VERASCORE_CLAIM_URL;
|
|
22738
22878
|
var init_html = __esm({
|
|
22739
22879
|
"src/dashboard/html.ts"() {
|
|
22740
22880
|
init_multi_html();
|
|
22741
22881
|
HERO_COPY = "Your agent is protected.";
|
|
22882
|
+
VERASCORE_CLAIM_URL = "https://www.verascore.ai";
|
|
22742
22883
|
}
|
|
22743
22884
|
});
|
|
22744
22885
|
|
|
22745
|
-
// src/
|
|
22746
|
-
function
|
|
22747
|
-
|
|
22748
|
-
let diff = 0;
|
|
22749
|
-
for (let i = 0; i < a.length; i++) {
|
|
22750
|
-
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
22751
|
-
}
|
|
22752
|
-
return diff === 0;
|
|
22886
|
+
// src/policy-engine/constants.ts
|
|
22887
|
+
function isPolicySlot(value) {
|
|
22888
|
+
return typeof value === "string" && POLICY_SLOTS.includes(value);
|
|
22753
22889
|
}
|
|
22754
|
-
|
|
22755
|
-
|
|
22756
|
-
|
|
22757
|
-
|
|
22890
|
+
var COMPILED_POLICY_SCHEMA_VERSION, POLICY_UPDATE_EVENT_TYPE, POLICY_SLOTS, CHANNEL_TEMPLATE_IDS, BUDGET_UNITS;
|
|
22891
|
+
var init_constants = __esm({
|
|
22892
|
+
"src/policy-engine/constants.ts"() {
|
|
22893
|
+
COMPILED_POLICY_SCHEMA_VERSION = "0.1";
|
|
22894
|
+
POLICY_UPDATE_EVENT_TYPE = "policy_update";
|
|
22895
|
+
POLICY_SLOTS = [
|
|
22896
|
+
"memory",
|
|
22897
|
+
"credentials",
|
|
22898
|
+
"plans",
|
|
22899
|
+
"outputs"
|
|
22900
|
+
];
|
|
22901
|
+
CHANNEL_TEMPLATE_IDS = [
|
|
22902
|
+
"read-outputs-only",
|
|
22903
|
+
"bidirectional-sync",
|
|
22904
|
+
"credential-share-scoped",
|
|
22905
|
+
"plan-inspect-read-only",
|
|
22906
|
+
"escrow-handoff"
|
|
22907
|
+
];
|
|
22908
|
+
BUDGET_UNITS = ["tokens", "usd"];
|
|
22758
22909
|
}
|
|
22759
|
-
|
|
22760
|
-
|
|
22761
|
-
|
|
22762
|
-
function isAuthorized(deps, req, url) {
|
|
22763
|
-
if (!deps.authToken) return true;
|
|
22764
|
-
const token = extractToken(req, url);
|
|
22765
|
-
if (!token) return false;
|
|
22766
|
-
return constantTimeEquals(token, deps.authToken);
|
|
22767
|
-
}
|
|
22768
|
-
function writeJSON(res, status, payload) {
|
|
22769
|
-
res.writeHead(status, {
|
|
22770
|
-
"Content-Type": "application/json",
|
|
22771
|
-
"Cache-Control": "no-store"
|
|
22772
|
-
});
|
|
22773
|
-
res.end(JSON.stringify(payload));
|
|
22774
|
-
}
|
|
22775
|
-
function writeText(res, status, body, contentType = "text/plain") {
|
|
22776
|
-
res.writeHead(status, {
|
|
22777
|
-
"Content-Type": contentType,
|
|
22778
|
-
"Cache-Control": "no-store"
|
|
22779
|
-
});
|
|
22780
|
-
res.end(body);
|
|
22910
|
+
});
|
|
22911
|
+
function isTemplateName(value) {
|
|
22912
|
+
return typeof value === "string" && TEMPLATE_NAMES.includes(value);
|
|
22781
22913
|
}
|
|
22782
|
-
|
|
22783
|
-
|
|
22784
|
-
|
|
22785
|
-
const method = (req.method ?? "GET").toUpperCase();
|
|
22786
|
-
const path = url.pathname;
|
|
22787
|
-
if (!isAuthorized(deps, req, url)) {
|
|
22788
|
-
writeJSON(res, 401, { error: "unauthorized" });
|
|
22789
|
-
return true;
|
|
22914
|
+
function validateMetadata(name, data) {
|
|
22915
|
+
if (typeof data !== "object" || data === null) {
|
|
22916
|
+
throw new TemplateValidationError(name, "template.json must be an object");
|
|
22790
22917
|
}
|
|
22791
|
-
|
|
22792
|
-
|
|
22793
|
-
|
|
22918
|
+
const d = data;
|
|
22919
|
+
if (typeof d.name !== "string" || d.name !== name) {
|
|
22920
|
+
throw new TemplateValidationError(name, `template.json name must be "${name}"`);
|
|
22794
22921
|
}
|
|
22795
|
-
if (
|
|
22796
|
-
|
|
22797
|
-
const html = renderDashboardHTML({ snapshot, authToken: deps.authToken });
|
|
22798
|
-
writeText(res, 200, html, "text/html; charset=utf-8");
|
|
22799
|
-
return true;
|
|
22922
|
+
if (typeof d.version !== "string" || !/^\d+\.\d+\.\d+$/.test(d.version)) {
|
|
22923
|
+
throw new TemplateValidationError(name, "template.json version must be semver");
|
|
22800
22924
|
}
|
|
22801
|
-
if (
|
|
22802
|
-
|
|
22803
|
-
|
|
22804
|
-
|
|
22925
|
+
if (typeof d.channel !== "string" || !CHANNEL_TEMPLATE_IDS.includes(d.channel)) {
|
|
22926
|
+
throw new TemplateValidationError(
|
|
22927
|
+
name,
|
|
22928
|
+
`template.json channel must be one of: ${CHANNEL_TEMPLATE_IDS.join(", ")}`
|
|
22929
|
+
);
|
|
22805
22930
|
}
|
|
22806
|
-
|
|
22807
|
-
|
|
22808
|
-
|
|
22809
|
-
|
|
22810
|
-
|
|
22811
|
-
|
|
22812
|
-
|
|
22931
|
+
if (typeof d.tier !== "string" || !["A", "B", "C"].includes(d.tier)) {
|
|
22932
|
+
throw new TemplateValidationError(name, "template.json tier must be A, B, or C");
|
|
22933
|
+
}
|
|
22934
|
+
if (typeof d.target_archetype !== "string" || d.target_archetype.length === 0) {
|
|
22935
|
+
throw new TemplateValidationError(name, "template.json target_archetype must be a non-empty string");
|
|
22936
|
+
}
|
|
22937
|
+
if (typeof d.description !== "string" || d.description.length === 0) {
|
|
22938
|
+
throw new TemplateValidationError(name, "template.json description must be a non-empty string");
|
|
22939
|
+
}
|
|
22940
|
+
}
|
|
22941
|
+
function validateDefaults(name, data) {
|
|
22942
|
+
if (typeof data !== "object" || data === null) {
|
|
22943
|
+
throw new TemplateValidationError(name, "defaults.json must be an object");
|
|
22944
|
+
}
|
|
22945
|
+
const d = data;
|
|
22946
|
+
if (!Array.isArray(d.egress)) {
|
|
22947
|
+
throw new TemplateValidationError(name, "defaults.json egress must be an array");
|
|
22948
|
+
}
|
|
22949
|
+
for (const entry of d.egress) {
|
|
22950
|
+
if (typeof entry !== "object" || entry === null) {
|
|
22951
|
+
throw new TemplateValidationError(name, "defaults.json egress entry must be an object");
|
|
22813
22952
|
}
|
|
22814
|
-
const
|
|
22815
|
-
|
|
22816
|
-
|
|
22817
|
-
|
|
22818
|
-
|
|
22819
|
-
|
|
22953
|
+
const e = entry;
|
|
22954
|
+
if (typeof e.destination !== "string" || e.destination.length === 0) {
|
|
22955
|
+
throw new TemplateValidationError(name, "egress entry destination must be a non-empty string");
|
|
22956
|
+
}
|
|
22957
|
+
if (!Array.isArray(e.methods)) {
|
|
22958
|
+
throw new TemplateValidationError(name, "egress entry methods must be an array");
|
|
22820
22959
|
}
|
|
22821
|
-
return true;
|
|
22822
22960
|
}
|
|
22823
|
-
if (
|
|
22824
|
-
|
|
22825
|
-
|
|
22961
|
+
if (typeof d.budgets !== "object" || d.budgets === null) {
|
|
22962
|
+
throw new TemplateValidationError(name, "defaults.json budgets must be an object");
|
|
22963
|
+
}
|
|
22964
|
+
if (typeof d.retention !== "object" || d.retention === null) {
|
|
22965
|
+
throw new TemplateValidationError(name, "defaults.json retention must be an object");
|
|
22966
|
+
}
|
|
22967
|
+
const ret = d.retention;
|
|
22968
|
+
if (typeof ret.windows !== "object" || ret.windows === null) {
|
|
22969
|
+
throw new TemplateValidationError(name, "defaults.json retention.windows must be an object");
|
|
22826
22970
|
}
|
|
22827
|
-
return false;
|
|
22828
22971
|
}
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
"
|
|
22832
|
-
|
|
22833
|
-
|
|
22834
|
-
|
|
22835
|
-
|
|
22836
|
-
|
|
22837
|
-
|
|
22838
|
-
|
|
22839
|
-
|
|
22840
|
-
`);
|
|
22841
|
-
const unsubscribe = deps.onEvent ? deps.onEvent((event) => {
|
|
22842
|
-
try {
|
|
22843
|
-
res.write(`event: ${event.type}
|
|
22844
|
-
data: ${JSON.stringify(event.data)}
|
|
22845
|
-
|
|
22846
|
-
`);
|
|
22847
|
-
} catch {
|
|
22972
|
+
function validateCommitments(name, data) {
|
|
22973
|
+
if (typeof data !== "object" || data === null) {
|
|
22974
|
+
throw new TemplateValidationError(name, "commitments.json must be an object");
|
|
22975
|
+
}
|
|
22976
|
+
const d = data;
|
|
22977
|
+
if (!Array.isArray(d.shapes)) {
|
|
22978
|
+
throw new TemplateValidationError(name, "commitments.json shapes must be an array");
|
|
22979
|
+
}
|
|
22980
|
+
for (const shape of d.shapes) {
|
|
22981
|
+
if (typeof shape !== "object" || shape === null) {
|
|
22982
|
+
throw new TemplateValidationError(name, "commitment shape must be an object");
|
|
22848
22983
|
}
|
|
22849
|
-
|
|
22850
|
-
|
|
22851
|
-
|
|
22852
|
-
try {
|
|
22853
|
-
res.write(": keepalive\n\n");
|
|
22854
|
-
} catch {
|
|
22984
|
+
const s = shape;
|
|
22985
|
+
if (typeof s.commitment_class !== "string" || s.commitment_class.length === 0) {
|
|
22986
|
+
throw new TemplateValidationError(name, "commitment shape commitment_class must be a non-empty string");
|
|
22855
22987
|
}
|
|
22856
|
-
|
|
22857
|
-
|
|
22858
|
-
|
|
22859
|
-
|
|
22860
|
-
|
|
22861
|
-
|
|
22862
|
-
|
|
22988
|
+
if (typeof s.example_deliverable !== "string") {
|
|
22989
|
+
throw new TemplateValidationError(name, "commitment shape example_deliverable must be a string");
|
|
22990
|
+
}
|
|
22991
|
+
if (typeof s.example_deadline_or_terminal !== "string") {
|
|
22992
|
+
throw new TemplateValidationError(name, "commitment shape example_deadline_or_terminal must be a string");
|
|
22993
|
+
}
|
|
22994
|
+
}
|
|
22863
22995
|
}
|
|
22864
|
-
|
|
22865
|
-
|
|
22866
|
-
|
|
22867
|
-
|
|
22996
|
+
function lintOnboarding(_name, content) {
|
|
22997
|
+
const violations = [];
|
|
22998
|
+
if (content.includes("\u2014")) {
|
|
22999
|
+
violations.push("onboarding.md contains em-dashes (U+2014). Replace with commas, semicolons, colons, or parentheses.");
|
|
22868
23000
|
}
|
|
22869
|
-
|
|
22870
|
-
|
|
22871
|
-
|
|
22872
|
-
|
|
22873
|
-
|
|
22874
|
-
|
|
22875
|
-
|
|
22876
|
-
|
|
22877
|
-
|
|
22878
|
-
|
|
22879
|
-
|
|
22880
|
-
|
|
22881
|
-
|
|
22882
|
-
|
|
22883
|
-
|
|
22884
|
-
|
|
22885
|
-
|
|
22886
|
-
|
|
22887
|
-
|
|
22888
|
-
|
|
22889
|
-
|
|
22890
|
-
|
|
23001
|
+
if (content.includes("\u2013")) {
|
|
23002
|
+
violations.push("onboarding.md contains en-dashes (U+2013). Replace with hyphens or other punctuation.");
|
|
23003
|
+
}
|
|
23004
|
+
return { clean: violations.length === 0, violations };
|
|
23005
|
+
}
|
|
23006
|
+
function resolveTemplatesDir() {
|
|
23007
|
+
const thisFile = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href)));
|
|
23008
|
+
const thisDir = path.dirname(thisFile);
|
|
23009
|
+
if (thisDir.includes("/dist/")) {
|
|
23010
|
+
return thisDir.replace("/dist/templates", "/src/templates");
|
|
23011
|
+
}
|
|
23012
|
+
return thisDir;
|
|
23013
|
+
}
|
|
23014
|
+
function loadTemplateBundle(name, templatesDir) {
|
|
23015
|
+
const dir = path.join(templatesDir, name);
|
|
23016
|
+
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
|
23017
|
+
throw new TemplateValidationError(name, `template directory not found: ${dir}`);
|
|
23018
|
+
}
|
|
23019
|
+
const metadataRaw = JSON.parse(fs.readFileSync(path.join(dir, "template.json"), "utf-8"));
|
|
23020
|
+
validateMetadata(name, metadataRaw);
|
|
23021
|
+
const policyEnglish = fs.readFileSync(path.join(dir, "policy.md"), "utf-8").trim();
|
|
23022
|
+
if (policyEnglish.length === 0) {
|
|
23023
|
+
throw new TemplateValidationError(name, "policy.md must not be empty");
|
|
23024
|
+
}
|
|
23025
|
+
const defaultsRaw = JSON.parse(fs.readFileSync(path.join(dir, "defaults.json"), "utf-8"));
|
|
23026
|
+
validateDefaults(name, defaultsRaw);
|
|
23027
|
+
const commitmentsRaw = JSON.parse(fs.readFileSync(path.join(dir, "commitments.json"), "utf-8"));
|
|
23028
|
+
validateCommitments(name, commitmentsRaw);
|
|
23029
|
+
const onboarding = fs.readFileSync(path.join(dir, "onboarding.md"), "utf-8").trim();
|
|
23030
|
+
if (onboarding.length === 0) {
|
|
23031
|
+
throw new TemplateValidationError(name, "onboarding.md must not be empty");
|
|
23032
|
+
}
|
|
23033
|
+
const lint = lintOnboarding(name, onboarding);
|
|
23034
|
+
if (!lint.clean) {
|
|
23035
|
+
throw new TemplateValidationError(name, lint.violations.join("; "));
|
|
23036
|
+
}
|
|
23037
|
+
return {
|
|
23038
|
+
metadata: metadataRaw,
|
|
23039
|
+
policy_english: policyEnglish,
|
|
23040
|
+
defaults: defaultsRaw,
|
|
23041
|
+
commitments: commitmentsRaw,
|
|
23042
|
+
onboarding
|
|
22891
23043
|
};
|
|
22892
|
-
|
|
22893
|
-
|
|
22894
|
-
|
|
22895
|
-
|
|
22896
|
-
|
|
22897
|
-
|
|
22898
|
-
|
|
22899
|
-
|
|
22900
|
-
|
|
22901
|
-
|
|
22902
|
-
|
|
22903
|
-
|
|
22904
|
-
|
|
22905
|
-
|
|
23044
|
+
}
|
|
23045
|
+
function ensureLoaded() {
|
|
23046
|
+
if (_cache) return _cache;
|
|
23047
|
+
_cache = /* @__PURE__ */ new Map();
|
|
23048
|
+
const dir = resolveTemplatesDir();
|
|
23049
|
+
for (const name of TEMPLATE_NAMES) {
|
|
23050
|
+
_cache.set(name, loadTemplateBundle(name, dir));
|
|
23051
|
+
}
|
|
23052
|
+
return _cache;
|
|
23053
|
+
}
|
|
23054
|
+
function listTemplates() {
|
|
23055
|
+
const cache = ensureLoaded();
|
|
23056
|
+
return TEMPLATE_NAMES.map((name) => {
|
|
23057
|
+
const bundle = cache.get(name);
|
|
23058
|
+
return {
|
|
23059
|
+
metadata: bundle.metadata,
|
|
23060
|
+
onboarding: bundle.onboarding
|
|
23061
|
+
};
|
|
22906
23062
|
});
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
|
|
22911
|
-
|
|
22912
|
-
|
|
23063
|
+
}
|
|
23064
|
+
function getTemplate2(name) {
|
|
23065
|
+
if (!isTemplateName(name)) return null;
|
|
23066
|
+
const cache = ensureLoaded();
|
|
23067
|
+
return cache.get(name) ?? null;
|
|
23068
|
+
}
|
|
23069
|
+
function getTemplateEntry(name) {
|
|
23070
|
+
const bundle = getTemplate2(name);
|
|
23071
|
+
if (!bundle) return null;
|
|
23072
|
+
return { metadata: bundle.metadata, onboarding: bundle.onboarding };
|
|
23073
|
+
}
|
|
23074
|
+
var TEMPLATE_NAMES, TemplateValidationError, _cache;
|
|
23075
|
+
var init_registry2 = __esm({
|
|
23076
|
+
"src/templates/registry.ts"() {
|
|
23077
|
+
init_constants();
|
|
23078
|
+
TEMPLATE_NAMES = [
|
|
23079
|
+
"research-assistant",
|
|
23080
|
+
"coding-assistant",
|
|
23081
|
+
"ops-runner",
|
|
23082
|
+
"planner",
|
|
23083
|
+
"handoff-coordinator",
|
|
23084
|
+
"x-miner",
|
|
23085
|
+
"github-miner"
|
|
23086
|
+
];
|
|
23087
|
+
TemplateValidationError = class extends Error {
|
|
23088
|
+
constructor(templateName, message) {
|
|
23089
|
+
super(`template "${templateName}": ${message}`);
|
|
23090
|
+
this.name = "TemplateValidationError";
|
|
23091
|
+
}
|
|
23092
|
+
};
|
|
23093
|
+
_cache = null;
|
|
23094
|
+
}
|
|
23095
|
+
});
|
|
23096
|
+
|
|
23097
|
+
// src/policy-engine/null-policy.ts
|
|
23098
|
+
function buildNullPolicy(params) {
|
|
23099
|
+
const denyRule = (slot) => ({
|
|
23100
|
+
slot,
|
|
23101
|
+
mode: "deny",
|
|
23102
|
+
grants: []
|
|
22913
23103
|
});
|
|
22914
|
-
const actualPort = (() => {
|
|
22915
|
-
const addr = server.address();
|
|
22916
|
-
if (addr && typeof addr === "object") return addr.port;
|
|
22917
|
-
return port;
|
|
22918
|
-
})();
|
|
22919
|
-
const url = `http://${host}:${actualPort}`;
|
|
22920
23104
|
return {
|
|
22921
|
-
|
|
22922
|
-
|
|
22923
|
-
|
|
22924
|
-
|
|
22925
|
-
|
|
22926
|
-
|
|
22927
|
-
|
|
22928
|
-
|
|
22929
|
-
|
|
23105
|
+
schema_version: "0.1",
|
|
23106
|
+
agent_id: params.agent_id,
|
|
23107
|
+
fortress_id: params.fortress_id,
|
|
23108
|
+
policy_version: 0,
|
|
23109
|
+
slots: {
|
|
23110
|
+
memory: denyRule("memory"),
|
|
23111
|
+
credentials: denyRule("credentials"),
|
|
23112
|
+
plans: denyRule("plans"),
|
|
23113
|
+
outputs: denyRule("outputs")
|
|
23114
|
+
},
|
|
23115
|
+
capabilities: {
|
|
23116
|
+
concordia_commitment_classes: [],
|
|
23117
|
+
honeypot_skill_ids: [],
|
|
23118
|
+
is_sentinel: false
|
|
23119
|
+
},
|
|
23120
|
+
auto_trigger_ladder: {
|
|
23121
|
+
honeypot_auto_freeze: true,
|
|
23122
|
+
threshold_rule_action: "operator_approved",
|
|
23123
|
+
ml_anomaly_action: "operator_approved"
|
|
23124
|
+
},
|
|
23125
|
+
source_english: "(null policy \u2014 no operator authoring yet; hermetic default denies all four slots)",
|
|
23126
|
+
compiled_at: "1970-01-01T00:00:00.000Z"
|
|
22930
23127
|
};
|
|
22931
23128
|
}
|
|
22932
|
-
var
|
|
22933
|
-
|
|
22934
|
-
"src/dashboard/server.ts"() {
|
|
22935
|
-
init_api();
|
|
22936
|
-
DEFAULT_PORT = 3501;
|
|
22937
|
-
DEFAULT_HOST = "127.0.0.1";
|
|
23129
|
+
var init_null_policy = __esm({
|
|
23130
|
+
"src/policy-engine/null-policy.ts"() {
|
|
22938
23131
|
}
|
|
22939
23132
|
});
|
|
22940
23133
|
|
|
22941
|
-
// src/
|
|
22942
|
-
|
|
22943
|
-
|
|
22944
|
-
|
|
22945
|
-
|
|
22946
|
-
|
|
22947
|
-
|
|
22948
|
-
|
|
22949
|
-
|
|
22950
|
-
|
|
22951
|
-
|
|
22952
|
-
|
|
22953
|
-
|
|
22954
|
-
|
|
22955
|
-
|
|
23134
|
+
// src/policy-engine/channel-templates.ts
|
|
23135
|
+
function basePolicy(params) {
|
|
23136
|
+
if (params.merge_into) {
|
|
23137
|
+
const p2 = {
|
|
23138
|
+
...params.merge_into,
|
|
23139
|
+
policy_version: params.policy_version,
|
|
23140
|
+
compiled_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23141
|
+
slots: {
|
|
23142
|
+
memory: cloneRule(params.merge_into.slots.memory),
|
|
23143
|
+
credentials: cloneRule(params.merge_into.slots.credentials),
|
|
23144
|
+
plans: cloneRule(params.merge_into.slots.plans),
|
|
23145
|
+
outputs: cloneRule(params.merge_into.slots.outputs)
|
|
23146
|
+
},
|
|
23147
|
+
capabilities: {
|
|
23148
|
+
...params.merge_into.capabilities,
|
|
23149
|
+
concordia_commitment_classes: [
|
|
23150
|
+
...params.merge_into.capabilities.concordia_commitment_classes
|
|
23151
|
+
],
|
|
23152
|
+
honeypot_skill_ids: [...params.merge_into.capabilities.honeypot_skill_ids]
|
|
23153
|
+
},
|
|
23154
|
+
auto_trigger_ladder: { ...params.merge_into.auto_trigger_ladder }
|
|
23155
|
+
};
|
|
23156
|
+
if (params.parent_version !== void 0) p2.parent_version = params.parent_version;
|
|
23157
|
+
else delete p2.parent_version;
|
|
23158
|
+
return p2;
|
|
23159
|
+
}
|
|
23160
|
+
const p = buildNullPolicy({
|
|
23161
|
+
agent_id: params.agent_id,
|
|
23162
|
+
fortress_id: params.fortress_id
|
|
23163
|
+
});
|
|
23164
|
+
p.policy_version = params.policy_version;
|
|
23165
|
+
p.compiled_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
23166
|
+
if (params.parent_version !== void 0) p.parent_version = params.parent_version;
|
|
23167
|
+
return p;
|
|
23168
|
+
}
|
|
23169
|
+
function cloneRule(r) {
|
|
23170
|
+
return {
|
|
23171
|
+
slot: r.slot,
|
|
23172
|
+
mode: r.mode,
|
|
23173
|
+
grants: r.grants.map((g) => ({ ...g, scope: g.scope ? { ...g.scope } : void 0 }))
|
|
23174
|
+
};
|
|
23175
|
+
}
|
|
23176
|
+
function grantOn(policy, slot, grant) {
|
|
23177
|
+
const rule = policy.slots[slot];
|
|
23178
|
+
rule.mode = "grant";
|
|
23179
|
+
const dup = rule.grants.find(
|
|
23180
|
+
(g) => g.counterparty === grant.counterparty && g.action === grant.action
|
|
23181
|
+
);
|
|
23182
|
+
if (dup) {
|
|
23183
|
+
if (grant.scope) {
|
|
23184
|
+
dup.scope = { ...dup.scope ?? {}, ...grant.scope };
|
|
23185
|
+
}
|
|
23186
|
+
return;
|
|
23187
|
+
}
|
|
23188
|
+
rule.grants.push(grant);
|
|
23189
|
+
rule.grants.sort((a, b) => {
|
|
23190
|
+
if (a.counterparty === b.counterparty) return a.action.localeCompare(b.action);
|
|
23191
|
+
return a.counterparty.localeCompare(b.counterparty);
|
|
23192
|
+
});
|
|
23193
|
+
}
|
|
23194
|
+
function applyChannelTemplate(id, params) {
|
|
23195
|
+
const entry = REGISTRY[id];
|
|
23196
|
+
if (!entry) {
|
|
23197
|
+
throw new Error(`unknown channel template: ${id}`);
|
|
23198
|
+
}
|
|
23199
|
+
return entry.factory(params);
|
|
23200
|
+
}
|
|
23201
|
+
var readOutputsOnly, bidirectionalSync, credentialShareScoped, planInspectReadOnly, escrowHandoff, REGISTRY;
|
|
23202
|
+
var init_channel_templates = __esm({
|
|
23203
|
+
"src/policy-engine/channel-templates.ts"() {
|
|
23204
|
+
init_constants();
|
|
23205
|
+
init_null_policy();
|
|
23206
|
+
readOutputsOnly = (params) => {
|
|
23207
|
+
const p = basePolicy(params);
|
|
23208
|
+
p.source_english = `${params.counterparty} may read ${params.agent_id}'s outputs \u2014 read-only, no other access.`;
|
|
23209
|
+
grantOn(p, "outputs", {
|
|
23210
|
+
counterparty: params.counterparty,
|
|
23211
|
+
action: "read",
|
|
23212
|
+
...params.scope ? { scope: { ...params.scope } } : {}
|
|
23213
|
+
});
|
|
23214
|
+
return p;
|
|
23215
|
+
};
|
|
23216
|
+
bidirectionalSync = (params) => {
|
|
23217
|
+
const p = basePolicy(params);
|
|
23218
|
+
p.source_english = `Bidirectional sync between ${params.agent_id} and ${params.counterparty} on memory + outputs.`;
|
|
23219
|
+
for (const slot of ["memory", "outputs"]) {
|
|
23220
|
+
for (const action of ["read", "subscribe"]) {
|
|
23221
|
+
grantOn(p, slot, {
|
|
23222
|
+
counterparty: params.counterparty,
|
|
23223
|
+
action,
|
|
23224
|
+
...params.scope ? { scope: { ...params.scope } } : {}
|
|
23225
|
+
});
|
|
23226
|
+
}
|
|
23227
|
+
}
|
|
23228
|
+
return p;
|
|
23229
|
+
};
|
|
23230
|
+
credentialShareScoped = (params) => {
|
|
23231
|
+
const p = basePolicy(params);
|
|
23232
|
+
const credentialId = (params.scope && typeof params.scope.credential_id === "string" ? params.scope.credential_id : void 0) ?? "(unspecified)";
|
|
23233
|
+
p.source_english = `${params.agent_id} may share credential "${credentialId}" with ${params.counterparty}; no other credential access.`;
|
|
23234
|
+
grantOn(p, "credentials", {
|
|
23235
|
+
counterparty: params.counterparty,
|
|
23236
|
+
action: "share",
|
|
23237
|
+
scope: {
|
|
23238
|
+
credential_id: credentialId,
|
|
23239
|
+
...params.scope ?? {}
|
|
23240
|
+
}
|
|
23241
|
+
});
|
|
23242
|
+
return p;
|
|
23243
|
+
};
|
|
23244
|
+
planInspectReadOnly = (params) => {
|
|
23245
|
+
const p = basePolicy(params);
|
|
23246
|
+
p.source_english = `${params.counterparty} may read-only inspect ${params.agent_id}'s plans.`;
|
|
23247
|
+
grantOn(p, "plans", {
|
|
23248
|
+
counterparty: params.counterparty,
|
|
23249
|
+
action: "read",
|
|
23250
|
+
...params.scope ? { scope: { ...params.scope } } : {}
|
|
23251
|
+
});
|
|
23252
|
+
return p;
|
|
23253
|
+
};
|
|
23254
|
+
escrowHandoff = (params) => {
|
|
23255
|
+
const p = basePolicy(params);
|
|
23256
|
+
p.source_english = `${params.agent_id} may escrow-handoff outputs and plan-read to ${params.counterparty}. Commitment class: intra-mesh-escrow.`;
|
|
23257
|
+
grantOn(p, "plans", {
|
|
23258
|
+
counterparty: params.counterparty,
|
|
23259
|
+
action: "read",
|
|
23260
|
+
...params.scope ? { scope: { ...params.scope } } : {}
|
|
23261
|
+
});
|
|
23262
|
+
for (const action of ["read", "subscribe"]) {
|
|
23263
|
+
grantOn(p, "outputs", {
|
|
23264
|
+
counterparty: params.counterparty,
|
|
23265
|
+
action,
|
|
23266
|
+
...params.scope ? { scope: { ...params.scope } } : {}
|
|
23267
|
+
});
|
|
23268
|
+
}
|
|
23269
|
+
const cls = "intra-mesh-escrow";
|
|
23270
|
+
if (!p.capabilities.concordia_commitment_classes.includes(cls)) {
|
|
23271
|
+
p.capabilities.concordia_commitment_classes.push(cls);
|
|
23272
|
+
p.capabilities.concordia_commitment_classes.sort();
|
|
23273
|
+
}
|
|
23274
|
+
return p;
|
|
23275
|
+
};
|
|
23276
|
+
REGISTRY = {
|
|
23277
|
+
"read-outputs-only": {
|
|
23278
|
+
id: "read-outputs-only",
|
|
23279
|
+
label: "Read outputs only",
|
|
23280
|
+
description: "Counterparty may read this agent's outputs. No memory, credentials, or plans access.",
|
|
23281
|
+
factory: readOutputsOnly
|
|
23282
|
+
},
|
|
23283
|
+
"bidirectional-sync": {
|
|
23284
|
+
id: "bidirectional-sync",
|
|
23285
|
+
label: "Bidirectional memory + output sync",
|
|
23286
|
+
description: "Both agents may read and subscribe to each other's memory and outputs. Credentials and plans remain hermetic.",
|
|
23287
|
+
factory: bidirectionalSync
|
|
23288
|
+
},
|
|
23289
|
+
"credential-share-scoped": {
|
|
23290
|
+
id: "credential-share-scoped",
|
|
23291
|
+
label: "Scoped credential share",
|
|
23292
|
+
description: "Share one specific credential with counterparty. Requires scope.credential_id. No broad credential access.",
|
|
23293
|
+
factory: credentialShareScoped
|
|
23294
|
+
},
|
|
23295
|
+
"plan-inspect-read-only": {
|
|
23296
|
+
id: "plan-inspect-read-only",
|
|
23297
|
+
label: "Plan inspect (read-only)",
|
|
23298
|
+
description: "Counterparty may read-only inspect this agent's plans. Intended for supervisor / sentinel patterns.",
|
|
23299
|
+
factory: planInspectReadOnly
|
|
23300
|
+
},
|
|
23301
|
+
"escrow-handoff": {
|
|
23302
|
+
id: "escrow-handoff",
|
|
23303
|
+
label: "Escrow handoff",
|
|
23304
|
+
description: "Escrow-style handoff. Counterparty reads plan, reads + subscribes to outputs, and the intra-mesh-escrow commitment class is declared. Memory and credentials remain hermetic.",
|
|
23305
|
+
factory: escrowHandoff
|
|
23306
|
+
}
|
|
23307
|
+
};
|
|
23308
|
+
}
|
|
23309
|
+
});
|
|
23310
|
+
|
|
23311
|
+
// src/mesh/errors.ts
|
|
23312
|
+
var MeshError, MeshEnvelopeError, MeshReservedExtensionKeyError, MeshReservedEventTypeError;
|
|
23313
|
+
var init_errors = __esm({
|
|
23314
|
+
"src/mesh/errors.ts"() {
|
|
23315
|
+
MeshError = class extends Error {
|
|
23316
|
+
constructor(message) {
|
|
23317
|
+
super(message);
|
|
23318
|
+
this.name = "MeshError";
|
|
23319
|
+
}
|
|
23320
|
+
};
|
|
23321
|
+
MeshEnvelopeError = class extends MeshError {
|
|
23322
|
+
constructor(message) {
|
|
23323
|
+
super(message);
|
|
23324
|
+
this.name = "MeshEnvelopeError";
|
|
23325
|
+
}
|
|
23326
|
+
};
|
|
23327
|
+
MeshReservedExtensionKeyError = class extends MeshEnvelopeError {
|
|
23328
|
+
constructor(key) {
|
|
23329
|
+
super(
|
|
23330
|
+
`v0.1 emitters MUST NOT populate reserved extension_envelope key: ${key}`
|
|
23331
|
+
);
|
|
23332
|
+
this.name = "MeshReservedExtensionKeyError";
|
|
23333
|
+
}
|
|
23334
|
+
};
|
|
23335
|
+
MeshReservedEventTypeError = class extends MeshEnvelopeError {
|
|
23336
|
+
constructor(eventType) {
|
|
23337
|
+
super(
|
|
23338
|
+
`v0.1 emitters MUST NOT emit reserved-namespace event_type: ${eventType}`
|
|
23339
|
+
);
|
|
23340
|
+
this.name = "MeshReservedEventTypeError";
|
|
23341
|
+
}
|
|
23342
|
+
};
|
|
23343
|
+
}
|
|
23344
|
+
});
|
|
23345
|
+
|
|
23346
|
+
// src/mesh/canonical-json.ts
|
|
23347
|
+
function canonicalize2(value) {
|
|
23348
|
+
if (value === void 0) {
|
|
23349
|
+
throw new MeshCanonicalJsonError(
|
|
23350
|
+
"canonicalize(): top-level undefined is not serializable"
|
|
23351
|
+
);
|
|
23352
|
+
}
|
|
23353
|
+
return encode(value);
|
|
23354
|
+
}
|
|
23355
|
+
function encode(value) {
|
|
23356
|
+
if (value === null) return "null";
|
|
23357
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
23358
|
+
if (typeof value === "number") {
|
|
23359
|
+
if (!Number.isFinite(value)) {
|
|
23360
|
+
throw new MeshCanonicalJsonError(
|
|
23361
|
+
`canonicalize(): non-finite number (${String(value)}) is not serializable`
|
|
23362
|
+
);
|
|
23363
|
+
}
|
|
23364
|
+
return JSON.stringify(value);
|
|
23365
|
+
}
|
|
23366
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
23367
|
+
if (Array.isArray(value)) return encodeArray(value);
|
|
23368
|
+
if (typeof value === "object") return encodeObject(value);
|
|
23369
|
+
throw new MeshCanonicalJsonError(
|
|
23370
|
+
`canonicalize(): unsupported type ${typeof value}`
|
|
23371
|
+
);
|
|
23372
|
+
}
|
|
23373
|
+
function encodeArray(arr) {
|
|
23374
|
+
const parts = [];
|
|
23375
|
+
for (const item of arr) {
|
|
23376
|
+
parts.push(item === void 0 ? "null" : encode(item));
|
|
23377
|
+
}
|
|
23378
|
+
return "[" + parts.join(",") + "]";
|
|
23379
|
+
}
|
|
23380
|
+
function encodeObject(obj) {
|
|
23381
|
+
const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
|
|
23382
|
+
const parts = [];
|
|
23383
|
+
for (const k of keys) {
|
|
23384
|
+
parts.push(JSON.stringify(k) + ":" + encode(obj[k]));
|
|
23385
|
+
}
|
|
23386
|
+
return "{" + parts.join(",") + "}";
|
|
23387
|
+
}
|
|
23388
|
+
function canonicalizeToBytes(value) {
|
|
23389
|
+
return new TextEncoder().encode(canonicalize2(value));
|
|
23390
|
+
}
|
|
23391
|
+
var MeshCanonicalJsonError;
|
|
23392
|
+
var init_canonical_json = __esm({
|
|
23393
|
+
"src/mesh/canonical-json.ts"() {
|
|
23394
|
+
init_errors();
|
|
23395
|
+
MeshCanonicalJsonError = class extends MeshError {
|
|
23396
|
+
constructor(message) {
|
|
23397
|
+
super(message);
|
|
23398
|
+
this.name = "MeshCanonicalJsonError";
|
|
23399
|
+
}
|
|
23400
|
+
};
|
|
23401
|
+
}
|
|
23402
|
+
});
|
|
23403
|
+
|
|
23404
|
+
// src/policy-engine/errors.ts
|
|
23405
|
+
var PolicyEngineError, CompiledPolicyShapeError;
|
|
23406
|
+
var init_errors2 = __esm({
|
|
23407
|
+
"src/policy-engine/errors.ts"() {
|
|
23408
|
+
PolicyEngineError = class extends Error {
|
|
23409
|
+
constructor(message) {
|
|
23410
|
+
super(message);
|
|
23411
|
+
this.name = "PolicyEngineError";
|
|
23412
|
+
}
|
|
23413
|
+
};
|
|
23414
|
+
CompiledPolicyShapeError = class extends PolicyEngineError {
|
|
23415
|
+
constructor(message) {
|
|
23416
|
+
super(message);
|
|
23417
|
+
this.name = "CompiledPolicyShapeError";
|
|
23418
|
+
}
|
|
23419
|
+
};
|
|
23420
|
+
}
|
|
23421
|
+
});
|
|
23422
|
+
|
|
23423
|
+
// src/policy-engine/canonical-policy.ts
|
|
23424
|
+
function checkSlotGrant(value, path) {
|
|
23425
|
+
if (typeof value !== "object" || value === null) {
|
|
23426
|
+
throw new CompiledPolicyShapeError(`${path}: grant must be an object`);
|
|
23427
|
+
}
|
|
23428
|
+
const g = value;
|
|
23429
|
+
if (typeof g.counterparty !== "string" || g.counterparty.length === 0) {
|
|
23430
|
+
throw new CompiledPolicyShapeError(
|
|
23431
|
+
`${path}.counterparty must be a non-empty string`
|
|
23432
|
+
);
|
|
23433
|
+
}
|
|
23434
|
+
if (typeof g.action !== "string" || g.action.length === 0) {
|
|
23435
|
+
throw new CompiledPolicyShapeError(
|
|
23436
|
+
`${path}.action must be a non-empty string`
|
|
23437
|
+
);
|
|
23438
|
+
}
|
|
23439
|
+
if (g.scope !== void 0) {
|
|
23440
|
+
if (typeof g.scope !== "object" || g.scope === null || Array.isArray(g.scope)) {
|
|
23441
|
+
throw new CompiledPolicyShapeError(
|
|
23442
|
+
`${path}.scope must be an object when present`
|
|
23443
|
+
);
|
|
23444
|
+
}
|
|
23445
|
+
}
|
|
23446
|
+
if (g.max_uses_per_day !== void 0) {
|
|
23447
|
+
if (typeof g.max_uses_per_day !== "number" || !Number.isInteger(g.max_uses_per_day) || g.max_uses_per_day < 0) {
|
|
23448
|
+
throw new CompiledPolicyShapeError(
|
|
23449
|
+
`${path}.max_uses_per_day must be a non-negative integer when present`
|
|
23450
|
+
);
|
|
23451
|
+
}
|
|
23452
|
+
}
|
|
23453
|
+
}
|
|
23454
|
+
function checkSlotRule(slot, value, path) {
|
|
23455
|
+
if (typeof value !== "object" || value === null) {
|
|
23456
|
+
throw new CompiledPolicyShapeError(`${path}: slot rule must be an object`);
|
|
23457
|
+
}
|
|
23458
|
+
const r = value;
|
|
23459
|
+
if (r.slot !== slot) {
|
|
23460
|
+
throw new CompiledPolicyShapeError(
|
|
23461
|
+
`${path}.slot expected ${slot}, got ${String(r.slot)}`
|
|
23462
|
+
);
|
|
23463
|
+
}
|
|
23464
|
+
if (r.mode !== "deny" && r.mode !== "grant") {
|
|
23465
|
+
throw new CompiledPolicyShapeError(
|
|
23466
|
+
`${path}.mode must be "deny" or "grant"`
|
|
23467
|
+
);
|
|
23468
|
+
}
|
|
23469
|
+
if (!Array.isArray(r.grants)) {
|
|
23470
|
+
throw new CompiledPolicyShapeError(
|
|
23471
|
+
`${path}.grants must be an array (possibly empty)`
|
|
23472
|
+
);
|
|
23473
|
+
}
|
|
23474
|
+
if (r.mode === "deny" && r.grants.length > 0) {
|
|
23475
|
+
throw new CompiledPolicyShapeError(
|
|
23476
|
+
`${path}: deny-mode slot rule must carry zero grants`
|
|
23477
|
+
);
|
|
23478
|
+
}
|
|
23479
|
+
r.grants.forEach((g, i) => checkSlotGrant(g, `${path}.grants[${i}]`));
|
|
23480
|
+
}
|
|
23481
|
+
function validateCompiledPolicyShape(candidate) {
|
|
23482
|
+
if (typeof candidate !== "object" || candidate === null) {
|
|
23483
|
+
throw new CompiledPolicyShapeError("compiled policy must be an object");
|
|
23484
|
+
}
|
|
23485
|
+
const p = candidate;
|
|
23486
|
+
if (p.schema_version !== COMPILED_POLICY_SCHEMA_VERSION) {
|
|
23487
|
+
throw new CompiledPolicyShapeError(
|
|
23488
|
+
`schema_version ${String(p.schema_version)} not supported at v0.1 (expected "${COMPILED_POLICY_SCHEMA_VERSION}")`
|
|
23489
|
+
);
|
|
23490
|
+
}
|
|
23491
|
+
if (typeof p.agent_id !== "string" || p.agent_id.length === 0) {
|
|
23492
|
+
throw new CompiledPolicyShapeError("agent_id must be a non-empty string");
|
|
23493
|
+
}
|
|
23494
|
+
if (typeof p.fortress_id !== "string" || p.fortress_id.length === 0) {
|
|
23495
|
+
throw new CompiledPolicyShapeError(
|
|
23496
|
+
"fortress_id must be a non-empty string"
|
|
23497
|
+
);
|
|
23498
|
+
}
|
|
23499
|
+
if (typeof p.policy_version !== "number" || !Number.isInteger(p.policy_version) || p.policy_version < 0) {
|
|
23500
|
+
throw new CompiledPolicyShapeError(
|
|
23501
|
+
"policy_version must be a non-negative integer"
|
|
23502
|
+
);
|
|
23503
|
+
}
|
|
23504
|
+
if (p.parent_version !== void 0 && (typeof p.parent_version !== "number" || !Number.isInteger(p.parent_version) || p.parent_version < 0)) {
|
|
23505
|
+
throw new CompiledPolicyShapeError(
|
|
23506
|
+
"parent_version must be a non-negative integer when present"
|
|
23507
|
+
);
|
|
23508
|
+
}
|
|
23509
|
+
if (typeof p.slots !== "object" || p.slots === null) {
|
|
23510
|
+
throw new CompiledPolicyShapeError("slots must be an object");
|
|
23511
|
+
}
|
|
23512
|
+
const slots = p.slots;
|
|
23513
|
+
const slotKeys = Object.keys(slots).sort();
|
|
23514
|
+
const expectedSlotKeys = [...POLICY_SLOTS].sort();
|
|
23515
|
+
if (slotKeys.length !== expectedSlotKeys.length || !slotKeys.every((k, i) => k === expectedSlotKeys[i])) {
|
|
23516
|
+
throw new CompiledPolicyShapeError(
|
|
23517
|
+
`slots must contain exactly ${expectedSlotKeys.join(", ")}; got ${slotKeys.join(", ") || "(none)"}`
|
|
23518
|
+
);
|
|
23519
|
+
}
|
|
23520
|
+
for (const slot of POLICY_SLOTS) {
|
|
23521
|
+
checkSlotRule(slot, slots[slot], `slots.${slot}`);
|
|
23522
|
+
}
|
|
23523
|
+
if (typeof p.capabilities !== "object" || p.capabilities === null) {
|
|
23524
|
+
throw new CompiledPolicyShapeError("capabilities must be an object");
|
|
23525
|
+
}
|
|
23526
|
+
const caps = p.capabilities;
|
|
23527
|
+
if (!Array.isArray(caps.concordia_commitment_classes) || !caps.concordia_commitment_classes.every((c) => typeof c === "string")) {
|
|
23528
|
+
throw new CompiledPolicyShapeError(
|
|
23529
|
+
"capabilities.concordia_commitment_classes must be a string[]"
|
|
23530
|
+
);
|
|
23531
|
+
}
|
|
23532
|
+
if (!Array.isArray(caps.honeypot_skill_ids) || !caps.honeypot_skill_ids.every((c) => typeof c === "string")) {
|
|
23533
|
+
throw new CompiledPolicyShapeError(
|
|
23534
|
+
"capabilities.honeypot_skill_ids must be a string[]"
|
|
23535
|
+
);
|
|
23536
|
+
}
|
|
23537
|
+
if (typeof caps.is_sentinel !== "boolean") {
|
|
23538
|
+
throw new CompiledPolicyShapeError(
|
|
23539
|
+
"capabilities.is_sentinel must be boolean"
|
|
23540
|
+
);
|
|
23541
|
+
}
|
|
23542
|
+
if (typeof p.auto_trigger_ladder !== "object" || p.auto_trigger_ladder === null) {
|
|
23543
|
+
throw new CompiledPolicyShapeError(
|
|
23544
|
+
"auto_trigger_ladder must be an object"
|
|
23545
|
+
);
|
|
23546
|
+
}
|
|
23547
|
+
const lad = p.auto_trigger_ladder;
|
|
23548
|
+
if (typeof lad.honeypot_auto_freeze !== "boolean") {
|
|
23549
|
+
throw new CompiledPolicyShapeError(
|
|
23550
|
+
"auto_trigger_ladder.honeypot_auto_freeze must be boolean"
|
|
23551
|
+
);
|
|
23552
|
+
}
|
|
23553
|
+
if (lad.threshold_rule_action !== "operator_approved" && lad.threshold_rule_action !== "auto") {
|
|
23554
|
+
throw new CompiledPolicyShapeError(
|
|
23555
|
+
'auto_trigger_ladder.threshold_rule_action must be "operator_approved" or "auto"'
|
|
23556
|
+
);
|
|
23557
|
+
}
|
|
23558
|
+
if (lad.ml_anomaly_action !== "operator_approved" && lad.ml_anomaly_action !== "auto") {
|
|
23559
|
+
throw new CompiledPolicyShapeError(
|
|
23560
|
+
'auto_trigger_ladder.ml_anomaly_action must be "operator_approved" or "auto"'
|
|
23561
|
+
);
|
|
23562
|
+
}
|
|
23563
|
+
if (typeof p.source_english !== "string") {
|
|
23564
|
+
throw new CompiledPolicyShapeError("source_english must be a string");
|
|
23565
|
+
}
|
|
23566
|
+
if (typeof p.compiled_at !== "string" || Number.isNaN(Date.parse(p.compiled_at))) {
|
|
23567
|
+
throw new CompiledPolicyShapeError(
|
|
23568
|
+
"compiled_at must be an ISO8601 timestamp string"
|
|
23569
|
+
);
|
|
23570
|
+
}
|
|
23571
|
+
for (const key of slotKeys) {
|
|
23572
|
+
if (!isPolicySlot(key)) {
|
|
23573
|
+
throw new CompiledPolicyShapeError(
|
|
23574
|
+
`slots.${key} is not a recognized policy slot`
|
|
23575
|
+
);
|
|
23576
|
+
}
|
|
23577
|
+
}
|
|
23578
|
+
if (p.egress !== void 0) {
|
|
23579
|
+
checkEgressPolicy(p.egress, "egress");
|
|
23580
|
+
}
|
|
23581
|
+
if (p.budgets !== void 0) {
|
|
23582
|
+
checkBudgetPolicy(p.budgets, "budgets");
|
|
23583
|
+
}
|
|
23584
|
+
if (p.retention !== void 0) {
|
|
23585
|
+
checkRetentionPolicy(p.retention, "retention");
|
|
23586
|
+
}
|
|
23587
|
+
}
|
|
23588
|
+
function checkEgressPolicy(value, path) {
|
|
23589
|
+
if (typeof value !== "object" || value === null) {
|
|
23590
|
+
throw new CompiledPolicyShapeError(`${path} must be an object`);
|
|
23591
|
+
}
|
|
23592
|
+
const eg = value;
|
|
23593
|
+
if (!Array.isArray(eg.allowlist)) {
|
|
23594
|
+
throw new CompiledPolicyShapeError(`${path}.allowlist must be an array`);
|
|
23595
|
+
}
|
|
23596
|
+
for (let i = 0; i < eg.allowlist.length; i++) {
|
|
23597
|
+
const rule = eg.allowlist[i];
|
|
23598
|
+
if (typeof rule !== "object" || rule === null) {
|
|
23599
|
+
throw new CompiledPolicyShapeError(
|
|
23600
|
+
`${path}.allowlist[${i}] must be an object`
|
|
23601
|
+
);
|
|
23602
|
+
}
|
|
23603
|
+
const r = rule;
|
|
23604
|
+
if (typeof r.destination !== "string" || r.destination.length === 0) {
|
|
23605
|
+
throw new CompiledPolicyShapeError(
|
|
23606
|
+
`${path}.allowlist[${i}].destination must be a non-empty string`
|
|
23607
|
+
);
|
|
23608
|
+
}
|
|
23609
|
+
if (!Array.isArray(r.methods) || !r.methods.every((m) => typeof m === "string")) {
|
|
23610
|
+
throw new CompiledPolicyShapeError(
|
|
23611
|
+
`${path}.allowlist[${i}].methods must be a string[]`
|
|
23612
|
+
);
|
|
23613
|
+
}
|
|
23614
|
+
}
|
|
23615
|
+
}
|
|
23616
|
+
function checkBudgetLimit(value, path) {
|
|
23617
|
+
if (typeof value !== "object" || value === null) {
|
|
23618
|
+
throw new CompiledPolicyShapeError(`${path} must be an object`);
|
|
23619
|
+
}
|
|
23620
|
+
const lim = value;
|
|
23621
|
+
if (typeof lim.amount !== "number" || lim.amount <= 0 || !Number.isFinite(lim.amount)) {
|
|
23622
|
+
throw new CompiledPolicyShapeError(
|
|
23623
|
+
`${path}.amount must be a positive finite number`
|
|
23624
|
+
);
|
|
23625
|
+
}
|
|
23626
|
+
if (!BUDGET_UNITS.includes(lim.unit)) {
|
|
23627
|
+
throw new CompiledPolicyShapeError(
|
|
23628
|
+
`${path}.unit must be one of: ${BUDGET_UNITS.join(", ")}`
|
|
23629
|
+
);
|
|
23630
|
+
}
|
|
23631
|
+
if (lim.soft_warn_threshold !== void 0) {
|
|
23632
|
+
if (typeof lim.soft_warn_threshold !== "number" || lim.soft_warn_threshold <= 0 || lim.soft_warn_threshold >= 1) {
|
|
23633
|
+
throw new CompiledPolicyShapeError(
|
|
23634
|
+
`${path}.soft_warn_threshold must be in (0, 1) when present`
|
|
23635
|
+
);
|
|
23636
|
+
}
|
|
23637
|
+
}
|
|
23638
|
+
}
|
|
23639
|
+
function checkBudgetPolicy(value, path) {
|
|
23640
|
+
if (typeof value !== "object" || value === null) {
|
|
23641
|
+
throw new CompiledPolicyShapeError(`${path} must be an object`);
|
|
23642
|
+
}
|
|
23643
|
+
const bp = value;
|
|
23644
|
+
if (bp.daily !== void 0) checkBudgetLimit(bp.daily, `${path}.daily`);
|
|
23645
|
+
if (bp.monthly !== void 0) checkBudgetLimit(bp.monthly, `${path}.monthly`);
|
|
23646
|
+
if (bp.daily === void 0 && bp.monthly === void 0) {
|
|
23647
|
+
throw new CompiledPolicyShapeError(
|
|
23648
|
+
`${path} must have at least one of daily or monthly`
|
|
23649
|
+
);
|
|
23650
|
+
}
|
|
23651
|
+
}
|
|
23652
|
+
function checkRetentionPolicy(value, path) {
|
|
23653
|
+
if (typeof value !== "object" || value === null) {
|
|
23654
|
+
throw new CompiledPolicyShapeError(`${path} must be an object`);
|
|
23655
|
+
}
|
|
23656
|
+
const rp = value;
|
|
23657
|
+
if (typeof rp.windows !== "object" || rp.windows === null) {
|
|
23658
|
+
throw new CompiledPolicyShapeError(`${path}.windows must be an object`);
|
|
23659
|
+
}
|
|
23660
|
+
const wins = rp.windows;
|
|
23661
|
+
for (const key of Object.keys(wins)) {
|
|
23662
|
+
if (!isPolicySlot(key)) {
|
|
23663
|
+
throw new CompiledPolicyShapeError(
|
|
23664
|
+
`${path}.windows.${key} is not a recognized policy slot`
|
|
23665
|
+
);
|
|
23666
|
+
}
|
|
23667
|
+
const w = wins[key];
|
|
23668
|
+
if (typeof w !== "object" || w === null) {
|
|
23669
|
+
throw new CompiledPolicyShapeError(
|
|
23670
|
+
`${path}.windows.${key} must be an object`
|
|
23671
|
+
);
|
|
23672
|
+
}
|
|
23673
|
+
const win = w;
|
|
23674
|
+
if (typeof win.max_age_seconds !== "number" || !Number.isInteger(win.max_age_seconds) || win.max_age_seconds <= 0) {
|
|
23675
|
+
throw new CompiledPolicyShapeError(
|
|
23676
|
+
`${path}.windows.${key}.max_age_seconds must be a positive integer`
|
|
23677
|
+
);
|
|
23678
|
+
}
|
|
23679
|
+
if (win.archive !== void 0 && typeof win.archive !== "boolean") {
|
|
23680
|
+
throw new CompiledPolicyShapeError(
|
|
23681
|
+
`${path}.windows.${key}.archive must be boolean when present`
|
|
23682
|
+
);
|
|
23683
|
+
}
|
|
23684
|
+
}
|
|
23685
|
+
}
|
|
23686
|
+
function encodePolicyBlob(policy) {
|
|
23687
|
+
validateCompiledPolicyShape(policy);
|
|
23688
|
+
return toBase64url(canonicalizeToBytes(policy));
|
|
23689
|
+
}
|
|
23690
|
+
var init_canonical_policy = __esm({
|
|
23691
|
+
"src/policy-engine/canonical-policy.ts"() {
|
|
23692
|
+
init_canonical_json();
|
|
23693
|
+
init_encoding();
|
|
23694
|
+
init_constants();
|
|
23695
|
+
init_errors2();
|
|
23696
|
+
}
|
|
23697
|
+
});
|
|
23698
|
+
|
|
23699
|
+
// src/mesh/constants.ts
|
|
23700
|
+
function isReservedEventType(s) {
|
|
23701
|
+
return RESERVED_EVENT_TYPE_PREFIXES.some((p) => s.startsWith(p));
|
|
23702
|
+
}
|
|
23703
|
+
function isReservedExtensionKey(k) {
|
|
23704
|
+
return RESERVED_EXTENSION_ENVELOPE_KEYS.includes(k);
|
|
23705
|
+
}
|
|
23706
|
+
var PROTOCOL_VERSION, RESERVED_EVENT_TYPE_PREFIXES, RESERVED_EXTENSION_ENVELOPE_KEYS;
|
|
23707
|
+
var init_constants2 = __esm({
|
|
23708
|
+
"src/mesh/constants.ts"() {
|
|
23709
|
+
PROTOCOL_VERSION = "0.1";
|
|
23710
|
+
RESERVED_EVENT_TYPE_PREFIXES = [
|
|
23711
|
+
"EXTENSION_",
|
|
23712
|
+
"cross_fortress_",
|
|
23713
|
+
"multi_master_"
|
|
23714
|
+
];
|
|
23715
|
+
RESERVED_EXTENSION_ENVELOPE_KEYS = [
|
|
23716
|
+
"cross_fortress_read_grant",
|
|
23717
|
+
"cross_fortress_read_query",
|
|
23718
|
+
"cross_fortress_read_response",
|
|
23719
|
+
"multi_master_policy_merge",
|
|
23720
|
+
"audit_replication_full_n_way",
|
|
23721
|
+
"auto_promote_canonical_audit",
|
|
23722
|
+
"agent_live_migration"
|
|
23723
|
+
];
|
|
23724
|
+
}
|
|
23725
|
+
});
|
|
23726
|
+
var init_trust_root = __esm({
|
|
23727
|
+
"src/mesh/trust-root.ts"() {
|
|
23728
|
+
init_encoding();
|
|
23729
|
+
init_identity();
|
|
23730
|
+
init_random();
|
|
23731
|
+
init_canonical_json();
|
|
23732
|
+
init_constants2();
|
|
23733
|
+
init_errors();
|
|
23734
|
+
}
|
|
23735
|
+
});
|
|
23736
|
+
function packSignedEvent(params) {
|
|
23737
|
+
if (isReservedEventType(params.event_type)) {
|
|
23738
|
+
throw new MeshReservedEventTypeError(params.event_type);
|
|
23739
|
+
}
|
|
23740
|
+
const ext = params.extension_envelope ?? {};
|
|
23741
|
+
for (const key of Object.keys(ext)) {
|
|
23742
|
+
if (isReservedExtensionKey(key)) {
|
|
23743
|
+
throw new MeshReservedExtensionKeyError(key);
|
|
23744
|
+
}
|
|
23745
|
+
}
|
|
23746
|
+
const payloadBytes = canonicalizeToBytes(params.payload);
|
|
23747
|
+
const payload_hash = toBase64url(sha256.sha256(payloadBytes));
|
|
23748
|
+
const body = {
|
|
23749
|
+
protocol_version: PROTOCOL_VERSION,
|
|
23750
|
+
event_type: params.event_type,
|
|
23751
|
+
event_id: generateEventId(),
|
|
23752
|
+
emitter_node: params.emitter_node,
|
|
23753
|
+
emitter_principal: params.emitter_principal,
|
|
23754
|
+
fortress_id: params.fortress_id,
|
|
23755
|
+
causal_parents: params.causal_parents ?? [],
|
|
23756
|
+
payload: params.payload,
|
|
23757
|
+
payload_hash,
|
|
23758
|
+
emitted_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23759
|
+
monotonic_seq: params.monotonic_seq,
|
|
23760
|
+
extension_envelope: ext
|
|
23761
|
+
};
|
|
23762
|
+
const bytesToSign = canonicalizeToBytes(body);
|
|
23763
|
+
const nodeSig = ed25519.ed25519.sign(bytesToSign, params.node_private_key);
|
|
23764
|
+
const evt = {
|
|
23765
|
+
...body,
|
|
23766
|
+
node_signature: toBase64url(nodeSig)
|
|
23767
|
+
};
|
|
23768
|
+
if (params.principal_private_key) {
|
|
23769
|
+
const principalSig = ed25519.ed25519.sign(bytesToSign, params.principal_private_key);
|
|
23770
|
+
evt.principal_signature = toBase64url(principalSig);
|
|
23771
|
+
}
|
|
23772
|
+
return evt;
|
|
23773
|
+
}
|
|
23774
|
+
function generateEventId() {
|
|
23775
|
+
const bytes = randomBytes(16);
|
|
23776
|
+
let hex = "";
|
|
23777
|
+
for (const b of bytes) hex += b.toString(16).padStart(2, "0");
|
|
23778
|
+
return hex;
|
|
23779
|
+
}
|
|
23780
|
+
var init_envelope = __esm({
|
|
23781
|
+
"src/mesh/envelope.ts"() {
|
|
23782
|
+
init_encoding();
|
|
23783
|
+
init_random();
|
|
23784
|
+
init_canonical_json();
|
|
23785
|
+
init_constants2();
|
|
23786
|
+
init_errors();
|
|
23787
|
+
init_trust_root();
|
|
23788
|
+
}
|
|
23789
|
+
});
|
|
23790
|
+
|
|
23791
|
+
// src/policy-engine/envelope.ts
|
|
23792
|
+
function packPolicyUpdate(params) {
|
|
23793
|
+
validateCompiledPolicyShape(params.policy);
|
|
23794
|
+
const blob = encodePolicyBlob(params.policy);
|
|
23795
|
+
const payload = {
|
|
23796
|
+
agent_id: params.policy.agent_id,
|
|
23797
|
+
policy_version: params.policy.policy_version,
|
|
23798
|
+
policy_blob: blob,
|
|
23799
|
+
...params.policy.parent_version !== void 0 ? { parent_version: params.policy.parent_version } : {}
|
|
23800
|
+
};
|
|
23801
|
+
return packSignedEvent({
|
|
23802
|
+
event_type: POLICY_UPDATE_EVENT_TYPE,
|
|
23803
|
+
emitter_node: params.emitter_node,
|
|
23804
|
+
emitter_principal: params.emitter_principal,
|
|
23805
|
+
fortress_id: params.policy.fortress_id,
|
|
23806
|
+
causal_parents: params.causal_parents,
|
|
23807
|
+
payload,
|
|
23808
|
+
monotonic_seq: params.monotonic_seq,
|
|
23809
|
+
node_private_key: params.node_private_key,
|
|
23810
|
+
principal_private_key: params.principal_private_key
|
|
23811
|
+
});
|
|
23812
|
+
}
|
|
23813
|
+
var init_envelope2 = __esm({
|
|
23814
|
+
"src/policy-engine/envelope.ts"() {
|
|
23815
|
+
init_constants();
|
|
23816
|
+
init_canonical_policy();
|
|
23817
|
+
init_errors2();
|
|
23818
|
+
init_envelope();
|
|
23819
|
+
init_errors();
|
|
23820
|
+
}
|
|
23821
|
+
});
|
|
23822
|
+
|
|
23823
|
+
// src/templates/init.ts
|
|
23824
|
+
function deterministicCompiledAt(version) {
|
|
23825
|
+
const [major, minor, patch] = version.split(".").map(Number);
|
|
23826
|
+
const epoch = /* @__PURE__ */ new Date("2026-01-01T00:00:00.000Z");
|
|
23827
|
+
epoch.setUTCDate(epoch.getUTCDate() + (major - 1) * 365 + minor * 30 + patch);
|
|
23828
|
+
return epoch.toISOString();
|
|
23829
|
+
}
|
|
23830
|
+
function buildCompiledPolicyFromTemplate(bundle, params) {
|
|
23831
|
+
const policy = applyChannelTemplate(
|
|
23832
|
+
bundle.metadata.channel,
|
|
23833
|
+
{
|
|
23834
|
+
agent_id: params.agent_id,
|
|
23835
|
+
counterparty: params.counterparty,
|
|
23836
|
+
fortress_id: params.fortress_id,
|
|
23837
|
+
policy_version: params.policy_version
|
|
23838
|
+
}
|
|
23839
|
+
);
|
|
23840
|
+
policy.compiled_at = deterministicCompiledAt(bundle.metadata.version);
|
|
23841
|
+
policy.source_english = bundle.policy_english;
|
|
23842
|
+
if (bundle.defaults.egress.length > 0) {
|
|
23843
|
+
const egress = {
|
|
23844
|
+
allowlist: bundle.defaults.egress.map((e) => ({
|
|
23845
|
+
destination: e.destination,
|
|
23846
|
+
methods: [...e.methods]
|
|
23847
|
+
}))
|
|
23848
|
+
};
|
|
23849
|
+
policy.egress = egress;
|
|
23850
|
+
}
|
|
23851
|
+
const budgets = {};
|
|
23852
|
+
if (bundle.defaults.budgets.daily) {
|
|
23853
|
+
budgets.daily = { ...bundle.defaults.budgets.daily };
|
|
23854
|
+
}
|
|
23855
|
+
if (bundle.defaults.budgets.monthly) {
|
|
23856
|
+
budgets.monthly = { ...bundle.defaults.budgets.monthly };
|
|
23857
|
+
}
|
|
23858
|
+
if (budgets.daily || budgets.monthly) {
|
|
23859
|
+
policy.budgets = budgets;
|
|
23860
|
+
}
|
|
23861
|
+
const windows = bundle.defaults.retention.windows;
|
|
23862
|
+
if (Object.keys(windows).length > 0) {
|
|
23863
|
+
const retention = {
|
|
23864
|
+
windows: {}
|
|
23865
|
+
};
|
|
23866
|
+
for (const [slot, win] of Object.entries(windows)) {
|
|
23867
|
+
if (win) {
|
|
23868
|
+
retention.windows[slot] = { ...win };
|
|
23869
|
+
}
|
|
23870
|
+
}
|
|
23871
|
+
policy.retention = retention;
|
|
23872
|
+
}
|
|
23873
|
+
const classes = bundle.commitments.shapes.map((s) => s.commitment_class);
|
|
23874
|
+
for (const cls of classes) {
|
|
23875
|
+
if (!policy.capabilities.concordia_commitment_classes.includes(cls)) {
|
|
23876
|
+
policy.capabilities.concordia_commitment_classes.push(cls);
|
|
23877
|
+
}
|
|
23878
|
+
}
|
|
23879
|
+
policy.capabilities.concordia_commitment_classes.sort();
|
|
23880
|
+
validateCompiledPolicyShape(policy);
|
|
23881
|
+
return policy;
|
|
23882
|
+
}
|
|
23883
|
+
function initTemplate(params) {
|
|
23884
|
+
const bundle = getTemplate2(params.template_name);
|
|
23885
|
+
if (!bundle) {
|
|
23886
|
+
throw new Error(`unknown template: ${params.template_name}`);
|
|
23887
|
+
}
|
|
23888
|
+
const compiled = buildCompiledPolicyFromTemplate(bundle, {
|
|
23889
|
+
agent_id: params.agent_id,
|
|
23890
|
+
fortress_id: params.fortress_id,
|
|
23891
|
+
counterparty: params.counterparty ?? "*",
|
|
23892
|
+
policy_version: params.policy_version ?? 1
|
|
23893
|
+
});
|
|
23894
|
+
const signed_event = packPolicyUpdate({
|
|
23895
|
+
policy: compiled,
|
|
23896
|
+
emitter_node: params.emitter_node,
|
|
23897
|
+
emitter_principal: params.emitter_principal,
|
|
23898
|
+
monotonic_seq: params.monotonic_seq,
|
|
23899
|
+
node_private_key: params.node_private_key,
|
|
23900
|
+
principal_private_key: params.principal_private_key
|
|
23901
|
+
});
|
|
23902
|
+
return { compiled, signed_event, bundle };
|
|
23903
|
+
}
|
|
23904
|
+
var init_init = __esm({
|
|
23905
|
+
"src/templates/init.ts"() {
|
|
23906
|
+
init_channel_templates();
|
|
23907
|
+
init_canonical_policy();
|
|
23908
|
+
init_envelope2();
|
|
23909
|
+
init_registry2();
|
|
23910
|
+
}
|
|
23911
|
+
});
|
|
23912
|
+
function constantTimeEquals(a, b) {
|
|
23913
|
+
if (a.length !== b.length) return false;
|
|
23914
|
+
let diff = 0;
|
|
23915
|
+
for (let i = 0; i < a.length; i++) {
|
|
23916
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
23917
|
+
}
|
|
23918
|
+
return diff === 0;
|
|
23919
|
+
}
|
|
23920
|
+
function extractToken(req, url) {
|
|
23921
|
+
const header = req.headers.authorization;
|
|
23922
|
+
if (header && header.startsWith("Bearer ")) {
|
|
23923
|
+
return header.slice(7).trim();
|
|
23924
|
+
}
|
|
23925
|
+
const q = url.searchParams.get("token");
|
|
23926
|
+
return q ?? null;
|
|
23927
|
+
}
|
|
23928
|
+
function isAuthorized(deps, req, url) {
|
|
23929
|
+
if (!deps.authToken) return true;
|
|
23930
|
+
const token = extractToken(req, url);
|
|
23931
|
+
if (!token) return false;
|
|
23932
|
+
return constantTimeEquals(token, deps.authToken);
|
|
23933
|
+
}
|
|
23934
|
+
function writeJSON(res, status, payload) {
|
|
23935
|
+
res.writeHead(status, {
|
|
23936
|
+
"Content-Type": "application/json",
|
|
23937
|
+
"Cache-Control": "no-store"
|
|
23938
|
+
});
|
|
23939
|
+
res.end(JSON.stringify(payload));
|
|
23940
|
+
}
|
|
23941
|
+
async function readJSONBody(req) {
|
|
23942
|
+
const chunks = [];
|
|
23943
|
+
let size = 0;
|
|
23944
|
+
const MAX = 256 * 1024;
|
|
23945
|
+
for await (const chunk of req) {
|
|
23946
|
+
size += chunk.length;
|
|
23947
|
+
if (size > MAX) throw new Error("request body too large");
|
|
23948
|
+
chunks.push(chunk);
|
|
23949
|
+
}
|
|
23950
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
23951
|
+
if (!body) return {};
|
|
23952
|
+
return JSON.parse(body);
|
|
23953
|
+
}
|
|
23954
|
+
function generateEphemeralKey() {
|
|
23955
|
+
return new Uint8Array(crypto.randomBytes(32));
|
|
23956
|
+
}
|
|
23957
|
+
function writeText(res, status, body, contentType = "text/plain") {
|
|
23958
|
+
res.writeHead(status, {
|
|
23959
|
+
"Content-Type": contentType,
|
|
23960
|
+
"Cache-Control": "no-store"
|
|
23961
|
+
});
|
|
23962
|
+
res.end(body);
|
|
23963
|
+
}
|
|
23964
|
+
async function handleRequest(deps, req, res) {
|
|
23965
|
+
const host = req.headers.host || "localhost";
|
|
23966
|
+
const url = new URL(req.url ?? "/", `http://${host}`);
|
|
23967
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
23968
|
+
const path = url.pathname;
|
|
23969
|
+
if (!isAuthorized(deps, req, url)) {
|
|
23970
|
+
writeJSON(res, 401, { error: "unauthorized" });
|
|
23971
|
+
return true;
|
|
23972
|
+
}
|
|
23973
|
+
if (method === "GET" && path === "/api/health") {
|
|
23974
|
+
writeJSON(res, 200, { ok: true, mode: deps.sources.mode });
|
|
23975
|
+
return true;
|
|
23976
|
+
}
|
|
23977
|
+
if (method === "GET" && (path === "/" || path === "/index.html")) {
|
|
23978
|
+
const snapshot = await getProtectionSnapshot(deps.sources);
|
|
23979
|
+
const html = renderDashboardHTML({ snapshot, authToken: deps.authToken });
|
|
23980
|
+
writeText(res, 200, html, "text/html; charset=utf-8");
|
|
23981
|
+
return true;
|
|
23982
|
+
}
|
|
23983
|
+
if (method === "GET" && path === "/api/snapshot") {
|
|
23984
|
+
const snapshot = await getProtectionSnapshot(deps.sources);
|
|
23985
|
+
writeJSON(res, 200, snapshot);
|
|
23986
|
+
return true;
|
|
23987
|
+
}
|
|
23988
|
+
const approvalMatch = /^\/api\/approvals\/([^/]+)\/(allow|deny)$/.exec(path);
|
|
23989
|
+
if (method === "POST" && approvalMatch) {
|
|
23990
|
+
const id = decodeURIComponent(approvalMatch[1]);
|
|
23991
|
+
const action = approvalMatch[2];
|
|
23992
|
+
if (!deps.approvals) {
|
|
23993
|
+
writeJSON(res, 503, { error: "approvals_unavailable" });
|
|
23994
|
+
return true;
|
|
23995
|
+
}
|
|
23996
|
+
const handler = action === "allow" ? deps.approvals.allow : deps.approvals.deny;
|
|
23997
|
+
try {
|
|
23998
|
+
const ok2 = await handler(id);
|
|
23999
|
+
writeJSON(res, ok2 ? 200 : 404, { id, action, ok: ok2 });
|
|
24000
|
+
} catch (err) {
|
|
24001
|
+
writeJSON(res, 500, { error: "approval_failed", message: err.message });
|
|
24002
|
+
}
|
|
24003
|
+
return true;
|
|
24004
|
+
}
|
|
24005
|
+
if (method === "GET" && path === "/api/stream") {
|
|
24006
|
+
await handleStream(deps, res);
|
|
24007
|
+
return true;
|
|
24008
|
+
}
|
|
24009
|
+
if (method === "GET" && path === "/api/templates") {
|
|
24010
|
+
try {
|
|
24011
|
+
const templates = listTemplates();
|
|
24012
|
+
writeJSON(res, 200, { templates });
|
|
24013
|
+
} catch (err) {
|
|
24014
|
+
writeJSON(res, 500, {
|
|
24015
|
+
error: "template_load_failed",
|
|
24016
|
+
message: err.message
|
|
24017
|
+
});
|
|
24018
|
+
}
|
|
24019
|
+
return true;
|
|
24020
|
+
}
|
|
24021
|
+
const templateMatch = /^\/api\/templates\/([^/]+)$/.exec(path);
|
|
24022
|
+
if (method === "GET" && templateMatch) {
|
|
24023
|
+
const name = decodeURIComponent(templateMatch[1]);
|
|
24024
|
+
try {
|
|
24025
|
+
const entry = getTemplateEntry(name);
|
|
24026
|
+
if (!entry) {
|
|
24027
|
+
writeJSON(res, 404, { error: "template_not_found", name });
|
|
24028
|
+
return true;
|
|
24029
|
+
}
|
|
24030
|
+
writeJSON(res, 200, entry);
|
|
24031
|
+
} catch (err) {
|
|
24032
|
+
writeJSON(res, 500, {
|
|
24033
|
+
error: "template_load_failed",
|
|
24034
|
+
message: err.message
|
|
24035
|
+
});
|
|
24036
|
+
}
|
|
24037
|
+
return true;
|
|
24038
|
+
}
|
|
24039
|
+
const initMatch = /^\/api\/templates\/([^/]+)\/init$/.exec(path);
|
|
24040
|
+
if (method === "POST" && initMatch) {
|
|
24041
|
+
const name = decodeURIComponent(initMatch[1]);
|
|
24042
|
+
try {
|
|
24043
|
+
const bundle = getTemplate2(name);
|
|
24044
|
+
if (!bundle) {
|
|
24045
|
+
writeJSON(res, 404, { error: "template_not_found", name });
|
|
24046
|
+
return true;
|
|
24047
|
+
}
|
|
24048
|
+
const body = await readJSONBody(req);
|
|
24049
|
+
if (!body.agent_name || typeof body.agent_name !== "string") {
|
|
24050
|
+
writeJSON(res, 400, {
|
|
24051
|
+
error: "validation_error",
|
|
24052
|
+
message: "agent_name is required and must be a string"
|
|
24053
|
+
});
|
|
24054
|
+
return true;
|
|
24055
|
+
}
|
|
24056
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(body.agent_name)) {
|
|
24057
|
+
writeJSON(res, 400, {
|
|
24058
|
+
error: "validation_error",
|
|
24059
|
+
message: "agent_name must contain only alphanumeric characters, hyphens, and underscores"
|
|
24060
|
+
});
|
|
24061
|
+
return true;
|
|
24062
|
+
}
|
|
24063
|
+
const nodeId = deps.nodeId ?? "dashboard-node";
|
|
24064
|
+
const nodePrivateKey = deps.nodePrivateKey ?? generateEphemeralKey();
|
|
24065
|
+
const principalId = deps.principalId ?? "dashboard-principal";
|
|
24066
|
+
const fortressId = deps.fortressId ?? "default";
|
|
24067
|
+
const result = initTemplate({
|
|
24068
|
+
template_name: name,
|
|
24069
|
+
agent_id: body.agent_name,
|
|
24070
|
+
fortress_id: fortressId,
|
|
24071
|
+
counterparty: "*",
|
|
24072
|
+
policy_version: 1,
|
|
24073
|
+
emitter_node: nodeId,
|
|
24074
|
+
emitter_principal: principalId,
|
|
24075
|
+
monotonic_seq: 1,
|
|
24076
|
+
node_private_key: nodePrivateKey
|
|
24077
|
+
});
|
|
24078
|
+
writeJSON(res, 200, {
|
|
24079
|
+
agent_id: body.agent_name,
|
|
24080
|
+
signed_event_id: result.signed_event.event_id,
|
|
24081
|
+
policy_version: result.compiled.policy_version,
|
|
24082
|
+
template_name: name,
|
|
24083
|
+
attestation_panel_url: `/console#agent_roster`
|
|
24084
|
+
});
|
|
24085
|
+
} catch (err) {
|
|
24086
|
+
writeJSON(res, 500, {
|
|
24087
|
+
error: "template_init_failed",
|
|
24088
|
+
message: err.message
|
|
24089
|
+
});
|
|
24090
|
+
}
|
|
24091
|
+
return true;
|
|
24092
|
+
}
|
|
24093
|
+
return false;
|
|
24094
|
+
}
|
|
24095
|
+
async function handleStream(deps, res) {
|
|
24096
|
+
res.writeHead(200, {
|
|
24097
|
+
"Content-Type": "text/event-stream",
|
|
24098
|
+
"Cache-Control": "no-cache, no-transform",
|
|
24099
|
+
Connection: "keep-alive",
|
|
24100
|
+
"X-Accel-Buffering": "no"
|
|
24101
|
+
});
|
|
24102
|
+
const snapshot = await getProtectionSnapshot(deps.sources);
|
|
24103
|
+
res.write(`event: snapshot
|
|
24104
|
+
data: ${JSON.stringify(snapshot)}
|
|
24105
|
+
|
|
24106
|
+
`);
|
|
24107
|
+
const unsubscribe = deps.onEvent ? deps.onEvent((event) => {
|
|
24108
|
+
try {
|
|
24109
|
+
res.write(`event: ${event.type}
|
|
24110
|
+
data: ${JSON.stringify(event.data)}
|
|
24111
|
+
|
|
24112
|
+
`);
|
|
24113
|
+
} catch {
|
|
24114
|
+
}
|
|
24115
|
+
}) : () => {
|
|
24116
|
+
};
|
|
24117
|
+
const keepAlive = setInterval(() => {
|
|
24118
|
+
try {
|
|
24119
|
+
res.write(": keepalive\n\n");
|
|
24120
|
+
} catch {
|
|
24121
|
+
}
|
|
24122
|
+
}, 25e3);
|
|
24123
|
+
const cleanup = () => {
|
|
24124
|
+
clearInterval(keepAlive);
|
|
24125
|
+
unsubscribe();
|
|
24126
|
+
};
|
|
24127
|
+
res.on("close", cleanup);
|
|
24128
|
+
res.on("error", cleanup);
|
|
24129
|
+
}
|
|
24130
|
+
var init_api = __esm({
|
|
24131
|
+
"src/dashboard/api.ts"() {
|
|
24132
|
+
init_aggregator();
|
|
24133
|
+
init_html();
|
|
24134
|
+
init_registry2();
|
|
24135
|
+
init_init();
|
|
24136
|
+
}
|
|
24137
|
+
});
|
|
24138
|
+
async function startDashboardServer(options) {
|
|
24139
|
+
const port = options.port ?? DEFAULT_PORT;
|
|
24140
|
+
const host = options.host ?? DEFAULT_HOST;
|
|
24141
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
24142
|
+
const onEvent = (listener) => {
|
|
24143
|
+
listeners.add(listener);
|
|
24144
|
+
return () => listeners.delete(listener);
|
|
24145
|
+
};
|
|
24146
|
+
const publish = (event) => {
|
|
24147
|
+
for (const listener of listeners) {
|
|
24148
|
+
try {
|
|
24149
|
+
listener(event);
|
|
24150
|
+
} catch {
|
|
24151
|
+
}
|
|
24152
|
+
}
|
|
24153
|
+
};
|
|
24154
|
+
const deps = {
|
|
24155
|
+
sources: options.sources,
|
|
24156
|
+
authToken: options.authToken,
|
|
24157
|
+
approvals: options.approvals,
|
|
24158
|
+
onEvent
|
|
24159
|
+
};
|
|
24160
|
+
const server = http.createServer(async (req, res) => {
|
|
24161
|
+
try {
|
|
24162
|
+
const served = await handleRequest(deps, req, res);
|
|
24163
|
+
if (!served) {
|
|
24164
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
24165
|
+
res.end(JSON.stringify({ error: "not_found", path: req.url }));
|
|
24166
|
+
}
|
|
24167
|
+
} catch (err) {
|
|
24168
|
+
try {
|
|
24169
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
24170
|
+
res.end(JSON.stringify({ error: "internal", message: err.message }));
|
|
24171
|
+
} catch {
|
|
24172
|
+
}
|
|
24173
|
+
}
|
|
24174
|
+
});
|
|
24175
|
+
await new Promise((resolve2, reject) => {
|
|
24176
|
+
server.once("error", reject);
|
|
24177
|
+
server.listen(port, host, () => {
|
|
24178
|
+
server.off("error", reject);
|
|
24179
|
+
resolve2();
|
|
24180
|
+
});
|
|
24181
|
+
});
|
|
24182
|
+
const actualPort = (() => {
|
|
24183
|
+
const addr = server.address();
|
|
24184
|
+
if (addr && typeof addr === "object") return addr.port;
|
|
24185
|
+
return port;
|
|
24186
|
+
})();
|
|
24187
|
+
const url = `http://${host}:${actualPort}`;
|
|
24188
|
+
return {
|
|
24189
|
+
url,
|
|
24190
|
+
port: actualPort,
|
|
24191
|
+
host,
|
|
24192
|
+
stop: () => new Promise((resolve2, reject) => {
|
|
24193
|
+
server.close((err) => err ? reject(err) : resolve2());
|
|
24194
|
+
}),
|
|
24195
|
+
publish,
|
|
24196
|
+
publishActivity: (entry) => publish({ type: "activity", data: entry }),
|
|
24197
|
+
publishApproval: (approval) => publish({ type: "approval", data: approval })
|
|
24198
|
+
};
|
|
24199
|
+
}
|
|
24200
|
+
var DEFAULT_PORT, DEFAULT_HOST;
|
|
24201
|
+
var init_server = __esm({
|
|
24202
|
+
"src/dashboard/server.ts"() {
|
|
24203
|
+
init_api();
|
|
24204
|
+
DEFAULT_PORT = 3501;
|
|
24205
|
+
DEFAULT_HOST = "127.0.0.1";
|
|
24206
|
+
}
|
|
24207
|
+
});
|
|
24208
|
+
|
|
24209
|
+
// src/dashboard/index.ts
|
|
24210
|
+
async function startDashboard(options) {
|
|
24211
|
+
const activity = options.initialActivity ? [...options.initialActivity] : [];
|
|
24212
|
+
const pending = options.initialPendingApprovals ? [...options.initialPendingApprovals] : [];
|
|
24213
|
+
const sources = {
|
|
24214
|
+
mode: options.mode,
|
|
24215
|
+
server_version: options.serverVersion,
|
|
24216
|
+
...options.auditLog ? { auditLog: options.auditLog } : {},
|
|
24217
|
+
...options.identityManager ? { identityManager: options.identityManager } : {},
|
|
24218
|
+
...options.clientManager ? { clientManager: options.clientManager } : {},
|
|
24219
|
+
...options.baseline ? { baseline: options.baseline } : {},
|
|
24220
|
+
...options.policy ? { policy: options.policy } : {},
|
|
24221
|
+
...options.reputation ? { reputation: options.reputation } : {},
|
|
24222
|
+
...options.teeAvailable != null ? { teeAvailable: options.teeAvailable } : {},
|
|
24223
|
+
...options.l4Evidence ? { l4Evidence: options.l4Evidence } : {},
|
|
22956
24224
|
activity,
|
|
22957
24225
|
pendingApprovals: pending
|
|
22958
24226
|
};
|
|
@@ -23697,6 +24965,90 @@ var init_paths = __esm({
|
|
|
23697
24965
|
DEFAULT_DASHBOARD_PORT = 3501;
|
|
23698
24966
|
}
|
|
23699
24967
|
});
|
|
24968
|
+
function getPlatformPaths() {
|
|
24969
|
+
const home = os.homedir();
|
|
24970
|
+
return {
|
|
24971
|
+
"openclaw": [
|
|
24972
|
+
path.join(home, ".openclaw", "openclaw.json"),
|
|
24973
|
+
path.join(home, ".openclaw", "config.json"),
|
|
24974
|
+
path.join(home, "Library", "Application Support", "OpenClaw", "openclaw.json"),
|
|
24975
|
+
path.join(home, "Library", "Application Support", "OpenClaw", "config.json")
|
|
24976
|
+
],
|
|
24977
|
+
// Hermes Agent (NousResearch, v0.9.0) canonicals live under ~/.hermes.
|
|
24978
|
+
// Hermes ships `cli-config.yaml` as the primary surface per upstream docs.
|
|
24979
|
+
// Sanctuary wrap v1.0 detects the JSON variant only: operators who keep
|
|
24980
|
+
// YAML can still wrap via `sanctuary wrap --wrap <path>` after exporting
|
|
24981
|
+
// to JSON. YAML-native detection is flagged as a v1.x follow-up.
|
|
24982
|
+
"hermes": [
|
|
24983
|
+
path.join(home, ".hermes", "cli-config.json"),
|
|
24984
|
+
path.join(home, ".hermes", "config.json"),
|
|
24985
|
+
path.join(home, ".config", "hermes", "cli-config.json")
|
|
24986
|
+
],
|
|
24987
|
+
// Claude Code's modern canonical surface is ~/.claude.json (`claude mcp
|
|
24988
|
+
// add` writes here). The legacy ~/.claude/settings.json shape predates
|
|
24989
|
+
// it and is still respected if present. Probe order = preference order:
|
|
24990
|
+
// wrap operates on the first one that exists, and bootstraps a fresh
|
|
24991
|
+
// ~/.claude.json when neither is present (per the cli.ts bootstrap).
|
|
24992
|
+
"claude-code": [
|
|
24993
|
+
path.join(home, ".claude.json"),
|
|
24994
|
+
path.join(home, ".claude", "settings.json"),
|
|
24995
|
+
path.join(home, ".config", "claude-code", "settings.json")
|
|
24996
|
+
],
|
|
24997
|
+
"cursor": [
|
|
24998
|
+
path.join(home, ".cursor", "mcp.json")
|
|
24999
|
+
],
|
|
25000
|
+
// Cline is a VS Code extension (saoudrizwan.claude-dev). Its MCP settings
|
|
25001
|
+
// live under the VS Code globalStorage tree, which is OS-specific. We
|
|
25002
|
+
// enumerate the three supported OS layouts; at detection time only the
|
|
25003
|
+
// one matching the running OS will exist.
|
|
25004
|
+
"cline": [
|
|
25005
|
+
// macOS
|
|
25006
|
+
path.join(
|
|
25007
|
+
home,
|
|
25008
|
+
"Library",
|
|
25009
|
+
"Application Support",
|
|
25010
|
+
"Code",
|
|
25011
|
+
"User",
|
|
25012
|
+
"globalStorage",
|
|
25013
|
+
"saoudrizwan.claude-dev",
|
|
25014
|
+
"settings",
|
|
25015
|
+
"cline_mcp_settings.json"
|
|
25016
|
+
),
|
|
25017
|
+
// Linux
|
|
25018
|
+
path.join(
|
|
25019
|
+
home,
|
|
25020
|
+
".config",
|
|
25021
|
+
"Code",
|
|
25022
|
+
"User",
|
|
25023
|
+
"globalStorage",
|
|
25024
|
+
"saoudrizwan.claude-dev",
|
|
25025
|
+
"settings",
|
|
25026
|
+
"cline_mcp_settings.json"
|
|
25027
|
+
),
|
|
25028
|
+
// Windows (honour APPDATA when set, otherwise reconstruct under home)
|
|
25029
|
+
process.env.APPDATA ? path.join(
|
|
25030
|
+
process.env.APPDATA,
|
|
25031
|
+
"Code",
|
|
25032
|
+
"User",
|
|
25033
|
+
"globalStorage",
|
|
25034
|
+
"saoudrizwan.claude-dev",
|
|
25035
|
+
"settings",
|
|
25036
|
+
"cline_mcp_settings.json"
|
|
25037
|
+
) : path.join(
|
|
25038
|
+
home,
|
|
25039
|
+
"AppData",
|
|
25040
|
+
"Roaming",
|
|
25041
|
+
"Code",
|
|
25042
|
+
"User",
|
|
25043
|
+
"globalStorage",
|
|
25044
|
+
"saoudrizwan.claude-dev",
|
|
25045
|
+
"settings",
|
|
25046
|
+
"cline_mcp_settings.json"
|
|
25047
|
+
)
|
|
25048
|
+
],
|
|
25049
|
+
"generic": []
|
|
25050
|
+
};
|
|
25051
|
+
}
|
|
23700
25052
|
function backupDir() {
|
|
23701
25053
|
return path.join(resolveStoragePath(), "backup");
|
|
23702
25054
|
}
|
|
@@ -23740,7 +25092,7 @@ async function detectAgentConfigWithDiagnostics(platform4, configPath) {
|
|
|
23740
25092
|
return { config, pathsChecked, errors };
|
|
23741
25093
|
}
|
|
23742
25094
|
if (platform4) {
|
|
23743
|
-
const paths =
|
|
25095
|
+
const paths = getPlatformPaths()[platform4];
|
|
23744
25096
|
for (const path of paths) {
|
|
23745
25097
|
pathsChecked.push(path);
|
|
23746
25098
|
const { config, error } = await readConfigFileWithError(path, platform4);
|
|
@@ -23749,7 +25101,7 @@ async function detectAgentConfigWithDiagnostics(platform4, configPath) {
|
|
|
23749
25101
|
}
|
|
23750
25102
|
return { config: null, pathsChecked, errors };
|
|
23751
25103
|
}
|
|
23752
|
-
for (const [plat, paths] of Object.entries(
|
|
25104
|
+
for (const [plat, paths] of Object.entries(getPlatformPaths())) {
|
|
23753
25105
|
for (const path of paths) {
|
|
23754
25106
|
pathsChecked.push(path);
|
|
23755
25107
|
const { config, error } = await readConfigFileWithError(path, plat);
|
|
@@ -23807,7 +25159,7 @@ function extractServers(config, platform4) {
|
|
|
23807
25159
|
const mcpServers = obj.mcpServers;
|
|
23808
25160
|
if (mcpServers && typeof mcpServers === "object") {
|
|
23809
25161
|
for (const [name, serverConfig] of Object.entries(mcpServers)) {
|
|
23810
|
-
if (name
|
|
25162
|
+
if (isCanonicalSanctuaryName(name)) continue;
|
|
23811
25163
|
const entry = parseServerEntry(name, serverConfig);
|
|
23812
25164
|
if (entry) servers.push(entry);
|
|
23813
25165
|
}
|
|
@@ -23817,7 +25169,27 @@ function extractServers(config, platform4) {
|
|
|
23817
25169
|
const mcpServers = obj.mcpServers;
|
|
23818
25170
|
if (mcpServers && typeof mcpServers === "object") {
|
|
23819
25171
|
for (const [name, serverConfig] of Object.entries(mcpServers)) {
|
|
23820
|
-
if (name
|
|
25172
|
+
if (isCanonicalSanctuaryName(name)) continue;
|
|
25173
|
+
const entry = parseServerEntry(name, serverConfig);
|
|
25174
|
+
if (entry) servers.push(entry);
|
|
25175
|
+
}
|
|
25176
|
+
}
|
|
25177
|
+
}
|
|
25178
|
+
if (platform4 === "hermes") {
|
|
25179
|
+
const mcpServers = obj.mcp_servers;
|
|
25180
|
+
if (mcpServers && typeof mcpServers === "object") {
|
|
25181
|
+
for (const [name, serverConfig] of Object.entries(mcpServers)) {
|
|
25182
|
+
if (isCanonicalSanctuaryName(name)) continue;
|
|
25183
|
+
const entry = parseServerEntry(name, serverConfig);
|
|
25184
|
+
if (entry) servers.push(entry);
|
|
25185
|
+
}
|
|
25186
|
+
}
|
|
25187
|
+
}
|
|
25188
|
+
if (platform4 === "cline") {
|
|
25189
|
+
const mcpServers = obj.mcpServers;
|
|
25190
|
+
if (mcpServers && typeof mcpServers === "object") {
|
|
25191
|
+
for (const [name, serverConfig] of Object.entries(mcpServers)) {
|
|
25192
|
+
if (isCanonicalSanctuaryName(name)) continue;
|
|
23821
25193
|
const entry = parseServerEntry(name, serverConfig);
|
|
23822
25194
|
if (entry) servers.push(entry);
|
|
23823
25195
|
}
|
|
@@ -23825,6 +25197,9 @@ function extractServers(config, platform4) {
|
|
|
23825
25197
|
}
|
|
23826
25198
|
return servers;
|
|
23827
25199
|
}
|
|
25200
|
+
function isCanonicalSanctuaryName(name) {
|
|
25201
|
+
return name.toLowerCase() === "sanctuary";
|
|
25202
|
+
}
|
|
23828
25203
|
function parseServerEntry(name, config) {
|
|
23829
25204
|
if (!config || typeof config !== "object") return null;
|
|
23830
25205
|
const c = config;
|
|
@@ -23863,6 +25238,8 @@ async function rewriteConfigForCocoon(agentConfig, sanctuaryCommand, sanctuaryAr
|
|
|
23863
25238
|
if (agentConfig.platform === "openclaw") {
|
|
23864
25239
|
const existingMcp = raw.mcp ?? {};
|
|
23865
25240
|
existingServers = existingMcp.servers ?? {};
|
|
25241
|
+
} else if (agentConfig.platform === "hermes") {
|
|
25242
|
+
existingServers = raw.mcp_servers ?? {};
|
|
23866
25243
|
} else {
|
|
23867
25244
|
existingServers = raw.mcpServers ?? {};
|
|
23868
25245
|
}
|
|
@@ -23906,6 +25283,14 @@ async function rewriteConfigForCocoon(agentConfig, sanctuaryCommand, sanctuaryAr
|
|
|
23906
25283
|
}
|
|
23907
25284
|
};
|
|
23908
25285
|
delete rewritten.mcpServers;
|
|
25286
|
+
} else if (agentConfig.platform === "hermes") {
|
|
25287
|
+
rewritten = {
|
|
25288
|
+
...raw,
|
|
25289
|
+
mcp_servers: {
|
|
25290
|
+
...existingServers,
|
|
25291
|
+
sanctuary: sanctuaryEntry
|
|
25292
|
+
}
|
|
25293
|
+
};
|
|
23909
25294
|
} else {
|
|
23910
25295
|
rewritten = {
|
|
23911
25296
|
...raw,
|
|
@@ -23918,26 +25303,9 @@ async function rewriteConfigForCocoon(agentConfig, sanctuaryCommand, sanctuaryAr
|
|
|
23918
25303
|
await promises.writeFile(agentConfig.configPath, JSON.stringify(rewritten, null, 2), { mode: 384 });
|
|
23919
25304
|
return agentConfig.configPath;
|
|
23920
25305
|
}
|
|
23921
|
-
var PLATFORM_PATHS;
|
|
23922
25306
|
var init_config_reader = __esm({
|
|
23923
25307
|
"src/cocoon/config-reader.ts"() {
|
|
23924
25308
|
init_paths();
|
|
23925
|
-
PLATFORM_PATHS = {
|
|
23926
|
-
"openclaw": [
|
|
23927
|
-
path.join(os.homedir(), ".openclaw", "openclaw.json"),
|
|
23928
|
-
path.join(os.homedir(), ".openclaw", "config.json"),
|
|
23929
|
-
path.join(os.homedir(), "Library", "Application Support", "OpenClaw", "openclaw.json"),
|
|
23930
|
-
path.join(os.homedir(), "Library", "Application Support", "OpenClaw", "config.json")
|
|
23931
|
-
],
|
|
23932
|
-
"claude-code": [
|
|
23933
|
-
path.join(os.homedir(), ".claude", "settings.json"),
|
|
23934
|
-
path.join(os.homedir(), ".config", "claude-code", "settings.json")
|
|
23935
|
-
],
|
|
23936
|
-
"cursor": [
|
|
23937
|
-
path.join(os.homedir(), ".cursor", "mcp.json")
|
|
23938
|
-
],
|
|
23939
|
-
"generic": []
|
|
23940
|
-
};
|
|
23941
25309
|
}
|
|
23942
25310
|
});
|
|
23943
25311
|
|
|
@@ -24243,13 +25611,46 @@ async function runWrap(options, deps = {}) {
|
|
|
24243
25611
|
}
|
|
24244
25612
|
let platformHint;
|
|
24245
25613
|
if (options.openclaw) platformHint = "openclaw";
|
|
25614
|
+
else if (options.hermes) platformHint = "hermes";
|
|
24246
25615
|
else if (options.claudeCode) platformHint = "claude-code";
|
|
24247
25616
|
else if (options.cursor) platformHint = "cursor";
|
|
24248
|
-
|
|
25617
|
+
else if (options.cline) platformHint = "cline";
|
|
25618
|
+
let detection = await detectAgentConfigWithDiagnostics(
|
|
24249
25619
|
platformHint,
|
|
24250
25620
|
options.wrap
|
|
24251
25621
|
);
|
|
24252
|
-
|
|
25622
|
+
let agentConfig = detection.config;
|
|
25623
|
+
if (!agentConfig && platformHint && !options.wrap) {
|
|
25624
|
+
const candidatePaths = getPlatformPaths()[platformHint];
|
|
25625
|
+
const canonicalPath = candidatePaths[0];
|
|
25626
|
+
if (canonicalPath) {
|
|
25627
|
+
try {
|
|
25628
|
+
await promises.mkdir(path.dirname(canonicalPath), { recursive: true, mode: 448 });
|
|
25629
|
+
await promises.writeFile(canonicalPath, "{}", { mode: 384 });
|
|
25630
|
+
console.error(
|
|
25631
|
+
`
|
|
25632
|
+
No existing ${platformHint} config found.`
|
|
25633
|
+
);
|
|
25634
|
+
console.error(
|
|
25635
|
+
` Bootstrapped a fresh config at ${canonicalPath}.
|
|
25636
|
+
`
|
|
25637
|
+
);
|
|
25638
|
+
detection = await detectAgentConfigWithDiagnostics(
|
|
25639
|
+
platformHint,
|
|
25640
|
+
options.wrap
|
|
25641
|
+
);
|
|
25642
|
+
agentConfig = detection.config;
|
|
25643
|
+
} catch (err) {
|
|
25644
|
+
console.error(
|
|
25645
|
+
`
|
|
25646
|
+
Sanctuary: could not bootstrap ${platformHint} config at ${canonicalPath}`
|
|
25647
|
+
);
|
|
25648
|
+
console.error(` Error: ${err.message}
|
|
25649
|
+
`);
|
|
25650
|
+
process.exit(1);
|
|
25651
|
+
}
|
|
25652
|
+
}
|
|
25653
|
+
}
|
|
24253
25654
|
if (!agentConfig) {
|
|
24254
25655
|
console.error(`
|
|
24255
25656
|
Sanctuary \u2014 Configuration Not Found
|
|
@@ -24261,7 +25662,7 @@ async function runWrap(options, deps = {}) {
|
|
|
24261
25662
|
} else {
|
|
24262
25663
|
console.error(" Could not auto-detect any agent configuration.");
|
|
24263
25664
|
console.error(
|
|
24264
|
-
" Use --openclaw, --claude-code, --cursor, or --wrap /path/to/config.json"
|
|
25665
|
+
" Use --openclaw, --hermes, --claude-code, --cursor, --cline, or --wrap /path/to/config.json"
|
|
24265
25666
|
);
|
|
24266
25667
|
}
|
|
24267
25668
|
if (detection.pathsChecked.length > 0) {
|
|
@@ -24279,25 +25680,25 @@ async function runWrap(options, deps = {}) {
|
|
|
24279
25680
|
console.error("");
|
|
24280
25681
|
process.exit(1);
|
|
24281
25682
|
}
|
|
24282
|
-
|
|
25683
|
+
const hasSanctuaryInRaw = rawConfigContainsSanctuary(
|
|
25684
|
+
agentConfig.rawConfig,
|
|
25685
|
+
agentConfig.platform
|
|
25686
|
+
);
|
|
25687
|
+
if (hasSanctuaryInRaw) {
|
|
24283
25688
|
console.error(
|
|
24284
25689
|
`
|
|
24285
|
-
|
|
25690
|
+
Sanctuary already wrapped: updating the existing Sanctuary entry.
|
|
25691
|
+
`
|
|
24286
25692
|
);
|
|
24287
|
-
|
|
24288
|
-
`);
|
|
24289
|
-
process.exit(1);
|
|
24290
|
-
}
|
|
24291
|
-
const hasSanctuary = agentConfig.servers.some(
|
|
24292
|
-
(s) => s.name.toLowerCase() === "sanctuary"
|
|
24293
|
-
);
|
|
24294
|
-
if (hasSanctuary) {
|
|
25693
|
+
} else if (agentConfig.servers.length === 0) {
|
|
24295
25694
|
console.error(
|
|
24296
25695
|
`
|
|
24297
|
-
|
|
25696
|
+
Found ${agentConfig.platform} config at ${agentConfig.configPath} with no MCP servers yet.`
|
|
25697
|
+
);
|
|
25698
|
+
console.error(
|
|
25699
|
+
` Sanctuary will be installed as the only MCP server.
|
|
25700
|
+
`
|
|
24298
25701
|
);
|
|
24299
|
-
console.error(` Re-wrapping will update the existing Sanctuary entry.
|
|
24300
|
-
`);
|
|
24301
25702
|
}
|
|
24302
25703
|
console.error(`
|
|
24303
25704
|
Sanctuary wrap`);
|
|
@@ -24573,7 +25974,7 @@ async function verifyRewrittenConfig(configPath, backupPath) {
|
|
|
24573
25974
|
await restoreFromBackup(configPath, backupPath);
|
|
24574
25975
|
return false;
|
|
24575
25976
|
}
|
|
24576
|
-
const servers = parsed.mcp?.servers ?? parsed.mcpServers ?? {};
|
|
25977
|
+
const servers = parsed.mcp?.servers ?? parsed.mcpServers ?? parsed.mcp_servers ?? {};
|
|
24577
25978
|
if (!servers.sanctuary) {
|
|
24578
25979
|
console.error(`
|
|
24579
25980
|
Verification FAILED: No sanctuary entry in rewritten config.`);
|
|
@@ -24664,10 +26065,14 @@ function toolNameFor(platform4, _servers) {
|
|
|
24664
26065
|
switch (platform4) {
|
|
24665
26066
|
case "openclaw":
|
|
24666
26067
|
return "OpenClaw";
|
|
26068
|
+
case "hermes":
|
|
26069
|
+
return "Hermes Agent";
|
|
24667
26070
|
case "claude-code":
|
|
24668
26071
|
return "Claude Code";
|
|
24669
26072
|
case "cursor":
|
|
24670
26073
|
return "Cursor";
|
|
26074
|
+
case "cline":
|
|
26075
|
+
return "Cline";
|
|
24671
26076
|
default:
|
|
24672
26077
|
return "your agent";
|
|
24673
26078
|
}
|
|
@@ -24678,6 +26083,23 @@ function countUpstreamTools(servers) {
|
|
|
24678
26083
|
function readPackageVersion() {
|
|
24679
26084
|
return SANCTUARY_VERSION;
|
|
24680
26085
|
}
|
|
26086
|
+
function rawConfigContainsSanctuary(raw, agentPlatform) {
|
|
26087
|
+
if (!raw || typeof raw !== "object") return false;
|
|
26088
|
+
const obj = raw;
|
|
26089
|
+
let serversBag;
|
|
26090
|
+
if (agentPlatform === "openclaw") {
|
|
26091
|
+
const mcp = obj.mcp;
|
|
26092
|
+
serversBag = mcp?.servers ?? obj.mcpServers;
|
|
26093
|
+
} else if (agentPlatform === "hermes") {
|
|
26094
|
+
serversBag = obj.mcp_servers;
|
|
26095
|
+
} else {
|
|
26096
|
+
serversBag = obj.mcpServers;
|
|
26097
|
+
}
|
|
26098
|
+
if (!serversBag || typeof serversBag !== "object") return false;
|
|
26099
|
+
return Object.keys(serversBag).some(
|
|
26100
|
+
(name) => name.toLowerCase() === "sanctuary"
|
|
26101
|
+
);
|
|
26102
|
+
}
|
|
24681
26103
|
function parseWrapArgs(argv) {
|
|
24682
26104
|
const options = {};
|
|
24683
26105
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -24688,12 +26110,18 @@ function parseWrapArgs(argv) {
|
|
|
24688
26110
|
case "--openclaw":
|
|
24689
26111
|
options.openclaw = true;
|
|
24690
26112
|
break;
|
|
26113
|
+
case "--hermes":
|
|
26114
|
+
options.hermes = true;
|
|
26115
|
+
break;
|
|
24691
26116
|
case "--claude-code":
|
|
24692
26117
|
options.claudeCode = true;
|
|
24693
26118
|
break;
|
|
24694
26119
|
case "--cursor":
|
|
24695
26120
|
options.cursor = true;
|
|
24696
26121
|
break;
|
|
26122
|
+
case "--cline":
|
|
26123
|
+
options.cline = true;
|
|
26124
|
+
break;
|
|
24697
26125
|
case "--unwrap":
|
|
24698
26126
|
options.unwrap = true;
|
|
24699
26127
|
break;
|
|
@@ -24723,15 +26151,19 @@ function printWrapHelp() {
|
|
|
24723
26151
|
|
|
24724
26152
|
Usage:
|
|
24725
26153
|
sanctuary wrap --openclaw Wrap OpenClaw
|
|
26154
|
+
sanctuary wrap --hermes Wrap Hermes Agent (NousResearch)
|
|
24726
26155
|
sanctuary wrap --claude-code Wrap Claude Code
|
|
24727
26156
|
sanctuary wrap --cursor Wrap Cursor
|
|
26157
|
+
sanctuary wrap --cline Wrap Cline (VS Code extension)
|
|
24728
26158
|
sanctuary wrap --wrap <path> Wrap a specific MCP config file
|
|
24729
26159
|
sanctuary wrap --unwrap Restore original config
|
|
24730
26160
|
|
|
24731
26161
|
Options:
|
|
24732
26162
|
--openclaw Auto-detect and wrap OpenClaw
|
|
26163
|
+
--hermes Auto-detect and wrap Hermes Agent
|
|
24733
26164
|
--claude-code Auto-detect and wrap Claude Code
|
|
24734
26165
|
--cursor Auto-detect and wrap Cursor
|
|
26166
|
+
--cline Auto-detect and wrap Cline (VS Code extension)
|
|
24735
26167
|
--wrap <path> Wrap a specific MCP config file
|
|
24736
26168
|
--unwrap Restore original config from backup
|
|
24737
26169
|
--passphrase <p> Override the stored passphrase (one-off)
|
|
@@ -26674,6 +28106,230 @@ var init_secrets = __esm({
|
|
|
26674
28106
|
STDIN_READ_DEADLINE_MS = 3e4;
|
|
26675
28107
|
}
|
|
26676
28108
|
});
|
|
28109
|
+
|
|
28110
|
+
// src/templates/cli.ts
|
|
28111
|
+
var cli_exports3 = {};
|
|
28112
|
+
__export(cli_exports3, {
|
|
28113
|
+
runTemplateCommand: () => runTemplateCommand
|
|
28114
|
+
});
|
|
28115
|
+
async function runTemplateCommand(args) {
|
|
28116
|
+
const out = args.out ?? process.stdout;
|
|
28117
|
+
const err = args.err ?? process.stderr;
|
|
28118
|
+
const [sub, ...rest] = args.argv;
|
|
28119
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
28120
|
+
printTemplateHelp(out);
|
|
28121
|
+
return 0;
|
|
28122
|
+
}
|
|
28123
|
+
switch (sub) {
|
|
28124
|
+
case "list":
|
|
28125
|
+
return cmdList2(out);
|
|
28126
|
+
case "init":
|
|
28127
|
+
return cmdInit(rest, out, err);
|
|
28128
|
+
default:
|
|
28129
|
+
err.write(`Unknown template subcommand: ${sub}
|
|
28130
|
+
`);
|
|
28131
|
+
printTemplateHelp(err);
|
|
28132
|
+
return 1;
|
|
28133
|
+
}
|
|
28134
|
+
}
|
|
28135
|
+
function cmdList2(out, _err) {
|
|
28136
|
+
const templates = listTemplates();
|
|
28137
|
+
out.write("\nSanctuary Template Library\n");
|
|
28138
|
+
out.write("=".repeat(60) + "\n\n");
|
|
28139
|
+
out.write(
|
|
28140
|
+
padRight("NAME", 24) + padRight("CHANNEL", 26) + padRight("TIER", 6) + "DESCRIPTION\n"
|
|
28141
|
+
);
|
|
28142
|
+
out.write("-".repeat(100) + "\n");
|
|
28143
|
+
for (const entry of templates) {
|
|
28144
|
+
const m = entry.metadata;
|
|
28145
|
+
out.write(
|
|
28146
|
+
padRight(m.name, 24) + padRight(m.channel, 26) + padRight(m.tier, 6) + m.description + "\n"
|
|
28147
|
+
);
|
|
28148
|
+
}
|
|
28149
|
+
out.write("\n");
|
|
28150
|
+
out.write(`Use "sanctuary template init <name> --agent-id <id>" to load a template.
|
|
28151
|
+
|
|
28152
|
+
`);
|
|
28153
|
+
return 0;
|
|
28154
|
+
}
|
|
28155
|
+
function padRight(str, len) {
|
|
28156
|
+
return str.length >= len ? str + " " : str + " ".repeat(len - str.length);
|
|
28157
|
+
}
|
|
28158
|
+
function cmdInit(argv, out, err) {
|
|
28159
|
+
let templateName;
|
|
28160
|
+
let agentId;
|
|
28161
|
+
let fortressId = "fortress-default";
|
|
28162
|
+
let counterparty = "*";
|
|
28163
|
+
for (let i = 0; i < argv.length; i++) {
|
|
28164
|
+
if (argv[i] === "--agent-id" && argv[i + 1]) {
|
|
28165
|
+
agentId = argv[++i];
|
|
28166
|
+
} else if (argv[i] === "--fortress-id" && argv[i + 1]) {
|
|
28167
|
+
fortressId = argv[++i];
|
|
28168
|
+
} else if (argv[i] === "--counterparty" && argv[i + 1]) {
|
|
28169
|
+
counterparty = argv[++i];
|
|
28170
|
+
} else if (argv[i] === "--help" || argv[i] === "-h") {
|
|
28171
|
+
printInitHelp(out);
|
|
28172
|
+
return 0;
|
|
28173
|
+
} else if (!argv[i].startsWith("--")) {
|
|
28174
|
+
templateName = argv[i];
|
|
28175
|
+
}
|
|
28176
|
+
}
|
|
28177
|
+
if (!templateName) {
|
|
28178
|
+
err.write("Error: template name is required.\n");
|
|
28179
|
+
err.write(`Available templates: ${TEMPLATE_NAMES.join(", ")}
|
|
28180
|
+
`);
|
|
28181
|
+
return 1;
|
|
28182
|
+
}
|
|
28183
|
+
if (!isTemplateName(templateName)) {
|
|
28184
|
+
err.write(`Error: unknown template "${templateName}".
|
|
28185
|
+
`);
|
|
28186
|
+
err.write(`Available templates: ${TEMPLATE_NAMES.join(", ")}
|
|
28187
|
+
`);
|
|
28188
|
+
return 1;
|
|
28189
|
+
}
|
|
28190
|
+
if (!agentId) {
|
|
28191
|
+
err.write("Error: --agent-id is required.\n");
|
|
28192
|
+
return 1;
|
|
28193
|
+
}
|
|
28194
|
+
const bundle = getTemplate2(templateName);
|
|
28195
|
+
if (!bundle) {
|
|
28196
|
+
err.write(`Error: template "${templateName}" not found.
|
|
28197
|
+
`);
|
|
28198
|
+
return 1;
|
|
28199
|
+
}
|
|
28200
|
+
const compiled = buildCompiledPolicyFromTemplate(bundle, {
|
|
28201
|
+
agent_id: agentId,
|
|
28202
|
+
fortress_id: fortressId,
|
|
28203
|
+
counterparty,
|
|
28204
|
+
policy_version: 1
|
|
28205
|
+
});
|
|
28206
|
+
const blob = encodePolicyBlob(compiled);
|
|
28207
|
+
out.write("\nTemplate initialized successfully.\n");
|
|
28208
|
+
out.write("=".repeat(60) + "\n\n");
|
|
28209
|
+
out.write(`Template: ${bundle.metadata.name}
|
|
28210
|
+
`);
|
|
28211
|
+
out.write(`Channel: ${bundle.metadata.channel}
|
|
28212
|
+
`);
|
|
28213
|
+
out.write(`Tier: ${bundle.metadata.tier}
|
|
28214
|
+
`);
|
|
28215
|
+
out.write(`Agent ID: ${compiled.agent_id}
|
|
28216
|
+
`);
|
|
28217
|
+
out.write(`Fortress ID: ${compiled.fortress_id}
|
|
28218
|
+
`);
|
|
28219
|
+
out.write(`Policy Version: ${compiled.policy_version}
|
|
28220
|
+
`);
|
|
28221
|
+
out.write(`Schema Version: ${compiled.schema_version}
|
|
28222
|
+
`);
|
|
28223
|
+
out.write("\n");
|
|
28224
|
+
out.write("Slot access:\n");
|
|
28225
|
+
for (const slot of ["memory", "credentials", "plans", "outputs"]) {
|
|
28226
|
+
const rule = compiled.slots[slot];
|
|
28227
|
+
if (rule.mode === "deny") {
|
|
28228
|
+
out.write(` ${slot}: deny (hermetic)
|
|
28229
|
+
`);
|
|
28230
|
+
} else {
|
|
28231
|
+
const grants = rule.grants.map(
|
|
28232
|
+
(g) => `${g.action} by ${g.counterparty}`
|
|
28233
|
+
);
|
|
28234
|
+
out.write(` ${slot}: ${grants.join(", ")}
|
|
28235
|
+
`);
|
|
28236
|
+
}
|
|
28237
|
+
}
|
|
28238
|
+
if (compiled.capabilities.concordia_commitment_classes.length > 0) {
|
|
28239
|
+
out.write(
|
|
28240
|
+
`
|
|
28241
|
+
Commitment classes: ${compiled.capabilities.concordia_commitment_classes.join(", ")}
|
|
28242
|
+
`
|
|
28243
|
+
);
|
|
28244
|
+
}
|
|
28245
|
+
if (compiled.egress) {
|
|
28246
|
+
out.write(`
|
|
28247
|
+
Egress allowlist (${compiled.egress.allowlist.length} entries):
|
|
28248
|
+
`);
|
|
28249
|
+
for (const rule of compiled.egress.allowlist) {
|
|
28250
|
+
const methods = rule.methods.length > 0 ? rule.methods.join(",") : "*";
|
|
28251
|
+
out.write(` ${rule.destination} [${methods}]
|
|
28252
|
+
`);
|
|
28253
|
+
}
|
|
28254
|
+
} else {
|
|
28255
|
+
out.write("\nEgress: hermetic (no outbound access)\n");
|
|
28256
|
+
}
|
|
28257
|
+
if (compiled.budgets) {
|
|
28258
|
+
out.write("\nBudgets:\n");
|
|
28259
|
+
if (compiled.budgets.daily) {
|
|
28260
|
+
out.write(
|
|
28261
|
+
` Daily: ${compiled.budgets.daily.amount} ${compiled.budgets.daily.unit}
|
|
28262
|
+
`
|
|
28263
|
+
);
|
|
28264
|
+
}
|
|
28265
|
+
if (compiled.budgets.monthly) {
|
|
28266
|
+
out.write(
|
|
28267
|
+
` Monthly: ${compiled.budgets.monthly.amount} ${compiled.budgets.monthly.unit}
|
|
28268
|
+
`
|
|
28269
|
+
);
|
|
28270
|
+
}
|
|
28271
|
+
}
|
|
28272
|
+
if (compiled.retention) {
|
|
28273
|
+
out.write("\nRetention windows:\n");
|
|
28274
|
+
for (const [slot, win] of Object.entries(compiled.retention.windows)) {
|
|
28275
|
+
if (win) {
|
|
28276
|
+
const days = Math.round(win.max_age_seconds / 86400);
|
|
28277
|
+
out.write(` ${slot}: ${days} days${win.archive ? " (archive)" : ""}
|
|
28278
|
+
`);
|
|
28279
|
+
}
|
|
28280
|
+
}
|
|
28281
|
+
}
|
|
28282
|
+
out.write(`
|
|
28283
|
+
Policy blob (base64url, ${blob.length} chars):
|
|
28284
|
+
`);
|
|
28285
|
+
out.write(` ${blob.slice(0, 80)}...
|
|
28286
|
+
|
|
28287
|
+
`);
|
|
28288
|
+
out.write(
|
|
28289
|
+
"To sign and emit this policy as a policy_update event, use the\npolicy engine's packPolicyUpdate with your node signing key.\n\n"
|
|
28290
|
+
);
|
|
28291
|
+
return 0;
|
|
28292
|
+
}
|
|
28293
|
+
function printTemplateHelp(out) {
|
|
28294
|
+
out.write(`
|
|
28295
|
+
sanctuary template \u2014 Manage Sanctuary policy templates.
|
|
28296
|
+
|
|
28297
|
+
Usage:
|
|
28298
|
+
sanctuary template list List available templates
|
|
28299
|
+
sanctuary template init <name> [options] Load a template
|
|
28300
|
+
|
|
28301
|
+
Options:
|
|
28302
|
+
--help, -h Show this help
|
|
28303
|
+
|
|
28304
|
+
Use "sanctuary template init --help" for init-specific options.
|
|
28305
|
+
`);
|
|
28306
|
+
}
|
|
28307
|
+
function printInitHelp(out) {
|
|
28308
|
+
out.write(`
|
|
28309
|
+
sanctuary template init \u2014 Load a template into a fortress.
|
|
28310
|
+
|
|
28311
|
+
Usage:
|
|
28312
|
+
sanctuary template init <name> --agent-id <id> [options]
|
|
28313
|
+
|
|
28314
|
+
Arguments:
|
|
28315
|
+
<name> Template name (see "sanctuary template list")
|
|
28316
|
+
|
|
28317
|
+
Options:
|
|
28318
|
+
--agent-id <id> Agent ID to bind the policy to (required)
|
|
28319
|
+
--fortress-id <id> Fortress ID (default: "fortress-default")
|
|
28320
|
+
--counterparty <id> Counterparty for channel grants (default: "*")
|
|
28321
|
+
--help, -h Show this help
|
|
28322
|
+
|
|
28323
|
+
Available templates: ${TEMPLATE_NAMES.join(", ")}
|
|
28324
|
+
`);
|
|
28325
|
+
}
|
|
28326
|
+
var init_cli3 = __esm({
|
|
28327
|
+
"src/templates/cli.ts"() {
|
|
28328
|
+
init_registry2();
|
|
28329
|
+
init_init();
|
|
28330
|
+
init_canonical_policy();
|
|
28331
|
+
}
|
|
28332
|
+
});
|
|
26677
28333
|
async function isTenantDir(path$1) {
|
|
26678
28334
|
const [hasState, hasProfile, hasFallback] = await Promise.all([
|
|
26679
28335
|
dirExists(path.join(path$1, "state")),
|
|
@@ -26885,7 +28541,7 @@ async function runAgentsCommand(args) {
|
|
|
26885
28541
|
try {
|
|
26886
28542
|
switch (sub) {
|
|
26887
28543
|
case "list":
|
|
26888
|
-
return await
|
|
28544
|
+
return await cmdList3(rest, ctx);
|
|
26889
28545
|
case "show":
|
|
26890
28546
|
return await cmdShow(rest, ctx);
|
|
26891
28547
|
case "status":
|
|
@@ -26944,11 +28600,11 @@ function passphraseLabel(tenant) {
|
|
|
26944
28600
|
return "not-init";
|
|
26945
28601
|
}
|
|
26946
28602
|
}
|
|
26947
|
-
function
|
|
28603
|
+
function padRight2(s, width) {
|
|
26948
28604
|
if (s.length >= width) return s;
|
|
26949
28605
|
return s + " ".repeat(width - s.length);
|
|
26950
28606
|
}
|
|
26951
|
-
async function
|
|
28607
|
+
async function cmdList3(argv, ctx) {
|
|
26952
28608
|
const json = hasJsonFlag(argv);
|
|
26953
28609
|
const tenants = await discoverTenants(ctx.discoverOpts);
|
|
26954
28610
|
if (json) {
|
|
@@ -26980,7 +28636,7 @@ async function cmdList2(argv, ctx) {
|
|
|
26980
28636
|
}));
|
|
26981
28637
|
const nameW = Math.max(4, ...rows.map((r) => r.tenant.name.length));
|
|
26982
28638
|
const pathW = Math.max(12, ...rows.map((r) => r.tenant.storage_path.length));
|
|
26983
|
-
const header =
|
|
28639
|
+
const header = padRight2("NAME", nameW) + " " + padRight2("STATUS", 8) + padRight2("DASH", 6) + padRight2("HOOK", 6) + padRight2("PP", 15) + padRight2("STORAGE", pathW);
|
|
26984
28640
|
ctx.out.write(header + "\n");
|
|
26985
28641
|
for (const { tenant, probe } of rows) {
|
|
26986
28642
|
const status = probe.running ? "running" : tenant.initialized ? "stopped" : "empty";
|
|
@@ -26988,7 +28644,7 @@ async function cmdList2(argv, ctx) {
|
|
|
26988
28644
|
const webhook = tenant.runtime?.webhook_callback_port != null ? String(tenant.runtime.webhook_callback_port) : "-";
|
|
26989
28645
|
const pp = passphraseLabel(tenant);
|
|
26990
28646
|
ctx.out.write(
|
|
26991
|
-
|
|
28647
|
+
padRight2(tenant.name, nameW) + " " + padRight2(status, 8) + padRight2(dashboard, 6) + padRight2(webhook, 6) + padRight2(pp, 15) + padRight2(tenant.storage_path, pathW) + "\n"
|
|
26992
28648
|
);
|
|
26993
28649
|
}
|
|
26994
28650
|
return 0;
|
|
@@ -27097,13 +28753,13 @@ async function cmdStatus(argv, ctx) {
|
|
|
27097
28753
|
const dash = tenant.runtime?.dashboard_port != null ? `:${tenant.runtime.dashboard_port}` : "";
|
|
27098
28754
|
const last = tenant.last_activity ? ` \xB7 last ${formatRelative2(tenant.last_activity)}` : "";
|
|
27099
28755
|
ctx.out.write(
|
|
27100
|
-
`${
|
|
28756
|
+
`${padRight2(tenant.name, nameW)} ${padRight2(state, 8)}${dash}${last}
|
|
27101
28757
|
`
|
|
27102
28758
|
);
|
|
27103
28759
|
}
|
|
27104
28760
|
return 0;
|
|
27105
28761
|
}
|
|
27106
|
-
var
|
|
28762
|
+
var init_cli4 = __esm({
|
|
27107
28763
|
"src/cli/agents/cli.ts"() {
|
|
27108
28764
|
init_discovery();
|
|
27109
28765
|
init_health();
|
|
@@ -27127,7 +28783,7 @@ var init_agents = __esm({
|
|
|
27127
28783
|
init_discovery();
|
|
27128
28784
|
init_health();
|
|
27129
28785
|
init_runtime();
|
|
27130
|
-
|
|
28786
|
+
init_cli4();
|
|
27131
28787
|
}
|
|
27132
28788
|
});
|
|
27133
28789
|
|
|
@@ -27641,8 +29297,8 @@ Refusing to generate a new recovery key over the default root \u2014 that would
|
|
|
27641
29297
|
const dashboardHost = options.host ?? config.dashboard.host;
|
|
27642
29298
|
let authToken = config.dashboard.auth_token;
|
|
27643
29299
|
if (authToken === "auto") {
|
|
27644
|
-
const { randomBytes:
|
|
27645
|
-
authToken =
|
|
29300
|
+
const { randomBytes: randomBytes8 } = await import('crypto');
|
|
29301
|
+
authToken = randomBytes8(32).toString("hex");
|
|
27646
29302
|
}
|
|
27647
29303
|
const dashboard = new DashboardApprovalChannel({
|
|
27648
29304
|
port: dashboardPort,
|
|
@@ -27867,6 +29523,11 @@ async function main() {
|
|
|
27867
29523
|
const code = await runSecretsCommand2({ argv: args.slice(1) });
|
|
27868
29524
|
process.exit(code);
|
|
27869
29525
|
}
|
|
29526
|
+
if (args[0] === "template") {
|
|
29527
|
+
const { runTemplateCommand: runTemplateCommand2 } = await Promise.resolve().then(() => (init_cli3(), cli_exports3));
|
|
29528
|
+
const code = await runTemplateCommand2({ argv: args.slice(1) });
|
|
29529
|
+
process.exit(code);
|
|
29530
|
+
}
|
|
27870
29531
|
if (args[0] === "agents") {
|
|
27871
29532
|
const { runAgentsCommand: runAgentsCommand2 } = await Promise.resolve().then(() => (init_agents(), agents_exports));
|
|
27872
29533
|
const code = await runAgentsCommand2({ argv: args.slice(1) });
|
|
@@ -28063,6 +29724,9 @@ Subcommands:
|
|
|
28063
29724
|
Use "sanctuary dashboard --help" for options.
|
|
28064
29725
|
Pass --multi to render the multi-tenant overview.
|
|
28065
29726
|
|
|
29727
|
+
template Manage policy templates (list, init).
|
|
29728
|
+
Use "sanctuary template --help" for options.
|
|
29729
|
+
|
|
28066
29730
|
agents List / inspect tenants on a multi-agent host.
|
|
28067
29731
|
Use "sanctuary agents --help" for options.
|
|
28068
29732
|
|