@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.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((resolve, reject) => {
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
- resolve();
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((resolve) => {
9009
- this.httpServer.close(() => resolve());
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((resolve) => {
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
- resolve(response);
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((resolve, reject) => {
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
- resolve();
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((resolve) => {
9924
- this.callbackServer.close(() => resolve());
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((resolve) => {
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
- resolve(response);
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 sha2567 = createCommitment(canonicalString);
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: sha2567.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: sha2567.commitment,
13169
- blinding_factor: sha2567.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((resolve, reject) => {
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
- resolve(result);
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((resolve, reject) => {
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
- resolve();
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((resolve, reject) => {
23597
- server.close((err) => err ? reject(err) : resolve());
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((resolve) => setTimeout(resolve, 2e3));
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);