@sanctuary-framework/mcp-server 1.1.4 → 1.1.6

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 CHANGED
@@ -28139,6 +28139,13 @@ var init_hub_service = __esm({
28139
28139
  nowIso() {
28140
28140
  return this.now().toISOString();
28141
28141
  }
28142
+ refreshPersistedLocalAgents() {
28143
+ const readPersistedLocalAgents2 = this.deps.readPersistedLocalAgents;
28144
+ if (!readPersistedLocalAgents2) return;
28145
+ for (const record of readPersistedLocalAgents2()) {
28146
+ this.deps.agentRegistry.put(record);
28147
+ }
28148
+ }
28142
28149
  // ── Inbox ───────────────────────────────────────────────────────────
28143
28150
  listInbox() {
28144
28151
  const items = aggregateInbox(this.deps.inboxSources, this.inboxStore);
@@ -28150,6 +28157,7 @@ var init_hub_service = __esm({
28150
28157
  }
28151
28158
  // ── Agents ──────────────────────────────────────────────────────────
28152
28159
  listAgents(filter) {
28160
+ this.refreshPersistedLocalAgents();
28153
28161
  const safeFilter = {
28154
28162
  ...filter ?? {},
28155
28163
  identity_id: this.deps.identityId
@@ -28583,12 +28591,76 @@ var init_hub = __esm({
28583
28591
  init_api_router();
28584
28592
  }
28585
28593
  });
28594
+ function localAgentsFilePath(storagePath) {
28595
+ return path.join(storagePath, "state", "_hub", "local-agents.json");
28596
+ }
28597
+ function readPersistedLocalAgents(storagePath) {
28598
+ const filePath = localAgentsFilePath(storagePath);
28599
+ if (!fs.existsSync(filePath)) return [];
28600
+ try {
28601
+ const raw = fs.readFileSync(filePath, "utf8");
28602
+ const parsed = JSON.parse(raw);
28603
+ if (!parsed || !Array.isArray(parsed.agents)) return [];
28604
+ return parsed.agents;
28605
+ } catch {
28606
+ return [];
28607
+ }
28608
+ }
28609
+ function writePersistedLocalAgents(storagePath, agents) {
28610
+ const filePath = localAgentsFilePath(storagePath);
28611
+ const dir = path.dirname(filePath);
28612
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
28613
+ const payload = {
28614
+ version: PERSISTED_VERSION,
28615
+ agents
28616
+ };
28617
+ const tmpPath = `${filePath}.tmp`;
28618
+ fs.writeFileSync(tmpPath, `${JSON.stringify(payload, null, 2)}
28619
+ `, {
28620
+ mode: 384
28621
+ });
28622
+ fs.renameSync(tmpPath, filePath);
28623
+ fs.chmodSync(filePath, 384);
28624
+ }
28625
+ function upsertPersistedLocalAgent(storagePath, record) {
28626
+ const existing = readPersistedLocalAgents(storagePath);
28627
+ const idx = existing.findIndex((r) => r.agent_id === record.agent_id);
28628
+ let next;
28629
+ if (idx >= 0) {
28630
+ const prior = existing[idx];
28631
+ if (prior === void 0) {
28632
+ next = [...existing, record];
28633
+ } else {
28634
+ const updated = {
28635
+ ...record,
28636
+ wrapped_at: prior.wrapped_at,
28637
+ last_activity_at: (/* @__PURE__ */ new Date()).toISOString()
28638
+ };
28639
+ next = [...existing];
28640
+ next[idx] = updated;
28641
+ }
28642
+ } else {
28643
+ next = [...existing, record];
28644
+ }
28645
+ writePersistedLocalAgents(storagePath, next);
28646
+ return next;
28647
+ }
28648
+ var PERSISTED_VERSION;
28649
+ var init_agent_registry_persistence = __esm({
28650
+ "src/hub/agent-registry-persistence.ts"() {
28651
+ PERSISTED_VERSION = "1.1";
28652
+ }
28653
+ });
28586
28654
  function buildV11Bindings(inputs) {
28587
- const registry = new InMemoryLocalAgentRegistry([]);
28655
+ const seed = inputs.storagePath !== void 0 ? readPersistedLocalAgents(inputs.storagePath) : [];
28656
+ const registry = new InMemoryLocalAgentRegistry(seed);
28657
+ const storagePath = inputs.storagePath;
28658
+ const readPersisted = storagePath !== void 0 ? () => readPersistedLocalAgents(storagePath) : void 0;
28588
28659
  const hubService = new HubService({
28589
28660
  identityId: inputs.identityId,
28590
28661
  fortressId: inputs.fortressId,
28591
28662
  agentRegistry: registry,
28663
+ ...readPersisted ? { readPersistedLocalAgents: readPersisted } : {},
28592
28664
  inboxSources: {
28593
28665
  listPendingApprovals: () => [],
28594
28666
  listRecentBlockedEgress: () => [],
@@ -28622,6 +28694,7 @@ var init_wiring = __esm({
28622
28694
  "src/dashboard/v1_1/wiring.ts"() {
28623
28695
  init_hub();
28624
28696
  init_errors4();
28697
+ init_agent_registry_persistence();
28625
28698
  CapabilityErrorAgentController = class {
28626
28699
  fail(action) {
28627
28700
  throw new HubCapabilityError(
@@ -30654,7 +30727,12 @@ Refusing to start the cocoon while the reset-history marker is unreadable.`
30654
30727
  buildV11Bindings({
30655
30728
  identityId: embeddedHubIdentityId,
30656
30729
  fortressId: fortressIdFromStoragePath(config.storage_path),
30657
- auditLog
30730
+ auditLog,
30731
+ // v1.1.5 (Finding Z): rehydrate the hub agent registry from
30732
+ // `<storagePath>/state/_hub/local-agents.json` so the embedded
30733
+ // dashboard surfaces wraps performed by prior `sanctuary wrap`
30734
+ // invocations against this same fortress.
30735
+ storagePath: config.storage_path
30658
30736
  })
30659
30737
  );
30660
30738
  await dashboard.start();
@@ -31297,6 +31375,7 @@ __export(cli_exports, {
31297
31375
  PORT_FALLBACK_ATTEMPTS: () => PORT_FALLBACK_ATTEMPTS,
31298
31376
  formatMcpServerCount: () => formatMcpServerCount,
31299
31377
  formatWrapSuccess: () => formatWrapSuccess,
31378
+ formatWrapSuccessNoDashboard: () => formatWrapSuccessNoDashboard,
31300
31379
  parseCocoonArgs: () => parseCocoonArgs,
31301
31380
  parseWrapArgs: () => parseWrapArgs,
31302
31381
  promoteFortressToStoragePath: () => promoteFortressToStoragePath,
@@ -31569,6 +31648,28 @@ async function runWrap(options, deps = {}) {
31569
31648
  backupPath
31570
31649
  );
31571
31650
  if (!verifyOk) process.exit(1);
31651
+ try {
31652
+ upsertPersistedLocalAgent(
31653
+ storagePath,
31654
+ buildLocalAgentRecord({
31655
+ storagePath,
31656
+ platform: agentConfig.platform
31657
+ })
31658
+ );
31659
+ } catch (err) {
31660
+ console.error(
31661
+ ` Note: v1.1 hub agent record not persisted (${err.message}). Re-run \`sanctuary wrap\` to retry, or check storage permissions on ${storagePath}.`
31662
+ );
31663
+ }
31664
+ if (options.noDashboard) {
31665
+ const toolName2 = toolNameFor(agentConfig.platform, agentConfig.servers);
31666
+ printWrapSuccessNoDashboard({
31667
+ toolName: toolName2,
31668
+ version: readPackageVersion(),
31669
+ toolCount: countUpstreamTools(upstreamServers),
31670
+ serverCount: upstreamServers.length});
31671
+ return;
31672
+ }
31572
31673
  const authToken = generateAuthToken();
31573
31674
  const startFn = deps.startDashboard ?? ((opts) => startDashboard({
31574
31675
  port: opts.port,
@@ -31608,7 +31709,12 @@ async function runWrap(options, deps = {}) {
31608
31709
  buildV11Bindings({
31609
31710
  identityId: `fortress:${storagePath}`,
31610
31711
  fortressId: fortressIdFromStoragePath(storagePath),
31611
- auditLog: wrapAuditLog
31712
+ auditLog: wrapAuditLog,
31713
+ // v1.1.5 (Finding Z): rehydrate from the file the upsert
31714
+ // above just wrote, so the registry the wrap-auto dashboard
31715
+ // serves contains this wrap plus any prior wraps against the
31716
+ // same fortress.
31717
+ storagePath
31612
31718
  })
31613
31719
  );
31614
31720
  dashboard.setV11LoopbackAutoAuth(true);
@@ -31748,6 +31854,32 @@ function formatWrapSuccess(info) {
31748
31854
  function printWrapSuccess(info) {
31749
31855
  console.error(formatWrapSuccess(info));
31750
31856
  }
31857
+ function formatWrapSuccessNoDashboard(info) {
31858
+ const g = (s) => `\x1B[32m${s}\x1B[0m`;
31859
+ const d = (s) => `\x1B[2m${s}\x1B[0m`;
31860
+ const b = (s) => `\x1B[1m${s}\x1B[0m`;
31861
+ const check = "\u2713";
31862
+ const lines = [];
31863
+ lines.push("");
31864
+ lines.push(
31865
+ ` ${g(check)} Wrapped ${b(info.toolName)} with Sanctuary v${info.version}`
31866
+ );
31867
+ lines.push(
31868
+ ` ${g(check)} ${info.toolCount} tools registered across ${info.serverCount} upstream server${info.serverCount !== 1 ? "s" : ""}`
31869
+ );
31870
+ lines.push(
31871
+ ` ${d("Dashboard spawn skipped per --no-dashboard. Run `sanctuary dashboard` separately for a persistent dashboard.")}`
31872
+ );
31873
+ lines.push("");
31874
+ lines.push(
31875
+ ` ${b("Your agent is protected.")} L1 Full / L2 Degraded (no TEE) / L3 Full / L4 Full.`
31876
+ );
31877
+ lines.push("");
31878
+ return lines.join("\n");
31879
+ }
31880
+ function printWrapSuccessNoDashboard(info) {
31881
+ console.error(formatWrapSuccessNoDashboard(info));
31882
+ }
31751
31883
  async function verifyRewrittenConfig(configPath, backupPath) {
31752
31884
  try {
31753
31885
  const raw = await promises.readFile(configPath, "utf-8");
@@ -31864,6 +31996,57 @@ function toolNameFor(platform4, _servers) {
31864
31996
  return "your agent";
31865
31997
  }
31866
31998
  }
31999
+ function harnessKindForPlatform(platform4) {
32000
+ switch (platform4) {
32001
+ case "openclaw":
32002
+ return "openclaw";
32003
+ case "hermes":
32004
+ return "hermes";
32005
+ case "claude-code":
32006
+ return "claude_code";
32007
+ case "cursor":
32008
+ return "cursor";
32009
+ case "cline":
32010
+ return "cline";
32011
+ case "generic":
32012
+ return "generic_mcp";
32013
+ default: {
32014
+ return "other";
32015
+ }
32016
+ }
32017
+ }
32018
+ function buildLocalAgentRecord(input) {
32019
+ const harness = harnessKindForPlatform(input.platform);
32020
+ const fortressId = fortressIdFromStoragePath(input.storagePath);
32021
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
32022
+ return {
32023
+ version: "1.1",
32024
+ agent_id: `agent:${harness}:${fortressId}`,
32025
+ identity_id: `fortress:${input.storagePath}`,
32026
+ harness,
32027
+ model_provider: {
32028
+ vendor: "unknown",
32029
+ model_id: "unknown",
32030
+ runs_locally: false
32031
+ },
32032
+ policy_id: "unbound",
32033
+ status: "active",
32034
+ budget_summary: {
32035
+ last_refreshed_at: nowIso
32036
+ },
32037
+ last_activity_at: nowIso,
32038
+ wrapped_at: nowIso,
32039
+ capabilities: {
32040
+ can_pause: false,
32041
+ can_resume: false,
32042
+ can_restart: false,
32043
+ can_unwrap: true,
32044
+ can_lockdown: false,
32045
+ can_chat: false,
32046
+ can_change_template: false
32047
+ }
32048
+ };
32049
+ }
31867
32050
  function countUpstreamTools(servers) {
31868
32051
  return servers.length === 0 ? 0 : servers.length;
31869
32052
  }
@@ -31924,6 +32107,9 @@ function parseWrapArgs(argv) {
31924
32107
  case "--no-open":
31925
32108
  options.noOpen = true;
31926
32109
  break;
32110
+ case "--no-dashboard":
32111
+ options.noDashboard = true;
32112
+ break;
31927
32113
  case "--fortress":
31928
32114
  options.fortress = argv[++i];
31929
32115
  break;
@@ -31964,6 +32150,11 @@ function printWrapHelp() {
31964
32150
  --port <port> Preferred dashboard port (default: 3501)
31965
32151
  --dry-run Show what would happen without making changes
31966
32152
  --no-open Do not auto-open the dashboard in a browser
32153
+ --no-dashboard Do not spawn a per-call dashboard server. Wrap still
32154
+ persists the agent record so a separately-running
32155
+ \`sanctuary dashboard\` (or a later wrap) sees the
32156
+ harness. Use this for the clean operator setup
32157
+ (one persistent dashboard + many wraps).
31967
32158
  --help, -h Show this help
31968
32159
 
31969
32160
  What happens:
@@ -31981,6 +32172,7 @@ var init_cli2 = __esm({
31981
32172
  init_passphrase();
31982
32173
  init_dashboard2();
31983
32174
  init_wiring();
32175
+ init_agent_registry_persistence();
31984
32176
  init_filesystem();
31985
32177
  init_key_derivation();
31986
32178
  init_encoding();
@@ -35618,7 +35810,12 @@ Refusing to start the dashboard while the reset-history marker is unreadable.`
35618
35810
  buildV11Bindings({
35619
35811
  identityId: hubIdentityId,
35620
35812
  fortressId: fortressIdFromStoragePath(config.storage_path),
35621
- auditLog
35813
+ auditLog,
35814
+ // v1.1.5 (Finding Z): rehydrate the hub agent registry from
35815
+ // `<storagePath>/state/_hub/local-agents.json` so the standalone
35816
+ // dashboard surfaces wraps performed by prior `sanctuary wrap`
35817
+ // invocations against this same fortress.
35818
+ storagePath: config.storage_path
35622
35819
  })
35623
35820
  );
35624
35821
  const hostIsLoopback = dashboardHost === "127.0.0.1" || dashboardHost === "::1" || dashboardHost === "localhost";
@@ -35648,6 +35845,10 @@ Refusing to start the dashboard while the reset-history marker is unreadable.`
35648
35845
  console.error(`Sanctuary Dashboard v${SANCTUARY_VERSION} (standalone mode)`);
35649
35846
  console.error(`Storage: ${config.storage_path}`);
35650
35847
  console.error(`Identities loaded: ${loadResult.loaded}`);
35848
+ const persistedAgentsCount = readPersistedLocalAgents(
35849
+ config.storage_path
35850
+ ).length;
35851
+ console.error(`Local agents loaded: ${persistedAgentsCount}`);
35651
35852
  console.error(`Listening: http://${dashboardHost}:${dashboardPort}`);
35652
35853
  if (loadResult.total > 0 && loadResult.loaded === 0) {
35653
35854
  const service = keychainServiceFor(config.storage_path, os.homedir());
@@ -35706,6 +35907,7 @@ var init_dashboard_standalone = __esm({
35706
35907
  init_recovery_key_disclosure();
35707
35908
  init_discovery();
35708
35909
  init_wiring();
35910
+ init_agent_registry_persistence();
35709
35911
  }
35710
35912
  });
35711
35913