@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/index.js CHANGED
@@ -4,7 +4,7 @@ import { sha256 } from '@noble/hashes/sha256';
4
4
  import { hmac } from '@noble/hashes/hmac';
5
5
  import { RistrettoPoint, ed25519 } from '@noble/curves/ed25519';
6
6
  import { readFile, mkdir, writeFile, stat, unlink, readdir, chmod, access } from 'fs/promises';
7
- import { join, dirname } from 'path';
7
+ import { join, basename, dirname, resolve } from 'path';
8
8
  import { platform, homedir } from 'os';
9
9
  import { createRequire } from 'module';
10
10
  import { argon2id } from 'hash-wasm';
@@ -8931,7 +8931,7 @@ var DashboardApprovalChannel = class {
8931
8931
  server = createServer$2(handler);
8932
8932
  }
8933
8933
  this.httpServer = server;
8934
- return new Promise((resolve, reject) => {
8934
+ return new Promise((resolve2, reject) => {
8935
8935
  const protocol = this.useTLS ? "https" : "http";
8936
8936
  const baseUrl = `${protocol}://${this.config.host}:${this.config.port}`;
8937
8937
  server.listen(this.config.port, this.config.host, () => {
@@ -8956,7 +8956,7 @@ var DashboardApprovalChannel = class {
8956
8956
  if (shouldAutoOpen) {
8957
8957
  this.openInBrowser(sessionUrl);
8958
8958
  }
8959
- resolve();
8959
+ resolve2();
8960
8960
  });
8961
8961
  server.on("error", (err) => {
8962
8962
  if (err.code === "EADDRINUSE") {
@@ -9002,8 +9002,8 @@ var DashboardApprovalChannel = class {
9002
9002
  }
9003
9003
  this.rateLimits.clear();
9004
9004
  if (this.httpServer) {
9005
- return new Promise((resolve) => {
9006
- this.httpServer.close(() => resolve());
9005
+ return new Promise((resolve2) => {
9006
+ this.httpServer.close(() => resolve2());
9007
9007
  });
9008
9008
  }
9009
9009
  }
@@ -9017,7 +9017,7 @@ var DashboardApprovalChannel = class {
9017
9017
  `[Sanctuary] Approval required: ${request.operation} (Tier ${request.tier}) \u2014 open dashboard to respond
9018
9018
  `
9019
9019
  );
9020
- return new Promise((resolve) => {
9020
+ return new Promise((resolve2) => {
9021
9021
  const timer = setTimeout(() => {
9022
9022
  this.pending.delete(id);
9023
9023
  const response = {
@@ -9031,12 +9031,12 @@ var DashboardApprovalChannel = class {
9031
9031
  decision: response.decision,
9032
9032
  decided_by: "timeout"
9033
9033
  });
9034
- resolve(response);
9034
+ resolve2(response);
9035
9035
  }, this.config.timeout_seconds * 1e3);
9036
9036
  const pending = {
9037
9037
  id,
9038
9038
  request,
9039
- resolve,
9039
+ resolve: resolve2,
9040
9040
  timer,
9041
9041
  created_at: (/* @__PURE__ */ new Date()).toISOString()
9042
9042
  };
@@ -9882,7 +9882,7 @@ var WebhookApprovalChannel = class {
9882
9882
  * Start the callback listener server.
9883
9883
  */
9884
9884
  async start() {
9885
- return new Promise((resolve, reject) => {
9885
+ return new Promise((resolve2, reject) => {
9886
9886
  this.callbackServer = createServer$2(
9887
9887
  (req, res) => this.handleCallback(req, res)
9888
9888
  );
@@ -9897,7 +9897,7 @@ var WebhookApprovalChannel = class {
9897
9897
 
9898
9898
  `
9899
9899
  );
9900
- resolve();
9900
+ resolve2();
9901
9901
  }
9902
9902
  );
9903
9903
  this.callbackServer.on("error", reject);
@@ -9917,8 +9917,8 @@ var WebhookApprovalChannel = class {
9917
9917
  }
9918
9918
  this.pending.clear();
9919
9919
  if (this.callbackServer) {
9920
- return new Promise((resolve) => {
9921
- this.callbackServer.close(() => resolve());
9920
+ return new Promise((resolve2) => {
9921
+ this.callbackServer.close(() => resolve2());
9922
9922
  });
9923
9923
  }
9924
9924
  }
@@ -9931,7 +9931,7 @@ var WebhookApprovalChannel = class {
9931
9931
  `[Sanctuary] Webhook approval sent: ${request.operation} (Tier ${request.tier}) \u2014 awaiting callback
9932
9932
  `
9933
9933
  );
9934
- return new Promise((resolve) => {
9934
+ return new Promise((resolve2) => {
9935
9935
  const timer = setTimeout(() => {
9936
9936
  this.pending.delete(id);
9937
9937
  const response = {
@@ -9940,12 +9940,12 @@ var WebhookApprovalChannel = class {
9940
9940
  decided_at: (/* @__PURE__ */ new Date()).toISOString(),
9941
9941
  decided_by: "timeout"
9942
9942
  };
9943
- resolve(response);
9943
+ resolve2(response);
9944
9944
  }, this.config.timeout_seconds * 1e3);
9945
9945
  const pending = {
9946
9946
  id,
9947
9947
  request,
9948
- resolve,
9948
+ resolve: resolve2,
9949
9949
  timer,
9950
9950
  created_at: (/* @__PURE__ */ new Date()).toISOString()
9951
9951
  };
@@ -13139,7 +13139,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
13139
13139
  const now = (/* @__PURE__ */ new Date()).toISOString();
13140
13140
  const canonicalBytes = canonicalize(outcome);
13141
13141
  const canonicalString = new TextDecoder().decode(canonicalBytes);
13142
- const sha2567 = createCommitment(canonicalString);
13142
+ const sha2568 = createCommitment(canonicalString);
13143
13143
  let pedersenData;
13144
13144
  if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
13145
13145
  const pedersen = createPedersenCommitment(outcome.rounds);
@@ -13151,7 +13151,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
13151
13151
  const commitmentPayload = {
13152
13152
  bridge_commitment_id: commitmentId,
13153
13153
  session_id: outcome.session_id,
13154
- sha256_commitment: sha2567.commitment,
13154
+ sha256_commitment: sha2568.commitment,
13155
13155
  terms_hash: outcome.terms_hash,
13156
13156
  committer_did: identity.did,
13157
13157
  committed_at: now,
@@ -13162,8 +13162,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
13162
13162
  return {
13163
13163
  bridge_commitment_id: commitmentId,
13164
13164
  session_id: outcome.session_id,
13165
- sha256_commitment: sha2567.commitment,
13166
- blinding_factor: sha2567.blinding_factor,
13165
+ sha256_commitment: sha2568.commitment,
13166
+ blinding_factor: sha2568.blinding_factor,
13167
13167
  committer_did: identity.did,
13168
13168
  signature: toBase64url(signature),
13169
13169
  pedersen_commitment: pedersenData,
@@ -17330,13 +17330,13 @@ var ProxyRouter = class {
17330
17330
  * Call an upstream tool with a timeout.
17331
17331
  */
17332
17332
  async callWithTimeout(serverName, toolName, args, timeoutMs) {
17333
- return new Promise((resolve, reject) => {
17333
+ return new Promise((resolve2, reject) => {
17334
17334
  const timer = setTimeout(() => {
17335
17335
  reject(new Error(`Upstream tool call timed out after ${timeoutMs}ms`));
17336
17336
  }, timeoutMs);
17337
17337
  this.clientManager.callTool(serverName, toolName, args).then((result) => {
17338
17338
  clearTimeout(timer);
17339
- resolve(result);
17339
+ resolve2(result);
17340
17340
  }).catch((err) => {
17341
17341
  clearTimeout(timer);
17342
17342
  reject(err);
@@ -22385,9 +22385,7 @@ var TEMPLATE_NAMES = [
22385
22385
  "coding-assistant",
22386
22386
  "ops-runner",
22387
22387
  "planner",
22388
- "handoff-coordinator",
22389
- "x-miner",
22390
- "github-miner"
22388
+ "handoff-coordinator"
22391
22389
  ];
22392
22390
  function isTemplateName(value) {
22393
22391
  return typeof value === "string" && TEMPLATE_NAMES.includes(value);
@@ -23312,6 +23310,176 @@ function initTemplate(params) {
23312
23310
  });
23313
23311
  return { compiled, signed_event, bundle };
23314
23312
  }
23313
+ var DEFAULT_STORAGE_DIR = ".sanctuary";
23314
+ var KEYCHAIN_SERVICE_DEFAULT = "sanctuary-passphrase";
23315
+ function keychainServiceFor(storagePath, home = homedir()) {
23316
+ const defaultPath = join(home, DEFAULT_STORAGE_DIR);
23317
+ if (storagePath === defaultPath) return KEYCHAIN_SERVICE_DEFAULT;
23318
+ const digest = sha256(Buffer.from(storagePath, "utf-8"));
23319
+ const suffix = Buffer.from(digest).toString("hex").slice(0, 12);
23320
+ return `${KEYCHAIN_SERVICE_DEFAULT}-${suffix}`;
23321
+ }
23322
+ var RUNTIME_FILE_NAME = "runtime.json";
23323
+ function runtimePath(storagePath) {
23324
+ return join(storagePath, RUNTIME_FILE_NAME);
23325
+ }
23326
+ async function readTenantRuntime(storagePath) {
23327
+ try {
23328
+ const raw = await readFile(runtimePath(storagePath), "utf-8");
23329
+ const parsed = JSON.parse(raw);
23330
+ 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") {
23331
+ return null;
23332
+ }
23333
+ const state = {
23334
+ version: parsed.version,
23335
+ pid: parsed.pid,
23336
+ started_at: parsed.started_at,
23337
+ dashboard_host: parsed.dashboard_host,
23338
+ dashboard_port: parsed.dashboard_port,
23339
+ mode: parsed.mode
23340
+ };
23341
+ if (typeof parsed.webhook_callback_port === "number") {
23342
+ state.webhook_callback_port = parsed.webhook_callback_port;
23343
+ }
23344
+ if (typeof parsed.webhook_callback_host === "string") {
23345
+ state.webhook_callback_host = parsed.webhook_callback_host;
23346
+ }
23347
+ return state;
23348
+ } catch {
23349
+ return null;
23350
+ }
23351
+ }
23352
+
23353
+ // src/cli/agents/discovery.ts
23354
+ var EXTRAS_FILE_NAME = "agents-extra.json";
23355
+ async function isTenantDir(path) {
23356
+ const [hasState, hasProfile, hasFallback] = await Promise.all([
23357
+ dirExists(join(path, "state")),
23358
+ fileExists2(join(path, "cocoon-profile.json")),
23359
+ fileExists2(join(path, "passphrase.enc"))
23360
+ ]);
23361
+ const initialized = hasState;
23362
+ let passphraseStatus;
23363
+ if (hasFallback) passphraseStatus = "fallback-file";
23364
+ else if (hasProfile || hasState) passphraseStatus = "keychain";
23365
+ else passphraseStatus = "not-initialized";
23366
+ return { initialized, hasProfile, passphraseStatus };
23367
+ }
23368
+ async function dirExists(path) {
23369
+ try {
23370
+ const s = await stat(path);
23371
+ return s.isDirectory();
23372
+ } catch {
23373
+ return false;
23374
+ }
23375
+ }
23376
+ async function fileExists2(path) {
23377
+ try {
23378
+ const s = await stat(path);
23379
+ return s.isFile();
23380
+ } catch {
23381
+ return false;
23382
+ }
23383
+ }
23384
+ async function newestAuditMtime(storagePath) {
23385
+ const auditDir = join(storagePath, "state", "_audit");
23386
+ let entries = [];
23387
+ try {
23388
+ entries = await readdir(auditDir);
23389
+ } catch {
23390
+ return null;
23391
+ }
23392
+ let newest = 0;
23393
+ for (const name of entries) {
23394
+ try {
23395
+ const s = await stat(join(auditDir, name));
23396
+ if (s.isFile() && s.mtimeMs > newest) newest = s.mtimeMs;
23397
+ } catch {
23398
+ }
23399
+ }
23400
+ if (newest === 0) return null;
23401
+ return new Date(newest).toISOString();
23402
+ }
23403
+ async function readExtraPaths(root, env) {
23404
+ const out = [];
23405
+ const fromEnv = env.SANCTUARY_AGENTS_EXTRA_PATHS;
23406
+ if (fromEnv && fromEnv.length > 0) {
23407
+ for (const part of fromEnv.split(":")) {
23408
+ const trimmed = part.trim();
23409
+ if (trimmed.length > 0) out.push(resolve(trimmed));
23410
+ }
23411
+ }
23412
+ try {
23413
+ const raw = await readFile(join(root, EXTRAS_FILE_NAME), "utf-8");
23414
+ const parsed = JSON.parse(raw);
23415
+ if (Array.isArray(parsed)) {
23416
+ for (const p of parsed) {
23417
+ if (typeof p === "string" && p.trim().length > 0) out.push(resolve(p));
23418
+ }
23419
+ }
23420
+ } catch {
23421
+ }
23422
+ return Array.from(new Set(out));
23423
+ }
23424
+ async function describeTenant(name, storagePath, home) {
23425
+ const exists = await dirExists(storagePath);
23426
+ if (!exists) return null;
23427
+ const { initialized, hasProfile, passphraseStatus } = await isTenantDir(storagePath);
23428
+ if (!initialized && !hasProfile && passphraseStatus === "not-initialized") {
23429
+ return null;
23430
+ }
23431
+ const last_activity = await newestAuditMtime(storagePath);
23432
+ const runtime = await readTenantRuntime(storagePath);
23433
+ return {
23434
+ name,
23435
+ storage_path: storagePath,
23436
+ exists: true,
23437
+ initialized,
23438
+ has_cocoon_profile: hasProfile,
23439
+ keychain_service: keychainServiceFor(storagePath, home),
23440
+ passphrase_status: passphraseStatus,
23441
+ last_activity,
23442
+ runtime
23443
+ };
23444
+ }
23445
+ async function discoverTenants(options = {}) {
23446
+ const home = options.home ?? homedir();
23447
+ const env = options.env ?? process.env;
23448
+ const root = options.root ?? join(home, DEFAULT_STORAGE_DIR);
23449
+ const tenants = [];
23450
+ const rootTenant = await describeTenant("default", root, home);
23451
+ if (rootTenant) tenants.push(rootTenant);
23452
+ let children = [];
23453
+ try {
23454
+ children = await readdir(root);
23455
+ } catch {
23456
+ }
23457
+ for (const child of children) {
23458
+ const childPath = join(root, child);
23459
+ if (child.startsWith(".")) continue;
23460
+ if (child === "state" || child === "backup" || child === "config") continue;
23461
+ const s = await stat(childPath).catch(() => null);
23462
+ if (!s || !s.isDirectory()) continue;
23463
+ const desc = await describeTenant(child, childPath, home);
23464
+ if (desc) tenants.push(desc);
23465
+ }
23466
+ const extras = await readExtraPaths(root, env);
23467
+ for (const extra of extras) {
23468
+ if (tenants.some((t) => t.storage_path === extra)) continue;
23469
+ const desc = await describeTenant(basename(extra), extra, home);
23470
+ if (desc) tenants.push(desc);
23471
+ }
23472
+ tenants.sort((a, b) => {
23473
+ if (a.name === "default") return -1;
23474
+ if (b.name === "default") return 1;
23475
+ return a.name.localeCompare(b.name);
23476
+ });
23477
+ return tenants;
23478
+ }
23479
+ async function findTenant(name, options = {}) {
23480
+ const tenants = await discoverTenants(options);
23481
+ return tenants.find((t) => t.name === name) ?? null;
23482
+ }
23315
23483
 
23316
23484
  // src/dashboard/api.ts
23317
23485
  function constantTimeEquals(a, b) {
@@ -23465,6 +23633,18 @@ async function handleRequest(deps, req, res) {
23465
23633
  });
23466
23634
  return true;
23467
23635
  }
23636
+ const isAgentWrapped = deps.isAgentWrapped ?? (async (agentId) => {
23637
+ const tenant = await findTenant(agentId);
23638
+ if (!tenant) return false;
23639
+ return tenant.initialized || tenant.has_cocoon_profile;
23640
+ });
23641
+ if (!await isAgentWrapped(body.agent_name)) {
23642
+ writeJSON(res, 400, {
23643
+ error: "orphan_agent_id",
23644
+ message: `No wrapped harness found for agent_id "${body.agent_name}". Run \`sanctuary wrap\` to wrap the harness first, then retry template init.`
23645
+ });
23646
+ return true;
23647
+ }
23468
23648
  const nodeId = deps.nodeId ?? "dashboard-node";
23469
23649
  const nodePrivateKey = deps.nodePrivateKey ?? generateEphemeralKey();
23470
23650
  const principalId = deps.principalId ?? "dashboard-principal";
@@ -23573,11 +23753,11 @@ async function startDashboardServer(options) {
23573
23753
  }
23574
23754
  }
23575
23755
  });
23576
- await new Promise((resolve, reject) => {
23756
+ await new Promise((resolve2, reject) => {
23577
23757
  server.once("error", reject);
23578
23758
  server.listen(port, host, () => {
23579
23759
  server.off("error", reject);
23580
- resolve();
23760
+ resolve2();
23581
23761
  });
23582
23762
  });
23583
23763
  const actualPort = (() => {
@@ -23590,8 +23770,8 @@ async function startDashboardServer(options) {
23590
23770
  url,
23591
23771
  port: actualPort,
23592
23772
  host,
23593
- stop: () => new Promise((resolve, reject) => {
23594
- server.close((err) => err ? reject(err) : resolve());
23773
+ stop: () => new Promise((resolve2, reject) => {
23774
+ server.close((err) => err ? reject(err) : resolve2());
23595
23775
  }),
23596
23776
  publish,
23597
23777
  publishActivity: (entry) => publish({ type: "activity", data: entry }),
@@ -24192,7 +24372,7 @@ async function createSanctuaryServer(options) {
24192
24372
  clientManager.configure(enabledServers).catch((err) => {
24193
24373
  console.error(`[Sanctuary] Failed to configure upstream servers: ${err instanceof Error ? err.message : "unknown error"}`);
24194
24374
  });
24195
- await new Promise((resolve) => setTimeout(resolve, 2e3));
24375
+ await new Promise((resolve2) => setTimeout(resolve2, 2e3));
24196
24376
  const proxiedTools = proxyRouter.getProxiedTools();
24197
24377
  if (proxiedTools.length > 0) {
24198
24378
  allTools.push(...proxiedTools);