@sanctuary-framework/mcp-server 1.1.5 → 1.1.7

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.d.cts CHANGED
@@ -4325,6 +4325,12 @@ interface HubPolicyAndBudgetSources {
4325
4325
  * may persist records to disk or recompute them per call.
4326
4326
  */
4327
4327
  interface HubAgentRegistrySource {
4328
+ /**
4329
+ * Insert or replace a record. Used by wrap-side registration and by
4330
+ * read-side persistence refresh to merge records written after dashboard
4331
+ * boot.
4332
+ */
4333
+ put(record: LocalAgentRecord): void;
4328
4334
  list(filter?: LocalAgentRegistryFilter): LocalAgentRecord[];
4329
4335
  get(agentId: string): LocalAgentRecord | null;
4330
4336
  /**
@@ -4350,6 +4356,12 @@ interface HubServiceDeps {
4350
4356
  fortressId: string;
4351
4357
  /** Local agent registry source. */
4352
4358
  agentRegistry: HubAgentRegistrySource;
4359
+ /**
4360
+ * Optional best-effort persisted agent reader. When supplied, list
4361
+ * operations merge records written after hub construction before
4362
+ * returning the in-memory projection.
4363
+ */
4364
+ readPersistedLocalAgents?: () => LocalAgentRecord[];
4353
4365
  /** Inbox aggregation sources. */
4354
4366
  inboxSources: HubInboxSources;
4355
4367
  /** Activity feed sources. */
@@ -4403,6 +4415,7 @@ declare class HubService {
4403
4415
  constructor(deps: HubServiceDeps);
4404
4416
  private now;
4405
4417
  private nowIso;
4418
+ private refreshPersistedLocalAgents;
4406
4419
  listInbox(): HubInboxItem[];
4407
4420
  resolveInboxItem(itemId: string, action: HubInboxAction): Promise<HubInboxItem>;
4408
4421
  listAgents(filter?: LocalAgentRegistryFilter): LocalAgentRecord[];
package/dist/index.d.ts CHANGED
@@ -4325,6 +4325,12 @@ interface HubPolicyAndBudgetSources {
4325
4325
  * may persist records to disk or recompute them per call.
4326
4326
  */
4327
4327
  interface HubAgentRegistrySource {
4328
+ /**
4329
+ * Insert or replace a record. Used by wrap-side registration and by
4330
+ * read-side persistence refresh to merge records written after dashboard
4331
+ * boot.
4332
+ */
4333
+ put(record: LocalAgentRecord): void;
4328
4334
  list(filter?: LocalAgentRegistryFilter): LocalAgentRecord[];
4329
4335
  get(agentId: string): LocalAgentRecord | null;
4330
4336
  /**
@@ -4350,6 +4356,12 @@ interface HubServiceDeps {
4350
4356
  fortressId: string;
4351
4357
  /** Local agent registry source. */
4352
4358
  agentRegistry: HubAgentRegistrySource;
4359
+ /**
4360
+ * Optional best-effort persisted agent reader. When supplied, list
4361
+ * operations merge records written after hub construction before
4362
+ * returning the in-memory projection.
4363
+ */
4364
+ readPersistedLocalAgents?: () => LocalAgentRecord[];
4353
4365
  /** Inbox aggregation sources. */
4354
4366
  inboxSources: HubInboxSources;
4355
4367
  /** Activity feed sources. */
@@ -4403,6 +4415,7 @@ declare class HubService {
4403
4415
  constructor(deps: HubServiceDeps);
4404
4416
  private now;
4405
4417
  private nowIso;
4418
+ private refreshPersistedLocalAgents;
4406
4419
  listInbox(): HubInboxItem[];
4407
4420
  resolveInboxItem(itemId: string, action: HubInboxAction): Promise<HubInboxItem>;
4408
4421
  listAgents(filter?: LocalAgentRegistryFilter): LocalAgentRecord[];
package/dist/index.js CHANGED
@@ -11160,7 +11160,7 @@ async function handleRequest(deps, req, res) {
11160
11160
  writeJSON(res, 200, { ok: true, mode: deps.sources.mode });
11161
11161
  return true;
11162
11162
  }
11163
- if (method === "GET" && (path === "/" || path === "/index.html")) {
11163
+ if (method === "GET" && (path === "/v1.0" || path === "/v1.0/" || path === "/v1.0/index.html")) {
11164
11164
  const snapshot = await getProtectionSnapshot(deps.sources);
11165
11165
  const html = renderDashboardHTML({ snapshot, authToken: deps.authToken });
11166
11166
  writeText(res, 200, html, "text/html; charset=utf-8");
@@ -12001,7 +12001,7 @@ function renderMain() {
12001
12001
  const main = document.getElementById("main");
12002
12002
  if (!main) return;
12003
12003
  switch (state.route) {
12004
- case "dashboard": main.innerHTML = renderChatThread(); break;
12004
+ case "dashboard": main.innerHTML = renderDashboardWelcome(); break;
12005
12005
  case "agents": main.innerHTML = renderAgentsList(); break;
12006
12006
  case "agent-detail": main.innerHTML = renderAgentDetail(); break;
12007
12007
  case "policy": main.innerHTML = renderPolicyCenter(); break;
@@ -12013,48 +12013,32 @@ function renderMain() {
12013
12013
  }
12014
12014
  }
12015
12015
 
12016
- // ── Render: chat thread ────────────────────────────────────────────────
12017
- function renderChatThread() {
12018
- const activeId = state.chatActiveAgentId;
12019
- const agent = activeId ? state.agents.find(function (a) { return a.agent_id === activeId; }) : null;
12020
- const header = '<header style="padding:8px 0;border-bottom:1px solid var(--rule);margin-bottom:8px;">' +
12021
- '<strong>' + escHtml(agent ? agent.agent_id : "Concierge") + '</strong>' +
12022
- ' <span class="muted">' + (agent ? "agent" : "Sanctuary advisory only. Operator-agent direct command via chat lands in v1.2.") + '</span>' +
12023
- '</header>';
12024
-
12025
- // Three message kinds. (1) operator (UI-side suggestions, not backend POST).
12026
- // (2) agent-initiated approvals from inbox filtered by agent_id. (3) activity
12027
- // events filtered by agent_id rendered as system-style.
12028
- const msgs = [];
12029
- if (!agent) {
12030
- msgs.push('<div class="chat-msg system"><strong>Concierge:</strong> Pick an agent in the right pane to scope the thread. Tier 1 actions appear here as approve/deny prompts. Free text is advisory only at v1.1.</div>');
12031
- } else {
12032
- const pending = state.inbox.filter(function (i) { return i.kind === "approval_pending" && i.agent_id === activeId && !i.resolved; });
12033
- pending.forEach(function (i) {
12034
- const text = renderTemplate(i.display_template_id, i.display_template_args);
12035
- msgs.push(
12036
- '<div class="chat-msg agent" data-msg-kind="agent-initiated" data-item-id="' + escHtml(i.item_id) + '">' +
12037
- '<div>' + escHtml(text) + '</div>' +
12038
- '<div style="margin-top:6px;display:flex;gap:6px;">' +
12039
- '<button class="btn btn-primary" data-action="inbox-approve" data-item-id="' + escHtml(i.item_id) + '">Approve</button>' +
12040
- '<button class="btn" data-action="inbox-deny" data-item-id="' + escHtml(i.item_id) + '">Deny</button>' +
12041
- '<button class="btn" data-action="show-details" data-item-id="' + escHtml(i.item_id) + '">Show details</button>' +
12042
- '</div>' +
12043
- '</div>'
12044
- );
12045
- });
12046
- const events = state.activity.filter(function (e) { return e.agent_id === activeId; }).slice(0, 20);
12047
- events.forEach(function (e) {
12048
- const text = renderTemplate(e.display_template_id, e.display_template_args);
12049
- msgs.push('<div class="chat-msg system"><span class="muted">' + escHtml(shortTime(e.emitted_at)) + '</span> ' + escHtml(text) + '</div>');
12050
- });
12051
- }
12052
-
12053
- const composer = '<form class="composer" id="chat-composer">' +
12054
- '<input type="text" placeholder="Suggestion to concierge. (Direct commands land in v1.2.)" id="chat-input" />' +
12055
- '<button type="submit" class="btn">Send</button>' +
12056
- '</form>';
12057
- return header + '<div class="chat-thread">' + (msgs.length ? msgs.join("\n") : '<p class="muted">No messages yet.</p>') + '</div>' + composer;
12016
+ // ── Render: dashboard welcome ──────────────────────────────────────────
12017
+ // v1.1.7: replaces the half-built chat surface that v1.1.6 shipped with
12018
+ // a "What you can do today" summary card mapping each nav target to
12019
+ // the operator action it enables. Direct concierge chat is a v1.2 work
12020
+ // package (WP-V1.2-3 + WP-V1.2-4).
12021
+ function renderDashboardWelcome() {
12022
+ return [
12023
+ '<h1>What you can do today</h1>',
12024
+ '<div class="card">',
12025
+ '<dl class="kv">',
12026
+ '<dt><a href="#agents">Agents</a></dt>',
12027
+ '<dd>Pause, resume, restart, lockdown, or unwrap any wrapped harness.</dd>',
12028
+ '<dt><a href="#policy">Policy</a></dt>',
12029
+ '<dd>Review the active policy bound to each agent.</dd>',
12030
+ '<dt><a href="#privacy">Privacy</a></dt>',
12031
+ '<dd>See what context is flowing to which provider per channel.</dd>',
12032
+ '<dt><a href="#coordination">Coordination</a></dt>',
12033
+ '<dd>Inspect intra-fortress agent coordination state.</dd>',
12034
+ '<dt><a href="#health">Health</a></dt>',
12035
+ '<dd>Check fortress posture, cocoon status, and dashboard refresh.</dd>',
12036
+ '<dt><a href="#exit-drill">Exit drill</a></dt>',
12037
+ '<dd>Snapshot, verify, and prepare a portable exit bundle.</dd>',
12038
+ '</dl>',
12039
+ '</div>',
12040
+ '<p class="muted">Direct chat with the concierge ships in v1.2.</p>'
12041
+ ].join("");
12058
12042
  }
12059
12043
 
12060
12044
  // ── Render: agents list / detail ───────────────────────────────────────
@@ -12093,8 +12077,10 @@ function renderAgentDetail() {
12093
12077
  '<dt>Status</dt><dd><span class="glyph ' + map.glyph + '"></span> ' + escHtml(map.label) + '</dd>' +
12094
12078
  '</dl>' +
12095
12079
  '</div>' +
12096
- '<div class="card"><h3>Timeline</h3>' + timeline + '</div>' +
12097
- '<button class="btn" data-action="set-route" data-route="dashboard">Open chat</button>';
12080
+ '<div class="card"><h3>Timeline</h3>' + timeline + '</div>';
12081
+ // v1.1.7: "Open chat" button removed alongside the half-built chat
12082
+ // surface (Finding EE). The agent-detail timeline + capability buttons
12083
+ // are the operator's interaction surface at v1.1; chat ships in v1.2.
12098
12084
  }
12099
12085
 
12100
12086
  // ── Render: privacy ────────────────────────────────────────────────────
@@ -12264,11 +12250,13 @@ function renderFortress() {
12264
12250
  : "This harness does not support " + mi.label.toLowerCase() + ".";
12265
12251
  return '<button class="btn" data-action="agent-' + mi.action + '" data-agent-id="' + escHtml(a.agent_id) + '"' + (mi.enabled ? '' : ' disabled') + ' title="' + escHtml(tip) + '">' + escHtml(mi.label) + '</button>';
12266
12252
  }).join("");
12267
- return '<div class="row" data-agent-row="' + escHtml(a.agent_id) + '">' +
12268
- '<span class="glyph ' + map.glyph + '" title="' + escHtml(REASON_LABELS[a.status_reason_class] || "") + '"></span>' +
12269
- '<div class="grow"><strong>' + escHtml(a.agent_id) + '</strong></div>' +
12270
- '<span class="pill">' + escHtml(map.label) + '</span>' +
12271
- '<div style="display:flex;gap:4px;flex-wrap:wrap;">' + buttons + '</div>' +
12253
+ return '<div class="row agent-row" data-agent-row="' + escHtml(a.agent_id) + '">' +
12254
+ '<div class="agent-row-head">' +
12255
+ '<span class="glyph ' + map.glyph + '" title="' + escHtml(REASON_LABELS[a.status_reason_class] || "") + '"></span>' +
12256
+ '<div class="grow"><strong>' + escHtml(a.agent_id) + '</strong></div>' +
12257
+ '<span class="pill">' + escHtml(map.label) + '</span>' +
12258
+ '</div>' +
12259
+ '<div class="agent-row-actions">' + buttons + '</div>' +
12272
12260
  '</div>';
12273
12261
  }).join("\n")
12274
12262
  : '<p class="muted">No agents wrapped.</p>';
@@ -12512,19 +12500,10 @@ document.addEventListener("click", function (ev) {
12512
12500
  }
12513
12501
  });
12514
12502
 
12515
- document.addEventListener("submit", function (ev) {
12516
- if (ev.target && ev.target.id === "chat-composer") {
12517
- ev.preventDefault();
12518
- const inp = document.getElementById("chat-input");
12519
- if (!inp) return;
12520
- const txt = inp.value.trim();
12521
- inp.value = "";
12522
- if (!txt) return;
12523
- // Concierge suggestion engine, NOT a backend command POST.
12524
- // Operator-typed direct commands land in v1.2.
12525
- toast("Concierge: I cannot run that as a command at v1.1. Use the inbox or the Agents view to take action.");
12526
- }
12527
- });
12503
+ // v1.1.7: chat composer submit handler removed alongside the half-built
12504
+ // chat surface (Finding EE). Direct concierge chat ships in v1.2; until
12505
+ // then the dashboard view renders a static welcome card with no form
12506
+ // inputs that could be confused for a working command surface.
12528
12507
 
12529
12508
  // Theme: system preference only at v1.1.
12530
12509
  const mq = window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null;
@@ -12662,6 +12641,10 @@ body {
12662
12641
  .row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px dashed var(--rule); }
12663
12642
  .row:last-child { border-bottom: 0; }
12664
12643
  .row .grow { flex: 1; min-width: 0; }
12644
+ .agent-row { flex-direction: column; align-items: stretch; gap: 6px; }
12645
+ .agent-row-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
12646
+ .agent-row-head .grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
12647
+ .agent-row-actions { display: flex; flex-wrap: wrap; gap: 4px; }
12665
12648
  .kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; font-size: 12px; }
12666
12649
  .kv dt { color: var(--ink-3); }
12667
12650
  .kv dd { margin: 0; color: var(--ink); }
@@ -12766,12 +12749,8 @@ function renderDashboardV11Html(options = {}) {
12766
12749
 
12767
12750
  // src/dashboard/v1_1/routes.ts
12768
12751
  function handleDashboardV11Route(deps, req, res) {
12769
- const routePath = deps.routePath ?? "/v1.1";
12770
- const host = req.headers.host || "localhost";
12771
- const url = new URL(req.url ?? "/", `http://${host}`);
12772
12752
  const method = (req.method ?? "GET").toUpperCase();
12773
12753
  if (method !== "GET") return false;
12774
- if (url.pathname !== routePath && url.pathname !== `${routePath}/`) return false;
12775
12754
  const html = renderDashboardV11Html(deps);
12776
12755
  res.writeHead(200, {
12777
12756
  "Content-Type": "text/html; charset=utf-8",
@@ -12784,7 +12763,7 @@ function handleDashboardV11Route(deps, req, res) {
12784
12763
  // src/dashboard/v1_1/dispatch.ts
12785
12764
  async function dispatchV11Request(inputs, req, res, url, method) {
12786
12765
  const { bindings, authToken, loopbackAutoAuth } = inputs;
12787
- if (method === "GET" && (url.pathname === "/v1.1" || url.pathname === "/v1.1/")) {
12766
+ if (method === "GET" && (url.pathname === "/" || url.pathname === "/dashboard" || url.pathname === "/v1.1" || url.pathname === "/v1.1/")) {
12788
12767
  return handleDashboardV11Route(
12789
12768
  {
12790
12769
  identityId: bindings.identityId,
@@ -12832,7 +12811,7 @@ var RATE_LIMIT_DECISIONS = 20;
12832
12811
  var MAX_RATE_LIMIT_ENTRIES = 1e4;
12833
12812
  function isDashboardViewRoute(method, path) {
12834
12813
  if (method !== "GET") return false;
12835
- return path === "/" || path === "/dashboard" || path === "/fortress" || path === "/events";
12814
+ return path === "/" || path === "/dashboard" || path === "/v1.0" || path === "/fortress" || path === "/events";
12836
12815
  }
12837
12816
  var DashboardApprovalChannel = class {
12838
12817
  config;
@@ -13336,7 +13315,7 @@ var DashboardApprovalChannel = class {
13336
13315
  }
13337
13316
  return;
13338
13317
  }
13339
- if (method === "GET" && url.pathname === "/" && this.authToken) {
13318
+ if (method === "GET" && url.pathname === "/v1.0" && this.authToken) {
13340
13319
  if (!this.isAuthenticated(req, url)) {
13341
13320
  this.serveLoginPage(res);
13342
13321
  return;
@@ -13349,7 +13328,7 @@ var DashboardApprovalChannel = class {
13349
13328
  try {
13350
13329
  if (method === "GET" && url.pathname === "/fortress") {
13351
13330
  this.serveFortressView(res);
13352
- } else if (method === "GET" && (url.pathname === "/" || url.pathname === "/dashboard")) {
13331
+ } else if (method === "GET" && url.pathname === "/v1.0") {
13353
13332
  if (this.fortressHTML) {
13354
13333
  this.serveFortressView(res);
13355
13334
  } else {
@@ -26871,6 +26850,13 @@ var HubService = class {
26871
26850
  nowIso() {
26872
26851
  return this.now().toISOString();
26873
26852
  }
26853
+ refreshPersistedLocalAgents() {
26854
+ const readPersistedLocalAgents2 = this.deps.readPersistedLocalAgents;
26855
+ if (!readPersistedLocalAgents2) return;
26856
+ for (const record of readPersistedLocalAgents2()) {
26857
+ this.deps.agentRegistry.put(record);
26858
+ }
26859
+ }
26874
26860
  // ── Inbox ───────────────────────────────────────────────────────────
26875
26861
  listInbox() {
26876
26862
  const items = aggregateInbox(this.deps.inboxSources, this.inboxStore);
@@ -26882,6 +26868,7 @@ var HubService = class {
26882
26868
  }
26883
26869
  // ── Agents ──────────────────────────────────────────────────────────
26884
26870
  listAgents(filter) {
26871
+ this.refreshPersistedLocalAgents();
26885
26872
  const safeFilter = {
26886
26873
  ...filter ?? {},
26887
26874
  identity_id: this.deps.identityId
@@ -27346,10 +27333,13 @@ var CapabilityErrorAgentController = class {
27346
27333
  function buildV11Bindings(inputs) {
27347
27334
  const seed = inputs.storagePath !== void 0 ? readPersistedLocalAgents(inputs.storagePath) : [];
27348
27335
  const registry = new InMemoryLocalAgentRegistry(seed);
27336
+ const storagePath = inputs.storagePath;
27337
+ const readPersisted = storagePath !== void 0 ? () => readPersistedLocalAgents(storagePath) : void 0;
27349
27338
  const hubService = new HubService({
27350
27339
  identityId: inputs.identityId,
27351
27340
  fortressId: inputs.fortressId,
27352
27341
  agentRegistry: registry,
27342
+ ...readPersisted ? { readPersistedLocalAgents: readPersisted } : {},
27353
27343
  inboxSources: {
27354
27344
  listPendingApprovals: () => [],
27355
27345
  listRecentBlockedEgress: () => [],