@sanctuary-framework/mcp-server 1.0.0-rc.1 → 1.0.0-rc.2
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 +1747 -1716
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1747 -1716
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +208 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +209 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -8934,7 +8934,7 @@ var DashboardApprovalChannel = class {
|
|
|
8934
8934
|
server = http.createServer(handler);
|
|
8935
8935
|
}
|
|
8936
8936
|
this.httpServer = server;
|
|
8937
|
-
return new Promise((
|
|
8937
|
+
return new Promise((resolve2, reject) => {
|
|
8938
8938
|
const protocol = this.useTLS ? "https" : "http";
|
|
8939
8939
|
const baseUrl = `${protocol}://${this.config.host}:${this.config.port}`;
|
|
8940
8940
|
server.listen(this.config.port, this.config.host, () => {
|
|
@@ -8959,7 +8959,7 @@ var DashboardApprovalChannel = class {
|
|
|
8959
8959
|
if (shouldAutoOpen) {
|
|
8960
8960
|
this.openInBrowser(sessionUrl);
|
|
8961
8961
|
}
|
|
8962
|
-
|
|
8962
|
+
resolve2();
|
|
8963
8963
|
});
|
|
8964
8964
|
server.on("error", (err) => {
|
|
8965
8965
|
if (err.code === "EADDRINUSE") {
|
|
@@ -9005,8 +9005,8 @@ var DashboardApprovalChannel = class {
|
|
|
9005
9005
|
}
|
|
9006
9006
|
this.rateLimits.clear();
|
|
9007
9007
|
if (this.httpServer) {
|
|
9008
|
-
return new Promise((
|
|
9009
|
-
this.httpServer.close(() =>
|
|
9008
|
+
return new Promise((resolve2) => {
|
|
9009
|
+
this.httpServer.close(() => resolve2());
|
|
9010
9010
|
});
|
|
9011
9011
|
}
|
|
9012
9012
|
}
|
|
@@ -9020,7 +9020,7 @@ var DashboardApprovalChannel = class {
|
|
|
9020
9020
|
`[Sanctuary] Approval required: ${request.operation} (Tier ${request.tier}) \u2014 open dashboard to respond
|
|
9021
9021
|
`
|
|
9022
9022
|
);
|
|
9023
|
-
return new Promise((
|
|
9023
|
+
return new Promise((resolve2) => {
|
|
9024
9024
|
const timer = setTimeout(() => {
|
|
9025
9025
|
this.pending.delete(id);
|
|
9026
9026
|
const response = {
|
|
@@ -9034,12 +9034,12 @@ var DashboardApprovalChannel = class {
|
|
|
9034
9034
|
decision: response.decision,
|
|
9035
9035
|
decided_by: "timeout"
|
|
9036
9036
|
});
|
|
9037
|
-
|
|
9037
|
+
resolve2(response);
|
|
9038
9038
|
}, this.config.timeout_seconds * 1e3);
|
|
9039
9039
|
const pending = {
|
|
9040
9040
|
id,
|
|
9041
9041
|
request,
|
|
9042
|
-
resolve,
|
|
9042
|
+
resolve: resolve2,
|
|
9043
9043
|
timer,
|
|
9044
9044
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9045
9045
|
};
|
|
@@ -9885,7 +9885,7 @@ var WebhookApprovalChannel = class {
|
|
|
9885
9885
|
* Start the callback listener server.
|
|
9886
9886
|
*/
|
|
9887
9887
|
async start() {
|
|
9888
|
-
return new Promise((
|
|
9888
|
+
return new Promise((resolve2, reject) => {
|
|
9889
9889
|
this.callbackServer = http.createServer(
|
|
9890
9890
|
(req, res) => this.handleCallback(req, res)
|
|
9891
9891
|
);
|
|
@@ -9900,7 +9900,7 @@ var WebhookApprovalChannel = class {
|
|
|
9900
9900
|
|
|
9901
9901
|
`
|
|
9902
9902
|
);
|
|
9903
|
-
|
|
9903
|
+
resolve2();
|
|
9904
9904
|
}
|
|
9905
9905
|
);
|
|
9906
9906
|
this.callbackServer.on("error", reject);
|
|
@@ -9920,8 +9920,8 @@ var WebhookApprovalChannel = class {
|
|
|
9920
9920
|
}
|
|
9921
9921
|
this.pending.clear();
|
|
9922
9922
|
if (this.callbackServer) {
|
|
9923
|
-
return new Promise((
|
|
9924
|
-
this.callbackServer.close(() =>
|
|
9923
|
+
return new Promise((resolve2) => {
|
|
9924
|
+
this.callbackServer.close(() => resolve2());
|
|
9925
9925
|
});
|
|
9926
9926
|
}
|
|
9927
9927
|
}
|
|
@@ -9934,7 +9934,7 @@ var WebhookApprovalChannel = class {
|
|
|
9934
9934
|
`[Sanctuary] Webhook approval sent: ${request.operation} (Tier ${request.tier}) \u2014 awaiting callback
|
|
9935
9935
|
`
|
|
9936
9936
|
);
|
|
9937
|
-
return new Promise((
|
|
9937
|
+
return new Promise((resolve2) => {
|
|
9938
9938
|
const timer = setTimeout(() => {
|
|
9939
9939
|
this.pending.delete(id);
|
|
9940
9940
|
const response = {
|
|
@@ -9943,12 +9943,12 @@ var WebhookApprovalChannel = class {
|
|
|
9943
9943
|
decided_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9944
9944
|
decided_by: "timeout"
|
|
9945
9945
|
};
|
|
9946
|
-
|
|
9946
|
+
resolve2(response);
|
|
9947
9947
|
}, this.config.timeout_seconds * 1e3);
|
|
9948
9948
|
const pending = {
|
|
9949
9949
|
id,
|
|
9950
9950
|
request,
|
|
9951
|
-
resolve,
|
|
9951
|
+
resolve: resolve2,
|
|
9952
9952
|
timer,
|
|
9953
9953
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9954
9954
|
};
|
|
@@ -13142,7 +13142,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
13142
13142
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13143
13143
|
const canonicalBytes = canonicalize(outcome);
|
|
13144
13144
|
const canonicalString = new TextDecoder().decode(canonicalBytes);
|
|
13145
|
-
const
|
|
13145
|
+
const sha2568 = createCommitment(canonicalString);
|
|
13146
13146
|
let pedersenData;
|
|
13147
13147
|
if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
|
|
13148
13148
|
const pedersen = createPedersenCommitment(outcome.rounds);
|
|
@@ -13154,7 +13154,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
13154
13154
|
const commitmentPayload = {
|
|
13155
13155
|
bridge_commitment_id: commitmentId,
|
|
13156
13156
|
session_id: outcome.session_id,
|
|
13157
|
-
sha256_commitment:
|
|
13157
|
+
sha256_commitment: sha2568.commitment,
|
|
13158
13158
|
terms_hash: outcome.terms_hash,
|
|
13159
13159
|
committer_did: identity.did,
|
|
13160
13160
|
committed_at: now,
|
|
@@ -13165,8 +13165,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
13165
13165
|
return {
|
|
13166
13166
|
bridge_commitment_id: commitmentId,
|
|
13167
13167
|
session_id: outcome.session_id,
|
|
13168
|
-
sha256_commitment:
|
|
13169
|
-
blinding_factor:
|
|
13168
|
+
sha256_commitment: sha2568.commitment,
|
|
13169
|
+
blinding_factor: sha2568.blinding_factor,
|
|
13170
13170
|
committer_did: identity.did,
|
|
13171
13171
|
signature: toBase64url(signature),
|
|
13172
13172
|
pedersen_commitment: pedersenData,
|
|
@@ -17333,13 +17333,13 @@ var ProxyRouter = class {
|
|
|
17333
17333
|
* Call an upstream tool with a timeout.
|
|
17334
17334
|
*/
|
|
17335
17335
|
async callWithTimeout(serverName, toolName, args, timeoutMs) {
|
|
17336
|
-
return new Promise((
|
|
17336
|
+
return new Promise((resolve2, reject) => {
|
|
17337
17337
|
const timer = setTimeout(() => {
|
|
17338
17338
|
reject(new Error(`Upstream tool call timed out after ${timeoutMs}ms`));
|
|
17339
17339
|
}, timeoutMs);
|
|
17340
17340
|
this.clientManager.callTool(serverName, toolName, args).then((result) => {
|
|
17341
17341
|
clearTimeout(timer);
|
|
17342
|
-
|
|
17342
|
+
resolve2(result);
|
|
17343
17343
|
}).catch((err) => {
|
|
17344
17344
|
clearTimeout(timer);
|
|
17345
17345
|
reject(err);
|
|
@@ -22388,9 +22388,7 @@ var TEMPLATE_NAMES = [
|
|
|
22388
22388
|
"coding-assistant",
|
|
22389
22389
|
"ops-runner",
|
|
22390
22390
|
"planner",
|
|
22391
|
-
"handoff-coordinator"
|
|
22392
|
-
"x-miner",
|
|
22393
|
-
"github-miner"
|
|
22391
|
+
"handoff-coordinator"
|
|
22394
22392
|
];
|
|
22395
22393
|
function isTemplateName(value) {
|
|
22396
22394
|
return typeof value === "string" && TEMPLATE_NAMES.includes(value);
|
|
@@ -23315,6 +23313,176 @@ function initTemplate(params) {
|
|
|
23315
23313
|
});
|
|
23316
23314
|
return { compiled, signed_event, bundle };
|
|
23317
23315
|
}
|
|
23316
|
+
var DEFAULT_STORAGE_DIR = ".sanctuary";
|
|
23317
|
+
var KEYCHAIN_SERVICE_DEFAULT = "sanctuary-passphrase";
|
|
23318
|
+
function keychainServiceFor(storagePath, home = os.homedir()) {
|
|
23319
|
+
const defaultPath = path.join(home, DEFAULT_STORAGE_DIR);
|
|
23320
|
+
if (storagePath === defaultPath) return KEYCHAIN_SERVICE_DEFAULT;
|
|
23321
|
+
const digest = sha256.sha256(Buffer.from(storagePath, "utf-8"));
|
|
23322
|
+
const suffix = Buffer.from(digest).toString("hex").slice(0, 12);
|
|
23323
|
+
return `${KEYCHAIN_SERVICE_DEFAULT}-${suffix}`;
|
|
23324
|
+
}
|
|
23325
|
+
var RUNTIME_FILE_NAME = "runtime.json";
|
|
23326
|
+
function runtimePath(storagePath) {
|
|
23327
|
+
return path.join(storagePath, RUNTIME_FILE_NAME);
|
|
23328
|
+
}
|
|
23329
|
+
async function readTenantRuntime(storagePath) {
|
|
23330
|
+
try {
|
|
23331
|
+
const raw = await promises.readFile(runtimePath(storagePath), "utf-8");
|
|
23332
|
+
const parsed = JSON.parse(raw);
|
|
23333
|
+
if (typeof parsed.dashboard_port !== "number" || typeof parsed.pid !== "number" || typeof parsed.started_at !== "string" || typeof parsed.version !== "string" || typeof parsed.dashboard_host !== "string" || typeof parsed.mode !== "string") {
|
|
23334
|
+
return null;
|
|
23335
|
+
}
|
|
23336
|
+
const state = {
|
|
23337
|
+
version: parsed.version,
|
|
23338
|
+
pid: parsed.pid,
|
|
23339
|
+
started_at: parsed.started_at,
|
|
23340
|
+
dashboard_host: parsed.dashboard_host,
|
|
23341
|
+
dashboard_port: parsed.dashboard_port,
|
|
23342
|
+
mode: parsed.mode
|
|
23343
|
+
};
|
|
23344
|
+
if (typeof parsed.webhook_callback_port === "number") {
|
|
23345
|
+
state.webhook_callback_port = parsed.webhook_callback_port;
|
|
23346
|
+
}
|
|
23347
|
+
if (typeof parsed.webhook_callback_host === "string") {
|
|
23348
|
+
state.webhook_callback_host = parsed.webhook_callback_host;
|
|
23349
|
+
}
|
|
23350
|
+
return state;
|
|
23351
|
+
} catch {
|
|
23352
|
+
return null;
|
|
23353
|
+
}
|
|
23354
|
+
}
|
|
23355
|
+
|
|
23356
|
+
// src/cli/agents/discovery.ts
|
|
23357
|
+
var EXTRAS_FILE_NAME = "agents-extra.json";
|
|
23358
|
+
async function isTenantDir(path$1) {
|
|
23359
|
+
const [hasState, hasProfile, hasFallback] = await Promise.all([
|
|
23360
|
+
dirExists(path.join(path$1, "state")),
|
|
23361
|
+
fileExists2(path.join(path$1, "cocoon-profile.json")),
|
|
23362
|
+
fileExists2(path.join(path$1, "passphrase.enc"))
|
|
23363
|
+
]);
|
|
23364
|
+
const initialized = hasState;
|
|
23365
|
+
let passphraseStatus;
|
|
23366
|
+
if (hasFallback) passphraseStatus = "fallback-file";
|
|
23367
|
+
else if (hasProfile || hasState) passphraseStatus = "keychain";
|
|
23368
|
+
else passphraseStatus = "not-initialized";
|
|
23369
|
+
return { initialized, hasProfile, passphraseStatus };
|
|
23370
|
+
}
|
|
23371
|
+
async function dirExists(path) {
|
|
23372
|
+
try {
|
|
23373
|
+
const s = await promises.stat(path);
|
|
23374
|
+
return s.isDirectory();
|
|
23375
|
+
} catch {
|
|
23376
|
+
return false;
|
|
23377
|
+
}
|
|
23378
|
+
}
|
|
23379
|
+
async function fileExists2(path) {
|
|
23380
|
+
try {
|
|
23381
|
+
const s = await promises.stat(path);
|
|
23382
|
+
return s.isFile();
|
|
23383
|
+
} catch {
|
|
23384
|
+
return false;
|
|
23385
|
+
}
|
|
23386
|
+
}
|
|
23387
|
+
async function newestAuditMtime(storagePath) {
|
|
23388
|
+
const auditDir = path.join(storagePath, "state", "_audit");
|
|
23389
|
+
let entries = [];
|
|
23390
|
+
try {
|
|
23391
|
+
entries = await promises.readdir(auditDir);
|
|
23392
|
+
} catch {
|
|
23393
|
+
return null;
|
|
23394
|
+
}
|
|
23395
|
+
let newest = 0;
|
|
23396
|
+
for (const name of entries) {
|
|
23397
|
+
try {
|
|
23398
|
+
const s = await promises.stat(path.join(auditDir, name));
|
|
23399
|
+
if (s.isFile() && s.mtimeMs > newest) newest = s.mtimeMs;
|
|
23400
|
+
} catch {
|
|
23401
|
+
}
|
|
23402
|
+
}
|
|
23403
|
+
if (newest === 0) return null;
|
|
23404
|
+
return new Date(newest).toISOString();
|
|
23405
|
+
}
|
|
23406
|
+
async function readExtraPaths(root, env) {
|
|
23407
|
+
const out = [];
|
|
23408
|
+
const fromEnv = env.SANCTUARY_AGENTS_EXTRA_PATHS;
|
|
23409
|
+
if (fromEnv && fromEnv.length > 0) {
|
|
23410
|
+
for (const part of fromEnv.split(":")) {
|
|
23411
|
+
const trimmed = part.trim();
|
|
23412
|
+
if (trimmed.length > 0) out.push(path.resolve(trimmed));
|
|
23413
|
+
}
|
|
23414
|
+
}
|
|
23415
|
+
try {
|
|
23416
|
+
const raw = await promises.readFile(path.join(root, EXTRAS_FILE_NAME), "utf-8");
|
|
23417
|
+
const parsed = JSON.parse(raw);
|
|
23418
|
+
if (Array.isArray(parsed)) {
|
|
23419
|
+
for (const p of parsed) {
|
|
23420
|
+
if (typeof p === "string" && p.trim().length > 0) out.push(path.resolve(p));
|
|
23421
|
+
}
|
|
23422
|
+
}
|
|
23423
|
+
} catch {
|
|
23424
|
+
}
|
|
23425
|
+
return Array.from(new Set(out));
|
|
23426
|
+
}
|
|
23427
|
+
async function describeTenant(name, storagePath, home) {
|
|
23428
|
+
const exists = await dirExists(storagePath);
|
|
23429
|
+
if (!exists) return null;
|
|
23430
|
+
const { initialized, hasProfile, passphraseStatus } = await isTenantDir(storagePath);
|
|
23431
|
+
if (!initialized && !hasProfile && passphraseStatus === "not-initialized") {
|
|
23432
|
+
return null;
|
|
23433
|
+
}
|
|
23434
|
+
const last_activity = await newestAuditMtime(storagePath);
|
|
23435
|
+
const runtime = await readTenantRuntime(storagePath);
|
|
23436
|
+
return {
|
|
23437
|
+
name,
|
|
23438
|
+
storage_path: storagePath,
|
|
23439
|
+
exists: true,
|
|
23440
|
+
initialized,
|
|
23441
|
+
has_cocoon_profile: hasProfile,
|
|
23442
|
+
keychain_service: keychainServiceFor(storagePath, home),
|
|
23443
|
+
passphrase_status: passphraseStatus,
|
|
23444
|
+
last_activity,
|
|
23445
|
+
runtime
|
|
23446
|
+
};
|
|
23447
|
+
}
|
|
23448
|
+
async function discoverTenants(options = {}) {
|
|
23449
|
+
const home = options.home ?? os.homedir();
|
|
23450
|
+
const env = options.env ?? process.env;
|
|
23451
|
+
const root = options.root ?? path.join(home, DEFAULT_STORAGE_DIR);
|
|
23452
|
+
const tenants = [];
|
|
23453
|
+
const rootTenant = await describeTenant("default", root, home);
|
|
23454
|
+
if (rootTenant) tenants.push(rootTenant);
|
|
23455
|
+
let children = [];
|
|
23456
|
+
try {
|
|
23457
|
+
children = await promises.readdir(root);
|
|
23458
|
+
} catch {
|
|
23459
|
+
}
|
|
23460
|
+
for (const child of children) {
|
|
23461
|
+
const childPath = path.join(root, child);
|
|
23462
|
+
if (child.startsWith(".")) continue;
|
|
23463
|
+
if (child === "state" || child === "backup" || child === "config") continue;
|
|
23464
|
+
const s = await promises.stat(childPath).catch(() => null);
|
|
23465
|
+
if (!s || !s.isDirectory()) continue;
|
|
23466
|
+
const desc = await describeTenant(child, childPath, home);
|
|
23467
|
+
if (desc) tenants.push(desc);
|
|
23468
|
+
}
|
|
23469
|
+
const extras = await readExtraPaths(root, env);
|
|
23470
|
+
for (const extra of extras) {
|
|
23471
|
+
if (tenants.some((t) => t.storage_path === extra)) continue;
|
|
23472
|
+
const desc = await describeTenant(path.basename(extra), extra, home);
|
|
23473
|
+
if (desc) tenants.push(desc);
|
|
23474
|
+
}
|
|
23475
|
+
tenants.sort((a, b) => {
|
|
23476
|
+
if (a.name === "default") return -1;
|
|
23477
|
+
if (b.name === "default") return 1;
|
|
23478
|
+
return a.name.localeCompare(b.name);
|
|
23479
|
+
});
|
|
23480
|
+
return tenants;
|
|
23481
|
+
}
|
|
23482
|
+
async function findTenant(name, options = {}) {
|
|
23483
|
+
const tenants = await discoverTenants(options);
|
|
23484
|
+
return tenants.find((t) => t.name === name) ?? null;
|
|
23485
|
+
}
|
|
23318
23486
|
|
|
23319
23487
|
// src/dashboard/api.ts
|
|
23320
23488
|
function constantTimeEquals(a, b) {
|
|
@@ -23468,6 +23636,18 @@ async function handleRequest(deps, req, res) {
|
|
|
23468
23636
|
});
|
|
23469
23637
|
return true;
|
|
23470
23638
|
}
|
|
23639
|
+
const isAgentWrapped = deps.isAgentWrapped ?? (async (agentId) => {
|
|
23640
|
+
const tenant = await findTenant(agentId);
|
|
23641
|
+
if (!tenant) return false;
|
|
23642
|
+
return tenant.initialized || tenant.has_cocoon_profile;
|
|
23643
|
+
});
|
|
23644
|
+
if (!await isAgentWrapped(body.agent_name)) {
|
|
23645
|
+
writeJSON(res, 400, {
|
|
23646
|
+
error: "orphan_agent_id",
|
|
23647
|
+
message: `No wrapped harness found for agent_id "${body.agent_name}". Run \`sanctuary wrap\` to wrap the harness first, then retry template init.`
|
|
23648
|
+
});
|
|
23649
|
+
return true;
|
|
23650
|
+
}
|
|
23471
23651
|
const nodeId = deps.nodeId ?? "dashboard-node";
|
|
23472
23652
|
const nodePrivateKey = deps.nodePrivateKey ?? generateEphemeralKey();
|
|
23473
23653
|
const principalId = deps.principalId ?? "dashboard-principal";
|
|
@@ -23576,11 +23756,11 @@ async function startDashboardServer(options) {
|
|
|
23576
23756
|
}
|
|
23577
23757
|
}
|
|
23578
23758
|
});
|
|
23579
|
-
await new Promise((
|
|
23759
|
+
await new Promise((resolve2, reject) => {
|
|
23580
23760
|
server.once("error", reject);
|
|
23581
23761
|
server.listen(port, host, () => {
|
|
23582
23762
|
server.off("error", reject);
|
|
23583
|
-
|
|
23763
|
+
resolve2();
|
|
23584
23764
|
});
|
|
23585
23765
|
});
|
|
23586
23766
|
const actualPort = (() => {
|
|
@@ -23593,8 +23773,8 @@ async function startDashboardServer(options) {
|
|
|
23593
23773
|
url,
|
|
23594
23774
|
port: actualPort,
|
|
23595
23775
|
host,
|
|
23596
|
-
stop: () => new Promise((
|
|
23597
|
-
server.close((err) => err ? reject(err) :
|
|
23776
|
+
stop: () => new Promise((resolve2, reject) => {
|
|
23777
|
+
server.close((err) => err ? reject(err) : resolve2());
|
|
23598
23778
|
}),
|
|
23599
23779
|
publish,
|
|
23600
23780
|
publishActivity: (entry) => publish({ type: "activity", data: entry }),
|
|
@@ -24195,7 +24375,7 @@ async function createSanctuaryServer(options) {
|
|
|
24195
24375
|
clientManager.configure(enabledServers).catch((err) => {
|
|
24196
24376
|
console.error(`[Sanctuary] Failed to configure upstream servers: ${err instanceof Error ? err.message : "unknown error"}`);
|
|
24197
24377
|
});
|
|
24198
|
-
await new Promise((
|
|
24378
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2e3));
|
|
24199
24379
|
const proxiedTools = proxyRouter.getProxiedTools();
|
|
24200
24380
|
if (proxiedTools.length > 0) {
|
|
24201
24381
|
allTools.push(...proxiedTools);
|