@sanctuary-framework/mcp-server 1.2.0 → 1.2.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.
Files changed (36) hide show
  1. package/dist/cli.cjs +1952 -405
  2. package/dist/cli.cjs.map +1 -1
  3. package/dist/cli.js +1953 -406
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.cjs +1646 -305
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +105 -18
  8. package/dist/index.d.ts +105 -18
  9. package/dist/index.js +1646 -306
  10. package/dist/index.js.map +1 -1
  11. package/dist/templates/coding-assistant/commitments.json +14 -0
  12. package/dist/templates/coding-assistant/defaults.json +34 -0
  13. package/dist/templates/coding-assistant/onboarding.md +24 -0
  14. package/dist/templates/coding-assistant/policy.md +1 -0
  15. package/dist/templates/coding-assistant/template.json +23 -0
  16. package/dist/templates/handoff-coordinator/commitments.json +14 -0
  17. package/dist/templates/handoff-coordinator/defaults.json +10 -0
  18. package/dist/templates/handoff-coordinator/onboarding.md +23 -0
  19. package/dist/templates/handoff-coordinator/policy.md +1 -0
  20. package/dist/templates/handoff-coordinator/template.json +17 -0
  21. package/dist/templates/ops-runner/commitments.json +14 -0
  22. package/dist/templates/ops-runner/defaults.json +12 -0
  23. package/dist/templates/ops-runner/onboarding.md +25 -0
  24. package/dist/templates/ops-runner/policy.md +1 -0
  25. package/dist/templates/ops-runner/template.json +16 -0
  26. package/dist/templates/planner/commitments.json +9 -0
  27. package/dist/templates/planner/defaults.json +10 -0
  28. package/dist/templates/planner/onboarding.md +22 -0
  29. package/dist/templates/planner/policy.md +1 -0
  30. package/dist/templates/planner/template.json +8 -0
  31. package/dist/templates/research-assistant/commitments.json +9 -0
  32. package/dist/templates/research-assistant/defaults.json +25 -0
  33. package/dist/templates/research-assistant/onboarding.md +21 -0
  34. package/dist/templates/research-assistant/policy.md +1 -0
  35. package/dist/templates/research-assistant/template.json +8 -0
  36. package/package.json +4 -4
package/dist/cli.cjs CHANGED
@@ -398,23 +398,44 @@ var init_random = __esm({
398
398
  "src/core/random.ts"() {
399
399
  }
400
400
  });
401
- var FilesystemStorage;
401
+ function bijectiveEncode(name) {
402
+ return name.replace(
403
+ SAFE_CHARS,
404
+ (ch) => "!" + ch.charCodeAt(0).toString(16).padStart(2, "0").toUpperCase()
405
+ );
406
+ }
407
+ function legacyNamespaceSanitize(name) {
408
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_");
409
+ }
410
+ function legacyKeySanitize(name) {
411
+ return name.replace(/[^a-zA-Z0-9_.-]/g, "_");
412
+ }
413
+ var SAFE_CHARS, FilesystemStorage;
402
414
  var init_filesystem = __esm({
403
415
  "src/storage/filesystem.ts"() {
404
416
  init_random();
417
+ SAFE_CHARS = /[^A-Za-z0-9_.\-]/g;
405
418
  FilesystemStorage = class {
406
419
  basePath;
407
420
  constructor(basePath) {
408
421
  this.basePath = basePath;
409
422
  }
410
423
  entryPath(namespace, key) {
411
- const safeNamespace = namespace.replace(/[^a-zA-Z0-9_-]/g, "_");
412
- const safeKey = key.replace(/[^a-zA-Z0-9_.-]/g, "_");
424
+ const safeNamespace = bijectiveEncode(namespace);
425
+ const safeKey = bijectiveEncode(key);
413
426
  return path.join(this.basePath, safeNamespace, `${safeKey}.enc`);
414
427
  }
415
428
  namespacePath(namespace) {
416
- const safeNamespace = namespace.replace(/[^a-zA-Z0-9_-]/g, "_");
417
- return path.join(this.basePath, safeNamespace);
429
+ return path.join(this.basePath, bijectiveEncode(namespace));
430
+ }
431
+ // Legacy on-disk paths produced by the pre-#41 sanitizer. Returned for
432
+ // ENOENT-fallback in read/exists/delete; never written to.
433
+ legacyEntryPath(namespace, key) {
434
+ return path.join(
435
+ this.basePath,
436
+ legacyNamespaceSanitize(namespace),
437
+ `${legacyKeySanitize(key)}.enc`
438
+ );
418
439
  }
419
440
  async write(namespace, key, data) {
420
441
  const dirPath = this.namespacePath(namespace);
@@ -423,7 +444,13 @@ var init_filesystem = __esm({
423
444
  await promises.writeFile(filePath, data, { mode: 384 });
424
445
  }
425
446
  async read(namespace, key) {
426
- const filePath = this.entryPath(namespace, key);
447
+ const buf = await this.readAtPath(this.entryPath(namespace, key));
448
+ if (buf !== null) return buf;
449
+ const legacy = this.legacyEntryPath(namespace, key);
450
+ if (legacy === this.entryPath(namespace, key)) return null;
451
+ return this.readAtPath(legacy);
452
+ }
453
+ async readAtPath(filePath) {
427
454
  try {
428
455
  const buf = await promises.readFile(filePath);
429
456
  return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
@@ -435,7 +462,13 @@ var init_filesystem = __esm({
435
462
  }
436
463
  }
437
464
  async delete(namespace, key, secureOverwrite = true) {
438
- const filePath = this.entryPath(namespace, key);
465
+ const newPath = this.entryPath(namespace, key);
466
+ if (await this.deleteAtPath(newPath, secureOverwrite)) return true;
467
+ const legacy = this.legacyEntryPath(namespace, key);
468
+ if (legacy === newPath) return false;
469
+ return this.deleteAtPath(legacy, secureOverwrite);
470
+ }
471
+ async deleteAtPath(filePath, secureOverwrite) {
439
472
  try {
440
473
  if (secureOverwrite) {
441
474
  const fileStat = await promises.stat(filePath);
@@ -481,12 +514,19 @@ var init_filesystem = __esm({
481
514
  }
482
515
  }
483
516
  async exists(namespace, key) {
484
- const filePath = this.entryPath(namespace, key);
517
+ const newPath = this.entryPath(namespace, key);
485
518
  try {
486
- await promises.stat(filePath);
519
+ await promises.stat(newPath);
487
520
  return true;
488
521
  } catch {
489
- return false;
522
+ const legacy = this.legacyEntryPath(namespace, key);
523
+ if (legacy === newPath) return false;
524
+ try {
525
+ await promises.stat(legacy);
526
+ return true;
527
+ } catch {
528
+ return false;
529
+ }
490
530
  }
491
531
  }
492
532
  async totalSize() {
@@ -843,6 +883,14 @@ var init_identity = __esm({
843
883
  init_random();
844
884
  }
845
885
  });
886
+
887
+ // src/core/key-derivation.ts
888
+ var key_derivation_exports = {};
889
+ __export(key_derivation_exports, {
890
+ deriveMasterKey: () => deriveMasterKey,
891
+ deriveNamespaceKey: () => deriveNamespaceKey,
892
+ derivePurposeKey: () => derivePurposeKey
893
+ });
846
894
  async function deriveMasterKey(passphrase, existingParams) {
847
895
  const salt = existingParams ? fromBase64url(existingParams.salt) : generateSalt();
848
896
  const params = existingParams ?? {
@@ -1503,6 +1551,11 @@ var init_router = __esm({
1503
1551
  });
1504
1552
 
1505
1553
  // src/l1-cognitive/tools.ts
1554
+ var tools_exports = {};
1555
+ __export(tools_exports, {
1556
+ IdentityManager: () => IdentityManager,
1557
+ createL1Tools: () => createL1Tools
1558
+ });
1506
1559
  function getReservedNamespaceViolation(namespace) {
1507
1560
  for (const prefix of RESERVED_NAMESPACE_PREFIXES2) {
1508
1561
  if (namespace === prefix || namespace.startsWith(prefix + "/")) {
@@ -4881,6 +4934,35 @@ var init_types = __esm({
4881
4934
  }
4882
4935
  });
4883
4936
 
4937
+ // src/mesh/constants.ts
4938
+ function isReservedEventType(s) {
4939
+ return RESERVED_EVENT_TYPE_PREFIXES.some((p) => s.startsWith(p));
4940
+ }
4941
+ function isReservedExtensionKey(k) {
4942
+ return RESERVED_EXTENSION_ENVELOPE_KEYS.includes(k);
4943
+ }
4944
+ var PROTOCOL_VERSION, SIGNATURE_SCHEME_V1, RESERVED_EVENT_TYPE_PREFIXES, RESERVED_EXTENSION_ENVELOPE_KEYS;
4945
+ var init_constants = __esm({
4946
+ "src/mesh/constants.ts"() {
4947
+ PROTOCOL_VERSION = "0.1";
4948
+ SIGNATURE_SCHEME_V1 = "ed25519-v1";
4949
+ RESERVED_EVENT_TYPE_PREFIXES = [
4950
+ "EXTENSION_",
4951
+ "cross_fortress_",
4952
+ "multi_master_"
4953
+ ];
4954
+ RESERVED_EXTENSION_ENVELOPE_KEYS = [
4955
+ "cross_fortress_read_grant",
4956
+ "cross_fortress_read_query",
4957
+ "cross_fortress_read_response",
4958
+ "multi_master_policy_merge",
4959
+ "audit_replication_full_n_way",
4960
+ "auto_promote_canonical_audit",
4961
+ "agent_live_migration"
4962
+ ];
4963
+ }
4964
+ });
4965
+
4884
4966
  // src/shr/generator.ts
4885
4967
  function deriveL4Degradations(evidence, now = /* @__PURE__ */ new Date()) {
4886
4968
  const out = [];
@@ -5029,6 +5111,7 @@ function generateSHR(identityId, opts) {
5029
5111
  return {
5030
5112
  body,
5031
5113
  signed_by: identity.public_key,
5114
+ signature_scheme: SIGNATURE_SCHEME_V1,
5032
5115
  signature: toBase64url(signatureBytes)
5033
5116
  };
5034
5117
  }
@@ -5039,6 +5122,7 @@ var init_generator = __esm({
5039
5122
  init_identity();
5040
5123
  init_encoding();
5041
5124
  init_key_derivation();
5125
+ init_constants();
5042
5126
  DEFAULT_VALIDITY_MS = 60 * 60 * 1e3;
5043
5127
  DEFAULT_FRESHNESS_WINDOW_DAYS = 30;
5044
5128
  DEFAULT_LOW_TIER_DOMINANCE_THRESHOLD = 0.6;
@@ -7277,14 +7361,14 @@ function generateDashboardHTML(options) {
7277
7361
  // cookie (set by /auth/session and sent automatically by the
7278
7362
  // browser) or as a ?session= query parameter, both of which Stack
7279
7363
  // A's checkAuth honours. Loopback callers also bypass auth via the
7280
- // v0.10.2 _autoAuthLocalhost path, which is the path moltbook
7364
+ // v0.10.2 _autoAuthLocalhost path, which is the path Mini1
7281
7365
  // hits when the dashboard is auto-opened on 127.0.0.1.
7282
7366
  //
7283
7367
  // The endpoint itself is /events \u2014 Stack A's route table mounts it
7284
7368
  // there, and the previous /api/events URL was a 404 in every real
7285
7369
  // boot from v0.10.0 through v0.10.4. The retry loop that result
7286
7370
  // produced is exactly the "status bar flashing blue continuously"
7287
- // moltbook reported on v0.10.4.
7371
+ // Mini1 reported on v0.10.4.
7288
7372
  const eventSource = new EventSource(API_BASE + '/events');
7289
7373
 
7290
7374
  eventSource.addEventListener('init', (e) => {
@@ -10309,8 +10393,8 @@ var init_html = __esm({
10309
10393
  function isPolicySlot(value) {
10310
10394
  return typeof value === "string" && POLICY_SLOTS.includes(value);
10311
10395
  }
10312
- var COMPILED_POLICY_SCHEMA_VERSION, POLICY_UPDATE_EVENT_TYPE, POLICY_SLOTS, CHANNEL_TEMPLATE_IDS, COUNTERPARTY_WILDCARD, BUDGET_UNITS;
10313
- var init_constants = __esm({
10396
+ var COMPILED_POLICY_SCHEMA_VERSION, POLICY_UPDATE_EVENT_TYPE, POLICY_SLOTS, CHANNEL_TEMPLATE_IDS, BUDGET_UNITS;
10397
+ var init_constants2 = __esm({
10314
10398
  "src/policy-engine/constants.ts"() {
10315
10399
  COMPILED_POLICY_SCHEMA_VERSION = "0.1";
10316
10400
  POLICY_UPDATE_EVENT_TYPE = "policy_update";
@@ -10325,10 +10409,8 @@ var init_constants = __esm({
10325
10409
  "read-then-report",
10326
10410
  "scheduled-digest",
10327
10411
  "plan-draft-only",
10328
- "fortress-relay",
10329
- "concierge-loop"
10412
+ "fortress-relay"
10330
10413
  ];
10331
- COUNTERPARTY_WILDCARD = "*";
10332
10414
  BUDGET_UNITS = ["tokens", "usd"];
10333
10415
  }
10334
10416
  });
@@ -10483,8 +10565,12 @@ function lintOnboarding(_name, content) {
10483
10565
  function resolveTemplatesDir() {
10484
10566
  const thisFile = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href)));
10485
10567
  const thisDir = path.dirname(thisFile);
10486
- if (thisDir.includes("/dist/")) {
10487
- return thisDir.replace("/dist/templates", "/src/templates");
10568
+ if (thisDir.includes("/dist")) {
10569
+ const templatesSubdir = path.join(thisDir, "templates");
10570
+ const candidateDir = fs.existsSync(path.join(thisDir, TEMPLATE_NAMES[0])) ? thisDir : fs.existsSync(path.join(templatesSubdir, TEMPLATE_NAMES[0])) ? templatesSubdir : null;
10571
+ if (candidateDir) return candidateDir;
10572
+ const srcFallback = thisDir.endsWith("/templates") ? thisDir.replace("/dist/templates", "/src/templates") : path.join(thisDir.replace("/dist", "/src"), "templates");
10573
+ return srcFallback;
10488
10574
  }
10489
10575
  return thisDir;
10490
10576
  }
@@ -10551,7 +10637,7 @@ function getTemplateEntry(name) {
10551
10637
  var TEMPLATE_NAMES, TemplateValidationError, _cache;
10552
10638
  var init_registry = __esm({
10553
10639
  "src/templates/registry.ts"() {
10554
- init_constants();
10640
+ init_constants2();
10555
10641
  TEMPLATE_NAMES = [
10556
10642
  "research-assistant",
10557
10643
  "coding-assistant",
@@ -10705,10 +10791,10 @@ function applyChannelTemplate(id, params) {
10705
10791
  if (!entry) throw new Error(`unknown channel template: ${id}`);
10706
10792
  return entry.factory(params);
10707
10793
  }
10708
- var requestApproveAct, readThenReport, scheduledDigest, planDraftOnly, fortressRelay, conciergeLoop, REGISTRY;
10794
+ var requestApproveAct, readThenReport, scheduledDigest, planDraftOnly, fortressRelay, REGISTRY;
10709
10795
  var init_channel_templates = __esm({
10710
10796
  "src/policy-engine/channel-templates.ts"() {
10711
- init_constants();
10797
+ init_constants2();
10712
10798
  init_null_policy();
10713
10799
  requestApproveAct = (params) => {
10714
10800
  const p = basePolicy(params);
@@ -10791,23 +10877,6 @@ var init_channel_templates = __esm({
10791
10877
  setRetentionDays(p, 90);
10792
10878
  return p;
10793
10879
  };
10794
- conciergeLoop = (params) => {
10795
- const p = basePolicy(params);
10796
- p.source_english = "Bidirectional Q&A with the operator. Reads local fortress state; never writes outward.";
10797
- grantOn(p, "memory", {
10798
- counterparty: params.counterparty,
10799
- action: "read",
10800
- scope: { local_fortress_state_only: true, ...params.scope ?? {} }
10801
- });
10802
- grantOn(p, "outputs", {
10803
- counterparty: params.counterparty || COUNTERPARTY_WILDCARD,
10804
- action: "read",
10805
- scope: { operator_chat_only: true, ...params.scope ?? {} }
10806
- });
10807
- p.egress = { allowlist: [] };
10808
- setRetentionDays(p, 14);
10809
- return p;
10810
- };
10811
10880
  REGISTRY = {
10812
10881
  "request-approve-act": {
10813
10882
  id: "request-approve-act",
@@ -10843,13 +10912,6 @@ var init_channel_templates = __esm({
10843
10912
  label: "Fortress relay",
10844
10913
  description: "Routes signed events between peer fortresses. Commits bind only when both sides sign.",
10845
10914
  factory: fortressRelay
10846
- },
10847
- "concierge-loop": {
10848
- id: "concierge-loop",
10849
- severity: "LOW",
10850
- label: "Concierge loop",
10851
- description: "Bidirectional Q&A with the operator. Reads local fortress state; never writes outward.",
10852
- factory: conciergeLoop
10853
10915
  }
10854
10916
  };
10855
10917
  }
@@ -10919,8 +10981,14 @@ function encode(value) {
10919
10981
  }
10920
10982
  function encodeArray(arr) {
10921
10983
  const parts = [];
10922
- for (const item of arr) {
10923
- parts.push(item === void 0 ? "null" : encode(item));
10984
+ for (let i = 0; i < arr.length; i++) {
10985
+ const item = arr[i];
10986
+ if (item === void 0) {
10987
+ throw new MeshCanonicalJsonError(
10988
+ `canonicalize(): undefined is not a valid JSON value at array index ${i}`
10989
+ );
10990
+ }
10991
+ parts.push(encode(item));
10924
10992
  }
10925
10993
  return "[" + parts.join(",") + "]";
10926
10994
  }
@@ -11238,45 +11306,17 @@ var init_canonical_policy = __esm({
11238
11306
  "src/policy-engine/canonical-policy.ts"() {
11239
11307
  init_canonical_json();
11240
11308
  init_encoding();
11241
- init_constants();
11309
+ init_constants2();
11242
11310
  init_errors2();
11243
11311
  }
11244
11312
  });
11245
-
11246
- // src/mesh/constants.ts
11247
- function isReservedEventType(s) {
11248
- return RESERVED_EVENT_TYPE_PREFIXES.some((p) => s.startsWith(p));
11249
- }
11250
- function isReservedExtensionKey(k) {
11251
- return RESERVED_EXTENSION_ENVELOPE_KEYS.includes(k);
11252
- }
11253
- var PROTOCOL_VERSION, RESERVED_EVENT_TYPE_PREFIXES, RESERVED_EXTENSION_ENVELOPE_KEYS;
11254
- var init_constants2 = __esm({
11255
- "src/mesh/constants.ts"() {
11256
- PROTOCOL_VERSION = "0.1";
11257
- RESERVED_EVENT_TYPE_PREFIXES = [
11258
- "EXTENSION_",
11259
- "cross_fortress_",
11260
- "multi_master_"
11261
- ];
11262
- RESERVED_EXTENSION_ENVELOPE_KEYS = [
11263
- "cross_fortress_read_grant",
11264
- "cross_fortress_read_query",
11265
- "cross_fortress_read_response",
11266
- "multi_master_policy_merge",
11267
- "audit_replication_full_n_way",
11268
- "auto_promote_canonical_audit",
11269
- "agent_live_migration"
11270
- ];
11271
- }
11272
- });
11273
11313
  var init_trust_root = __esm({
11274
11314
  "src/mesh/trust-root.ts"() {
11275
11315
  init_encoding();
11276
11316
  init_identity();
11277
11317
  init_random();
11278
11318
  init_canonical_json();
11279
- init_constants2();
11319
+ init_constants();
11280
11320
  init_errors();
11281
11321
  }
11282
11322
  });
@@ -11329,7 +11369,7 @@ var init_envelope = __esm({
11329
11369
  init_encoding();
11330
11370
  init_random();
11331
11371
  init_canonical_json();
11332
- init_constants2();
11372
+ init_constants();
11333
11373
  init_errors();
11334
11374
  init_trust_root();
11335
11375
  }
@@ -11359,7 +11399,7 @@ function packPolicyUpdate(params) {
11359
11399
  }
11360
11400
  var init_envelope2 = __esm({
11361
11401
  "src/policy-engine/envelope.ts"() {
11362
- init_constants();
11402
+ init_constants2();
11363
11403
  init_canonical_policy();
11364
11404
  init_errors2();
11365
11405
  init_envelope();
@@ -11493,7 +11533,7 @@ function initTemplate(params) {
11493
11533
  var init_init = __esm({
11494
11534
  "src/templates/init.ts"() {
11495
11535
  init_channel_templates();
11496
- init_constants();
11536
+ init_constants2();
11497
11537
  init_canonical_policy();
11498
11538
  init_envelope2();
11499
11539
  init_registry();
@@ -11771,7 +11811,7 @@ function deriveMachineKey(home) {
11771
11811
  return hkdf.hkdf(sha256.sha256, material, void 0, "sanctuary-passphrase-v1", 32);
11772
11812
  }
11773
11813
  async function defaultExec(cmd, args, input) {
11774
- return new Promise((resolve5, reject) => {
11814
+ return new Promise((resolve6, reject) => {
11775
11815
  const child = child_process.spawn(cmd, args, { stdio: ["pipe", "pipe", "pipe"] });
11776
11816
  let stdout = "";
11777
11817
  let stderr = "";
@@ -11782,7 +11822,7 @@ async function defaultExec(cmd, args, input) {
11782
11822
  stderr += d.toString();
11783
11823
  });
11784
11824
  child.on("error", reject);
11785
- child.on("close", (code) => resolve5({ stdout, stderr, code }));
11825
+ child.on("close", (code) => resolve6({ stdout, stderr, code }));
11786
11826
  if (input !== void 0) {
11787
11827
  child.stdin.write(input);
11788
11828
  }
@@ -12904,7 +12944,7 @@ async function api(path, opts) {
12904
12944
  // /policies, /activity responses on subsequent GETs even when the
12905
12945
  // server-side state has changed (e.g. recent-failures buffer cleared
12906
12946
  // on substrate flip). The pre-rc.5 client used bare fetch with no
12907
- // cache control, which on moltbook Safari produced a stale view of
12947
+ // cache control, which on Mini1 Safari produced a stale view of
12908
12948
  // server state and made the operator-visible badge color stick to
12909
12949
  // its prior value across substrate changes. Belt + suspenders:
12910
12950
  // cache: "no-store" turns off the response cache; the _t query
@@ -13066,12 +13106,6 @@ const CHANNEL_TEMPLATES = [
13066
13106
  severity: "MEDIUM",
13067
13107
  title: "Fortress relay",
13068
13108
  description: "Routes signed events between peer fortresses. Commits bind only when both sides sign."
13069
- },
13070
- {
13071
- id: "concierge-loop",
13072
- severity: "LOW",
13073
- title: "Concierge loop",
13074
- description: "Bidirectional Q&A with the operator. Reads local fortress state; never writes outward."
13075
13109
  }
13076
13110
  ];
13077
13111
 
@@ -13118,6 +13152,24 @@ function setRoute(route) {
13118
13152
  renderFortress();
13119
13153
  }
13120
13154
 
13155
+ // Renders the global attestation badge (Q1 layer 1, persistent across
13156
+ // surfaces). Tone is driven by state.topbarPills.attestation. Pending
13157
+ // state shows a dashed seal ring; verified shows solid; degraded shows
13158
+ // outlined core; unverified shows the broken-seal mark. Observation
13159
+ // language only; Castle Layer 1 enforcement ships in WP-V1.x-CASTLE-WALL.
13160
+ function renderTopbarAttestationBadge(stateName) {
13161
+ const valid = stateName === "verified" || stateName === "degraded" || stateName === "unverified" || stateName === "pending";
13162
+ const cls = valid ? stateName : "pending";
13163
+ const ringDashed = cls === "pending" ? " dashed" : "";
13164
+ return '<span class="att-global ' + cls + '" data-pill="attestation" title="Fortress attestation">' +
13165
+ '<span class="seal">' +
13166
+ '<span class="seal-ring' + ringDashed + '"></span>' +
13167
+ '<span class="seal-core"></span>' +
13168
+ '</span>' +
13169
+ '<span class="label">' + escHtml(cls) + '</span>' +
13170
+ '</span>';
13171
+ }
13172
+
13121
13173
  function renderTopbar() {
13122
13174
  const pillEl = document.getElementById("topbar-pills");
13123
13175
  if (!pillEl) return;
@@ -13134,7 +13186,7 @@ function renderTopbar() {
13134
13186
  versionPill,
13135
13187
  '<span class="pill" data-pill="deployment">deployment: ' + escHtml(state.topbarPills.deployment) + '</span>',
13136
13188
  '<span class="pill" data-pill="mode">mode: ' + escHtml(state.topbarPills.mode) + '</span>',
13137
- '<span class="pill tone-' + escHtml(state.topbarPills.attestation) + '" data-pill="attestation">attestation: ' + escHtml(state.topbarPills.attestation) + '</span>'
13189
+ renderTopbarAttestationBadge(state.topbarPills.attestation)
13138
13190
  ].join("");
13139
13191
  // Lockdown button three-state UX (binding addendum 3).
13140
13192
  const btn = document.getElementById("btn-lockdown");
@@ -13230,6 +13282,7 @@ function renderMain() {
13230
13282
  case "agent-detail": nextHtml = renderAgentDetail(); break;
13231
13283
  case "policy": nextHtml = renderPolicyCenter(); break;
13232
13284
  case "intelligence": nextHtml = renderIntelligenceCenter(); break;
13285
+ case "attestation": nextHtml = renderAttestation(); break;
13233
13286
  case "privacy": nextHtml = renderPrivacyPage(); break;
13234
13287
  case "coordination": nextHtml = renderCoordinationPage(); break;
13235
13288
  case "health": nextHtml = renderHealthPage(); break;
@@ -13308,9 +13361,9 @@ function renderMain() {
13308
13361
  // "Concierge unavailable; substrate not configured") sourced from the
13309
13362
  // last response's served_by + display_label.
13310
13363
  const CONCIERGE_SUGGESTIONS = [
13311
- { id: "summarize-hour", label: "summarize the last hour", query: "Summarize what happened in this fortress in the last hour." },
13312
- { id: "agent-touched", label: "what has each agent touched today", query: "What has each wrapped agent done today? Group by agent." },
13313
- { id: "open-approvals", label: "any open approvals?", query: "Are there any open Tier 1 approvals or pending inbox items I should look at?" }
13364
+ { id: "summarize-hour", category: "Summarize", label: "summarize the last hour", query: "Summarize what happened in this fortress in the last hour." },
13365
+ { id: "agent-touched", category: "Inspect", label: "what has each agent touched today", query: "What has each wrapped agent done today? Group by agent." },
13366
+ { id: "open-approvals", category: "Approvals", label: "any open approvals?", query: "Are there any open Tier 1 approvals or pending inbox items I should look at?" }
13314
13367
  ];
13315
13368
 
13316
13369
  // Direct-agent chat surface was removed in the v1.2 reshape; the
@@ -13327,59 +13380,316 @@ function renderDashboardConcierge() {
13327
13380
  const badge = c.badge && c.badge.displayLabel
13328
13381
  ? '<span class="pill mono concierge-badge" title="Substrate that served the most recent response">' + escHtml(c.badge.displayLabel) + '</span>'
13329
13382
  : '<span class="pill muted concierge-badge">Concierge: substrate not yet contacted</span>';
13330
- const messages = c.messages.length
13383
+ const sendDisabled = c.sending ? ' disabled' : '';
13384
+ const sendLabel = c.sending ? 'Sending...' : 'Send';
13385
+ // Sprint Piece 2 PR 2: empty state lives INSIDE the concierge-history
13386
+ // container so the DDD e2e selector .concierge-history matches both
13387
+ // empty and active state. The container's flex layout hosts a single
13388
+ // .concierge-empty child that fills the available height with a serif
13389
+ // headline and a 3-up suggest grid; the grid replaces the v1.2 bottom
13390
+ // chip row, which is retired with this polish.
13391
+ const emptyState =
13392
+ '<div class="concierge-empty">' +
13393
+ '<div class="concierge-empty-headline">' +
13394
+ '<h2>Where would you like to begin.</h2>' +
13395
+ '<p>Ask anything about your fortress. Sanctuary holds your context, your agents, your policy. It will answer plainly, or hand you to the right surface.</p>' +
13396
+ '</div>' +
13397
+ '<div class="concierge-suggest-grid">' +
13398
+ CONCIERGE_SUGGESTIONS.map(function (s) {
13399
+ return '<button class="concierge-suggest" data-action="concierge-suggestion" data-suggestion-id="' + escHtml(s.id) + '"' + sendDisabled + '>' +
13400
+ '<span class="label">' + escHtml(s.category || '') + '</span>' +
13401
+ escHtml(s.label) +
13402
+ '</button>';
13403
+ }).join("") +
13404
+ '</div>' +
13405
+ '</div>';
13406
+ const messagesHtml = c.messages.length
13331
13407
  ? c.messages.map(function (m) {
13332
13408
  const cls = m.role === "operator" ? "concierge-msg-operator" : "concierge-msg-concierge";
13333
- const author = m.role === "operator" ? "you" : "Sanctuary Fortress concierge";
13409
+ const authorLabel = m.role === "operator" ? "you" : "sanctuary";
13410
+ const metaParts = [];
13411
+ if (m.created_at) metaParts.push(escHtml(shortTime(m.created_at)));
13412
+ if (m.role === "concierge" && m.served_by) metaParts.push('substrate: ' + escHtml(m.served_by));
13413
+ const meta = metaParts.length
13414
+ ? '<div class="concierge-msg-meta"><span>' + metaParts.join(' · ') + '</span></div>'
13415
+ : '';
13334
13416
  return '<div class="concierge-msg ' + cls + '">' +
13335
- '<div class="concierge-msg-author muted">' + escHtml(author) + ' · ' + escHtml(shortTime(m.created_at)) + '</div>' +
13417
+ '<span class="concierge-msg-author">' + escHtml(authorLabel) + '</span>' +
13336
13418
  '<div class="concierge-msg-body">' + escHtml(m.body) + '</div>' +
13419
+ meta +
13337
13420
  '</div>';
13338
13421
  }).join("\n")
13339
- : '<p class="muted concierge-empty">No messages yet. Ask the concierge anything about your fortress: it can summarize agent activity, surface open approvals, or describe the current policy.</p>';
13422
+ : emptyState;
13340
13423
  const errorBanner = c.error
13341
13424
  ? '<div class="banner banner-warn">' + escHtml(c.error) + '</div>'
13342
13425
  : "";
13343
- const sendDisabled = c.sending ? ' disabled' : '';
13344
- const sendLabel = c.sending ? 'Sending...' : 'Send';
13345
- const chips = CONCIERGE_SUGGESTIONS.map(function (s) {
13346
- return '<button class="btn chip" data-action="concierge-suggestion" data-suggestion-id="' + escHtml(s.id) + '"' + sendDisabled + '>' + escHtml(s.label) + '</button>';
13347
- }).join("\n");
13348
13426
  const activeChatsPanel = renderActiveChatsPanel();
13349
13427
  return [
13350
- '<h1>Chat <span class="muted">/ This fortress</span></h1>',
13351
- activeChatsPanel,
13352
- '<div class="card concierge-card">',
13353
- '<div class="concierge-header">',
13354
- '<div class="concierge-persona"><strong>Sanctuary Fortress concierge</strong> <span class="muted">read-only over fortress state</span></div>',
13355
- badge,
13428
+ '<div class="concierge-wrap">',
13429
+ '<div class="page-head"><div>',
13430
+ '<p class="eyebrow">Concierge</p>',
13431
+ '<h1>Talk to your fortress.</h1>',
13432
+ '<p class="sub">A direct line to Sanctuary, routed through the substrate you chose. Nothing leaves without your hand on it.</p>',
13433
+ '</div></div>',
13434
+ activeChatsPanel,
13435
+ '<div class="card concierge-card">',
13436
+ '<div class="concierge-header">',
13437
+ '<div class="concierge-persona">',
13438
+ '<div class="glyph-ring"></div>',
13439
+ '<div class="concierge-persona-text"><strong>Sanctuary Fortress concierge</strong><small>read-only over fortress state</small></div>',
13440
+ '</div>',
13441
+ '<div class="concierge-meta">' + badge + '</div>',
13442
+ '</div>',
13443
+ errorBanner,
13444
+ '<div class="concierge-history" id="concierge-history">' + messagesHtml + '</div>',
13445
+ '<form class="concierge-composer" data-action="concierge-submit">',
13446
+ '<div class="input-wrap">',
13447
+ '<input type="text" name="concierge-input" placeholder="Type to Sanctuary. Enter to send." value="' + escHtml(c.composer) + '" data-action="concierge-input"' + sendDisabled + ' autocomplete="off">',
13448
+ '<span class="composer-meta">Enter</span>',
13449
+ '</div>',
13450
+ '<button type="submit" class="btn btn-primary" data-action="concierge-send"' + sendDisabled + '>' + escHtml(sendLabel) + '</button>',
13451
+ '</form>',
13452
+ '<p class="muted concierge-foot">First time? <a href="#intelligence">Pick a substrate</a> to enable concierge replies.</p>',
13356
13453
  '</div>',
13357
- errorBanner,
13358
- '<div class="concierge-history" id="concierge-history">' + messages + '</div>',
13359
- '<form class="concierge-composer" data-action="concierge-submit">',
13360
- '<input type="text" name="concierge-input" placeholder="Ask the concierge about this fortress..." value="' + escHtml(c.composer) + '" data-action="concierge-input"' + sendDisabled + ' autocomplete="off">',
13361
- '<button type="submit" class="btn btn-primary" data-action="concierge-send"' + sendDisabled + '>' + escHtml(sendLabel) + '</button>',
13362
- '</form>',
13363
- '<div class="concierge-chips">' + chips + '</div>',
13364
- '<p class="muted concierge-foot">First time? <a href="#intelligence">Pick a substrate</a> to enable concierge replies.</p>',
13365
13454
  '</div>'
13366
13455
  ].join("");
13367
13456
  }
13368
13457
 
13369
13458
  // ── Render: agents list / detail ───────────────────────────────────────
13459
+ //
13460
+ // Sprint Piece 2 PR 4 polish: empty state uses .agents-empty with the
13461
+ // concentric icon-frame + a terminal-block CTA. Populated state uses the
13462
+ // .agents-layout grid with the .agents-list 4-column table (Agent /
13463
+ // State / Attestation / Last seen). The empty-state branch keeps the
13464
+ // literal '<h1>Agents</h1>' start and the "No wrapped agents yet." copy
13465
+ // because agents-empty-state-canary.test.ts pins both.
13466
+ function agentInitials(agentId) {
13467
+ const tail = String(agentId || "").split(":").pop() || "";
13468
+ const cleaned = tail.replace(/[^a-zA-Z0-9]/g, "");
13469
+ return (cleaned.slice(0, 2) || "??").toUpperCase();
13470
+ }
13471
+ function agentStateClass(status) {
13472
+ if (status === "active") return "live";
13473
+ if (status === "locked_down" || status === "error") return "off";
13474
+ return "idle";
13475
+ }
13476
+ // Per-agent attestation badge (Q1 layer 2). Square chip beside each
13477
+ // agent: a bounded glyph beside a bounded entity. Color and fill pattern
13478
+ // carry meaning together so the badge reads even monochrome. The "locked"
13479
+ // status maps to the unverified visual (rust + hatched mark) since a
13480
+ // locked-down agent has no current attestation; the inspect-pane copy
13481
+ // explains the distinction. Pure visual surface; no state derivation.
13482
+ function renderAgentAttestationBadge(status) {
13483
+ let cls;
13484
+ let label;
13485
+ if (status === "active") { cls = "verified"; label = "verified"; }
13486
+ else if (status === "locked_down") { cls = "unverified"; label = "locked"; }
13487
+ else if (status === "error") { cls = "unverified"; label = "unverified"; }
13488
+ else { cls = "degraded"; label = "degraded"; }
13489
+ return '<span class="att-agent ' + cls + '" title="Agent attestation"><span class="mark"></span>' + escHtml(label) + '</span>';
13490
+ }
13491
+ // Per-action attestation tick (Q1 layer 3). Tiny inline shape on every
13492
+ // timeline row. Two-byte signature fragment is enough at low resolution;
13493
+ // the full signature is one click away. Neutral state shows a circle
13494
+ // instead of a tick when the signer was unreachable; the action is still
13495
+ // recorded. Visual surface only.
13496
+ function renderActionAttestationBadge(stateName, sig) {
13497
+ const valid = stateName === "verified" || stateName === "degraded" || stateName === "unverified" || stateName === "neutral";
13498
+ const cls = valid ? stateName : "neutral";
13499
+ const sigText = sig ? String(sig) : "--";
13500
+ return '<span class="att-action ' + cls + '" title="Action attestation">' +
13501
+ '<span class="tick"></span>' +
13502
+ '<span>' + escHtml(sigText) + '</span>' +
13503
+ '</span>';
13504
+ }
13505
+ // Attestation gallery surface (Q1 four classes: global / per-agent /
13506
+ // per-action / per-transaction custody-provenance stub). Reference for
13507
+ // operators: shows what each badge looks like across verified, degraded,
13508
+ // unverified, and (where applicable) pending or neutral states. Pure
13509
+ // visual; no derivation, no live data. Castle Layer 3 cooperative-MCP UX
13510
+ // surface; Castle Layer 1 enforcement ships in WP-V1.x-CASTLE-WALL.
13511
+ function renderAttestation() {
13512
+ return '<div class="att-gallery">' +
13513
+ '<div class="page-head"><div>' +
13514
+ '<p class="eyebrow">Attestation</p>' +
13515
+ '<h1>Four classes of badge.</h1>' +
13516
+ '<p class="sub">A signature you can see. From the whole fortress, down to a single action. Degrade, never destroy: a failed signature becomes neutral with a tooltip; the surface keeps working.</p>' +
13517
+ '</div></div>' +
13518
+ // Global
13519
+ '<div class="att-section">' +
13520
+ '<div class="att-section-head"><div>' +
13521
+ '<h2>Global. The fortress itself.</h2>' +
13522
+ '<p>Lives in the topbar. Visible on every surface. Tells you the fortress identity is currently signed and matches the binary you installed.</p>' +
13523
+ '</div><span class="label">topbar</span></div>' +
13524
+ attRow(renderTopbarAttestationBadge("verified"), "Verified", "Identity matches. Binary matches. Default state for a healthy fortress.") +
13525
+ attRow(renderTopbarAttestationBadge("degraded"), "Degraded", "The signature is older than the staleness window, or one of two co-signers is unreachable. The fortress keeps running.") +
13526
+ attRow(renderTopbarAttestationBadge("unverified"), "Unverified", "The signature did not validate. The surface still works; lockdown is still available; the badge tells you to investigate.") +
13527
+ attRow(renderTopbarAttestationBadge("pending"), "Pending", "First-run state. Fortress is signing for the first time. Settles in seconds.") +
13528
+ '</div>' +
13529
+ // Per-agent
13530
+ '<div class="att-section">' +
13531
+ '<div class="att-section-head"><div>' +
13532
+ '<h2>Per-agent. In the agents list and inspect pane.</h2>' +
13533
+ '<p>A square chip beside each agent. Square because an agent is bounded; the fortress (a circle) contains it.</p>' +
13534
+ '</div><span class="label">agents view</span></div>' +
13535
+ '<div class="att-row">' +
13536
+ '<div class="demo" style="display:flex; gap:8px; flex-wrap:wrap;">' +
13537
+ renderAgentAttestationBadge("active") +
13538
+ renderAgentAttestationBadgeForState("degraded", "degraded") +
13539
+ renderAgentAttestationBadgeForState("unverified", "unverified") +
13540
+ '</div>' +
13541
+ '<div class="desc"><strong>Verified, degraded, unverified</strong>' +
13542
+ '<small>Color and the fill pattern carry meaning together. A solid square reads "attested" at a glance; a hatched square reads "trouble" at a glance, even monochrome.</small>' +
13543
+ '</div>' +
13544
+ '</div>' +
13545
+ '</div>' +
13546
+ // Per-action
13547
+ '<div class="att-section">' +
13548
+ '<div class="att-section-head"><div>' +
13549
+ '<h2>Per-action. Inline in the activity timeline.</h2>' +
13550
+ '<p>Each entry in any timeline carries a small signature fragment. Hover to expand. A tick instead of a fill keeps the row visually quiet at low resolution.</p>' +
13551
+ '</div><span class="label">timeline</span></div>' +
13552
+ '<div class="att-row">' +
13553
+ '<div class="demo" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">' +
13554
+ '<span style="font-size:13px; color:var(--ink-2);">14:22:08 doc-reviewer summarized intake.pdf</span>' +
13555
+ renderActionAttestationBadge("verified", "9c7d..2a") +
13556
+ '</div>' +
13557
+ '<div class="desc"><strong>Verified action</strong>' +
13558
+ '<small>The most common shape. Two-byte signature fragment is enough; the full signature is one click away.</small>' +
13559
+ '</div>' +
13560
+ '</div>' +
13561
+ '<div class="att-row">' +
13562
+ '<div class="demo" style="display:flex; gap:10px; align-items:center;">' +
13563
+ '<span style="font-size:13px; color:var(--ink-2);">14:11:47 privacy filter redacted payload</span>' +
13564
+ renderActionAttestationBadge("degraded", "b440..71") +
13565
+ '</div>' +
13566
+ '<div class="desc"><strong>Degraded action</strong>' +
13567
+ '<small>The action signed, but the signature class was less than the policy preferred. Useful when a substrate is still warming up.</small>' +
13568
+ '</div>' +
13569
+ '</div>' +
13570
+ '<div class="att-row">' +
13571
+ '<div class="demo" style="display:flex; gap:10px; align-items:center;">' +
13572
+ '<span style="font-size:13px; color:var(--ink-2);">14:09:02 agent attempted external link</span>' +
13573
+ renderActionAttestationBadge("neutral", "--") +
13574
+ '</div>' +
13575
+ '<div class="desc"><strong>Neutral. Degrade, not destroy.</strong>' +
13576
+ '<small>The signer was unreachable. Rather than hide the action, the badge becomes neutral and a tooltip explains. The action is still recorded.</small>' +
13577
+ '</div>' +
13578
+ '</div>' +
13579
+ '</div>' +
13580
+ // Custody stub
13581
+ '<div class="att-section">' +
13582
+ '<div class="att-section-head"><div>' +
13583
+ '<h2>Custody. Stub for v1.x.</h2>' +
13584
+ '<p>A fourth class, surfaced conservatively. Reserved for forthcoming custody-provenance signatures (x402 payment receipts, ERC-8004 identity assertions). Visible, dashed, clearly stubbed.</p>' +
13585
+ '</div><span class="label">stub</span></div>' +
13586
+ '<div class="att-row">' +
13587
+ '<div class="demo">' +
13588
+ '<span class="att-custody" title="Custody-provenance, v1.x">' +
13589
+ '<span class="seal-stub"></span>' +
13590
+ '<span class="stub-tag">custody. stub</span>' +
13591
+ '</span>' +
13592
+ '</div>' +
13593
+ '<div class="desc"><strong>Custody. Stub.</strong>' +
13594
+ '<small>Dashed border signals "shape reserved, content pending." Will populate when custody signatures land in a future release. Cannot be confused with a verified badge at any zoom level.</small>' +
13595
+ '</div>' +
13596
+ '</div>' +
13597
+ '</div>' +
13598
+ // Tooltip
13599
+ '<div class="att-section">' +
13600
+ '<div class="att-section-head"><div>' +
13601
+ '<h2>Tooltip on failure.</h2>' +
13602
+ '<p>A failed badge is never silent. The tooltip explains in plain language, suggests one action, and confirms the surface is still working.</p>' +
13603
+ '</div><span class="label">degrade not destroy</span></div>' +
13604
+ '<div class="att-row">' +
13605
+ '<div class="demo">' +
13606
+ '<span class="att-tooltip">The signer at sig.fortress.local did not respond in 4s. Your fortress kept working. Try: open Health to see the signer status.</span>' +
13607
+ '</div>' +
13608
+ '<div class="desc"><strong>Plain-language tooltip</strong>' +
13609
+ '<small>Three lines, in order: what happened, what did not break, what to do. No jargon, no stack trace.</small>' +
13610
+ '</div>' +
13611
+ '</div>' +
13612
+ '</div>' +
13613
+ '</div>';
13614
+ }
13615
+ function attRow(demoHtml, strong, smallText) {
13616
+ return '<div class="att-row">' +
13617
+ '<div class="demo">' + demoHtml + '</div>' +
13618
+ '<div class="desc"><strong>' + escHtml(strong) + '</strong>' +
13619
+ '<small>' + escHtml(smallText) + '</small>' +
13620
+ '</div>' +
13621
+ '</div>';
13622
+ }
13623
+ // Gallery-only variant: render a per-agent badge for a given visual state
13624
+ // (verified / degraded / unverified) without going through the agent
13625
+ // status mapping. Used by renderAttestation to show all three states
13626
+ // side by side as design reference.
13627
+ function renderAgentAttestationBadgeForState(cls, label) {
13628
+ return '<span class="att-agent ' + escHtml(cls) + '" title="Agent attestation"><span class="mark"></span>' + escHtml(label) + '</span>';
13629
+ }
13630
+ function relTimeFromIso(iso) {
13631
+ if (!iso) return "";
13632
+ const d = new Date(iso);
13633
+ if (isNaN(d.getTime())) return iso;
13634
+ const diffMs = Date.now() - d.getTime();
13635
+ const diffSec = Math.max(0, Math.floor(diffMs / 1000));
13636
+ if (diffSec < 60) return diffSec + "s ago";
13637
+ const diffMin = Math.floor(diffSec / 60);
13638
+ if (diffMin < 60) return diffMin + "m ago";
13639
+ const diffHr = Math.floor(diffMin / 60);
13640
+ if (diffHr < 24) return diffHr + "h ago";
13641
+ const diffDay = Math.floor(diffHr / 24);
13642
+ return diffDay + "d ago";
13643
+ }
13370
13644
  function renderAgentsList() {
13371
- if (!state.agents.length) return '<h1>Agents</h1><p class="muted">No wrapped agents yet. Run <code>sanctuary wrap</code> to wrap a harness.</p>';
13645
+ if (!state.agents.length) return '<h1>Agents</h1>' +
13646
+ '<div class="agents-empty">' +
13647
+ '<div class="icon-frame"><div class="core"></div></div>' +
13648
+ '<h2>No wrapped agents yet.</h2>' +
13649
+ '<p>Wrap an agent to give it a portable identity, a charter, and approval gates. Run <code>sanctuary wrap</code> in any project where your agent lives.</p>' +
13650
+ '<div class="terminal-block"><span class="cmd"><span class="prompt">$</span>sanctuary wrap</span></div>' +
13651
+ '</div>';
13652
+ const count = state.agents.length;
13653
+ const subCopy = count + ' wrapped. Click one to inspect its activity, policy, and pending approvals.';
13372
13654
  const rows = state.agents.map(function (a) {
13373
13655
  const map = STATUS_MAP[a.status] || STATUS_MAP.unknown;
13374
- const reason = a.status_reason_class ? (REASON_LABELS[a.status_reason_class] || "") : "";
13375
- return '<div class="row">' +
13376
- '<span class="glyph ' + map.glyph + '"></span>' +
13377
- '<div class="grow"><strong>' + escHtml(a.agent_id) + '</strong> <span class="muted mono">' + escHtml(a.harness) + '</span></div>' +
13378
- '<span class="pill" title="' + escHtml(reason) + '">' + escHtml(map.label) + '</span>' +
13379
- '<button class="btn" data-action="open-agent" data-agent-id="' + escHtml(a.agent_id) + '">Open</button>' +
13656
+ const dotCls = agentStateClass(a.status);
13657
+ const initials = agentInitials(a.agent_id);
13658
+ const role = escHtml(a.harness) + (a.model_provider && a.model_provider.model_id ? ' · ' + escHtml(a.model_provider.model_id) : '');
13659
+ const isSelected = state.selectedAgentId === a.agent_id;
13660
+ return '<div class="agent-row' + (isSelected ? ' selected' : '') + '" data-action="open-agent" data-agent-id="' + escHtml(a.agent_id) + '" role="button" tabindex="0" title="Open inspect panel for ' + escHtml(a.agent_id) + '">' +
13661
+ '<div class="agent-identity">' +
13662
+ '<div class="agent-glyph">' + escHtml(initials) + '</div>' +
13663
+ '<div class="agent-name">' +
13664
+ '<strong>' + escHtml(a.agent_id) + '</strong>' +
13665
+ '<small>' + role + '</small>' +
13666
+ '</div>' +
13667
+ '</div>' +
13668
+ '<span class="agent-state">' +
13669
+ '<span class="state-dot ' + dotCls + '"></span>' +
13670
+ escHtml(map.label) +
13671
+ '</span>' +
13672
+ renderAgentAttestationBadge(a.status) +
13673
+ '<span class="agent-last">' + escHtml(relTimeFromIso(a.last_activity_at)) + '</span>' +
13380
13674
  '</div>';
13381
13675
  }).join("\n");
13382
- return '<h1>Agents</h1><div class="card">' + rows + '</div>';
13676
+ return '<div class="agents-wrap">' +
13677
+ '<div class="page-head">' +
13678
+ '<div>' +
13679
+ '<p class="eyebrow">Agents</p>' +
13680
+ '<h1>Agents.</h1>' +
13681
+ '<p class="sub">' + escHtml(subCopy) + '</p>' +
13682
+ '</div>' +
13683
+ '</div>' +
13684
+ '<div class="agents-layout">' +
13685
+ '<div class="agents-list">' +
13686
+ '<div class="agents-list-head">' +
13687
+ '<span>Agent</span><span>State</span><span>Attestation</span><span>Last seen</span>' +
13688
+ '</div>' +
13689
+ rows +
13690
+ '</div>' +
13691
+ '</div>' +
13692
+ '</div>';
13383
13693
  }
13384
13694
 
13385
13695
  function renderAgentDetail() {
@@ -13390,7 +13700,10 @@ function renderAgentDetail() {
13390
13700
  const timeline = events.length
13391
13701
  ? events.map(function (e) {
13392
13702
  const t = renderTemplate(e.display_template_id, e.display_template_args);
13393
- return '<div class="row"><span class="muted">' + escHtml(shortTime(e.emitted_at)) + '</span><span>' + escHtml(t) + '</span></div>';
13703
+ const badgeHtml = e.attestation
13704
+ ? ' ' + renderActionAttestationBadge(e.attestation.state, e.attestation.fragment)
13705
+ : '';
13706
+ return '<div class="row"><span class="muted">' + escHtml(shortTime(e.emitted_at)) + '</span><span>' + escHtml(t) + badgeHtml + '</span></div>';
13394
13707
  }).join("\n")
13395
13708
  : '<p class="muted">No activity yet.</p>';
13396
13709
  // WP-V1.2 reshape click-to-inspect surface. Clicking "Open inspect
@@ -13434,53 +13747,99 @@ function renderAgentInspectPanel(agent) {
13434
13747
  : "";
13435
13748
 
13436
13749
  // State 2: panel loaded.
13750
+ // Sprint Piece 2 PR 4 polish: outer wrapper combines .card with
13751
+ // .inspect-pane (sticky right rail, internal scroll, sectioned body).
13752
+ // The .card class is preserved so the rendered surface keeps its
13753
+ // shared card chrome; .inspect-pane overrides .card padding so the
13754
+ // inspect-head and inspect-body control their own spacing per design.
13437
13755
  if (panel) {
13756
+ const dotCls = agentStateClass(agent.status);
13757
+ const stateMap = STATUS_MAP[agent.status] || STATUS_MAP.unknown;
13438
13758
  const activity = (panel.recent_activity || []).slice(0, 20);
13439
13759
  const activityHtml = activity.length
13440
- ? activity.map(function (e) {
13760
+ ? '<div class="timeline">' +
13761
+ activity.map(function (e) {
13441
13762
  const t = renderTemplate(e.display_template_id, e.display_template_args);
13442
- return '<div class="row"><span class="muted mono">' + escHtml(shortTime(e.emitted_at)) + '</span><span>' + escHtml(t) + '</span></div>';
13443
- }).join("\n")
13763
+ const badgeHtml = e.attestation
13764
+ ? renderActionAttestationBadge(e.attestation.state, e.attestation.fragment)
13765
+ : '';
13766
+ return '<div class="timeline-item ok">' +
13767
+ '<div class="ts">' + escHtml(shortTime(e.emitted_at)) + '</div>' +
13768
+ '<div class="what">' + escHtml(t) + '</div>' +
13769
+ (badgeHtml ? '<div class="att">' + badgeHtml + '</div>' : '') +
13770
+ '</div>';
13771
+ }).join("") +
13772
+ '</div>'
13444
13773
  : '<p class="muted">No recent activity for this agent.</p>';
13445
13774
 
13446
13775
  const approvals = panel.pending_approvals || [];
13447
13776
  const approvalsHtml = approvals.length
13448
13777
  ? approvals.map(function (item) {
13449
13778
  const promptText = renderTemplate(item.display_template_id, item.display_template_args);
13450
- return '<div class="row">' +
13451
- '<span class="pill tone-info">' + escHtml(item.tier || "tier1") + '</span>' +
13452
- '<div class="grow">' + escHtml(promptText) + '</div>' +
13453
- '<button class="btn btn-primary" data-action="inbox-approve" data-item-id="' + escHtml(item.item_id) + '">Approve</button>' +
13454
- '<button class="btn" data-action="inbox-deny" data-item-id="' + escHtml(item.item_id) + '">Deny</button>' +
13779
+ return '<div class="approval-row">' +
13780
+ '<div class="what">' +
13781
+ '<span class="pill tone-degraded">' + escHtml(item.tier || "tier1") + '</span>' +
13782
+ escHtml(promptText) +
13783
+ '</div>' +
13784
+ '<div class="actions">' +
13785
+ '<button class="btn" data-action="inbox-deny" data-item-id="' + escHtml(item.item_id) + '">Deny</button>' +
13786
+ '<button class="btn btn-primary" data-action="inbox-approve" data-item-id="' + escHtml(item.item_id) + '">Approve once</button>' +
13787
+ '</div>' +
13455
13788
  '</div>';
13456
- }).join("\n")
13789
+ }).join("")
13457
13790
  : '<p class="muted">No pending approvals routed through this agent.</p>';
13458
13791
 
13459
- const policyLine = panel.policy_summary
13460
- ? '<dt>Policy</dt><dd class="mono">' + escHtml(panel.policy_summary.display_label || panel.policy_summary.policy_id) + '</dd>' +
13792
+ const policySection = panel.policy_summary
13793
+ ? '<div class="policy-line"><span class="k">Policy</span><span class="v">' + escHtml(panel.policy_summary.display_label || panel.policy_summary.policy_id) + '</span></div>' +
13461
13794
  (panel.policy_summary.channel_template_id
13462
- ? '<dt>Template</dt><dd class="mono">' + escHtml(panel.policy_summary.channel_template_id) + '</dd>'
13795
+ ? '<div class="policy-line"><span class="k">Template</span><span class="v">' + escHtml(panel.policy_summary.channel_template_id) + '</span></div>'
13463
13796
  : '') +
13464
- '<dt>Bound</dt><dd class="mono">' + escHtml(shortTime(panel.policy_summary.bound_at)) + '</dd>'
13465
- : '<dt>Policy</dt><dd class="muted">No bound policy yet.</dd>';
13466
-
13467
- return '<div class="card">' +
13468
- '<div class="concierge-header">' +
13469
- '<div class="concierge-persona"><strong>Inspect ' + escHtml(agent.agent_id) + '</strong> ' +
13470
- '<span class="muted">opened ' + escHtml(shortTime(panel.opened_at)) + '</span></div>' +
13471
- '<button class="btn" data-action="agent-inspect-open" data-agent-id="' + escHtml(agent.agent_id) + '">Refresh</button>' +
13797
+ '<div class="policy-line"><span class="k">Bound</span><span class="v">' + escHtml(shortTime(panel.policy_summary.bound_at)) + '</span></div>'
13798
+ : '<div class="policy-line"><span class="k">Policy</span><span class="v">No bound policy yet.</span></div>';
13799
+
13800
+ const modelLine = agent.model_provider
13801
+ ? '<div class="policy-line"><span class="k">Model</span><span class="v">' + escHtml(agent.model_provider.vendor) + ' / ' + escHtml(agent.model_provider.model_id) + '</span></div>'
13802
+ : '';
13803
+
13804
+ return '<div class="card inspect-pane">' +
13805
+ '<div class="inspect-head">' +
13806
+ '<div class="row1">' +
13807
+ '<div class="agent-glyph">' + escHtml(agentInitials(agent.agent_id)) + '</div>' +
13808
+ '<h3>' + escHtml(agent.agent_id) + '</h3>' +
13809
+ '<span style="margin-left:auto;">' + renderAgentAttestationBadge(agent.status) + '</span>' +
13810
+ '</div>' +
13811
+ '<div class="meta">' +
13812
+ '<span class="pill ' + (dotCls === "live" ? "tone-verified" : "tone-degraded") + '"><span class="state-dot ' + dotCls + '" style="margin-right:4px;"></span>' + escHtml(stateMap.label) + '</span>' +
13813
+ '<span class="pill">opened ' + escHtml(shortTime(panel.opened_at)) + '</span>' +
13814
+ '<button class="btn btn-quiet" data-action="agent-inspect-open" data-agent-id="' + escHtml(agent.agent_id) + '" title="Refresh inspect panel">Refresh</button>' +
13815
+ '</div>' +
13816
+ '</div>' +
13817
+ '<div class="inspect-body">' +
13818
+ errorBanner +
13819
+ '<div class="inspect-section">' +
13820
+ '<h4>Pending approvals' + (approvals.length ? ' <span class="count">' + approvals.length + '</span>' : '') + '</h4>' +
13821
+ approvalsHtml +
13822
+ '</div>' +
13823
+ '<div class="inspect-section">' +
13824
+ '<h4>Recent activity</h4>' +
13825
+ activityHtml +
13826
+ '</div>' +
13827
+ '<div class="inspect-section">' +
13828
+ '<h4>Policy summary</h4>' +
13829
+ policySection +
13830
+ '</div>' +
13831
+ '<div class="inspect-section">' +
13832
+ '<h4>Identity</h4>' +
13833
+ '<div class="policy-line"><span class="k">Agent id</span><span class="v">' + escHtml(agent.agent_id) + '</span></div>' +
13834
+ '<div class="policy-line"><span class="k">Harness</span><span class="v">' + escHtml(agent.harness) + '</span></div>' +
13835
+ modelLine +
13836
+ '<div class="policy-line"><span class="k">Wrapped at</span><span class="v">' + escHtml(shortTime(agent.wrapped_at)) + '</span></div>' +
13837
+ '</div>' +
13838
+ '<p class="muted" style="margin-top:10px;font-size:12px;">' +
13839
+ '<a href="#activity?agent=' + escHtml(agent.agent_id) + '">View full activity</a> · ' +
13840
+ '<a href="#policy">Edit policy</a>' +
13841
+ '</p>' +
13472
13842
  '</div>' +
13473
- errorBanner +
13474
- '<h3>Pending approvals</h3>' +
13475
- approvalsHtml +
13476
- '<h3 style="margin-top:14px;">Recent activity</h3>' +
13477
- activityHtml +
13478
- '<h3 style="margin-top:14px;">Policy</h3>' +
13479
- '<dl class="kv">' + policyLine + '</dl>' +
13480
- '<p class="muted" style="margin-top:10px;font-size:12px;">' +
13481
- '<a href="#activity?agent=' + escHtml(agent.agent_id) + '">View full activity</a> · ' +
13482
- '<a href="#policy">Edit policy</a>' +
13483
- '</p>' +
13484
13843
  '</div>';
13485
13844
  }
13486
13845
 
@@ -13629,6 +13988,16 @@ function statusDotClass(status) {
13629
13988
  return "red";
13630
13989
  }
13631
13990
 
13991
+ // Card-grid polish (Sprint Piece 2 PR 3) maps the badge dot class onto
13992
+ // the shaped glyph token. Sage circle for ok, ochre triangle for warn,
13993
+ // rust diamond for fail. Keep aligned with .status-glyph rules in
13994
+ // html.ts and the .intel-card-status modifier classes.
13995
+ function statusGlyphClass(dotClass) {
13996
+ if (dotClass === "green") return "ok";
13997
+ if (dotClass === "yellow") return "warn";
13998
+ return "fail";
13999
+ }
14000
+
13632
14001
  function statusLabel(health) {
13633
14002
  if (health === "ok") return "Working";
13634
14003
  if (health === "degraded") return "Degraded";
@@ -13668,13 +14037,18 @@ function renderIntelligenceCenter() {
13668
14037
  }
13669
14038
  const status = state.intelligence.status;
13670
14039
  const config = state.intelligence.config || {};
13671
- const surfaceRows = SURFACES_ORDER.map(function (surfaceId) {
14040
+ const surfaceCards = SURFACES_ORDER.map(function (surfaceId) {
13672
14041
  const surfaceStatus = (status.surfaces || []).find(function (s) { return s.surface === surfaceId; });
13673
14042
  if (!surfaceStatus) {
13674
- return '<div class="intel-row"><div class="intel-row-name">' + escHtml(SURFACE_LABELS[surfaceId] || surfaceId) +
13675
- '<small>' + escHtml(surfaceId) + '</small></div>' +
13676
- '<div class="intel-row-body muted">No status reported.</div>' +
13677
- '<div></div></div>';
14043
+ return '<div class="intel-row intel-card" data-intel-surface="' + escHtml(surfaceId) + '">' +
14044
+ '<div class="intel-card-head">' +
14045
+ '<div class="intel-card-name">' +
14046
+ '<strong>' + escHtml(SURFACE_LABELS[surfaceId] || surfaceId) + '</strong>' +
14047
+ '<small>' + escHtml(surfaceId) + '</small>' +
14048
+ '</div>' +
14049
+ '</div>' +
14050
+ '<div class="muted">No status reported.</div>' +
14051
+ '</div>';
13678
14052
  }
13679
14053
  const substrate = surfaceStatus.chosen;
13680
14054
  const localPick = (config.local_model_picks || {})[surfaceId];
@@ -13692,41 +14066,62 @@ function renderIntelligenceCenter() {
13692
14066
  if (provider) currentBadge = currentBadge + " (" + (FRONTIER_PROVIDER_LABELS[provider] || provider) + ")";
13693
14067
  }
13694
14068
  const dotClass = statusDotClass((surfaceStatus.badge || {}).status || "red");
14069
+ const glyphClass = statusGlyphClass(dotClass);
13695
14070
  const failures = surfaceStatus.recentFailures || [];
13696
14071
  const expanded = !!state.intelligence.expandedFailures[surfaceId];
13697
- let failuresBlock = "";
14072
+
14073
+ // Card foot. The failures toggle is the load-bearing affordance for
14074
+ // the rc.6 ZZ test (button[data-action="intel-failures-toggle"] with
14075
+ // text "recent failures (N)"). Pluralization is "failures" regardless
14076
+ // of N for backward compatibility with the seeded test contract.
14077
+ // Surface zero-failure state as a quiet mono note so the card still
14078
+ // has visual rhythm in its foot row.
14079
+ let footHtml;
13698
14080
  if (failures.length > 0) {
13699
14081
  const toggleLabel = (expanded ? "Hide" : "View") + " recent failures (" + failures.length + ")";
13700
- const list = expanded
13701
- ? '<ul class="intel-row-failures-list">' +
13702
- failures.slice().reverse().map(function (f) {
13703
- return '<li><span class="muted mono">' + escHtml(shortTime(f.ts)) + '</span> ' +
13704
- '<span class="pill warn">' + escHtml(f.failureClass) + '</span> ' +
13705
- '<span class="muted">' + escHtml(f.snippet) + '</span></li>';
13706
- }).join("") +
13707
- '</ul>'
13708
- : '';
13709
- failuresBlock =
13710
- '<div class="intel-row-failures">' +
13711
- '<button class="btn btn-link" data-action="intel-failures-toggle" data-intel-surface="' + escHtml(surfaceId) + '">' +
13712
- escHtml(toggleLabel) +
13713
- '</button>' +
13714
- list +
14082
+ footHtml =
14083
+ '<button class="intel-failures-toggle' + (expanded ? ' open' : '') + '" data-action="intel-failures-toggle" data-intel-surface="' + escHtml(surfaceId) + '">' +
14084
+ '<span class="caret"></span>' +
14085
+ escHtml(toggleLabel) +
14086
+ '</button>';
14087
+ } else {
14088
+ footHtml = '<span class="muted mono" style="font-size: 11px;">no recent failures</span>';
14089
+ }
14090
+
14091
+ let failuresBlock = "";
14092
+ if (failures.length > 0 && expanded) {
14093
+ const rows = failures.slice().reverse().map(function (f) {
14094
+ return '<div class="intel-failure-row">' +
14095
+ '<span class="ts">' + escHtml(shortTime(f.ts)) + '</span>' +
14096
+ '<div>' +
14097
+ '<div class="err-class">' + escHtml(f.failureClass) + '</div>' +
14098
+ '<div>' + escHtml(f.snippet) + '</div>' +
14099
+ '</div>' +
13715
14100
  '</div>';
14101
+ }).join("");
14102
+ failuresBlock = '<div class="intel-failures">' + rows + '</div>';
13716
14103
  }
13717
- return '<div class="intel-row" data-intel-surface="' + escHtml(surfaceId) + '">' +
13718
- '<div class="intel-row-name">' + escHtml(SURFACE_LABELS[surfaceId] || surfaceId) +
13719
- '<small>' + escHtml(surfaceId) + '</small></div>' +
13720
- '<div class="intel-row-body">' +
13721
- '<div class="intel-row-current">' +
13722
- '<span class="intel-status-dot ' + dotClass + '" title="' + escHtml(statusLabel(surfaceStatus.health)) + '"></span>' +
13723
- '<span class="pill">' + escHtml(currentBadge) + '</span>' +
13724
- '<span class="muted mono">' + escHtml(statusLabel(surfaceStatus.health)) + '</span>' +
14104
+
14105
+ return '<div class="intel-row intel-card" data-intel-surface="' + escHtml(surfaceId) + '">' +
14106
+ '<div class="intel-card-head">' +
14107
+ '<div class="intel-card-name">' +
14108
+ '<strong>' + escHtml(SURFACE_LABELS[surfaceId] || surfaceId) + '</strong>' +
14109
+ '<small>' + escHtml(surfaceId) + '</small>' +
13725
14110
  '</div>' +
13726
- '<div class="intel-row-tradeoff">' + escHtml(substrateTradeoff(substrate)) + '</div>' +
13727
- failuresBlock +
14111
+ '<span class="intel-card-status ' + glyphClass + '" title="' + escHtml(statusLabel(surfaceStatus.health)) + '">' +
14112
+ '<span class="status-glyph ' + glyphClass + '"></span>' +
14113
+ escHtml(statusLabel(surfaceStatus.health)) +
14114
+ '</span>' +
13728
14115
  '</div>' +
13729
- '<div><button class="btn" data-action="intel-picker-open" data-intel-surface="' + escHtml(surfaceId) + '">Change</button></div>' +
14116
+ '<div class="intel-substrate">' +
14117
+ '<div class="sub-line primary">' +
14118
+ '<span>' + escHtml(currentBadge) + '</span>' +
14119
+ '<button class="btn-quiet" data-action="intel-picker-open" data-intel-surface="' + escHtml(surfaceId) + '">Change</button>' +
14120
+ '</div>' +
14121
+ '</div>' +
14122
+ '<div class="intel-row-tradeoff">' + escHtml(substrateTradeoff(substrate)) + '</div>' +
14123
+ '<div class="intel-card-foot">' + footHtml + '</div>' +
14124
+ failuresBlock +
13730
14125
  '</div>';
13731
14126
  }).join("\n");
13732
14127
 
@@ -13738,11 +14133,15 @@ function renderIntelligenceCenter() {
13738
14133
 
13739
14134
  const modal = state.intelligence.picker.open ? renderIntelligencePicker() : "";
13740
14135
 
13741
- return '<section class="intel-center">' +
13742
- '<p class="eyebrow">INTELLIGENCE</p>' +
13743
- '<h1>Intelligence Substrate</h1>' +
13744
- '<p class="intel-subtitle">Choose how Sanctuary thinks. Tradeoffs visible per surface. Multi-option framing is preserved: no single substrate is the right answer.</p>' +
13745
- '<section class="intel-panel"><h2>Surfaces</h2>' + surfaceRows + '</section>' +
14136
+ return '<section class="intel-wrap">' +
14137
+ '<div class="page-head">' +
14138
+ '<div>' +
14139
+ '<p class="eyebrow">Intelligence</p>' +
14140
+ '<h1>Substrate routing.</h1>' +
14141
+ '<p class="sub">Six surfaces, six choices. Each surface picks where its thinking happens. Local for privacy. Hosted for capability. Hybrid for both.</p>' +
14142
+ '</div>' +
14143
+ '</div>' +
14144
+ '<div class="intel-grid">' + surfaceCards + '</div>' +
13746
14145
  '<section class="intel-panel"><h2>Host capability</h2>' +
13747
14146
  '<dl class="intel-hardware">' +
13748
14147
  '<dt>Total RAM</dt><dd>' + escHtml(hardware.totalRamGb || "?") + ' GB</dd>' +
@@ -14535,6 +14934,13 @@ document.addEventListener("click", function (ev) {
14535
14934
  const intelLocalModel = tgt.getAttribute("data-intel-local-model");
14536
14935
  const intelFrontierProvider = tgt.getAttribute("data-intel-frontier-provider");
14537
14936
  if (action === "lockdown") return void onLockdownClick();
14937
+ if (action === "theme-toggle") {
14938
+ const isDark = document.documentElement.getAttribute("data-theme") === "dark";
14939
+ const next = isDark ? "light" : "dark";
14940
+ sessionStorage.setItem(THEME_KEY, next);
14941
+ applyTheme(next);
14942
+ return;
14943
+ }
14538
14944
  if (action === "intel-reload") { return void fetchIntelligenceState().then(rerender); }
14539
14945
  if (action === "intel-picker-open" && intelSurface) return void onIntelPickerOpen(intelSurface);
14540
14946
  if (action === "intel-picker-close") return onIntelPickerClose();
@@ -14688,12 +15094,30 @@ document.addEventListener("input", function (ev) {
14688
15094
  // then the dashboard view renders a static welcome card with no form
14689
15095
  // inputs that could be confused for a working command surface.
14690
15096
 
14691
- // Theme: system preference only at v1.1.
15097
+ // Theme: explicit operator preference (sessionStorage) overrides system
15098
+ // pref. The toggle button in the topbar dispatches data-action
15099
+ // "theme-toggle" which writes the chosen theme and updates the
15100
+ // [data-theme] attribute. When no explicit choice exists, fall back to
15101
+ // system preference and track changes so dark-mode-at-sunset behavior
15102
+ // keeps working on macOS / Windows.
15103
+ const THEME_KEY = "sanctuary-v11-theme";
15104
+ function applyTheme(theme) {
15105
+ if (theme === "dark") document.documentElement.setAttribute("data-theme", "dark");
15106
+ else document.documentElement.removeAttribute("data-theme");
15107
+ }
15108
+ const explicitTheme = sessionStorage.getItem(THEME_KEY);
14692
15109
  const mq = window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null;
14693
- if (mq && mq.matches) document.documentElement.setAttribute("data-theme", "dark");
15110
+ if (explicitTheme === "dark" || explicitTheme === "light") {
15111
+ applyTheme(explicitTheme);
15112
+ } else if (mq && mq.matches) {
15113
+ applyTheme("dark");
15114
+ }
14694
15115
  if (mq) mq.addEventListener("change", function (e) {
14695
- if (e.matches) document.documentElement.setAttribute("data-theme", "dark");
14696
- else document.documentElement.removeAttribute("data-theme");
15116
+ // Only honor system pref changes when the operator has not made an
15117
+ // explicit choice. Once they toggle, the choice sticks for the
15118
+ // session.
15119
+ if (sessionStorage.getItem(THEME_KEY)) return;
15120
+ applyTheme(e.matches ? "dark" : "light");
14697
15121
  });
14698
15122
 
14699
15123
  // Boot.
@@ -14717,9 +15141,11 @@ function renderDashboardV11Html(options = {}) {
14717
15141
  const fortressId = options.fortressId ?? "fortress";
14718
15142
  const sanctuaryVersion = options.sanctuaryVersion ?? SANCTUARY_VERSION;
14719
15143
  const embedClient = options.embedClient !== false;
14720
- const nav = NAV_ITEMS.map(
14721
- (n) => `<a href="#${n.id}" data-route="${n.id}"><span>${escHtml3(n.label)}</span></a>`
14722
- ).join("\n ");
15144
+ const nav = NAV_ITEMS.map((n) => {
15145
+ const iconPath = NAV_ICON_PATHS[n.id] ?? "";
15146
+ const icon = iconPath ? SVG_OPEN + iconPath + "</svg>" : "";
15147
+ return `<a href="#${n.id}" data-route="${n.id}">${icon}<span>${escHtml3(n.label)}</span></a>`;
15148
+ }).join("\n ");
14723
15149
  const config = JSON.stringify({
14724
15150
  authToken,
14725
15151
  hubApiBase,
@@ -14751,8 +15177,12 @@ function renderDashboardV11Html(options = {}) {
14751
15177
  <span class="pill" data-pill="version">v${escHtml3(sanctuaryVersion)}</span>
14752
15178
  <span class="pill" data-pill="deployment">deployment: local</span>
14753
15179
  <span class="pill" data-pill="mode">mode: solo</span>
14754
- <span class="pill" data-pill="attestation">attestation: pending</span>
15180
+ <span class="att-global pending" data-pill="attestation" title="Fortress attestation"><span class="seal"><span class="seal-ring dashed"></span><span class="seal-core"></span></span><span class="label">pending</span></span>
14755
15181
  </div>
15182
+ <button class="btn btn-icon" id="btn-theme-toggle" data-action="theme-toggle" aria-label="Toggle theme" title="Toggle theme">
15183
+ <span class="icon-moon">${THEME_ICON_MOON}</span>
15184
+ <span class="icon-sun">${THEME_ICON_SUN}</span>
15185
+ </button>
14756
15186
  <button class="btn btn-danger" id="btn-lockdown" data-action="lockdown">Lockdown</button>
14757
15187
  </header>
14758
15188
  <main class="main" id="main"><p class="muted">Loading dashboard.</p></main>
@@ -14764,7 +15194,7 @@ function renderDashboardV11Html(options = {}) {
14764
15194
  </body>
14765
15195
  </html>`;
14766
15196
  }
14767
- var STYLES, NAV_ITEMS;
15197
+ var STYLES, NAV_ITEMS, NAV_ICON_PATHS, SVG_OPEN, THEME_ICON_MOON, THEME_ICON_SUN;
14768
15198
  var init_html2 = __esm({
14769
15199
  "src/dashboard/v1_1/html.ts"() {
14770
15200
  init_client();
@@ -14795,6 +15225,26 @@ var init_html2 = __esm({
14795
15225
  --rad: 6px;
14796
15226
  --rad-lg: 10px;
14797
15227
  --shadow: 0 1px 2px rgba(0,0,0,0.04), 0 0 0 1px rgba(0,0,0,0.02);
15228
+ /* Type scale. Names are size-relative, not semantic, so refactors do
15229
+ not need to invent new names. Existing rules used these literal px
15230
+ values; tokens make future polish a one-line change. */
15231
+ --text-xs: 11px;
15232
+ --text-sm: 12px;
15233
+ --text-base: 13px;
15234
+ --text-md: 14px;
15235
+ --text-lg: 16px;
15236
+ --text-xl: 22px;
15237
+ --text-display: 36px;
15238
+ /* Spacing scale (4px multiples). Layout-specific magic numbers
15239
+ (220px sidebar, 360px fortress rail, concierge-card heights) stay
15240
+ as literals because the token system is for component padding /
15241
+ margin / gap, not grid track sizing. */
15242
+ --space-1: 4px;
15243
+ --space-2: 8px;
15244
+ --space-3: 12px;
15245
+ --space-4: 16px;
15246
+ --space-5: 24px;
15247
+ --space-6: 32px;
14798
15248
  }
14799
15249
  [data-theme="dark"] {
14800
15250
  --paper: #121210;
@@ -14821,7 +15271,7 @@ var init_html2 = __esm({
14821
15271
  html, body { margin: 0; padding: 0; }
14822
15272
  body {
14823
15273
  font-family: var(--sans);
14824
- font-size: 14px;
15274
+ font-size: var(--text-md);
14825
15275
  background: var(--paper);
14826
15276
  color: var(--ink);
14827
15277
  line-height: 1.45;
@@ -14842,20 +15292,27 @@ body {
14842
15292
  "sidebar main";
14843
15293
  }
14844
15294
  .sidebar { grid-area: sidebar; background: var(--paper-2); border-right: 1px solid var(--rule); padding: 12px 8px; }
14845
- .sidebar h1 { font-family: var(--serif); font-size: 16px; margin: 4px 8px 16px; }
15295
+ .sidebar h1 { font-family: var(--serif); font-size: var(--text-lg); margin: 4px 8px 16px; }
14846
15296
  .sidebar nav { display: flex; flex-direction: column; gap: 2px; }
14847
15297
  .sidebar nav a {
14848
- display: block; padding: 6px 10px; border-radius: var(--rad);
14849
- color: var(--ink-2); text-decoration: none; font-size: 13px;
15298
+ display: flex; align-items: center; gap: var(--space-2);
15299
+ padding: 6px 10px; border-radius: var(--rad);
15300
+ color: var(--ink-2); text-decoration: none; font-size: var(--text-base);
15301
+ }
15302
+ .sidebar nav a svg {
15303
+ flex-shrink: 0; width: 16px; height: 16px;
15304
+ color: var(--ink-3);
14850
15305
  }
14851
15306
  .sidebar nav a:hover { background: var(--paper-3); }
15307
+ .sidebar nav a:hover svg { color: var(--ink-2); }
14852
15308
  .sidebar nav a.active { background: var(--surface); color: var(--ink); border: 1px solid var(--rule); }
14853
- .topbar { grid-area: topbar; display: flex; align-items: center; gap: 12px; padding: 0 16px; border-bottom: 1px solid var(--rule); background: var(--surface); }
14854
- .topbar .brand { font-family: var(--serif); font-size: 14px; }
15309
+ .sidebar nav a.active svg { color: var(--ink); }
15310
+ .topbar { grid-area: topbar; display: flex; align-items: center; gap: var(--space-3); padding: 0 16px; border-bottom: 1px solid var(--rule); background: var(--surface); }
15311
+ .topbar .brand { font-family: var(--serif); font-size: var(--text-md); }
14855
15312
  .topbar .pills { display: flex; gap: 6px; flex: 1; }
14856
15313
  .pill {
14857
15314
  display: inline-flex; align-items: center; gap: 4px;
14858
- padding: 2px 8px; border-radius: 12px; font-size: 11px;
15315
+ padding: 2px 8px; border-radius: 12px; font-size: var(--text-xs);
14859
15316
  font-family: var(--mono); border: 1px solid var(--rule);
14860
15317
  background: var(--surface-2); color: var(--ink-2);
14861
15318
  }
@@ -14867,7 +15324,7 @@ body {
14867
15324
  display: inline-flex; align-items: center; gap: 4px;
14868
15325
  padding: 4px 10px; border-radius: var(--rad);
14869
15326
  background: var(--surface); border: 1px solid var(--rule);
14870
- font-family: var(--sans); font-size: 12px; color: var(--ink);
15327
+ font-family: var(--sans); font-size: var(--text-sm); color: var(--ink);
14871
15328
  cursor: pointer;
14872
15329
  }
14873
15330
  .btn:hover:not(:disabled) { background: var(--surface-2); }
@@ -14877,21 +15334,30 @@ body {
14877
15334
  .btn.btn-danger { background: var(--rust-bg); color: var(--rust); border-color: var(--rust); }
14878
15335
  .btn.tier1-pending { background: var(--ochre-bg); color: var(--ochre); border-color: var(--ochre); }
14879
15336
  .btn.tier1-engaged { background: var(--rust-bg); color: var(--rust); border-color: var(--rust); }
15337
+ .btn.btn-icon {
15338
+ padding: 4px 6px;
15339
+ display: inline-flex; align-items: center; justify-content: center;
15340
+ color: var(--ink-2);
15341
+ }
15342
+ .btn.btn-icon svg { width: 16px; height: 16px; }
15343
+ .btn.btn-icon .icon-sun { display: none; }
15344
+ [data-theme="dark"] .btn.btn-icon .icon-moon { display: none; }
15345
+ [data-theme="dark"] .btn.btn-icon .icon-sun { display: inline; }
14880
15346
  .main { grid-area: main; overflow-y: auto; padding: 16px 24px; }
14881
- .fortress { grid-area: fortress; overflow-y: auto; border-left: 1px solid var(--rule); background: var(--paper-2); padding: 12px; display: flex; flex-direction: column; gap: 10px; }
15347
+ .fortress { grid-area: fortress; overflow-y: auto; border-left: 1px solid var(--rule); background: var(--paper-2); padding: var(--space-3); display: flex; flex-direction: column; gap: 10px; }
14882
15348
  .app.route-full .fortress { display: none; }
14883
15349
  .card {
14884
15350
  background: var(--surface); border: 1px solid var(--rule);
14885
- border-radius: var(--rad); padding: 12px;
15351
+ border-radius: var(--rad); padding: var(--space-3);
14886
15352
  }
14887
- .card h3 { margin: 0 0 8px; font-size: 13px; font-weight: 600; color: var(--ink); }
15353
+ .card h3 { margin: 0 0 8px; font-size: var(--text-base); font-weight: 600; color: var(--ink); }
14888
15354
  .muted { color: var(--ink-3); }
14889
- .mono { font-family: var(--mono); font-size: 12px; }
14890
- .row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px dashed var(--rule); }
15355
+ .mono { font-family: var(--mono); font-size: var(--text-sm); }
15356
+ .row { display: flex; align-items: center; gap: var(--space-2); padding: 6px 0; border-bottom: 1px dashed var(--rule); }
14891
15357
  .row:last-child { border-bottom: 0; }
14892
15358
  .row .grow { flex: 1; min-width: 0; }
14893
15359
  .agent-row { flex-direction: column; align-items: stretch; gap: 6px; }
14894
- .agent-row-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
15360
+ .agent-row-head { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
14895
15361
  .agent-row-head .grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
14896
15362
  .agent-row-actions { display: flex; flex-wrap: wrap; gap: 4px; }
14897
15363
  /* Click-to-inspect affordance: the head sub-row of a fortress-column
@@ -14901,7 +15367,7 @@ body {
14901
15367
  .agent-row-head[data-action="agent-row-inspect-open"] { cursor: pointer; border-radius: var(--rad); padding: 4px 6px; margin: -4px -6px; }
14902
15368
  .agent-row-head[data-action="agent-row-inspect-open"]:hover { background: var(--paper-3); }
14903
15369
  .agent-row-head[data-action="agent-row-inspect-open"]:focus-visible { outline: 2px solid var(--ink-3); outline-offset: 1px; }
14904
- .kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; font-size: 12px; }
15370
+ .kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; font-size: var(--text-sm); }
14905
15371
  .kv dt { color: var(--ink-3); }
14906
15372
  .kv dd { margin: 0; color: var(--ink); }
14907
15373
  .glyph { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--ink-4); }
@@ -14912,74 +15378,74 @@ body {
14912
15378
  .toast {
14913
15379
  position: fixed; bottom: 16px; right: 16px;
14914
15380
  background: var(--ink); color: var(--paper); padding: 8px 12px;
14915
- border-radius: var(--rad); font-size: 12px; z-index: 1000;
15381
+ border-radius: var(--rad); font-size: var(--text-sm); z-index: 1000;
14916
15382
  max-width: 360px;
14917
15383
  }
14918
15384
  .toast.error { background: var(--rust); color: var(--paper); }
14919
- .layer-card { background: var(--surface-2); border: 1px solid var(--rule); border-radius: var(--rad); padding: 8px; }
14920
- .layer-card h4 { margin: 0 0 4px; font-size: 12px; font-weight: 600; }
14921
- .layer-card p { margin: 0; font-size: 11px; color: var(--ink-3); }
14922
- .chat-thread { display: flex; flex-direction: column; gap: 8px; padding-bottom: 12px; }
15385
+ .layer-card { background: var(--surface-2); border: 1px solid var(--rule); border-radius: var(--rad); padding: var(--space-2); }
15386
+ .layer-card h4 { margin: 0 0 4px; font-size: var(--text-sm); font-weight: 600; }
15387
+ .layer-card p { margin: 0; font-size: var(--text-xs); color: var(--ink-3); }
15388
+ .chat-thread { display: flex; flex-direction: column; gap: var(--space-2); padding-bottom: 12px; }
14923
15389
  .chat-msg { padding: 8px 10px; border-radius: var(--rad); border: 1px solid var(--rule); background: var(--surface); max-width: 78%; }
14924
- .chat-msg.system { background: var(--paper-3); color: var(--ink-3); font-size: 12px; max-width: 100%; }
15390
+ .chat-msg.system { background: var(--paper-3); color: var(--ink-3); font-size: var(--text-sm); max-width: 100%; }
14925
15391
  .chat-msg.agent { align-self: flex-start; }
14926
15392
  .chat-msg.operator { align-self: flex-end; background: var(--ink); color: var(--paper); }
14927
15393
  .chat-msg .meta { font-size: 10px; color: var(--ink-4); margin-top: 4px; }
14928
- .composer { display: flex; gap: 8px; padding: 8px; border-top: 1px solid var(--rule); }
15394
+ .composer { display: flex; gap: var(--space-2); padding: var(--space-2); border-top: 1px solid var(--rule); }
14929
15395
  .composer input { flex: 1; padding: 6px 8px; border: 1px solid var(--rule); border-radius: var(--rad); font-family: var(--sans); }
14930
15396
  .wizard-step { padding: 10px; border: 1px solid var(--rule); border-radius: var(--rad); margin-bottom: 8px; background: var(--surface); }
14931
15397
  .wizard-step.active { border-color: var(--ink); }
14932
15398
  .wizard-step.done { background: var(--sage-bg); border-color: var(--sage); }
14933
- .code-block { font-family: var(--mono); background: var(--paper-3); padding: 8px; border-radius: var(--rad); font-size: 12px; overflow-x: auto; }
15399
+ .code-block { font-family: var(--mono); background: var(--paper-3); padding: var(--space-2); border-radius: var(--rad); font-size: var(--text-sm); overflow-x: auto; }
14934
15400
  .policy-center { max-width: 980px; margin: 0 auto; }
14935
- .policy-center .eyebrow { margin: 0 0 6px; color: var(--ink-3); font-family: var(--mono); font-size: 12px; letter-spacing: 0; }
14936
- .policy-center h1 { font-family: var(--serif); font-size: 36px; line-height: 1.08; font-weight: 400; margin: 0 0 10px; }
15401
+ .policy-center .eyebrow { margin: 0 0 6px; color: var(--ink-3); font-family: var(--mono); font-size: var(--text-sm); letter-spacing: 0; }
15402
+ .policy-center h1 { font-family: var(--serif); font-size: var(--text-display); line-height: 1.08; font-weight: 400; margin: 0 0 10px; }
14937
15403
  .policy-subtitle { max-width: 860px; color: var(--ink-2); font-size: 15px; margin: 0 0 24px; }
14938
15404
  .policy-panel { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--rad-lg); padding: 20px; margin: 18px 0; }
14939
- .policy-panel h2 { font-family: var(--serif); font-size: 22px; font-weight: 400; margin: 0 0 14px; }
14940
- .template-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
15405
+ .policy-panel h2 { font-family: var(--serif); font-size: var(--text-xl); font-weight: 400; margin: 0 0 14px; }
15406
+ .template-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3); }
14941
15407
  .template-card { background: var(--surface-2); border: 1px solid var(--rule); border-radius: var(--rad); padding: 14px; min-height: 132px; }
14942
- .template-card-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-bottom: 12px; }
14943
- .severity { border-radius: 999px; padding: 2px 9px; font-family: var(--mono); font-size: 11px; font-weight: 700; }
15408
+ .template-card-head { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); margin-bottom: 12px; }
15409
+ .severity { border-radius: 999px; padding: 2px 9px; font-family: var(--mono); font-size: var(--text-xs); font-weight: 700; }
14944
15410
  .severity.low { color: var(--sage); background: var(--sage-bg); }
14945
15411
  .severity.medium { color: var(--ochre); background: var(--ochre-bg); }
14946
15412
  .template-id { background: var(--paper-3); border-radius: var(--rad); padding: 2px 7px; color: var(--ink-3); }
14947
- .template-card h3 { font-size: 16px; margin: 0 0 6px; }
14948
- .template-card p { color: var(--ink-3); margin: 0; font-size: 14px; }
15413
+ .template-card h3 { font-size: var(--text-lg); margin: 0 0 6px; }
15414
+ .template-card p { color: var(--ink-3); margin: 0; font-size: var(--text-md); }
14949
15415
  .rules-scroll { overflow-x: auto; }
14950
15416
  .rules-table { width: 100%; border-collapse: collapse; min-width: 760px; }
14951
- .rules-table th { text-align: left; color: var(--ink-3); font-family: var(--mono); font-size: 12px; letter-spacing: 0; padding: 8px 10px; border-bottom: 1px solid var(--rule); }
15417
+ .rules-table th { text-align: left; color: var(--ink-3); font-family: var(--mono); font-size: var(--text-sm); letter-spacing: 0; padding: 8px 10px; border-bottom: 1px solid var(--rule); }
14952
15418
  .rules-table td { padding: 12px 10px; border-bottom: 1px solid var(--rule); vertical-align: top; }
14953
15419
  .link-btn, .template-cell { border: 0; background: transparent; color: var(--ink); padding: 0; cursor: pointer; font: inherit; text-align: left; }
14954
15420
  .template-cell { font-family: var(--mono); max-width: 180px; overflow-wrap: anywhere; }
14955
15421
  .template-picker { position: absolute; z-index: 20; margin-top: 8px; width: min(420px, calc(100vw - 80px)); background: var(--surface); border: 1px solid var(--rule-2); border-radius: var(--rad); box-shadow: var(--shadow); padding: 10px; }
14956
15422
  .template-picker-options { display: grid; gap: 6px; max-height: 320px; overflow-y: auto; }
14957
- .template-option { display: grid; grid-template-columns: 18px 1fr; gap: 8px; padding: 8px; border: 1px solid var(--rule); border-radius: var(--rad); background: var(--surface-2); }
15423
+ .template-option { display: grid; grid-template-columns: 18px 1fr; gap: var(--space-2); padding: var(--space-2); border: 1px solid var(--rule); border-radius: var(--rad); background: var(--surface-2); }
14958
15424
  .template-option small { display: block; color: var(--ink-3); margin-top: 2px; }
14959
- .template-picker-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; }
15425
+ .template-picker-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: 10px; }
14960
15426
  .allow-count { color: var(--sage); font-weight: 700; }
14961
15427
  .block-count { color: var(--rust); }
14962
15428
  .toggle-on { display: inline-block; width: 28px; height: 16px; border-radius: 999px; background: var(--sage); position: relative; }
14963
15429
  .toggle-on::after { content: ""; position: absolute; right: 2px; top: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--surface); }
14964
15430
  .error-text { color: var(--rust); margin: 8px 0 0; }
14965
15431
  .intel-center { max-width: 980px; margin: 0 auto; }
14966
- .intel-center .eyebrow { margin: 0 0 6px; color: var(--ink-3); font-family: var(--mono); font-size: 12px; letter-spacing: 0; }
14967
- .intel-center h1 { font-family: var(--serif); font-size: 36px; line-height: 1.08; font-weight: 400; margin: 0 0 10px; }
15432
+ .intel-center .eyebrow { margin: 0 0 6px; color: var(--ink-3); font-family: var(--mono); font-size: var(--text-sm); letter-spacing: 0; }
15433
+ .intel-center h1 { font-family: var(--serif); font-size: var(--text-display); line-height: 1.08; font-weight: 400; margin: 0 0 10px; }
14968
15434
  .intel-subtitle { max-width: 860px; color: var(--ink-2); font-size: 15px; margin: 0 0 24px; }
14969
15435
  .intel-panel { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--rad-lg); padding: 20px; margin: 18px 0; }
14970
- .intel-panel h2 { font-family: var(--serif); font-size: 22px; font-weight: 400; margin: 0 0 14px; }
14971
- .intel-row { display: grid; grid-template-columns: 200px 1fr auto; gap: 16px; padding: 14px 0; border-bottom: 1px solid var(--rule); align-items: start; }
15436
+ .intel-panel h2 { font-family: var(--serif); font-size: var(--text-xl); font-weight: 400; margin: 0 0 14px; }
15437
+ .intel-row { display: grid; grid-template-columns: 200px 1fr auto; gap: var(--space-4); padding: 14px 0; border-bottom: 1px solid var(--rule); align-items: start; }
14972
15438
  .intel-row:last-child { border-bottom: 0; }
14973
15439
  .intel-row-name { font-weight: 600; }
14974
- .intel-row-name small { display: block; color: var(--ink-3); font-weight: 400; font-size: 12px; margin-top: 2px; font-family: var(--mono); }
15440
+ .intel-row-name small { display: block; color: var(--ink-3); font-weight: 400; font-size: var(--text-sm); margin-top: 2px; font-family: var(--mono); }
14975
15441
  .intel-row-body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
14976
- .intel-row-current { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
14977
- .intel-row-tradeoff { color: var(--ink-2); font-size: 13px; line-height: 1.5; }
15442
+ .intel-row-current { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
15443
+ .intel-row-tradeoff { color: var(--ink-2); font-size: var(--text-base); line-height: 1.5; }
14978
15444
  .intel-status-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
14979
15445
  .intel-status-dot.green { background: var(--sage); }
14980
15446
  .intel-status-dot.yellow { background: var(--ochre); }
14981
15447
  .intel-status-dot.red { background: var(--rust); }
14982
- .intel-hardware { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; font-size: 13px; }
15448
+ .intel-hardware { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; font-size: var(--text-base); }
14983
15449
  .intel-hardware dt { color: var(--ink-3); font-family: var(--mono); }
14984
15450
  .intel-hardware dd { margin: 0; }
14985
15451
  .intel-modal-backdrop {
@@ -14992,33 +15458,162 @@ body {
14992
15458
  box-shadow: 0 8px 32px rgba(0,0,0,0.18); padding: 24px;
14993
15459
  width: 100%; max-width: 640px;
14994
15460
  }
14995
- .intel-modal h2 { font-family: var(--serif); font-size: 22px; font-weight: 400; margin: 0 0 8px; }
14996
- .intel-modal-subtitle { color: var(--ink-3); margin: 0 0 18px; font-size: 13px; }
15461
+ .intel-modal h2 { font-family: var(--serif); font-size: var(--text-xl); font-weight: 400; margin: 0 0 8px; }
15462
+ .intel-modal-subtitle { color: var(--ink-3); margin: 0 0 18px; font-size: var(--text-base); }
14997
15463
  .intel-option {
14998
- border: 1px solid var(--rule); border-radius: var(--rad); padding: 12px;
15464
+ border: 1px solid var(--rule); border-radius: var(--rad); padding: var(--space-3);
14999
15465
  margin-bottom: 10px; background: var(--surface-2); cursor: pointer;
15000
15466
  display: grid; grid-template-columns: 18px 1fr; gap: 10px; align-items: start;
15001
15467
  }
15002
15468
  .intel-option.selected { border-color: var(--ink); background: var(--surface); }
15003
- .intel-option-body strong { display: block; font-size: 14px; margin-bottom: 4px; }
15004
- .intel-option-body small { display: block; color: var(--ink-3); font-size: 12px; line-height: 1.5; }
15469
+ .intel-option-body strong { display: block; font-size: var(--text-md); margin-bottom: 4px; }
15470
+ .intel-option-body small { display: block; color: var(--ink-3); font-size: var(--text-sm); line-height: 1.5; }
15005
15471
  .intel-suboptions { margin-top: 10px; padding: 10px; background: var(--paper-3); border-radius: var(--rad); }
15006
- .intel-suboptions label { display: block; margin: 6px 0; font-size: 13px; }
15472
+ .intel-suboptions label { display: block; margin: 6px 0; font-size: var(--text-base); }
15007
15473
  .intel-suboptions input[type="text"], .intel-suboptions input[type="password"] {
15008
15474
  width: 100%; padding: 6px 8px; border: 1px solid var(--rule); border-radius: var(--rad);
15009
- font-family: var(--mono); font-size: 12px; box-sizing: border-box;
15475
+ font-family: var(--mono); font-size: var(--text-sm); box-sizing: border-box;
15476
+ }
15477
+ .intel-modal-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: 18px; }
15478
+ /*
15479
+ * Intelligence panel polish (Sprint Piece 2 PR 3). Card grid layout
15480
+ * replaces the legacy 3-col row visual. The legacy .intel-row and
15481
+ * .intel-status-dot rules above are retained for the e2e selector
15482
+ * contract (.intel-row[data-intel-surface="..."]) and as the responsive
15483
+ * fallback. Cards render as a flex column with a substrate inset,
15484
+ * status badge with shaped glyph, and a recent-failures toggle in the
15485
+ * card foot.
15486
+ */
15487
+ .intel-wrap { max-width: 1000px; margin: 0 auto; }
15488
+ .intel-grid {
15489
+ display: grid;
15490
+ grid-template-columns: repeat(2, minmax(0, 1fr));
15491
+ gap: 12px;
15492
+ }
15493
+ .intel-card {
15494
+ background: var(--surface);
15495
+ border: 1px solid var(--rule);
15496
+ border-radius: var(--rad-lg);
15497
+ padding: 16px;
15498
+ display: flex; flex-direction: column;
15499
+ gap: 12px;
15500
+ }
15501
+ .intel-card-head {
15502
+ display: flex; align-items: flex-start; justify-content: space-between;
15503
+ gap: 10px;
15504
+ }
15505
+ .intel-card-name {
15506
+ display: flex; flex-direction: column; gap: 2px;
15507
+ min-width: 0;
15010
15508
  }
15011
- .intel-modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }
15509
+ .intel-card-name strong {
15510
+ font-family: var(--serif); font-weight: 500;
15511
+ font-size: 15px; letter-spacing: 0.005em;
15512
+ }
15513
+ .intel-card-name small {
15514
+ color: var(--ink-3); font-size: 11px; font-family: var(--mono);
15515
+ }
15516
+ .intel-card-status {
15517
+ display: inline-flex; align-items: center; gap: 6px;
15518
+ font-family: var(--mono); font-size: 11px;
15519
+ padding: 3px 9px; border-radius: 999px;
15520
+ border: 1px solid var(--rule); background: var(--surface-2);
15521
+ flex-shrink: 0;
15522
+ }
15523
+ .intel-card-status.ok { color: var(--sage); border-color: var(--sage); background: var(--sage-bg); }
15524
+ .intel-card-status.warn { color: var(--ochre); border-color: var(--ochre); background: var(--ochre-bg); }
15525
+ .intel-card-status.fail { color: var(--rust); border-color: var(--rust); background: var(--rust-bg); }
15526
+ .status-glyph {
15527
+ width: 10px; height: 10px;
15528
+ position: relative; flex-shrink: 0;
15529
+ }
15530
+ .status-glyph.ok::before {
15531
+ content: ""; position: absolute; inset: 0;
15532
+ border-radius: 50%; background: currentColor;
15533
+ }
15534
+ .status-glyph.warn::before {
15535
+ content: ""; position: absolute; inset: 0;
15536
+ background: currentColor;
15537
+ clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
15538
+ }
15539
+ .status-glyph.fail::before {
15540
+ content: ""; position: absolute; inset: 1px;
15541
+ background: currentColor;
15542
+ clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
15543
+ }
15544
+ .intel-substrate {
15545
+ display: flex; flex-direction: column; gap: 4px;
15546
+ padding: 10px 12px;
15547
+ background: var(--paper-2);
15548
+ border: 1px solid var(--rule);
15549
+ border-radius: var(--rad);
15550
+ }
15551
+ .intel-substrate .sub-line {
15552
+ display: flex; justify-content: space-between; align-items: center;
15553
+ gap: 10px; font-size: 12px;
15554
+ }
15555
+ .intel-substrate .sub-line.primary {
15556
+ font-family: var(--mono); font-size: 13px; color: var(--ink);
15557
+ }
15558
+ .intel-substrate .sub-line.secondary { color: var(--ink-3); font-size: 11px; }
15559
+ .intel-card-foot {
15560
+ display: flex; align-items: center; justify-content: space-between;
15561
+ gap: 8px; padding-top: 4px;
15562
+ }
15563
+ .intel-failures-toggle {
15564
+ display: inline-flex; align-items: center; gap: 6px;
15565
+ font-size: 12px; font-family: var(--mono);
15566
+ color: var(--ink-3);
15567
+ background: transparent; border: 0; padding: 0;
15568
+ cursor: pointer;
15569
+ }
15570
+ .intel-failures-toggle:hover { color: var(--ink); }
15571
+ .intel-failures-toggle .caret {
15572
+ display: inline-block; width: 0; height: 0;
15573
+ border-left: 4px solid transparent;
15574
+ border-right: 4px solid transparent;
15575
+ border-top: 5px solid currentColor;
15576
+ transition: transform 160ms ease;
15577
+ }
15578
+ .intel-failures-toggle.open .caret { transform: rotate(180deg); }
15579
+ .intel-failures {
15580
+ border-top: 1px solid var(--rule);
15581
+ padding-top: 12px;
15582
+ display: flex; flex-direction: column;
15583
+ gap: 8px;
15584
+ }
15585
+ .intel-failure-row {
15586
+ display: grid; grid-template-columns: 88px 1fr; gap: 12px;
15587
+ font-size: 12px; padding: 8px 10px;
15588
+ border-radius: var(--rad);
15589
+ background: var(--paper-2);
15590
+ border: 1px solid var(--rule);
15591
+ }
15592
+ .intel-failure-row .ts {
15593
+ font-family: var(--mono); font-size: 11px; color: var(--ink-3);
15594
+ }
15595
+ .intel-failure-row .err-class {
15596
+ font-family: var(--mono); font-size: 10px;
15597
+ letter-spacing: 0.04em; text-transform: uppercase;
15598
+ color: var(--rust); margin-bottom: 2px;
15599
+ }
15600
+ .btn-quiet {
15601
+ background: transparent; border: 1px solid var(--rule);
15602
+ padding: 2px 6px; font-size: 11px;
15603
+ border-radius: var(--rad); cursor: pointer;
15604
+ color: var(--ink-2); font-family: var(--sans);
15605
+ }
15606
+ .btn-quiet:hover { background: var(--surface-2); color: var(--ink); }
15012
15607
  .banner-warn {
15013
15608
  background: var(--ochre-bg); color: var(--ochre); border: 1px solid var(--ochre);
15014
- border-radius: var(--rad); padding: 8px 12px; margin: 8px 0; font-size: 13px;
15609
+ border-radius: var(--rad); padding: 8px 12px; margin: 8px 0; font-size: var(--text-base);
15015
15610
  }
15016
15611
  .banner-info {
15017
15612
  background: var(--indigo-bg); color: var(--indigo); border: 1px solid var(--indigo);
15018
- border-radius: var(--rad); padding: 8px 12px; margin: 8px 0; font-size: 13px;
15613
+ border-radius: var(--rad); padding: 8px 12px; margin: 8px 0; font-size: var(--text-base);
15019
15614
  }
15020
15615
  .btn.chip {
15021
- border-radius: 999px; padding: 4px 12px; font-size: 12px;
15616
+ border-radius: 999px; padding: 4px 12px; font-size: var(--text-sm);
15022
15617
  background: var(--surface-2); border-color: var(--rule);
15023
15618
  }
15024
15619
  .btn.chip:hover:not(:disabled) { background: var(--paper-3); }
@@ -15035,85 +15630,210 @@ body {
15035
15630
  * dynamically, but the input box is below the fold, so I still have
15036
15631
  * to scroll." rc.5 closes that with a structural layout fix.
15037
15632
  */
15633
+ /* Sprint Piece 2 PR 2 (2026-05-03): concierge surface polish.
15634
+ * Translates Claude Design references at
15635
+ * server/docs/design-refs/sprint-piece-2/surface-concierge.jsx and the
15636
+ * Surface 1 block of surfaces.css. The bounded-card layout above is
15637
+ * preserved verbatim because rc.5 and the DDD e2e suite depend on it;
15638
+ * the Claude Design reference uses height: 720px for the card, but
15639
+ * production keeps the calc-based bounded height so the card adapts
15640
+ * to the operator viewport. The polish lands the persona glyph-ring,
15641
+ * mono uppercase author labels, paper-2 background for concierge
15642
+ * replies, suggest-grid empty-state cards, and the composer input-wrap
15643
+ * with the keyboard-shortcut pill.
15644
+ */
15645
+ .concierge-wrap { max-width: 880px; margin: 0 auto; }
15646
+ .page-head {
15647
+ display: flex; align-items: flex-end; justify-content: space-between;
15648
+ gap: var(--space-4);
15649
+ margin-bottom: 18px; padding-bottom: 14px;
15650
+ border-bottom: 1px solid var(--rule);
15651
+ }
15652
+ .page-head .eyebrow {
15653
+ font-family: var(--mono); font-size: var(--text-xs);
15654
+ letter-spacing: 0.08em; text-transform: uppercase;
15655
+ color: var(--ink-3);
15656
+ margin: 0 0 6px;
15657
+ }
15658
+ .page-head h1 {
15659
+ font-family: var(--serif); font-weight: 400;
15660
+ font-size: 28px; letter-spacing: -0.01em;
15661
+ margin: 0 0 4px;
15662
+ }
15663
+ .page-head .sub {
15664
+ color: var(--ink-3); margin: 0;
15665
+ font-size: var(--text-base); max-width: 60ch;
15666
+ }
15038
15667
  .concierge-card {
15039
15668
  display: flex; flex-direction: column;
15040
15669
  height: calc(100vh - 180px);
15041
15670
  max-height: calc(100vh - 180px);
15042
15671
  min-height: 360px;
15043
- padding: 16px 18px;
15672
+ padding: 18px 22px 14px;
15044
15673
  gap: 0;
15045
15674
  }
15046
15675
  .concierge-header {
15047
15676
  display: flex; align-items: center; justify-content: space-between;
15048
- gap: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--rule);
15677
+ gap: var(--space-3); padding-bottom: 14px;
15678
+ border-bottom: 1px solid var(--rule);
15049
15679
  flex-wrap: wrap;
15050
15680
  flex-shrink: 0;
15051
15681
  }
15052
- .concierge-persona {
15053
- display: flex; align-items: baseline; gap: 8px;
15054
- font-size: 14px;
15682
+ .concierge-persona { display: flex; align-items: center; gap: 10px; }
15683
+ .concierge-persona .glyph-ring {
15684
+ width: 26px; height: 26px;
15685
+ border: 1.5px solid var(--ink-2);
15686
+ border-radius: 50%;
15687
+ position: relative;
15688
+ flex-shrink: 0;
15689
+ }
15690
+ .concierge-persona .glyph-ring::after {
15691
+ content: ""; position: absolute; inset: 5px;
15692
+ border-radius: 50%; background: var(--ink-2);
15693
+ }
15694
+ .concierge-persona-text { display: flex; flex-direction: column; }
15695
+ .concierge-persona-text strong {
15696
+ font-family: var(--serif); font-weight: 500;
15697
+ font-size: 15px; letter-spacing: 0.005em;
15698
+ }
15699
+ .concierge-persona-text small {
15700
+ color: var(--ink-3); font-size: var(--text-xs);
15701
+ font-family: var(--mono);
15702
+ }
15703
+ .concierge-meta {
15704
+ display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
15055
15705
  }
15056
- .concierge-persona strong { font-size: 14px; }
15057
15706
  .concierge-badge { white-space: nowrap; }
15058
15707
  .concierge-history {
15059
15708
  flex: 1 1 auto;
15060
15709
  min-height: 0;
15061
15710
  overflow-y: auto;
15062
- padding: 14px 4px;
15063
- display: flex; flex-direction: column; gap: 14px;
15711
+ padding: 18px 4px 6px;
15712
+ display: flex; flex-direction: column; gap: 18px;
15064
15713
  }
15065
15714
  .concierge-msg {
15066
- display: flex; flex-direction: column; gap: 4px;
15067
- max-width: 80%;
15715
+ display: flex; flex-direction: column; gap: 5px;
15716
+ max-width: 78%;
15068
15717
  }
15069
15718
  .concierge-msg-author {
15070
- font-size: 11px;
15719
+ font-size: 10px;
15720
+ font-family: var(--mono);
15721
+ letter-spacing: 0.04em;
15722
+ text-transform: uppercase;
15723
+ color: var(--ink-4);
15071
15724
  }
15072
15725
  .concierge-msg-body {
15073
- padding: 10px 14px; border-radius: 12px;
15074
- border: 1px solid var(--rule); background: var(--surface);
15075
- white-space: pre-wrap; word-wrap: break-word;
15076
- font-size: 14px; line-height: 1.5;
15726
+ padding: 11px 14px;
15727
+ border-radius: 12px;
15728
+ border: 1px solid var(--rule);
15729
+ background: var(--surface);
15730
+ font-size: var(--text-md);
15731
+ line-height: 1.55;
15732
+ white-space: pre-wrap;
15733
+ word-wrap: break-word;
15077
15734
  }
15078
15735
  .concierge-msg-concierge { align-self: flex-start; }
15736
+ .concierge-msg-concierge .concierge-msg-body {
15737
+ background: var(--paper-2);
15738
+ }
15079
15739
  .concierge-msg-operator { align-self: flex-end; align-items: flex-end; }
15080
15740
  .concierge-msg-operator .concierge-msg-body {
15081
15741
  background: var(--ink); color: var(--paper); border-color: var(--ink);
15082
15742
  }
15743
+ .concierge-msg-meta {
15744
+ display: flex; gap: 6px;
15745
+ font-size: 10px;
15746
+ color: var(--ink-4);
15747
+ font-family: var(--mono);
15748
+ }
15083
15749
  .concierge-empty {
15084
- padding: 24px 8px; text-align: left;
15085
- font-size: 13px; line-height: 1.6;
15750
+ flex: 1 1 auto;
15751
+ display: flex; flex-direction: column; gap: 22px;
15752
+ justify-content: center;
15753
+ padding: 24px 12px;
15754
+ }
15755
+ .concierge-empty-headline { max-width: 52ch; }
15756
+ .concierge-empty-headline h2 {
15757
+ font-family: var(--serif); font-weight: 400;
15758
+ font-size: var(--text-xl); margin: 0 0 6px;
15759
+ letter-spacing: -0.005em;
15760
+ }
15761
+ .concierge-empty-headline p {
15762
+ color: var(--ink-3); margin: 0;
15763
+ font-size: var(--text-md); line-height: 1.55;
15764
+ }
15765
+ .concierge-suggest-grid {
15766
+ display: grid;
15767
+ grid-template-columns: repeat(3, minmax(0, 1fr));
15768
+ gap: 10px;
15769
+ }
15770
+ .concierge-suggest {
15771
+ background: var(--surface);
15772
+ border: 1px solid var(--rule);
15773
+ border-radius: var(--rad);
15774
+ padding: 12px 14px;
15775
+ font-size: var(--text-base);
15776
+ cursor: pointer;
15777
+ display: flex; flex-direction: column;
15778
+ gap: 6px;
15779
+ text-align: left;
15780
+ font-family: var(--sans);
15781
+ color: var(--ink);
15782
+ }
15783
+ .concierge-suggest:hover:not(:disabled) {
15784
+ background: var(--surface-2);
15785
+ border-color: var(--rule-2);
15786
+ }
15787
+ .concierge-suggest:disabled {
15788
+ cursor: not-allowed; color: var(--ink-4); opacity: 0.7;
15789
+ }
15790
+ .concierge-suggest .label {
15791
+ font-family: var(--mono);
15792
+ font-size: 10px;
15793
+ letter-spacing: 0.06em;
15794
+ text-transform: uppercase;
15795
+ color: var(--ink-3);
15086
15796
  }
15087
15797
  .concierge-composer {
15088
15798
  display: flex; gap: 10px; align-items: center;
15089
- padding: 12px 0 8px;
15799
+ padding: 12px 0 4px;
15090
15800
  border-top: 1px solid var(--rule);
15091
15801
  flex-shrink: 0;
15092
15802
  }
15803
+ .concierge-composer .input-wrap {
15804
+ flex: 1;
15805
+ display: flex; align-items: center; gap: var(--space-2);
15806
+ padding: 8px 12px;
15807
+ border: 1px solid var(--rule);
15808
+ border-radius: var(--rad);
15809
+ background: var(--surface);
15810
+ }
15811
+ .concierge-composer .input-wrap:focus-within {
15812
+ border-color: var(--ink-3);
15813
+ }
15093
15814
  .concierge-composer input {
15094
15815
  flex: 1; min-width: 0;
15095
- padding: 10px 14px;
15096
- border: 1px solid var(--rule); border-radius: var(--rad);
15097
- font-family: var(--sans); font-size: 14px;
15098
- background: var(--surface); color: var(--ink);
15816
+ padding: 4px 0;
15817
+ border: 0;
15818
+ background: transparent;
15819
+ color: var(--ink);
15820
+ font-family: var(--sans);
15821
+ font-size: var(--text-md);
15822
+ outline: none;
15099
15823
  }
15100
- .concierge-composer input:focus {
15101
- outline: none; border-color: var(--ink-3);
15824
+ .concierge-composer input::placeholder { color: var(--ink-4); }
15825
+ .concierge-composer .composer-meta {
15826
+ font-family: var(--mono);
15827
+ font-size: 10px;
15828
+ color: var(--ink-4);
15829
+ letter-spacing: 0.04em;
15102
15830
  }
15103
15831
  .concierge-composer .btn-primary {
15104
- padding: 8px 18px; font-size: 13px; flex-shrink: 0;
15105
- }
15106
- .concierge-chips {
15107
- display: flex; flex-wrap: wrap; gap: 6px;
15108
- padding: 10px 0 0;
15109
- }
15110
- .concierge-chips::before {
15111
- content: "Try:"; color: var(--ink-3); font-size: 12px;
15112
- align-self: center; margin-right: 4px;
15832
+ padding: 8px 18px; font-size: var(--text-base); flex-shrink: 0;
15113
15833
  }
15114
15834
  .concierge-foot {
15115
15835
  margin: 12px 0 0; padding-top: 10px; border-top: 1px dashed var(--rule);
15116
- font-size: 12px;
15836
+ font-size: var(--text-sm);
15117
15837
  }
15118
15838
  .concierge-foot a { color: var(--ink-2); }
15119
15839
  .tier1-approval-card {
@@ -15121,20 +15841,532 @@ body {
15121
15841
  border-radius: var(--rad); padding: 14px 16px; margin: 12px 0;
15122
15842
  }
15123
15843
  .tier1-approval-card h3 {
15124
- margin: 0 0 8px; color: var(--ochre); font-size: 14px;
15844
+ margin: 0 0 8px; color: var(--ochre); font-size: var(--text-md);
15125
15845
  }
15126
- .tier1-approval-card p { margin: 0 0 12px; font-size: 13px; }
15846
+ .tier1-approval-card p { margin: 0 0 12px; font-size: var(--text-base); }
15127
15847
  .tier1-approval-card .actions {
15128
- display: flex; gap: 8px; flex-wrap: wrap;
15848
+ display: flex; gap: var(--space-2); flex-wrap: wrap;
15849
+ }
15850
+ /* Sprint Piece 2 PR 4 (2026-05-04): Agents view + Inspect pane polish.
15851
+ * Translates Claude Design references at
15852
+ * server/docs/design-refs/sprint-piece-2/surface-agents.jsx and the
15853
+ * Surface 3 block of surfaces.css. The fortress-column .agent-row,
15854
+ * .agent-row-head, and .agent-row-actions rules above are kept verbatim
15855
+ * because Finding DD tests pin them; the new Agents-view list scopes
15856
+ * its grid layout under .agents-list (descendant selector) so the
15857
+ * fortress-column rules are unaffected. The inspect pane combines the
15858
+ * existing .card surface with .inspect-pane structure (sticky right
15859
+ * rail, internal scroll, sectioned body) for the agent-detail view.
15860
+ */
15861
+ .agents-wrap { max-width: 1080px; margin: 0 auto; }
15862
+ .agents-layout {
15863
+ display: grid;
15864
+ grid-template-columns: 1fr 420px;
15865
+ gap: 20px;
15866
+ align-items: start;
15867
+ }
15868
+ .agents-list {
15869
+ background: var(--surface);
15870
+ border: 1px solid var(--rule);
15871
+ border-radius: var(--rad-lg);
15872
+ overflow: hidden;
15873
+ }
15874
+ .agents-list-head {
15875
+ display: grid;
15876
+ grid-template-columns: minmax(0, 1fr) 110px 120px 88px;
15877
+ gap: 12px;
15878
+ padding: 10px 16px;
15879
+ border-bottom: 1px solid var(--rule);
15880
+ background: var(--paper-2);
15881
+ font-family: var(--mono);
15882
+ font-size: 10px;
15883
+ letter-spacing: 0.06em;
15884
+ text-transform: uppercase;
15885
+ color: var(--ink-3);
15886
+ }
15887
+ .agents-list .agent-row {
15888
+ display: grid;
15889
+ grid-template-columns: minmax(0, 1fr) 110px 120px 88px;
15890
+ gap: 12px;
15891
+ padding: 14px 16px;
15892
+ border-bottom: 1px solid var(--rule);
15893
+ align-items: center;
15894
+ cursor: pointer;
15895
+ transition: background 120ms ease;
15896
+ }
15897
+ .agents-list .agent-row:last-child { border-bottom: 0; }
15898
+ .agents-list .agent-row:hover { background: var(--paper-2); }
15899
+ .agents-list .agent-row.selected {
15900
+ background: var(--paper-2);
15901
+ box-shadow: inset 3px 0 0 var(--ink);
15902
+ }
15903
+ .agent-identity { display: flex; align-items: center; gap: 10px; min-width: 0; }
15904
+ .agent-glyph {
15905
+ width: 28px; height: 28px;
15906
+ border-radius: var(--rad);
15907
+ background: var(--paper-3);
15908
+ border: 1px solid var(--rule);
15909
+ display: grid; place-items: center;
15910
+ flex-shrink: 0;
15911
+ font-family: var(--mono);
15912
+ font-size: 11px;
15913
+ color: var(--ink-2);
15914
+ font-weight: 600;
15915
+ }
15916
+ .agent-name {
15917
+ display: flex; flex-direction: column; min-width: 0;
15918
+ }
15919
+ .agent-name strong {
15920
+ font-size: var(--text-base); font-weight: 500;
15921
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
15922
+ }
15923
+ .agent-name small {
15924
+ font-family: var(--mono); font-size: var(--text-xs); color: var(--ink-3);
15925
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
15926
+ display: block;
15927
+ }
15928
+ .agent-state {
15929
+ display: inline-flex; align-items: center; gap: 6px;
15930
+ font-size: var(--text-xs);
15931
+ font-family: var(--mono);
15932
+ }
15933
+ .state-dot {
15934
+ width: 7px; height: 7px; border-radius: 50%;
15935
+ background: var(--ink-4);
15936
+ }
15937
+ .state-dot.live { background: var(--sage); animation: pulse-soft 2.4s ease-in-out infinite; }
15938
+ .state-dot.idle { background: var(--ochre); }
15939
+ .state-dot.off { background: var(--ink-4); }
15940
+ @keyframes pulse-soft {
15941
+ 0%, 100% { box-shadow: 0 0 0 0 currentColor; opacity: 1; }
15942
+ 50% { box-shadow: 0 0 0 4px transparent; opacity: 0.7; }
15943
+ }
15944
+ .agent-last {
15945
+ font-family: var(--mono); font-size: var(--text-xs); color: var(--ink-3);
15946
+ }
15947
+ /* Inspect pane (combined with .card outer wrapper for the
15948
+ * renderAgentInspectPanel return-shape regex anchored in
15949
+ * dashboard-welcome.test.ts:152). The .inspect-pane modifier overrides
15950
+ * .card padding so internal sections control their own spacing.
15951
+ */
15952
+ .inspect-pane {
15953
+ padding: 0;
15954
+ display: flex; flex-direction: column;
15955
+ position: sticky;
15956
+ top: 20px;
15957
+ max-height: calc(100vh - 100px);
15958
+ overflow: hidden;
15959
+ }
15960
+ .inspect-head {
15961
+ padding: 16px 18px;
15962
+ border-bottom: 1px solid var(--rule);
15963
+ display: flex; flex-direction: column; gap: 10px;
15964
+ }
15965
+ .inspect-head .row1 {
15966
+ display: flex; align-items: center; gap: 10px;
15967
+ }
15968
+ .inspect-head h3 {
15969
+ font-family: var(--serif); font-weight: 500;
15970
+ font-size: 17px; margin: 0;
15971
+ }
15972
+ .inspect-head .meta {
15973
+ display: flex; gap: 6px; flex-wrap: wrap;
15974
+ }
15975
+ .inspect-body {
15976
+ overflow-y: auto;
15977
+ padding: 4px 18px 18px;
15978
+ }
15979
+ .inspect-section {
15980
+ padding: 14px 0;
15981
+ border-bottom: 1px solid var(--rule);
15982
+ }
15983
+ .inspect-section:last-child { border-bottom: 0; }
15984
+ .inspect-section h4 {
15985
+ font-family: var(--mono);
15986
+ font-size: 10px;
15987
+ letter-spacing: 0.08em;
15988
+ text-transform: uppercase;
15989
+ color: var(--ink-3);
15990
+ margin: 0 0 10px;
15991
+ display: flex; align-items: center; justify-content: space-between;
15992
+ }
15993
+ .inspect-section h4 .count {
15994
+ font-family: var(--mono);
15995
+ background: var(--paper-3);
15996
+ border-radius: 999px;
15997
+ padding: 1px 7px;
15998
+ color: var(--ink-2);
15999
+ font-size: 10px;
16000
+ }
16001
+ .approval-row {
16002
+ background: var(--ochre-bg);
16003
+ border: 1px solid var(--ochre);
16004
+ border-radius: var(--rad);
16005
+ padding: 10px 12px;
16006
+ margin-bottom: 8px;
16007
+ display: flex; flex-direction: column; gap: 8px;
16008
+ }
16009
+ .approval-row .what { font-size: var(--text-base); color: var(--ink); }
16010
+ .approval-row .what .pill { margin-right: 6px; }
16011
+ .approval-row .why {
16012
+ font-size: var(--text-sm); color: var(--ink-2);
16013
+ padding-left: 10px;
16014
+ border-left: 2px solid var(--ochre);
16015
+ }
16016
+ .approval-row .actions { display: flex; gap: 6px; justify-content: flex-end; }
16017
+ .timeline {
16018
+ display: flex; flex-direction: column; gap: 0;
16019
+ position: relative;
16020
+ padding-left: 14px;
16021
+ }
16022
+ .timeline::before {
16023
+ content: "";
16024
+ position: absolute;
16025
+ left: 4px; top: 6px; bottom: 6px;
16026
+ width: 1px;
16027
+ background: var(--rule);
16028
+ }
16029
+ .timeline-item {
16030
+ position: relative;
16031
+ padding: 6px 0 10px;
16032
+ font-size: var(--text-sm);
16033
+ }
16034
+ .timeline-item::before {
16035
+ content: "";
16036
+ position: absolute;
16037
+ left: -14px; top: 11px;
16038
+ width: 8px; height: 8px;
16039
+ border-radius: 50%;
16040
+ background: var(--surface);
16041
+ border: 1.5px solid var(--ink-4);
16042
+ }
16043
+ .timeline-item.ok::before { border-color: var(--sage); }
16044
+ .timeline-item.warn::before { border-color: var(--ochre); }
16045
+ .timeline-item.fail::before { border-color: var(--rust); }
16046
+ .timeline-item .ts {
16047
+ font-family: var(--mono); font-size: 10px;
16048
+ color: var(--ink-3);
16049
+ letter-spacing: 0.02em;
16050
+ }
16051
+ .timeline-item .what {
16052
+ margin-top: 2px; color: var(--ink); font-size: var(--text-base);
16053
+ }
16054
+ .timeline-item .att {
16055
+ margin-top: 4px;
16056
+ display: inline-flex;
16057
+ }
16058
+ .policy-line {
16059
+ display: flex; justify-content: space-between; align-items: center;
16060
+ padding: 5px 0; font-size: var(--text-base);
16061
+ border-bottom: 1px dashed var(--rule);
16062
+ }
16063
+ .policy-line:last-child { border-bottom: 0; }
16064
+ .policy-line .k { color: var(--ink-3); }
16065
+ .policy-line .v { font-family: var(--mono); font-size: var(--text-sm); color: var(--ink); }
16066
+ /* Empty-state block for when no agents are wrapped. The
16067
+ * renderAgentsList empty-state branch begins with the literal
16068
+ * '<h1>Agents</h1>' (regex-pinned in agents-empty-state-canary.test.ts)
16069
+ * and the "No wrapped agents yet." copy is preserved verbatim.
16070
+ */
16071
+ .agents-empty {
16072
+ background: var(--surface);
16073
+ border: 1px dashed var(--rule-2);
16074
+ border-radius: var(--rad-lg);
16075
+ padding: 56px 40px;
16076
+ text-align: center;
16077
+ max-width: 720px;
16078
+ margin: 32px auto;
16079
+ }
16080
+ .agents-empty .icon-frame {
16081
+ width: 64px; height: 64px;
16082
+ margin: 0 auto 18px;
16083
+ border: 1px solid var(--rule);
16084
+ border-radius: 50%;
16085
+ display: grid; place-items: center;
16086
+ position: relative;
16087
+ }
16088
+ .agents-empty .icon-frame::before,
16089
+ .agents-empty .icon-frame::after {
16090
+ content: "";
16091
+ position: absolute;
16092
+ border: 1px solid var(--rule);
16093
+ border-radius: 50%;
16094
+ }
16095
+ .agents-empty .icon-frame::before { inset: -8px; opacity: 0.6; }
16096
+ .agents-empty .icon-frame::after { inset: -16px; opacity: 0.3; }
16097
+ .agents-empty .icon-frame .core {
16098
+ width: 22px; height: 22px;
16099
+ background: var(--ink);
16100
+ border-radius: 50%;
16101
+ }
16102
+ .agents-empty h2 {
16103
+ font-family: var(--serif);
16104
+ font-weight: 400;
16105
+ font-size: var(--text-xl);
16106
+ margin: 0 0 8px;
16107
+ }
16108
+ .agents-empty p {
16109
+ color: var(--ink-3);
16110
+ margin: 0 0 20px;
16111
+ font-size: var(--text-md);
16112
+ line-height: 1.55;
16113
+ max-width: 50ch;
16114
+ margin-left: auto; margin-right: auto;
16115
+ }
16116
+ .terminal-block {
16117
+ text-align: left;
16118
+ background: var(--paper-3);
16119
+ border: 1px solid var(--rule);
16120
+ border-radius: var(--rad);
16121
+ padding: 14px 16px;
16122
+ font-family: var(--mono);
16123
+ font-size: var(--text-base);
16124
+ margin: 0 auto 16px;
16125
+ max-width: 480px;
16126
+ display: flex; align-items: center; justify-content: space-between;
16127
+ }
16128
+ .terminal-block .cmd { color: var(--ink); }
16129
+ .terminal-block .cmd .prompt { color: var(--ink-3); margin-right: 8px; user-select: none; }
16130
+ .copy-btn {
16131
+ background: transparent; border: 0;
16132
+ color: var(--ink-3); cursor: pointer;
16133
+ font-family: var(--mono); font-size: var(--text-xs);
16134
+ padding: 2px 6px;
16135
+ border-radius: var(--rad);
16136
+ }
16137
+ .copy-btn:hover { color: var(--ink); background: var(--paper-2); }
16138
+
16139
+ /* Surface 5. Attestation badge gallery. */
16140
+ .att-gallery {
16141
+ display: flex; flex-direction: column; gap: 24px;
16142
+ max-width: 1000px;
16143
+ margin: 0 auto;
16144
+ }
16145
+ .att-section {
16146
+ background: var(--surface);
16147
+ border: 1px solid var(--rule);
16148
+ border-radius: var(--rad-lg);
16149
+ padding: 22px 24px;
16150
+ }
16151
+ .att-section-head {
16152
+ display: flex; justify-content: space-between; align-items: baseline;
16153
+ gap: 12px;
16154
+ margin-bottom: 16px;
16155
+ padding-bottom: 12px;
16156
+ border-bottom: 1px solid var(--rule);
16157
+ }
16158
+ .att-section-head h2 {
16159
+ font-family: var(--serif);
16160
+ font-weight: 400;
16161
+ font-size: 19px;
16162
+ margin: 0 0 4px;
16163
+ }
16164
+ .att-section-head p {
16165
+ color: var(--ink-3);
16166
+ margin: 0;
16167
+ font-size: 13px;
16168
+ line-height: 1.5;
16169
+ max-width: 64ch;
16170
+ }
16171
+ .att-section-head .label {
16172
+ font-family: var(--mono);
16173
+ font-size: 10px;
16174
+ letter-spacing: 0.08em;
16175
+ text-transform: uppercase;
16176
+ color: var(--ink-3);
16177
+ }
16178
+ .att-row {
16179
+ display: grid;
16180
+ grid-template-columns: 240px 1fr;
16181
+ gap: 24px;
16182
+ padding: 14px 0;
16183
+ border-bottom: 1px dashed var(--rule);
16184
+ align-items: center;
16185
+ }
16186
+ .att-row:last-child { border-bottom: 0; }
16187
+ .att-row .demo {
16188
+ display: flex; align-items: center; justify-content: flex-start;
16189
+ padding: 12px 16px;
16190
+ background: var(--paper-2);
16191
+ border: 1px solid var(--rule);
16192
+ border-radius: var(--rad);
16193
+ min-height: 56px;
16194
+ }
16195
+ .att-row .desc strong {
16196
+ font-size: 13px; display: block; margin-bottom: 3px;
16197
+ }
16198
+ .att-row .desc small {
16199
+ color: var(--ink-3); font-size: 12px;
16200
+ line-height: 1.5;
16201
+ }
16202
+
16203
+ /* Global persistent badge. Lives in the topbar across every surface. */
16204
+ .att-global {
16205
+ display: inline-flex; align-items: center;
16206
+ gap: 8px;
16207
+ padding: 4px 10px 4px 6px;
16208
+ border: 1px solid var(--rule);
16209
+ border-radius: 999px;
16210
+ background: var(--surface-2);
16211
+ font-family: var(--mono);
16212
+ font-size: 11px;
16213
+ color: var(--ink-2);
16214
+ }
16215
+ .att-global.verified { border-color: var(--sage); background: var(--sage-bg); color: var(--sage); }
16216
+ .att-global.degraded { border-color: var(--ochre); background: var(--ochre-bg); color: var(--ochre); }
16217
+ .att-global.unverified { border-color: var(--rust); background: var(--rust-bg); color: var(--rust); }
16218
+ .att-global .seal {
16219
+ width: 18px; height: 18px;
16220
+ position: relative;
16221
+ flex-shrink: 0;
16222
+ }
16223
+ .att-global .seal-ring {
16224
+ position: absolute; inset: 0;
16225
+ border: 1.5px solid currentColor;
16226
+ border-radius: 50%;
16227
+ }
16228
+ .att-global .seal-ring.dashed { border-style: dashed; }
16229
+ .att-global .seal-core {
16230
+ position: absolute; inset: 4px;
16231
+ background: currentColor;
16232
+ border-radius: 50%;
16233
+ opacity: 0.85;
16234
+ }
16235
+ .att-global.degraded .seal-core { background: transparent; border: 1px solid currentColor; }
16236
+ .att-global.unverified .seal-core {
16237
+ background: transparent;
16238
+ border: 1px solid currentColor;
15129
16239
  }
16240
+ .att-global.unverified .seal-core::after {
16241
+ content: ""; position: absolute; inset: 0;
16242
+ background: currentColor; opacity: 0.4;
16243
+ clip-path: polygon(0 0, 100% 100%, 100% 90%, 10% 0);
16244
+ }
16245
+ .att-global .label {
16246
+ font-family: var(--mono);
16247
+ font-size: 11px;
16248
+ letter-spacing: 0.02em;
16249
+ text-transform: uppercase;
16250
+ }
16251
+ .att-global .hash {
16252
+ font-family: var(--mono);
16253
+ font-size: 10px;
16254
+ opacity: 0.7;
16255
+ border-left: 1px solid currentColor;
16256
+ padding-left: 8px;
16257
+ margin-left: 2px;
16258
+ }
16259
+
16260
+ /* Per-agent badge. Square chip beside each agent. */
16261
+ .att-agent {
16262
+ display: inline-flex; align-items: center;
16263
+ gap: 6px;
16264
+ padding: 3px 7px;
16265
+ border-radius: var(--rad);
16266
+ border: 1px solid var(--rule);
16267
+ background: var(--surface);
16268
+ font-family: var(--mono);
16269
+ font-size: 10px;
16270
+ color: var(--ink-2);
16271
+ }
16272
+ .att-agent .mark {
16273
+ width: 10px; height: 10px;
16274
+ border: 1.5px solid currentColor;
16275
+ border-radius: 2px;
16276
+ position: relative;
16277
+ }
16278
+ .att-agent.verified { color: var(--sage); border-color: var(--sage); background: var(--sage-bg); }
16279
+ .att-agent.verified .mark { background: currentColor; }
16280
+ .att-agent.degraded { color: var(--ochre); border-color: var(--ochre); background: var(--ochre-bg); }
16281
+ .att-agent.unverified { color: var(--rust); border-color: var(--rust); background: var(--rust-bg); }
16282
+ .att-agent.unverified .mark {
16283
+ background: repeating-linear-gradient(
16284
+ 45deg, currentColor, currentColor 1px,
16285
+ transparent 1px, transparent 3px
16286
+ );
16287
+ }
16288
+
16289
+ /* Per-action badge. Tiny inline tick on timeline rows. */
16290
+ .att-action {
16291
+ display: inline-flex; align-items: center; gap: 4px;
16292
+ font-family: var(--mono);
16293
+ font-size: 10px;
16294
+ color: var(--ink-3);
16295
+ padding: 1px 6px;
16296
+ border-radius: 4px;
16297
+ background: var(--paper-3);
16298
+ border: 1px solid transparent;
16299
+ }
16300
+ .att-action .tick {
16301
+ width: 6px; height: 6px;
16302
+ border-radius: 1px;
16303
+ background: currentColor;
16304
+ }
16305
+ .att-action.verified { color: var(--sage); }
16306
+ .att-action.degraded { color: var(--ochre); }
16307
+ .att-action.unverified { color: var(--rust); }
16308
+ .att-action.neutral .tick { background: var(--ink-4); border-radius: 50%; }
16309
+
16310
+ /* Custody-provenance badge stub (v1.x). Visibly stubbed with dashed border. */
16311
+ .att-custody {
16312
+ display: inline-flex; align-items: center; gap: 8px;
16313
+ padding: 4px 10px 4px 6px;
16314
+ border-radius: var(--rad);
16315
+ border: 1px dashed var(--rule-2);
16316
+ background: var(--paper-3);
16317
+ color: var(--ink-3);
16318
+ font-family: var(--mono);
16319
+ font-size: 10px;
16320
+ }
16321
+ .att-custody .seal-stub {
16322
+ width: 16px; height: 16px;
16323
+ border: 1px dashed var(--ink-4);
16324
+ border-radius: 50%;
16325
+ position: relative;
16326
+ flex-shrink: 0;
16327
+ }
16328
+ .att-custody .seal-stub::after {
16329
+ content: ""; position: absolute; inset: 4px;
16330
+ border: 1px dashed var(--ink-4);
16331
+ border-radius: 50%;
16332
+ }
16333
+ .att-custody .stub-tag {
16334
+ letter-spacing: 0.06em;
16335
+ text-transform: uppercase;
16336
+ }
16337
+
16338
+ /* Tooltip surface for badges. */
16339
+ .att-tooltip {
16340
+ background: var(--ink);
16341
+ color: var(--paper);
16342
+ font-family: var(--mono);
16343
+ font-size: 11px;
16344
+ padding: 8px 10px;
16345
+ border-radius: var(--rad);
16346
+ max-width: 280px;
16347
+ line-height: 1.5;
16348
+ display: inline-block;
16349
+ }
16350
+ [data-theme="dark"] .att-tooltip {
16351
+ background: var(--paper-3);
16352
+ color: var(--ink);
16353
+ }
16354
+
15130
16355
  @media (max-width: 1100px) {
15131
16356
  .app, .app.route-full { grid-template-columns: 56px 1fr; grid-template-areas: "sidebar topbar" "sidebar main"; }
15132
16357
  .fortress { display: none; }
15133
16358
  .sidebar h1, .sidebar nav a span { display: none; }
16359
+ .sidebar nav a { justify-content: center; padding: 8px 6px; }
15134
16360
  .template-grid { grid-template-columns: 1fr; }
15135
16361
  .policy-center h1 { font-size: 30px; }
15136
16362
  .intel-center h1 { font-size: 30px; }
15137
16363
  .intel-row { grid-template-columns: 1fr; }
16364
+ .intel-grid { grid-template-columns: 1fr; }
16365
+ .intel-failure-row { grid-template-columns: 1fr; }
16366
+ .agents-layout { grid-template-columns: 1fr; }
16367
+ .agents-list-head, .agents-list .agent-row { grid-template-columns: minmax(0, 1fr) 90px 90px; }
16368
+ .agents-list-head span:nth-child(4), .agents-list .agent-row > .agent-last { display: none; }
16369
+ .inspect-pane { position: static; max-height: none; }
15138
16370
  }
15139
16371
  `;
15140
16372
  NAV_ITEMS = [
@@ -15142,11 +16374,26 @@ body {
15142
16374
  { id: "agents", label: "Agents" },
15143
16375
  { id: "policy", label: "Policy" },
15144
16376
  { id: "intelligence", label: "Intelligence" },
16377
+ { id: "attestation", label: "Attestation" },
15145
16378
  { id: "privacy", label: "Privacy" },
15146
16379
  { id: "coordination", label: "Coordination" },
15147
16380
  { id: "health", label: "Health" },
15148
16381
  { id: "exit-drill", label: "Exit drill" }
15149
16382
  ];
16383
+ NAV_ICON_PATHS = {
16384
+ dashboard: '<rect x="2" y="2" width="5" height="5" rx="1"/><rect x="9" y="2" width="5" height="5" rx="1"/><rect x="2" y="9" width="5" height="5" rx="1"/><rect x="9" y="9" width="5" height="5" rx="1"/>',
16385
+ agents: '<circle cx="6" cy="5.5" r="2.3"/><path d="M2 13.5c0-2.2 1.8-4 4-4s4 1.8 4 4"/><circle cx="11.5" cy="6" r="1.8"/><path d="M14 12.5c0-1.7-1.1-2.7-2.5-3"/>',
16386
+ policy: '<path d="M4 2h5l3 3v9H4z"/><path d="M9 2v3h3"/><path d="M6 9h4M6 11.5h4"/>',
16387
+ intelligence: '<rect x="3.5" y="3.5" width="9" height="9" rx="0.5"/><rect x="6" y="6" width="4" height="4"/><path d="M6 1.5v2M10 1.5v2M6 12.5v2M10 12.5v2M1.5 6h2M1.5 10h2M12.5 6h2M12.5 10h2"/>',
16388
+ attestation: '<circle cx="8" cy="8" r="5.5"/><circle cx="8" cy="8" r="2.2"/><path d="M8 1.5v1.5M8 13v1.5M1.5 8h1.5M13 8h1.5"/>',
16389
+ privacy: '<path d="M8 1.5L3 3v4.5c0 3 2.2 5.4 5 7 2.8-1.6 5-4 5-7V3z"/><path d="M6 8l1.5 1.5L10.5 6"/>',
16390
+ coordination: '<circle cx="4" cy="3.5" r="1.4"/><circle cx="4" cy="12.5" r="1.4"/><circle cx="12" cy="8" r="1.4"/><path d="M4 4.9V11.1M4 5c0 3 3 3 6.7 3"/>',
16391
+ health: '<path d="M2 8h2.5l1.5-4 3 8 1.5-4H14"/>',
16392
+ "exit-drill": '<path d="M9.5 2H3v12h6.5"/><path d="M11 5l3 3-3 3"/><path d="M14 8H6.5"/>'
16393
+ };
16394
+ SVG_OPEN = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">';
16395
+ THEME_ICON_MOON = SVG_OPEN + '<path d="M13 9.5A5.5 5.5 0 0 1 6.5 3 5.5 5.5 0 1 0 13 9.5z"/></svg>';
16396
+ THEME_ICON_SUN = SVG_OPEN + '<circle cx="8" cy="8" r="2.5"/><path d="M8 1.5v2M8 12.5v2M1.5 8h2M12.5 8h2M3.2 3.2l1.4 1.4M11.4 11.4l1.4 1.4M3.2 12.8l1.4-1.4M11.4 4.6l1.4-1.4"/></svg>';
15150
16397
  }
15151
16398
  });
15152
16399
 
@@ -15855,7 +17102,7 @@ var init_dashboard = __esm({
15855
17102
  server = http.createServer(handler);
15856
17103
  }
15857
17104
  this.httpServer = server;
15858
- return new Promise((resolve5, reject) => {
17105
+ return new Promise((resolve6, reject) => {
15859
17106
  const protocol = this.useTLS ? "https" : "http";
15860
17107
  const baseUrl = `${protocol}://${this.config.host}:${this.config.port}`;
15861
17108
  server.listen(this.config.port, this.config.host, () => {
@@ -15880,7 +17127,7 @@ var init_dashboard = __esm({
15880
17127
  if (shouldAutoOpen) {
15881
17128
  this.openInBrowser(sessionUrl);
15882
17129
  }
15883
- resolve5();
17130
+ resolve6();
15884
17131
  });
15885
17132
  server.on("error", (err) => {
15886
17133
  if (err.code === "EADDRINUSE") {
@@ -15926,8 +17173,8 @@ var init_dashboard = __esm({
15926
17173
  }
15927
17174
  this.rateLimits.clear();
15928
17175
  if (this.httpServer) {
15929
- return new Promise((resolve5) => {
15930
- this.httpServer.close(() => resolve5());
17176
+ return new Promise((resolve6) => {
17177
+ this.httpServer.close(() => resolve6());
15931
17178
  });
15932
17179
  }
15933
17180
  }
@@ -15941,7 +17188,7 @@ var init_dashboard = __esm({
15941
17188
  `[Sanctuary] Approval required: ${request.operation} (Tier ${request.tier}) \u2014 open dashboard to respond
15942
17189
  `
15943
17190
  );
15944
- return new Promise((resolve5) => {
17191
+ return new Promise((resolve6) => {
15945
17192
  const timer = setTimeout(() => {
15946
17193
  this.pending.delete(id);
15947
17194
  const response = {
@@ -15955,12 +17202,12 @@ var init_dashboard = __esm({
15955
17202
  decision: response.decision,
15956
17203
  decided_by: "timeout"
15957
17204
  });
15958
- resolve5(response);
17205
+ resolve6(response);
15959
17206
  }, this.config.timeout_seconds * 1e3);
15960
17207
  const pending = {
15961
17208
  id,
15962
17209
  request,
15963
- resolve: resolve5,
17210
+ resolve: resolve6,
15964
17211
  timer,
15965
17212
  created_at: (/* @__PURE__ */ new Date()).toISOString()
15966
17213
  };
@@ -16826,7 +18073,7 @@ var init_webhook = __esm({
16826
18073
  * Start the callback listener server.
16827
18074
  */
16828
18075
  async start() {
16829
- return new Promise((resolve5, reject) => {
18076
+ return new Promise((resolve6, reject) => {
16830
18077
  this.callbackServer = http.createServer(
16831
18078
  (req, res) => this.handleCallback(req, res)
16832
18079
  );
@@ -16841,7 +18088,7 @@ var init_webhook = __esm({
16841
18088
 
16842
18089
  `
16843
18090
  );
16844
- resolve5();
18091
+ resolve6();
16845
18092
  }
16846
18093
  );
16847
18094
  this.callbackServer.on("error", reject);
@@ -16861,8 +18108,8 @@ var init_webhook = __esm({
16861
18108
  }
16862
18109
  this.pending.clear();
16863
18110
  if (this.callbackServer) {
16864
- return new Promise((resolve5) => {
16865
- this.callbackServer.close(() => resolve5());
18111
+ return new Promise((resolve6) => {
18112
+ this.callbackServer.close(() => resolve6());
16866
18113
  });
16867
18114
  }
16868
18115
  }
@@ -16875,7 +18122,7 @@ var init_webhook = __esm({
16875
18122
  `[Sanctuary] Webhook approval sent: ${request.operation} (Tier ${request.tier}) \u2014 awaiting callback
16876
18123
  `
16877
18124
  );
16878
- return new Promise((resolve5) => {
18125
+ return new Promise((resolve6) => {
16879
18126
  const timer = setTimeout(() => {
16880
18127
  this.pending.delete(id);
16881
18128
  const response = {
@@ -16884,12 +18131,12 @@ var init_webhook = __esm({
16884
18131
  decided_at: (/* @__PURE__ */ new Date()).toISOString(),
16885
18132
  decided_by: "timeout"
16886
18133
  };
16887
- resolve5(response);
18134
+ resolve6(response);
16888
18135
  }, this.config.timeout_seconds * 1e3);
16889
18136
  const pending = {
16890
18137
  id,
16891
18138
  request,
16892
- resolve: resolve5,
18139
+ resolve: resolve6,
16893
18140
  timer,
16894
18141
  created_at: (/* @__PURE__ */ new Date()).toISOString()
16895
18142
  };
@@ -18554,8 +19801,8 @@ function verifySHR(shr, now) {
18554
19801
  const errors = [];
18555
19802
  const warnings = [];
18556
19803
  const currentTime = /* @__PURE__ */ new Date();
18557
- if (!shr.body || !shr.signed_by || !shr.signature) {
18558
- errors.push("Missing required SHR fields (body, signed_by, or signature)");
19804
+ if (!shr.body || !shr.signed_by || !shr.signature || !shr.signature_scheme) {
19805
+ errors.push("Missing required SHR fields (body, signed_by, signature_scheme, or signature)");
18559
19806
  return {
18560
19807
  valid: false,
18561
19808
  errors,
@@ -18568,6 +19815,9 @@ function verifySHR(shr, now) {
18568
19815
  if (shr.body.shr_version !== "1.0") {
18569
19816
  errors.push(`Unsupported SHR version: ${shr.body.shr_version}`);
18570
19817
  }
19818
+ if (shr.signature_scheme !== SIGNATURE_SCHEME_V1) {
19819
+ errors.push(`Unsupported SHR signature_scheme: ${String(shr.signature_scheme)}`);
19820
+ }
18571
19821
  const expiresAt = new Date(shr.body.expires_at);
18572
19822
  if (isNaN(expiresAt.getTime())) {
18573
19823
  errors.push("Invalid expires_at timestamp");
@@ -18629,6 +19879,7 @@ var init_verifier = __esm({
18629
19879
  init_types();
18630
19880
  init_identity();
18631
19881
  init_encoding();
19882
+ init_constants();
18632
19883
  }
18633
19884
  });
18634
19885
 
@@ -23588,7 +24839,7 @@ async function runOpenAIPrivacyFilter(text, config) {
23588
24839
  return parsed;
23589
24840
  }
23590
24841
  function runCommand(command, input, timeoutMs) {
23591
- return new Promise((resolve5, reject) => {
24842
+ return new Promise((resolve6, reject) => {
23592
24843
  const child = child_process.spawn(command, [], {
23593
24844
  stdio: ["pipe", "pipe", "pipe"],
23594
24845
  shell: false
@@ -23619,7 +24870,7 @@ function runCommand(command, input, timeoutMs) {
23619
24870
  ));
23620
24871
  return;
23621
24872
  }
23622
- resolve5(stdout);
24873
+ resolve6(stdout);
23623
24874
  });
23624
24875
  child.stdin.end(input);
23625
24876
  });
@@ -25495,13 +26746,13 @@ var init_proxy_router = __esm({
25495
26746
  * Call an upstream tool with a timeout.
25496
26747
  */
25497
26748
  async callWithTimeout(serverName, toolName, args, timeoutMs) {
25498
- return new Promise((resolve5, reject) => {
26749
+ return new Promise((resolve6, reject) => {
25499
26750
  const timer = setTimeout(() => {
25500
26751
  reject(new Error(`Upstream tool call timed out after ${timeoutMs}ms`));
25501
26752
  }, timeoutMs);
25502
26753
  this.clientManager.callTool(serverName, toolName, args).then((result) => {
25503
26754
  clearTimeout(timer);
25504
- resolve5(result);
26755
+ resolve6(result);
25505
26756
  }).catch((err) => {
25506
26757
  clearTimeout(timer);
25507
26758
  reject(err);
@@ -30006,8 +31257,6 @@ var init_inbox_aggregator = __esm({
30006
31257
  "src/hub/inbox-aggregator.ts"() {
30007
31258
  }
30008
31259
  });
30009
-
30010
- // src/hub/activity-feed.ts
30011
31260
  function categorizeOperation(layer, operation) {
30012
31261
  const op = operation.toLowerCase();
30013
31262
  if (op === "privacy_event" || op.startsWith("privacy_")) return "privacy";
@@ -30058,18 +31307,30 @@ function extractAgentIdHint(entry) {
30058
31307
  const value = details.agent_id;
30059
31308
  return typeof value === "string" && value.length > 0 ? value : void 0;
30060
31309
  }
31310
+ function deriveAttestationFragment(entryId) {
31311
+ const hash2 = crypto.createHash("sha256").update(entryId).digest("hex");
31312
+ return `${hash2.slice(0, 4)}..${hash2.slice(4, 6)}`;
31313
+ }
31314
+ function deriveAttestationState(entry) {
31315
+ return entry.result === "success" ? "verified" : "degraded";
31316
+ }
30061
31317
  function projectEntry(entry) {
30062
31318
  const agentIdHint = extractAgentIdHint(entry);
30063
31319
  const category = categorizeOperation(entry.layer, entry.operation);
31320
+ const entryId = `${entry.timestamp}|${entry.operation}|${entry.identity_id}`;
30064
31321
  return {
30065
31322
  version: "1.1",
30066
- entry_id: `${entry.timestamp}|${entry.operation}|${entry.identity_id}`,
31323
+ entry_id: entryId,
30067
31324
  emitted_at: entry.timestamp,
30068
31325
  ...agentIdHint ? { agent_id: agentIdHint } : {},
30069
31326
  identity_id: entry.identity_id,
30070
31327
  category,
30071
31328
  display_template_id: templateIdFor(category, entry.operation),
30072
- display_template_args: buildTemplateArgs(entry, agentIdHint)
31329
+ display_template_args: buildTemplateArgs(entry, agentIdHint),
31330
+ attestation: {
31331
+ state: deriveAttestationState(entry),
31332
+ fragment: deriveAttestationFragment(entryId)
31333
+ }
30073
31334
  };
30074
31335
  }
30075
31336
  async function aggregateActivity(sources, filter = {}) {
@@ -30134,7 +31395,7 @@ function snapshotForRecord(record, openInboxItemIds) {
30134
31395
  var HubService;
30135
31396
  var init_hub_service = __esm({
30136
31397
  "src/hub/hub-service.ts"() {
30137
- init_constants();
31398
+ init_constants2();
30138
31399
  init_channel_templates();
30139
31400
  init_constants3();
30140
31401
  init_errors4();
@@ -33047,10 +34308,10 @@ var init_memory = __esm({
33047
34308
  });
33048
34309
 
33049
34310
  // src/contracts/v1.1/constants.ts
33050
- var SIGNATURE_SCHEME_V1, EXIT_BUNDLE_MANIFEST_VERSION, EXIT_BUNDLE_ARTIFACT_KINDS;
34311
+ var SIGNATURE_SCHEME_V12, EXIT_BUNDLE_MANIFEST_VERSION, EXIT_BUNDLE_ARTIFACT_KINDS;
33051
34312
  var init_constants4 = __esm({
33052
34313
  "src/contracts/v1.1/constants.ts"() {
33053
- SIGNATURE_SCHEME_V1 = "ed25519-v1";
34314
+ SIGNATURE_SCHEME_V12 = "ed25519-v1";
33054
34315
  EXIT_BUNDLE_MANIFEST_VERSION = "SANCTUARY_EXIT_BUNDLE_V1";
33055
34316
  EXIT_BUNDLE_ARTIFACT_KINDS = [
33056
34317
  "public_identity",
@@ -33243,7 +34504,7 @@ function verifyReputationArtifact(reputationArtifact, publicKeysByDid) {
33243
34504
  unverifiable_attestations: unverifiable
33244
34505
  };
33245
34506
  }
33246
- async function verifyExitBundle(bundleDir) {
34507
+ async function verifyExitBundle(bundleDir, options = {}) {
33247
34508
  const root = path.resolve(bundleDir);
33248
34509
  let manifest;
33249
34510
  let manifestBytes;
@@ -33252,7 +34513,9 @@ async function verifyExitBundle(bundleDir) {
33252
34513
  manifestBytes = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
33253
34514
  manifest = JSON.parse(Buffer.from(raw).toString("utf8"));
33254
34515
  } catch {
33255
- return fail(root, null, "other", ["manifest.json is missing or unreadable"]);
34516
+ throw new InvalidExitBundleError(
34517
+ `Not a valid SANCTUARY_EXIT_BUNDLE_V1 directory: manifest.json missing at ${path.join(root, "manifest.json")}`
34518
+ );
33256
34519
  }
33257
34520
  const warnings = [];
33258
34521
  const unsupportedArtifacts = [];
@@ -33260,7 +34523,7 @@ async function verifyExitBundle(bundleDir) {
33260
34523
  if (!body || body.manifest_version !== EXIT_BUNDLE_MANIFEST_VERSION) {
33261
34524
  return fail(root, manifest, "manifest_unknown_version", warnings, unsupportedArtifacts);
33262
34525
  }
33263
- if (body.signature_scheme !== SIGNATURE_SCHEME_V1) {
34526
+ if (body.signature_scheme !== SIGNATURE_SCHEME_V12) {
33264
34527
  return fail(
33265
34528
  root,
33266
34529
  manifest,
@@ -33420,9 +34683,16 @@ async function verifyExitBundle(bundleDir) {
33420
34683
  }
33421
34684
  const reputationFailed = reputation?.bundle_signature_valid === false || (reputation?.invalid_attestations ?? 0) > 0;
33422
34685
  const identityFailed = identity ? !identity.signature_valid : false;
34686
+ const unverifiableCount = reputation?.unverifiable_attestations ?? 0;
34687
+ const unverifiableFailed = unverifiableCount > 0 && !options.acceptUnverifiableAttestations;
34688
+ if (unverifiableFailed) {
34689
+ warnings.push(
34690
+ `${unverifiableCount} reputation attestation(s) have unknown signer public keys; pass --accept-unverifiable-attestations to import anyway`
34691
+ );
34692
+ }
33423
34693
  return {
33424
34694
  version: "1.1",
33425
- passed: !reputationFailed && !identityFailed,
34695
+ passed: !reputationFailed && !identityFailed && !unverifiableFailed,
33426
34696
  verified_at: (/* @__PURE__ */ new Date()).toISOString(),
33427
34697
  manifest_path: path.join(root, "manifest.json"),
33428
34698
  manifest_hash: sha256Hex2(manifestBytes),
@@ -33439,10 +34709,10 @@ async function verifyExitBundle(bundleDir) {
33439
34709
  identity,
33440
34710
  audit,
33441
34711
  reputation,
33442
- failure_class: reputationFailed || identityFailed ? "other" : void 0
34712
+ failure_class: reputationFailed || identityFailed || unverifiableFailed ? "other" : void 0
33443
34713
  };
33444
34714
  }
33445
- var PRIVATE_MATERIAL_KEYS;
34715
+ var InvalidExitBundleError, PRIVATE_MATERIAL_KEYS;
33446
34716
  var init_verifier2 = __esm({
33447
34717
  "src/exit/verifier.ts"() {
33448
34718
  init_constants4();
@@ -33450,6 +34720,12 @@ var init_verifier2 = __esm({
33450
34720
  init_encoding();
33451
34721
  init_hashing();
33452
34722
  init_canonical_json();
34723
+ InvalidExitBundleError = class extends Error {
34724
+ constructor(message) {
34725
+ super(message);
34726
+ this.name = "InvalidExitBundleError";
34727
+ }
34728
+ };
33453
34729
  PRIVATE_MATERIAL_KEYS = /* @__PURE__ */ new Set([
33454
34730
  "private_key",
33455
34731
  "privatekey",
@@ -33755,7 +35031,7 @@ async function exportExitBundle(opts) {
33755
35031
  ),
33756
35032
  artifacts_aggregate_hash_alg: "sha256",
33757
35033
  export_approval_audit_id: exportApprovalAuditId,
33758
- signature_scheme: SIGNATURE_SCHEME_V1
35034
+ signature_scheme: SIGNATURE_SCHEME_V12
33759
35035
  };
33760
35036
  const signature = sign(
33761
35037
  canonicalizeToBytes(body),
@@ -33940,7 +35216,9 @@ async function stageArtifact(storage, namespace, key, value) {
33940
35216
  await storage.write(namespace, key, jsonBytes(value));
33941
35217
  }
33942
35218
  async function importExitBundle(opts) {
33943
- const verification = await verifyExitBundle(opts.bundleDir);
35219
+ const verification = await verifyExitBundle(opts.bundleDir, {
35220
+ acceptUnverifiableAttestations: opts.acceptUnverifiableAttestations
35221
+ });
33944
35222
  if (!verification.passed) {
33945
35223
  return {
33946
35224
  verified: false,
@@ -34036,6 +35314,23 @@ async function importExitBundle(opts) {
34036
35314
  unsupported_artifacts: verification.unsupported_artifacts
34037
35315
  };
34038
35316
  }
35317
+ if (conflicts.public_identity_exists && !opts.forceRebind) {
35318
+ throw new ExitBundleImportError(
35319
+ "IDENTITY_OVERWRITE_REFUSED",
35320
+ "Importing this bundle would overwrite an existing fortress public identity. Pass forceRebind: true (CLI: --force-rebind) to confirm explicit replacement."
35321
+ );
35322
+ }
35323
+ if (conflicts.public_identity_exists && opts.forceRebind && identityArtifact) {
35324
+ opts.auditLog.append(
35325
+ "l1",
35326
+ "exit_bundle_force_rebind",
35327
+ identityArtifact.json.bundle.identity_id,
35328
+ {
35329
+ manifest_version: manifest.body.manifest_version,
35330
+ fortress_id: manifest.body.identity_binding.fortress_id
35331
+ }
35332
+ );
35333
+ }
34039
35334
  const importId = importIdForManifest(manifest);
34040
35335
  const stagedArtifacts = [];
34041
35336
  if (identityArtifact) {
@@ -34146,7 +35441,7 @@ function exitBundleManifestShape() {
34146
35441
  manifest_version: EXIT_BUNDLE_MANIFEST_VERSION,
34147
35442
  artifacts: [...EXIT_BUNDLE_ARTIFACT_KINDS],
34148
35443
  hash_alg: "sha256",
34149
- signature_scheme: SIGNATURE_SCHEME_V1,
35444
+ signature_scheme: SIGNATURE_SCHEME_V12,
34150
35445
  required_top_level_file: "manifest.json",
34151
35446
  artifact_paths: [
34152
35447
  "artifacts/public_identity.json",
@@ -34159,7 +35454,7 @@ function exitBundleManifestShape() {
34159
35454
  ]
34160
35455
  };
34161
35456
  }
34162
- var ARTIFACT_DIR, EXIT_IMPORT_NAMESPACE, EXIT_PUBLIC_IDENTITIES_NAMESPACE, EXIT_AUDIT_RECEIPTS_NAMESPACE, EXIT_POLICY_SETS_NAMESPACE, EXIT_COMMITMENTS_NAMESPACE, EXIT_PLACEHOLDER_METADATA_NAMESPACE, PRIVACY_PLACEHOLDER_NAMESPACE;
35457
+ var ARTIFACT_DIR, EXIT_IMPORT_NAMESPACE, EXIT_PUBLIC_IDENTITIES_NAMESPACE, EXIT_AUDIT_RECEIPTS_NAMESPACE, EXIT_POLICY_SETS_NAMESPACE, EXIT_COMMITMENTS_NAMESPACE, EXIT_PLACEHOLDER_METADATA_NAMESPACE, PRIVACY_PLACEHOLDER_NAMESPACE, ExitBundleImportError;
34163
35458
  var init_bundle = __esm({
34164
35459
  "src/exit/bundle.ts"() {
34165
35460
  init_state_store();
@@ -34181,6 +35476,14 @@ var init_bundle = __esm({
34181
35476
  EXIT_COMMITMENTS_NAMESPACE = "_exit_commitments";
34182
35477
  EXIT_PLACEHOLDER_METADATA_NAMESPACE = "_exit_placeholder_metadata";
34183
35478
  PRIVACY_PLACEHOLDER_NAMESPACE = "_privacy_placeholder_vault";
35479
+ ExitBundleImportError = class extends Error {
35480
+ code;
35481
+ constructor(code, message) {
35482
+ super(message);
35483
+ this.name = "ExitBundleImportError";
35484
+ this.code = code;
35485
+ }
35486
+ };
34184
35487
  }
34185
35488
  });
34186
35489
  function write(stream, text) {
@@ -34279,6 +35582,11 @@ Options:
34279
35582
  --destination-identity-id <id> Destination signer for re-keyed state
34280
35583
  --state-namespace <name> Export a namespace; repeatable
34281
35584
  --conflict <skip|overwrite|version>
35585
+ --force-rebind On import: explicitly replace an existing fortress
35586
+ public identity (Tier 1 confirmation)
35587
+ --accept-unverifiable-attestations
35588
+ On import: accept reputation attestations whose
35589
+ signer DID is not in the bundle (Tier 1 confirmation)
34282
35590
  --json
34283
35591
  --yes, -y Explicit non-interactive Tier 1 approval
34284
35592
  --help, -h
@@ -34307,7 +35615,22 @@ async function runExitCommand(args) {
34307
35615
  write(err, "Usage: sanctuary exit verify <dir>\n");
34308
35616
  return 2;
34309
35617
  }
34310
- const result = await verifyExitBundle(dir);
35618
+ let result;
35619
+ try {
35620
+ result = await verifyExitBundle(dir, {
35621
+ acceptUnverifiableAttestations: hasFlag(
35622
+ argv,
35623
+ "--accept-unverifiable-attestations"
35624
+ )
35625
+ });
35626
+ } catch (e) {
35627
+ if (e instanceof InvalidExitBundleError) {
35628
+ write(err, `Error: ${e.message}
35629
+ `);
35630
+ return 1;
35631
+ }
35632
+ throw e;
35633
+ }
34311
35634
  if (json) {
34312
35635
  write(out, JSON.stringify(result, null, 2) + "\n");
34313
35636
  } else {
@@ -34385,9 +35708,15 @@ async function runExitCommand(args) {
34385
35708
  return 2;
34386
35709
  }
34387
35710
  const activate = hasFlag(argv, "--activate");
35711
+ const forceRebind = hasFlag(argv, "--force-rebind");
35712
+ const acceptUnverifiableAttestations = hasFlag(
35713
+ argv,
35714
+ "--accept-unverifiable-attestations"
35715
+ );
34388
35716
  if (activate) {
35717
+ const prompt2 = forceRebind ? "Tier 1 approval required: activate verified imported exit bundle AND replace the existing fortress public identity (force-rebind)?" : "Tier 1 approval required: activate verified imported exit bundle?";
34389
35718
  const approved = await confirmTier1(
34390
- "Tier 1 approval required: activate verified imported exit bundle?",
35719
+ prompt2,
34391
35720
  hasFlag(argv, "--yes") || hasFlag(argv, "-y"),
34392
35721
  stdin,
34393
35722
  err
@@ -34396,6 +35725,18 @@ async function runExitCommand(args) {
34396
35725
  write(err, "Aborted.\n");
34397
35726
  return 1;
34398
35727
  }
35728
+ if (acceptUnverifiableAttestations) {
35729
+ const acceptApproved = await confirmTier1(
35730
+ "Tier 1 approval required: accept unverifiable reputation attestations on import?",
35731
+ hasFlag(argv, "--yes") || hasFlag(argv, "-y"),
35732
+ stdin,
35733
+ err
35734
+ );
35735
+ if (!acceptApproved) {
35736
+ write(err, "Aborted.\n");
35737
+ return 1;
35738
+ }
35739
+ }
34399
35740
  }
34400
35741
  const ctx = await openExitContext(argv, env);
34401
35742
  const conflict = flagValue(argv, "--conflict") ?? "skip";
@@ -34403,19 +35744,31 @@ async function runExitCommand(args) {
34403
35744
  write(err, "--conflict must be skip, overwrite, or version\n");
34404
35745
  return 2;
34405
35746
  }
34406
- const result = await importExitBundle({
34407
- bundleDir: dir,
34408
- storage: ctx.storage,
34409
- masterKey: ctx.masterKey,
34410
- identityManager: ctx.identityManager,
34411
- auditLog: ctx.auditLog,
34412
- reputationStore: ctx.reputationStore,
34413
- activate,
34414
- conflictResolution: conflict,
34415
- sourcePassphrase: flagValue(argv, "--source-passphrase"),
34416
- sourceRecoveryKey: flagValue(argv, "--source-recovery-key"),
34417
- destinationSignerIdentityId: flagValue(argv, "--destination-identity-id")
34418
- });
35747
+ let result;
35748
+ try {
35749
+ result = await importExitBundle({
35750
+ bundleDir: dir,
35751
+ storage: ctx.storage,
35752
+ masterKey: ctx.masterKey,
35753
+ identityManager: ctx.identityManager,
35754
+ auditLog: ctx.auditLog,
35755
+ reputationStore: ctx.reputationStore,
35756
+ activate,
35757
+ forceRebind,
35758
+ acceptUnverifiableAttestations,
35759
+ conflictResolution: conflict,
35760
+ sourcePassphrase: flagValue(argv, "--source-passphrase"),
35761
+ sourceRecoveryKey: flagValue(argv, "--source-recovery-key"),
35762
+ destinationSignerIdentityId: flagValue(argv, "--destination-identity-id")
35763
+ });
35764
+ } catch (e) {
35765
+ if (e instanceof InvalidExitBundleError) {
35766
+ write(err, `Error: ${e.message}
35767
+ `);
35768
+ return 1;
35769
+ }
35770
+ throw e;
35771
+ }
34419
35772
  if (json) write(out, JSON.stringify(result, null, 2) + "\n");
34420
35773
  else {
34421
35774
  write(out, `verified: ${result.verified}
@@ -34470,6 +35823,7 @@ var init_cli = __esm({
34470
35823
  // src/exit/index.ts
34471
35824
  var exit_exports = {};
34472
35825
  __export(exit_exports, {
35826
+ ExitBundleImportError: () => ExitBundleImportError,
34473
35827
  exitBundleManifestShape: () => exitBundleManifestShape,
34474
35828
  exportExitBundle: () => exportExitBundle,
34475
35829
  importExitBundle: () => importExitBundle,
@@ -34526,11 +35880,11 @@ async function startDashboardServer(options) {
34526
35880
  }
34527
35881
  }
34528
35882
  });
34529
- await new Promise((resolve5, reject) => {
35883
+ await new Promise((resolve6, reject) => {
34530
35884
  server.once("error", reject);
34531
35885
  server.listen(port, host, () => {
34532
35886
  server.off("error", reject);
34533
- resolve5();
35887
+ resolve6();
34534
35888
  });
34535
35889
  });
34536
35890
  const actualPort = (() => {
@@ -34543,8 +35897,8 @@ async function startDashboardServer(options) {
34543
35897
  url,
34544
35898
  port: actualPort,
34545
35899
  host,
34546
- stop: () => new Promise((resolve5, reject) => {
34547
- server.close((err) => err ? reject(err) : resolve5());
35900
+ stop: () => new Promise((resolve6, reject) => {
35901
+ server.close((err) => err ? reject(err) : resolve6());
34548
35902
  }),
34549
35903
  publish,
34550
35904
  publishActivity: (entry) => publish({ type: "activity", data: entry }),
@@ -34692,7 +36046,7 @@ async function createSanctuaryServer(options) {
34692
36046
  const hasKeyParams = existingNamespaces.some((e) => e.key === "key-params");
34693
36047
  if (hasKeyParams) {
34694
36048
  throw new Error(
34695
- "Sanctuary: Found existing key derivation parameters but no recovery key hash.\nThis indicates a corrupted or incomplete installation.\nIf you previously used a passphrase, set SANCTUARY_PASSPHRASE to start."
36049
+ "Sanctuary: passphrase required.\n\nThe fortress at this path uses passphrase-mode key derivation.\nSet SANCTUARY_PASSPHRASE in your environment, or run\n'sanctuary export-passphrase' to retrieve it from the macOS Keychain."
34696
36050
  );
34697
36051
  }
34698
36052
  masterKey = generateRandomKey();
@@ -35217,7 +36571,7 @@ Refusing to start the cocoon while the reset-history marker is unreadable.`
35217
36571
  clientManager.configure(enabledServers).catch((err) => {
35218
36572
  console.error(`[Sanctuary] Failed to configure upstream servers: ${err instanceof Error ? err.message : "unknown error"}`);
35219
36573
  });
35220
- await new Promise((resolve5) => setTimeout(resolve5, 2e3));
36574
+ await new Promise((resolve6) => setTimeout(resolve6, 2e3));
35221
36575
  const proxiedTools = proxyRouter.getProxiedTools();
35222
36576
  if (proxiedTools.length > 0) {
35223
36577
  allTools.push(...proxiedTools);
@@ -36056,8 +37410,8 @@ async function runWrap(options, deps = {}) {
36056
37410
  passphraseValue = process.env.SANCTUARY_PASSPHRASE;
36057
37411
  } else {
36058
37412
  try {
36059
- const resolve5 = deps.resolvePassphrase ?? (() => getOrCreatePassphrase({ storagePath }));
36060
- const resolved = await resolve5();
37413
+ const resolve6 = deps.resolvePassphrase ?? (() => getOrCreatePassphrase({ storagePath }));
37414
+ const resolved = await resolve6();
36061
37415
  passphraseLocation = resolved.location;
36062
37416
  passphraseSource = resolved.source;
36063
37417
  passphraseValue = resolved.value;
@@ -36097,24 +37451,31 @@ async function runWrap(options, deps = {}) {
36097
37451
  }
36098
37452
  await promises.mkdir(storagePath, { recursive: true, mode: 448 });
36099
37453
  if (passphraseSource === "generated" && passphraseValue !== void 0) {
36100
- try {
36101
- await disclosePassphrase({
36102
- passphrase: passphraseValue,
36103
- storagePath,
36104
- fortressId: fortressIdFromStoragePath(storagePath),
36105
- // --no-open (CI / scripted) or non-TTY stdin both skip the prompt
36106
- // the same way init's --no-confirm does. Operator who scripted the
36107
- // call still gets the banner + the file; they will not see a hang.
36108
- mode: options.noOpen || process.stdin.isTTY !== true ? "no-confirm" : "interactive"
36109
- });
36110
- } catch (err) {
36111
- if (err instanceof PassphraseConfirmationDeclinedError || err instanceof PassphraseConfirmationNonInteractiveError) {
36112
- console.error(`
37454
+ if (options.writePassphraseBackup) {
37455
+ try {
37456
+ await disclosePassphrase({
37457
+ passphrase: passphraseValue,
37458
+ storagePath: path.dirname(options.writePassphraseBackup),
37459
+ fortressId: fortressIdFromStoragePath(storagePath),
37460
+ mode: options.noOpen || process.stdin.isTTY !== true ? "no-confirm" : "interactive"
37461
+ });
37462
+ } catch (err) {
37463
+ if (err instanceof PassphraseConfirmationDeclinedError || err instanceof PassphraseConfirmationNonInteractiveError) {
37464
+ console.error(`
36113
37465
  Sanctuary wrap: ${err.message}
36114
37466
  `);
36115
- process.exit(2);
37467
+ process.exit(2);
37468
+ }
37469
+ throw err;
36116
37470
  }
36117
- throw err;
37471
+ } else {
37472
+ process.stderr.write(
37473
+ `
37474
+ Passphrase stored in macOS Keychain.
37475
+ Run 'sanctuary export-passphrase' to retrieve it.
37476
+ To write a plaintext backup: sanctuary wrap ... --write-passphrase-backup <path>
37477
+ `
37478
+ );
36118
37479
  }
36119
37480
  }
36120
37481
  const profile = createWrapProfile(upstreamServers);
@@ -36205,6 +37566,8 @@ async function runWrap(options, deps = {}) {
36205
37566
  serverCount: upstreamServers.length});
36206
37567
  return;
36207
37568
  }
37569
+ let intelligenceHealthy;
37570
+ let intelligenceError;
36208
37571
  const authToken = generateAuthToken();
36209
37572
  const startFn = deps.startDashboard ?? ((opts) => startDashboard({
36210
37573
  port: opts.port,
@@ -36240,6 +37603,30 @@ async function runWrap(options, deps = {}) {
36240
37603
  );
36241
37604
  }
36242
37605
  const wrapAuditLog = new AuditLog(v11Storage, derived.key);
37606
+ try {
37607
+ const { IdentityManager: IdentityManager2 } = await Promise.resolve().then(() => (init_tools(), tools_exports));
37608
+ const { createIdentity: createIdentity2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
37609
+ const { derivePurposeKey: derivePurposeKey2 } = await Promise.resolve().then(() => (init_key_derivation(), key_derivation_exports));
37610
+ const identityMgr = new IdentityManager2(v11Storage, derived.key);
37611
+ const loadResult = await identityMgr.load();
37612
+ if (loadResult.loaded === 0) {
37613
+ const identityEncKey = derivePurposeKey2(derived.key, "identity-encryption");
37614
+ const { storedIdentity, publicIdentity } = createIdentity2(
37615
+ "default",
37616
+ identityEncKey,
37617
+ "passphrase"
37618
+ );
37619
+ await identityMgr.save(storedIdentity);
37620
+ wrapAuditLog.append("l1", "identity_create", publicIdentity.identity_id, {
37621
+ label: "default",
37622
+ source: "wrap-auto"
37623
+ });
37624
+ }
37625
+ } catch (err) {
37626
+ console.error(
37627
+ ` Note: default identity not created at wrap time (${err.message}).`
37628
+ );
37629
+ }
36243
37630
  let wrapIntelligenceSelector;
36244
37631
  try {
36245
37632
  wrapIntelligenceSelector = new SubstrateSelector({
@@ -36249,7 +37636,10 @@ async function runWrap(options, deps = {}) {
36249
37636
  identityId: `fortress:${storagePath}`
36250
37637
  });
36251
37638
  await wrapIntelligenceSelector.load();
37639
+ intelligenceHealthy = true;
36252
37640
  } catch (err) {
37641
+ intelligenceHealthy = false;
37642
+ intelligenceError = err.message;
36253
37643
  console.error(
36254
37644
  ` Note: Intelligence panel unavailable on wrap URL (${err.message}).`
36255
37645
  );
@@ -36317,7 +37707,10 @@ async function runWrap(options, deps = {}) {
36317
37707
  toolCount: countUpstreamTools(upstreamServers),
36318
37708
  serverCount: upstreamServers.length,
36319
37709
  dashboardUrl,
36320
- browserOpened: !options.noOpen});
37710
+ browserOpened: !options.noOpen,
37711
+ intelligenceHealthy,
37712
+ intelligenceError
37713
+ });
36321
37714
  }
36322
37715
  async function runCocoon(options) {
36323
37716
  console.error(
@@ -36373,12 +37766,12 @@ async function defaultOpenBrowser(url) {
36373
37766
  cmd = "xdg-open";
36374
37767
  args = [url];
36375
37768
  }
36376
- await new Promise((resolve5) => {
37769
+ await new Promise((resolve6) => {
36377
37770
  const child = child_process.spawn(cmd, args, { stdio: "ignore", detached: true });
36378
- child.on("error", () => resolve5());
37771
+ child.on("error", () => resolve6());
36379
37772
  child.on("spawn", () => {
36380
37773
  child.unref();
36381
- resolve5();
37774
+ resolve6();
36382
37775
  });
36383
37776
  });
36384
37777
  }
@@ -36404,7 +37797,15 @@ function formatWrapSuccess(info) {
36404
37797
  lines.push(` ${d("(browser auto-open suppressed)")}`);
36405
37798
  }
36406
37799
  lines.push("");
36407
- lines.push(` ${b("Your agent is protected.")} L1 Full / L2 Degraded (no TEE) / L3 Full / L4 Full.`);
37800
+ const l2Status = info.intelligenceHealthy === false ? "L2 Degraded (intelligence disabled)" : "L2 Degraded (no TEE)";
37801
+ lines.push(` ${b("Your agent is protected.")} L1 Full / ${l2Status} / L3 Full / L4 Full.`);
37802
+ if (info.intelligenceHealthy === false && info.intelligenceError) {
37803
+ const w = (s) => `\x1B[33m${s}\x1B[0m`;
37804
+ lines.push("");
37805
+ lines.push(` ${w("\u26A0")} L2 intelligence disabled: ${info.intelligenceError}`);
37806
+ lines.push(` Concierge chat and substrate-driven explanations will not work until this is resolved.`);
37807
+ lines.push(` Run 'sanctuary intelligence diagnose' to inspect substrate config.`);
37808
+ }
36408
37809
  lines.push("");
36409
37810
  return lines.join("\n");
36410
37811
  }
@@ -36428,9 +37829,16 @@ function formatWrapSuccessNoDashboard(info) {
36428
37829
  ` ${d("Dashboard spawn skipped per --no-dashboard. Run `sanctuary dashboard` separately for a persistent dashboard.")}`
36429
37830
  );
36430
37831
  lines.push("");
37832
+ const l2Status = info.intelligenceHealthy === false ? "L2 Degraded (intelligence disabled)" : "L2 Degraded (no TEE)";
36431
37833
  lines.push(
36432
- ` ${b("Your agent is protected.")} L1 Full / L2 Degraded (no TEE) / L3 Full / L4 Full.`
37834
+ ` ${b("Your agent is protected.")} L1 Full / ${l2Status} / L3 Full / L4 Full.`
36433
37835
  );
37836
+ if (info.intelligenceHealthy === false && info.intelligenceError) {
37837
+ const w = (s) => `\x1B[33m${s}\x1B[0m`;
37838
+ lines.push("");
37839
+ lines.push(` ${w("\u26A0")} L2 intelligence disabled: ${info.intelligenceError}`);
37840
+ lines.push(` Run 'sanctuary intelligence diagnose' to inspect substrate config.`);
37841
+ }
36434
37842
  lines.push("");
36435
37843
  return lines.join("\n");
36436
37844
  }
@@ -36630,7 +38038,22 @@ function rawConfigContainsSanctuary(raw, agentPlatform) {
36630
38038
  function parseWrapArgs(argv) {
36631
38039
  const options = {};
36632
38040
  for (let i = 0; i < argv.length; i++) {
36633
- switch (argv[i]) {
38041
+ const arg = argv[i];
38042
+ if (!arg.startsWith("-")) {
38043
+ const suggestion = WRAP_HARNESS_FLAGS.find(
38044
+ (f) => f.replace(/^--/, "") === arg
38045
+ );
38046
+ const hint = suggestion ? ` Did you mean ${suggestion}?` : "";
38047
+ throw new Error(
38048
+ `Unrecognized argument '${arg}'.${hint} Run 'sanctuary wrap --help' for valid flags.`
38049
+ );
38050
+ }
38051
+ if (!WRAP_BOOLEAN_FLAGS.has(arg) && !WRAP_VALUE_FLAGS.has(arg)) {
38052
+ throw new Error(
38053
+ `Unrecognized flag '${arg}'. Run 'sanctuary wrap --help' for valid flags.`
38054
+ );
38055
+ }
38056
+ switch (arg) {
36634
38057
  case "--wrap":
36635
38058
  options.wrap = argv[++i];
36636
38059
  break;
@@ -36673,6 +38096,9 @@ function parseWrapArgs(argv) {
36673
38096
  case "--dev-dist":
36674
38097
  options.devDist = argv[++i];
36675
38098
  break;
38099
+ case "--write-passphrase-backup":
38100
+ options.writePassphraseBackup = argv[++i];
38101
+ break;
36676
38102
  case "--help":
36677
38103
  case "-h":
36678
38104
  printWrapHelp();
@@ -36732,7 +38158,7 @@ function printWrapHelp() {
36732
38158
  5. Every tool call is logged, scanned, and tier-gated
36733
38159
  `);
36734
38160
  }
36735
- var COCOON_GOVERNOR_DEFAULTS, PORT_FALLBACK_ATTEMPTS, parseCocoonArgs;
38161
+ var COCOON_GOVERNOR_DEFAULTS, PORT_FALLBACK_ATTEMPTS, WRAP_VALUE_FLAGS, WRAP_BOOLEAN_FLAGS, WRAP_HARNESS_FLAGS, parseCocoonArgs;
36736
38162
  var init_cli2 = __esm({
36737
38163
  "src/cocoon/cli.ts"() {
36738
38164
  init_config_reader();
@@ -36755,6 +38181,28 @@ var init_cli2 = __esm({
36755
38181
  lifetime_limit: 1e3
36756
38182
  };
36757
38183
  PORT_FALLBACK_ATTEMPTS = 20;
38184
+ WRAP_VALUE_FLAGS = /* @__PURE__ */ new Set([
38185
+ "--wrap",
38186
+ "--passphrase",
38187
+ "--port",
38188
+ "--fortress",
38189
+ "--dev-dist",
38190
+ "--write-passphrase-backup"
38191
+ ]);
38192
+ WRAP_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
38193
+ "--openclaw",
38194
+ "--hermes",
38195
+ "--claude-code",
38196
+ "--cursor",
38197
+ "--cline",
38198
+ "--unwrap",
38199
+ "--dry-run",
38200
+ "--no-open",
38201
+ "--no-dashboard",
38202
+ "--help",
38203
+ "-h"
38204
+ ]);
38205
+ WRAP_HARNESS_FLAGS = ["--openclaw", "--hermes", "--claude-code", "--cursor", "--cline"];
36758
38206
  parseCocoonArgs = parseWrapArgs;
36759
38207
  }
36760
38208
  });
@@ -37652,7 +39100,7 @@ var init_backend_interface = __esm({
37652
39100
  }
37653
39101
  });
37654
39102
  async function runSecurity(args, input) {
37655
- return new Promise((resolve5, reject) => {
39103
+ return new Promise((resolve6, reject) => {
37656
39104
  const child = child_process.spawn(SECURITY_BIN, args, { stdio: ["pipe", "pipe", "pipe"] });
37657
39105
  let stdout = "";
37658
39106
  let stderr = "";
@@ -37674,7 +39122,7 @@ async function runSecurity(args, input) {
37674
39122
  reject(err);
37675
39123
  });
37676
39124
  child.on("close", (code) => {
37677
- resolve5({ stdout, stderr, code: code ?? -1 });
39125
+ resolve6({ stdout, stderr, code: code ?? -1 });
37678
39126
  });
37679
39127
  if (input !== void 0) {
37680
39128
  child.stdin.write(input);
@@ -38750,7 +40198,7 @@ async function readValue(stdin, prompt2) {
38750
40198
  return await readFirstLine(stdin);
38751
40199
  }
38752
40200
  async function readFirstLine(stdin) {
38753
- return new Promise((resolve5, reject) => {
40201
+ return new Promise((resolve6, reject) => {
38754
40202
  const rl = readline.createInterface({ input: stdin });
38755
40203
  let resolved = false;
38756
40204
  const finish = (value) => {
@@ -38761,7 +40209,7 @@ async function readFirstLine(stdin) {
38761
40209
  rl.close();
38762
40210
  } catch {
38763
40211
  }
38764
- resolve5(value);
40212
+ resolve6(value);
38765
40213
  };
38766
40214
  const deadline = setTimeout(() => {
38767
40215
  finish("");
@@ -38780,7 +40228,7 @@ async function promptSilently(stdin, prompt2) {
38780
40228
  process.stderr.write(`${prompt2}: `);
38781
40229
  stdin.setRawMode?.(true);
38782
40230
  stdin.resume();
38783
- return await new Promise((resolve5) => {
40231
+ return await new Promise((resolve6) => {
38784
40232
  let buf = "";
38785
40233
  const onData = (chunk) => {
38786
40234
  const s = chunk.toString("utf8");
@@ -38790,7 +40238,7 @@ async function promptSilently(stdin, prompt2) {
38790
40238
  stdin.pause();
38791
40239
  stdin.off("data", onData);
38792
40240
  process.stderr.write("\n");
38793
- resolve5(buf);
40241
+ resolve6(buf);
38794
40242
  return;
38795
40243
  }
38796
40244
  if (ch === "") {
@@ -39063,7 +40511,7 @@ async function probeTenantDashboard(tenant, options = {}) {
39063
40511
  return { running: false, status: null, reason: "no runtime.json" };
39064
40512
  }
39065
40513
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS4;
39066
- return await new Promise((resolve5) => {
40514
+ return await new Promise((resolve6) => {
39067
40515
  const req = http.get(
39068
40516
  {
39069
40517
  host: rt.dashboard_host,
@@ -39075,9 +40523,9 @@ async function probeTenantDashboard(tenant, options = {}) {
39075
40523
  res.resume();
39076
40524
  const status = res.statusCode ?? 0;
39077
40525
  if (status > 0 && status < 500) {
39078
- resolve5({ running: true, status, reason: null });
40526
+ resolve6({ running: true, status, reason: null });
39079
40527
  } else {
39080
- resolve5({
40528
+ resolve6({
39081
40529
  running: false,
39082
40530
  status,
39083
40531
  reason: `dashboard returned ${status}`
@@ -39087,10 +40535,10 @@ async function probeTenantDashboard(tenant, options = {}) {
39087
40535
  );
39088
40536
  req.on("timeout", () => {
39089
40537
  req.destroy();
39090
- resolve5({ running: false, status: null, reason: "timeout" });
40538
+ resolve6({ running: false, status: null, reason: "timeout" });
39091
40539
  });
39092
40540
  req.on("error", (err) => {
39093
- resolve5({
40541
+ resolve6({
39094
40542
  running: false,
39095
40543
  status: null,
39096
40544
  reason: err.code ?? err.message
@@ -39752,7 +41200,7 @@ async function prompt(lines, err, question) {
39752
41200
  return await lines.next();
39753
41201
  }
39754
41202
  async function defaultExec2(cmd, args) {
39755
- return await new Promise((resolve5, reject) => {
41203
+ return await new Promise((resolve6, reject) => {
39756
41204
  const child = child_process.spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
39757
41205
  let stdout = "";
39758
41206
  let stderr = "";
@@ -39763,7 +41211,7 @@ async function defaultExec2(cmd, args) {
39763
41211
  stderr += d.toString();
39764
41212
  });
39765
41213
  child.on("error", reject);
39766
- child.on("close", (code) => resolve5({ stdout, stderr, code }));
41214
+ child.on("close", (code) => resolve6({ stdout, stderr, code }));
39767
41215
  });
39768
41216
  }
39769
41217
  var LineReader;
@@ -39799,8 +41247,8 @@ var init_reset_passphrase = __esm({
39799
41247
  return Promise.resolve(this.queue.shift());
39800
41248
  }
39801
41249
  if (this.closed) return Promise.resolve("");
39802
- return new Promise((resolve5) => {
39803
- this.waiters.push(resolve5);
41250
+ return new Promise((resolve6) => {
41251
+ this.waiters.push(resolve6);
39804
41252
  });
39805
41253
  }
39806
41254
  close() {
@@ -39815,6 +41263,100 @@ var init_reset_passphrase = __esm({
39815
41263
  }
39816
41264
  });
39817
41265
 
41266
+ // src/cli/intelligence.ts
41267
+ var intelligence_exports = {};
41268
+ __export(intelligence_exports, {
41269
+ runIntelligenceCommand: () => runIntelligenceCommand
41270
+ });
41271
+ async function runIntelligenceCommand(opts) {
41272
+ const subcommand = opts.argv[0];
41273
+ if (subcommand === "diagnose" || subcommand === void 0) {
41274
+ return runDiagnose();
41275
+ }
41276
+ if (subcommand === "--help" || subcommand === "-h") {
41277
+ printHelp2();
41278
+ return 0;
41279
+ }
41280
+ console.error(`Unknown intelligence subcommand: ${subcommand}`);
41281
+ printHelp2();
41282
+ return 2;
41283
+ }
41284
+ function printHelp2() {
41285
+ console.error(`
41286
+ Usage: sanctuary intelligence <subcommand>
41287
+
41288
+ Subcommands:
41289
+ diagnose Print intelligence substrate config and last error.
41290
+ --help Show this help.
41291
+ `);
41292
+ }
41293
+ async function runDiagnose() {
41294
+ const storagePath = path.resolve(
41295
+ process.env.SANCTUARY_STORAGE_PATH ?? process.env.SANCTUARY_FORTRESS_PATH ?? `${process.env.HOME}/.sanctuary`
41296
+ );
41297
+ console.error(`Intelligence substrate diagnostics`);
41298
+ console.error(`Fortress: ${storagePath}`);
41299
+ console.error("");
41300
+ const intelligenceDir = path.resolve(storagePath, "state", "_intelligence");
41301
+ if (!fs.existsSync(intelligenceDir)) {
41302
+ console.error(
41303
+ `No intelligence config directory found at ${intelligenceDir}.`
41304
+ );
41305
+ console.error(
41306
+ "Intelligence substrate may not have been initialized yet."
41307
+ );
41308
+ console.error(
41309
+ "Ensure at least one substrate API key is set in your environment:"
41310
+ );
41311
+ console.error(
41312
+ " ANTHROPIC_API_KEY, OPENAI_API_KEY, VENICE_API_KEY, or OLLAMA_HOST"
41313
+ );
41314
+ return 1;
41315
+ }
41316
+ try {
41317
+ const entries = fs.readdirSync(intelligenceDir);
41318
+ console.error(`Intelligence config entries: ${entries.length}`);
41319
+ for (const entry of entries) {
41320
+ console.error(` ${entry}`);
41321
+ }
41322
+ } catch {
41323
+ console.error(`Could not read intelligence config directory.`);
41324
+ }
41325
+ const auditDir = path.resolve(storagePath, "state", "_audit");
41326
+ if (fs.existsSync(auditDir)) {
41327
+ try {
41328
+ const auditFiles = fs.readdirSync(auditDir).sort().reverse();
41329
+ const recentFiles = auditFiles.slice(0, 20);
41330
+ console.error("");
41331
+ console.error(`Recent audit entries (${recentFiles.length} of ${auditFiles.length}):`);
41332
+ for (const file of recentFiles) {
41333
+ console.error(` ${file}`);
41334
+ }
41335
+ } catch {
41336
+ console.error(`Could not read audit directory.`);
41337
+ }
41338
+ }
41339
+ console.error("");
41340
+ console.error("Substrate environment check:");
41341
+ const keys = [
41342
+ "ANTHROPIC_API_KEY",
41343
+ "OPENAI_API_KEY",
41344
+ "VENICE_API_KEY",
41345
+ "OLLAMA_HOST"
41346
+ ];
41347
+ for (const key of keys) {
41348
+ const val = process.env[key];
41349
+ console.error(
41350
+ ` ${key}: ${val ? `set (${val.slice(0, 4)}...)` : "not set"}`
41351
+ );
41352
+ }
41353
+ return 0;
41354
+ }
41355
+ var init_intelligence = __esm({
41356
+ "src/cli/intelligence.ts"() {
41357
+ }
41358
+ });
41359
+
39818
41360
  // src/mcp/broker-server.ts
39819
41361
  var broker_server_exports = {};
39820
41362
  __export(broker_server_exports, {
@@ -40105,11 +41647,11 @@ async function startMultiDashboardServer(options = {}) {
40105
41647
  }
40106
41648
  }
40107
41649
  });
40108
- await new Promise((resolve5, reject) => {
41650
+ await new Promise((resolve6, reject) => {
40109
41651
  server.once("error", reject);
40110
41652
  server.listen(port, host, () => {
40111
41653
  server.off("error", reject);
40112
- resolve5();
41654
+ resolve6();
40113
41655
  });
40114
41656
  });
40115
41657
  const addr = server.address();
@@ -40118,8 +41660,8 @@ async function startMultiDashboardServer(options = {}) {
40118
41660
  url: `http://${host}:${actualPort}`,
40119
41661
  port: actualPort,
40120
41662
  host,
40121
- stop: () => new Promise((resolve5, reject) => {
40122
- server.close((err) => err ? reject(err) : resolve5());
41663
+ stop: () => new Promise((resolve6, reject) => {
41664
+ server.close((err) => err ? reject(err) : resolve6());
40123
41665
  })
40124
41666
  };
40125
41667
  }
@@ -40519,7 +42061,7 @@ function formatUpdateMessage(current, latest) {
40519
42061
  return `[Sanctuary] Update available: ${current} \u2192 ${latest}. Run: npx @sanctuary-framework/mcp-server@latest`;
40520
42062
  }
40521
42063
  function fetchLatestVersion(currentVersion) {
40522
- return new Promise((resolve5) => {
42064
+ return new Promise((resolve6) => {
40523
42065
  const req = https.get(
40524
42066
  REGISTRY_URL,
40525
42067
  {
@@ -40529,7 +42071,7 @@ function fetchLatestVersion(currentVersion) {
40529
42071
  (res) => {
40530
42072
  if (res.statusCode !== 200) {
40531
42073
  res.resume();
40532
- resolve5(null);
42074
+ resolve6(null);
40533
42075
  return;
40534
42076
  }
40535
42077
  let data = "";
@@ -40538,7 +42080,7 @@ function fetchLatestVersion(currentVersion) {
40538
42080
  data += chunk;
40539
42081
  if (data.length > 32768) {
40540
42082
  res.destroy();
40541
- resolve5(null);
42083
+ resolve6(null);
40542
42084
  }
40543
42085
  });
40544
42086
  res.on("end", () => {
@@ -40546,20 +42088,20 @@ function fetchLatestVersion(currentVersion) {
40546
42088
  const json = JSON.parse(data);
40547
42089
  const latest = json.version;
40548
42090
  if (typeof latest === "string" && isNewerVersion(currentVersion, latest)) {
40549
- resolve5(latest);
42091
+ resolve6(latest);
40550
42092
  } else {
40551
- resolve5(null);
42093
+ resolve6(null);
40552
42094
  }
40553
42095
  } catch {
40554
- resolve5(null);
42096
+ resolve6(null);
40555
42097
  }
40556
42098
  });
40557
42099
  }
40558
42100
  );
40559
- req.on("error", () => resolve5(null));
42101
+ req.on("error", () => resolve6(null));
40560
42102
  req.on("timeout", () => {
40561
42103
  req.destroy();
40562
- resolve5(null);
42104
+ resolve6(null);
40563
42105
  });
40564
42106
  });
40565
42107
  }
@@ -40652,6 +42194,11 @@ async function main() {
40652
42194
  const code = await runResetPassphraseCommand2({ argv: args.slice(1) });
40653
42195
  process.exit(code);
40654
42196
  }
42197
+ if (args[0] === "intelligence") {
42198
+ const { runIntelligenceCommand: runIntelligenceCommand2 } = await Promise.resolve().then(() => (init_intelligence(), intelligence_exports));
42199
+ const code = await runIntelligenceCommand2({ argv: args.slice(1) });
42200
+ process.exit(code);
42201
+ }
40655
42202
  if (args[0] === "broker-server") {
40656
42203
  const { openBroker: openBroker2 } = await Promise.resolve().then(() => (init_open(), open_exports));
40657
42204
  const { createBrokerMcpServer: createBrokerMcpServer2 } = await Promise.resolve().then(() => (init_broker_server(), broker_server_exports));
@@ -40675,7 +42222,7 @@ async function main() {
40675
42222
  );
40676
42223
  passphrase = args[++i];
40677
42224
  } else if (args[i] === "--help" || args[i] === "-h") {
40678
- printHelp2();
42225
+ printHelp3();
40679
42226
  process.exit(0);
40680
42227
  } else if (args[i] === "--version" || args[i] === "-v") {
40681
42228
  console.log(`@sanctuary-framework/mcp-server ${PKG_VERSION4}`);
@@ -40820,7 +42367,7 @@ async function runExportPassphrase(args) {
40820
42367
  }
40821
42368
  process.stdout.write(stored.value + "\n");
40822
42369
  }
40823
- function printHelp2() {
42370
+ function printHelp3() {
40824
42371
  console.log(`
40825
42372
  @sanctuary-framework/mcp-server v${PKG_VERSION4}
40826
42373