@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.
- package/dist/cli.cjs +1952 -405
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1953 -406
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1646 -305
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +105 -18
- package/dist/index.d.ts +105 -18
- package/dist/index.js +1646 -306
- package/dist/index.js.map +1 -1
- package/dist/templates/coding-assistant/commitments.json +14 -0
- package/dist/templates/coding-assistant/defaults.json +34 -0
- package/dist/templates/coding-assistant/onboarding.md +24 -0
- package/dist/templates/coding-assistant/policy.md +1 -0
- package/dist/templates/coding-assistant/template.json +23 -0
- package/dist/templates/handoff-coordinator/commitments.json +14 -0
- package/dist/templates/handoff-coordinator/defaults.json +10 -0
- package/dist/templates/handoff-coordinator/onboarding.md +23 -0
- package/dist/templates/handoff-coordinator/policy.md +1 -0
- package/dist/templates/handoff-coordinator/template.json +17 -0
- package/dist/templates/ops-runner/commitments.json +14 -0
- package/dist/templates/ops-runner/defaults.json +12 -0
- package/dist/templates/ops-runner/onboarding.md +25 -0
- package/dist/templates/ops-runner/policy.md +1 -0
- package/dist/templates/ops-runner/template.json +16 -0
- package/dist/templates/planner/commitments.json +9 -0
- package/dist/templates/planner/defaults.json +10 -0
- package/dist/templates/planner/onboarding.md +22 -0
- package/dist/templates/planner/policy.md +1 -0
- package/dist/templates/planner/template.json +8 -0
- package/dist/templates/research-assistant/commitments.json +9 -0
- package/dist/templates/research-assistant/defaults.json +25 -0
- package/dist/templates/research-assistant/onboarding.md +21 -0
- package/dist/templates/research-assistant/policy.md +1 -0
- package/dist/templates/research-assistant/template.json +8 -0
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -703,19 +703,40 @@ function assertSanctuaryConfigShape(c) {
|
|
|
703
703
|
|
|
704
704
|
// src/storage/filesystem.ts
|
|
705
705
|
init_random();
|
|
706
|
+
var SAFE_CHARS = /[^A-Za-z0-9_.\-]/g;
|
|
707
|
+
function bijectiveEncode(name) {
|
|
708
|
+
return name.replace(
|
|
709
|
+
SAFE_CHARS,
|
|
710
|
+
(ch) => "!" + ch.charCodeAt(0).toString(16).padStart(2, "0").toUpperCase()
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
function legacyNamespaceSanitize(name) {
|
|
714
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
715
|
+
}
|
|
716
|
+
function legacyKeySanitize(name) {
|
|
717
|
+
return name.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
718
|
+
}
|
|
706
719
|
var FilesystemStorage = class {
|
|
707
720
|
basePath;
|
|
708
721
|
constructor(basePath) {
|
|
709
722
|
this.basePath = basePath;
|
|
710
723
|
}
|
|
711
724
|
entryPath(namespace, key) {
|
|
712
|
-
const safeNamespace = namespace
|
|
713
|
-
const safeKey = key
|
|
725
|
+
const safeNamespace = bijectiveEncode(namespace);
|
|
726
|
+
const safeKey = bijectiveEncode(key);
|
|
714
727
|
return join(this.basePath, safeNamespace, `${safeKey}.enc`);
|
|
715
728
|
}
|
|
716
729
|
namespacePath(namespace) {
|
|
717
|
-
|
|
718
|
-
|
|
730
|
+
return join(this.basePath, bijectiveEncode(namespace));
|
|
731
|
+
}
|
|
732
|
+
// Legacy on-disk paths produced by the pre-#41 sanitizer. Returned for
|
|
733
|
+
// ENOENT-fallback in read/exists/delete; never written to.
|
|
734
|
+
legacyEntryPath(namespace, key) {
|
|
735
|
+
return join(
|
|
736
|
+
this.basePath,
|
|
737
|
+
legacyNamespaceSanitize(namespace),
|
|
738
|
+
`${legacyKeySanitize(key)}.enc`
|
|
739
|
+
);
|
|
719
740
|
}
|
|
720
741
|
async write(namespace, key, data) {
|
|
721
742
|
const dirPath = this.namespacePath(namespace);
|
|
@@ -724,7 +745,13 @@ var FilesystemStorage = class {
|
|
|
724
745
|
await writeFile(filePath, data, { mode: 384 });
|
|
725
746
|
}
|
|
726
747
|
async read(namespace, key) {
|
|
727
|
-
const
|
|
748
|
+
const buf = await this.readAtPath(this.entryPath(namespace, key));
|
|
749
|
+
if (buf !== null) return buf;
|
|
750
|
+
const legacy = this.legacyEntryPath(namespace, key);
|
|
751
|
+
if (legacy === this.entryPath(namespace, key)) return null;
|
|
752
|
+
return this.readAtPath(legacy);
|
|
753
|
+
}
|
|
754
|
+
async readAtPath(filePath) {
|
|
728
755
|
try {
|
|
729
756
|
const buf = await readFile(filePath);
|
|
730
757
|
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
@@ -736,7 +763,13 @@ var FilesystemStorage = class {
|
|
|
736
763
|
}
|
|
737
764
|
}
|
|
738
765
|
async delete(namespace, key, secureOverwrite = true) {
|
|
739
|
-
const
|
|
766
|
+
const newPath = this.entryPath(namespace, key);
|
|
767
|
+
if (await this.deleteAtPath(newPath, secureOverwrite)) return true;
|
|
768
|
+
const legacy = this.legacyEntryPath(namespace, key);
|
|
769
|
+
if (legacy === newPath) return false;
|
|
770
|
+
return this.deleteAtPath(legacy, secureOverwrite);
|
|
771
|
+
}
|
|
772
|
+
async deleteAtPath(filePath, secureOverwrite) {
|
|
740
773
|
try {
|
|
741
774
|
if (secureOverwrite) {
|
|
742
775
|
const fileStat = await stat(filePath);
|
|
@@ -782,12 +815,19 @@ var FilesystemStorage = class {
|
|
|
782
815
|
}
|
|
783
816
|
}
|
|
784
817
|
async exists(namespace, key) {
|
|
785
|
-
const
|
|
818
|
+
const newPath = this.entryPath(namespace, key);
|
|
786
819
|
try {
|
|
787
|
-
await stat(
|
|
820
|
+
await stat(newPath);
|
|
788
821
|
return true;
|
|
789
822
|
} catch {
|
|
790
|
-
|
|
823
|
+
const legacy = this.legacyEntryPath(namespace, key);
|
|
824
|
+
if (legacy === newPath) return false;
|
|
825
|
+
try {
|
|
826
|
+
await stat(legacy);
|
|
827
|
+
return true;
|
|
828
|
+
} catch {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
791
831
|
}
|
|
792
832
|
}
|
|
793
833
|
async totalSize() {
|
|
@@ -4773,6 +4813,32 @@ function canonicalizeForSigning(body) {
|
|
|
4773
4813
|
// src/shr/generator.ts
|
|
4774
4814
|
init_identity();
|
|
4775
4815
|
init_encoding();
|
|
4816
|
+
|
|
4817
|
+
// src/mesh/constants.ts
|
|
4818
|
+
var PROTOCOL_VERSION = "0.1";
|
|
4819
|
+
var SIGNATURE_SCHEME_V1 = "ed25519-v1";
|
|
4820
|
+
var RESERVED_EVENT_TYPE_PREFIXES = [
|
|
4821
|
+
"EXTENSION_",
|
|
4822
|
+
"cross_fortress_",
|
|
4823
|
+
"multi_master_"
|
|
4824
|
+
];
|
|
4825
|
+
function isReservedEventType(s) {
|
|
4826
|
+
return RESERVED_EVENT_TYPE_PREFIXES.some((p) => s.startsWith(p));
|
|
4827
|
+
}
|
|
4828
|
+
var RESERVED_EXTENSION_ENVELOPE_KEYS = [
|
|
4829
|
+
"cross_fortress_read_grant",
|
|
4830
|
+
"cross_fortress_read_query",
|
|
4831
|
+
"cross_fortress_read_response",
|
|
4832
|
+
"multi_master_policy_merge",
|
|
4833
|
+
"audit_replication_full_n_way",
|
|
4834
|
+
"auto_promote_canonical_audit",
|
|
4835
|
+
"agent_live_migration"
|
|
4836
|
+
];
|
|
4837
|
+
function isReservedExtensionKey(k) {
|
|
4838
|
+
return RESERVED_EXTENSION_ENVELOPE_KEYS.includes(k);
|
|
4839
|
+
}
|
|
4840
|
+
|
|
4841
|
+
// src/shr/generator.ts
|
|
4776
4842
|
var DEFAULT_VALIDITY_MS = 60 * 60 * 1e3;
|
|
4777
4843
|
var DEFAULT_FRESHNESS_WINDOW_DAYS = 30;
|
|
4778
4844
|
var DEFAULT_LOW_TIER_DOMINANCE_THRESHOLD = 0.6;
|
|
@@ -4923,6 +4989,7 @@ function generateSHR(identityId, opts) {
|
|
|
4923
4989
|
return {
|
|
4924
4990
|
body,
|
|
4925
4991
|
signed_by: identity.public_key,
|
|
4992
|
+
signature_scheme: SIGNATURE_SCHEME_V1,
|
|
4926
4993
|
signature: toBase64url(signatureBytes)
|
|
4927
4994
|
};
|
|
4928
4995
|
}
|
|
@@ -7159,14 +7226,14 @@ function generateDashboardHTML(options) {
|
|
|
7159
7226
|
// cookie (set by /auth/session and sent automatically by the
|
|
7160
7227
|
// browser) or as a ?session= query parameter, both of which Stack
|
|
7161
7228
|
// A's checkAuth honours. Loopback callers also bypass auth via the
|
|
7162
|
-
// v0.10.2 _autoAuthLocalhost path, which is the path
|
|
7229
|
+
// v0.10.2 _autoAuthLocalhost path, which is the path Mini1
|
|
7163
7230
|
// hits when the dashboard is auto-opened on 127.0.0.1.
|
|
7164
7231
|
//
|
|
7165
7232
|
// The endpoint itself is /events \u2014 Stack A's route table mounts it
|
|
7166
7233
|
// there, and the previous /api/events URL was a 404 in every real
|
|
7167
7234
|
// boot from v0.10.0 through v0.10.4. The retry loop that result
|
|
7168
7235
|
// produced is exactly the "status bar flashing blue continuously"
|
|
7169
|
-
//
|
|
7236
|
+
// Mini1 reported on v0.10.4.
|
|
7170
7237
|
const eventSource = new EventSource(API_BASE + '/events');
|
|
7171
7238
|
|
|
7172
7239
|
eventSource.addEventListener('init', (e) => {
|
|
@@ -9978,10 +10045,8 @@ var CHANNEL_TEMPLATE_IDS = [
|
|
|
9978
10045
|
"read-then-report",
|
|
9979
10046
|
"scheduled-digest",
|
|
9980
10047
|
"plan-draft-only",
|
|
9981
|
-
"fortress-relay"
|
|
9982
|
-
"concierge-loop"
|
|
10048
|
+
"fortress-relay"
|
|
9983
10049
|
];
|
|
9984
|
-
var COUNTERPARTY_WILDCARD = "*";
|
|
9985
10050
|
var BUDGET_UNITS = ["tokens", "usd"];
|
|
9986
10051
|
|
|
9987
10052
|
// src/templates/registry.ts
|
|
@@ -10149,8 +10214,12 @@ function lintOnboarding(_name, content) {
|
|
|
10149
10214
|
function resolveTemplatesDir() {
|
|
10150
10215
|
const thisFile = fileURLToPath(import.meta.url);
|
|
10151
10216
|
const thisDir = dirname(thisFile);
|
|
10152
|
-
if (thisDir.includes("/dist
|
|
10153
|
-
|
|
10217
|
+
if (thisDir.includes("/dist")) {
|
|
10218
|
+
const templatesSubdir = join(thisDir, "templates");
|
|
10219
|
+
const candidateDir = existsSync(join(thisDir, TEMPLATE_NAMES[0])) ? thisDir : existsSync(join(templatesSubdir, TEMPLATE_NAMES[0])) ? templatesSubdir : null;
|
|
10220
|
+
if (candidateDir) return candidateDir;
|
|
10221
|
+
const srcFallback = thisDir.endsWith("/templates") ? thisDir.replace("/dist/templates", "/src/templates") : join(thisDir.replace("/dist", "/src"), "templates");
|
|
10222
|
+
return srcFallback;
|
|
10154
10223
|
}
|
|
10155
10224
|
return thisDir;
|
|
10156
10225
|
}
|
|
@@ -10419,23 +10488,6 @@ var fortressRelay = (params) => {
|
|
|
10419
10488
|
setRetentionDays(p, 90);
|
|
10420
10489
|
return p;
|
|
10421
10490
|
};
|
|
10422
|
-
var conciergeLoop = (params) => {
|
|
10423
|
-
const p = basePolicy(params);
|
|
10424
|
-
p.source_english = "Bidirectional Q&A with the operator. Reads local fortress state; never writes outward.";
|
|
10425
|
-
grantOn(p, "memory", {
|
|
10426
|
-
counterparty: params.counterparty,
|
|
10427
|
-
action: "read",
|
|
10428
|
-
scope: { local_fortress_state_only: true, ...params.scope ?? {} }
|
|
10429
|
-
});
|
|
10430
|
-
grantOn(p, "outputs", {
|
|
10431
|
-
counterparty: params.counterparty || COUNTERPARTY_WILDCARD,
|
|
10432
|
-
action: "read",
|
|
10433
|
-
scope: { operator_chat_only: true, ...params.scope ?? {} }
|
|
10434
|
-
});
|
|
10435
|
-
p.egress = { allowlist: [] };
|
|
10436
|
-
setRetentionDays(p, 14);
|
|
10437
|
-
return p;
|
|
10438
|
-
};
|
|
10439
10491
|
function allowedHostsFromScope(scope) {
|
|
10440
10492
|
const raw = scope?.allowed_hosts;
|
|
10441
10493
|
if (!Array.isArray(raw)) return [];
|
|
@@ -10476,13 +10528,6 @@ var REGISTRY = {
|
|
|
10476
10528
|
label: "Fortress relay",
|
|
10477
10529
|
description: "Routes signed events between peer fortresses. Commits bind only when both sides sign.",
|
|
10478
10530
|
factory: fortressRelay
|
|
10479
|
-
},
|
|
10480
|
-
"concierge-loop": {
|
|
10481
|
-
id: "concierge-loop",
|
|
10482
|
-
severity: "LOW",
|
|
10483
|
-
label: "Concierge loop",
|
|
10484
|
-
description: "Bidirectional Q&A with the operator. Reads local fortress state; never writes outward.",
|
|
10485
|
-
factory: conciergeLoop
|
|
10486
10531
|
}
|
|
10487
10532
|
};
|
|
10488
10533
|
function applyChannelTemplate(id, params) {
|
|
@@ -10556,8 +10601,14 @@ function encode(value) {
|
|
|
10556
10601
|
}
|
|
10557
10602
|
function encodeArray(arr) {
|
|
10558
10603
|
const parts = [];
|
|
10559
|
-
for (
|
|
10560
|
-
|
|
10604
|
+
for (let i = 0; i < arr.length; i++) {
|
|
10605
|
+
const item = arr[i];
|
|
10606
|
+
if (item === void 0) {
|
|
10607
|
+
throw new MeshCanonicalJsonError(
|
|
10608
|
+
`canonicalize(): undefined is not a valid JSON value at array index ${i}`
|
|
10609
|
+
);
|
|
10610
|
+
}
|
|
10611
|
+
parts.push(encode(item));
|
|
10561
10612
|
}
|
|
10562
10613
|
return "[" + parts.join(",") + "]";
|
|
10563
10614
|
}
|
|
@@ -10862,29 +10913,6 @@ function encodePolicyBlob(policy) {
|
|
|
10862
10913
|
init_encoding();
|
|
10863
10914
|
init_random();
|
|
10864
10915
|
|
|
10865
|
-
// src/mesh/constants.ts
|
|
10866
|
-
var PROTOCOL_VERSION = "0.1";
|
|
10867
|
-
var RESERVED_EVENT_TYPE_PREFIXES = [
|
|
10868
|
-
"EXTENSION_",
|
|
10869
|
-
"cross_fortress_",
|
|
10870
|
-
"multi_master_"
|
|
10871
|
-
];
|
|
10872
|
-
function isReservedEventType(s) {
|
|
10873
|
-
return RESERVED_EVENT_TYPE_PREFIXES.some((p) => s.startsWith(p));
|
|
10874
|
-
}
|
|
10875
|
-
var RESERVED_EXTENSION_ENVELOPE_KEYS = [
|
|
10876
|
-
"cross_fortress_read_grant",
|
|
10877
|
-
"cross_fortress_read_query",
|
|
10878
|
-
"cross_fortress_read_response",
|
|
10879
|
-
"multi_master_policy_merge",
|
|
10880
|
-
"audit_replication_full_n_way",
|
|
10881
|
-
"auto_promote_canonical_audit",
|
|
10882
|
-
"agent_live_migration"
|
|
10883
|
-
];
|
|
10884
|
-
function isReservedExtensionKey(k) {
|
|
10885
|
-
return RESERVED_EXTENSION_ENVELOPE_KEYS.includes(k);
|
|
10886
|
-
}
|
|
10887
|
-
|
|
10888
10916
|
// src/mesh/trust-root.ts
|
|
10889
10917
|
init_encoding();
|
|
10890
10918
|
init_identity();
|
|
@@ -12105,7 +12133,7 @@ async function api(path, opts) {
|
|
|
12105
12133
|
// /policies, /activity responses on subsequent GETs even when the
|
|
12106
12134
|
// server-side state has changed (e.g. recent-failures buffer cleared
|
|
12107
12135
|
// on substrate flip). The pre-rc.5 client used bare fetch with no
|
|
12108
|
-
// cache control, which on
|
|
12136
|
+
// cache control, which on Mini1 Safari produced a stale view of
|
|
12109
12137
|
// server state and made the operator-visible badge color stick to
|
|
12110
12138
|
// its prior value across substrate changes. Belt + suspenders:
|
|
12111
12139
|
// cache: "no-store" turns off the response cache; the _t query
|
|
@@ -12267,12 +12295,6 @@ const CHANNEL_TEMPLATES = [
|
|
|
12267
12295
|
severity: "MEDIUM",
|
|
12268
12296
|
title: "Fortress relay",
|
|
12269
12297
|
description: "Routes signed events between peer fortresses. Commits bind only when both sides sign."
|
|
12270
|
-
},
|
|
12271
|
-
{
|
|
12272
|
-
id: "concierge-loop",
|
|
12273
|
-
severity: "LOW",
|
|
12274
|
-
title: "Concierge loop",
|
|
12275
|
-
description: "Bidirectional Q&A with the operator. Reads local fortress state; never writes outward."
|
|
12276
12298
|
}
|
|
12277
12299
|
];
|
|
12278
12300
|
|
|
@@ -12319,6 +12341,24 @@ function setRoute(route) {
|
|
|
12319
12341
|
renderFortress();
|
|
12320
12342
|
}
|
|
12321
12343
|
|
|
12344
|
+
// Renders the global attestation badge (Q1 layer 1, persistent across
|
|
12345
|
+
// surfaces). Tone is driven by state.topbarPills.attestation. Pending
|
|
12346
|
+
// state shows a dashed seal ring; verified shows solid; degraded shows
|
|
12347
|
+
// outlined core; unverified shows the broken-seal mark. Observation
|
|
12348
|
+
// language only; Castle Layer 1 enforcement ships in WP-V1.x-CASTLE-WALL.
|
|
12349
|
+
function renderTopbarAttestationBadge(stateName) {
|
|
12350
|
+
const valid = stateName === "verified" || stateName === "degraded" || stateName === "unverified" || stateName === "pending";
|
|
12351
|
+
const cls = valid ? stateName : "pending";
|
|
12352
|
+
const ringDashed = cls === "pending" ? " dashed" : "";
|
|
12353
|
+
return '<span class="att-global ' + cls + '" data-pill="attestation" title="Fortress attestation">' +
|
|
12354
|
+
'<span class="seal">' +
|
|
12355
|
+
'<span class="seal-ring' + ringDashed + '"></span>' +
|
|
12356
|
+
'<span class="seal-core"></span>' +
|
|
12357
|
+
'</span>' +
|
|
12358
|
+
'<span class="label">' + escHtml(cls) + '</span>' +
|
|
12359
|
+
'</span>';
|
|
12360
|
+
}
|
|
12361
|
+
|
|
12322
12362
|
function renderTopbar() {
|
|
12323
12363
|
const pillEl = document.getElementById("topbar-pills");
|
|
12324
12364
|
if (!pillEl) return;
|
|
@@ -12335,7 +12375,7 @@ function renderTopbar() {
|
|
|
12335
12375
|
versionPill,
|
|
12336
12376
|
'<span class="pill" data-pill="deployment">deployment: ' + escHtml(state.topbarPills.deployment) + '</span>',
|
|
12337
12377
|
'<span class="pill" data-pill="mode">mode: ' + escHtml(state.topbarPills.mode) + '</span>',
|
|
12338
|
-
|
|
12378
|
+
renderTopbarAttestationBadge(state.topbarPills.attestation)
|
|
12339
12379
|
].join("");
|
|
12340
12380
|
// Lockdown button three-state UX (binding addendum 3).
|
|
12341
12381
|
const btn = document.getElementById("btn-lockdown");
|
|
@@ -12431,6 +12471,7 @@ function renderMain() {
|
|
|
12431
12471
|
case "agent-detail": nextHtml = renderAgentDetail(); break;
|
|
12432
12472
|
case "policy": nextHtml = renderPolicyCenter(); break;
|
|
12433
12473
|
case "intelligence": nextHtml = renderIntelligenceCenter(); break;
|
|
12474
|
+
case "attestation": nextHtml = renderAttestation(); break;
|
|
12434
12475
|
case "privacy": nextHtml = renderPrivacyPage(); break;
|
|
12435
12476
|
case "coordination": nextHtml = renderCoordinationPage(); break;
|
|
12436
12477
|
case "health": nextHtml = renderHealthPage(); break;
|
|
@@ -12509,9 +12550,9 @@ function renderMain() {
|
|
|
12509
12550
|
// "Concierge unavailable; substrate not configured") sourced from the
|
|
12510
12551
|
// last response's served_by + display_label.
|
|
12511
12552
|
const CONCIERGE_SUGGESTIONS = [
|
|
12512
|
-
{ id: "summarize-hour", label: "summarize the last hour", query: "Summarize what happened in this fortress in the last hour." },
|
|
12513
|
-
{ id: "agent-touched", label: "what has each agent touched today", query: "What has each wrapped agent done today? Group by agent." },
|
|
12514
|
-
{ id: "open-approvals", label: "any open approvals?", query: "Are there any open Tier 1 approvals or pending inbox items I should look at?" }
|
|
12553
|
+
{ id: "summarize-hour", category: "Summarize", label: "summarize the last hour", query: "Summarize what happened in this fortress in the last hour." },
|
|
12554
|
+
{ id: "agent-touched", category: "Inspect", label: "what has each agent touched today", query: "What has each wrapped agent done today? Group by agent." },
|
|
12555
|
+
{ 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?" }
|
|
12515
12556
|
];
|
|
12516
12557
|
|
|
12517
12558
|
// Direct-agent chat surface was removed in the v1.2 reshape; the
|
|
@@ -12528,59 +12569,316 @@ function renderDashboardConcierge() {
|
|
|
12528
12569
|
const badge = c.badge && c.badge.displayLabel
|
|
12529
12570
|
? '<span class="pill mono concierge-badge" title="Substrate that served the most recent response">' + escHtml(c.badge.displayLabel) + '</span>'
|
|
12530
12571
|
: '<span class="pill muted concierge-badge">Concierge: substrate not yet contacted</span>';
|
|
12531
|
-
const
|
|
12572
|
+
const sendDisabled = c.sending ? ' disabled' : '';
|
|
12573
|
+
const sendLabel = c.sending ? 'Sending...' : 'Send';
|
|
12574
|
+
// Sprint Piece 2 PR 2: empty state lives INSIDE the concierge-history
|
|
12575
|
+
// container so the DDD e2e selector .concierge-history matches both
|
|
12576
|
+
// empty and active state. The container's flex layout hosts a single
|
|
12577
|
+
// .concierge-empty child that fills the available height with a serif
|
|
12578
|
+
// headline and a 3-up suggest grid; the grid replaces the v1.2 bottom
|
|
12579
|
+
// chip row, which is retired with this polish.
|
|
12580
|
+
const emptyState =
|
|
12581
|
+
'<div class="concierge-empty">' +
|
|
12582
|
+
'<div class="concierge-empty-headline">' +
|
|
12583
|
+
'<h2>Where would you like to begin.</h2>' +
|
|
12584
|
+
'<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>' +
|
|
12585
|
+
'</div>' +
|
|
12586
|
+
'<div class="concierge-suggest-grid">' +
|
|
12587
|
+
CONCIERGE_SUGGESTIONS.map(function (s) {
|
|
12588
|
+
return '<button class="concierge-suggest" data-action="concierge-suggestion" data-suggestion-id="' + escHtml(s.id) + '"' + sendDisabled + '>' +
|
|
12589
|
+
'<span class="label">' + escHtml(s.category || '') + '</span>' +
|
|
12590
|
+
escHtml(s.label) +
|
|
12591
|
+
'</button>';
|
|
12592
|
+
}).join("") +
|
|
12593
|
+
'</div>' +
|
|
12594
|
+
'</div>';
|
|
12595
|
+
const messagesHtml = c.messages.length
|
|
12532
12596
|
? c.messages.map(function (m) {
|
|
12533
12597
|
const cls = m.role === "operator" ? "concierge-msg-operator" : "concierge-msg-concierge";
|
|
12534
|
-
const
|
|
12598
|
+
const authorLabel = m.role === "operator" ? "you" : "sanctuary";
|
|
12599
|
+
const metaParts = [];
|
|
12600
|
+
if (m.created_at) metaParts.push(escHtml(shortTime(m.created_at)));
|
|
12601
|
+
if (m.role === "concierge" && m.served_by) metaParts.push('substrate: ' + escHtml(m.served_by));
|
|
12602
|
+
const meta = metaParts.length
|
|
12603
|
+
? '<div class="concierge-msg-meta"><span>' + metaParts.join(' · ') + '</span></div>'
|
|
12604
|
+
: '';
|
|
12535
12605
|
return '<div class="concierge-msg ' + cls + '">' +
|
|
12536
|
-
'<
|
|
12606
|
+
'<span class="concierge-msg-author">' + escHtml(authorLabel) + '</span>' +
|
|
12537
12607
|
'<div class="concierge-msg-body">' + escHtml(m.body) + '</div>' +
|
|
12608
|
+
meta +
|
|
12538
12609
|
'</div>';
|
|
12539
12610
|
}).join("\n")
|
|
12540
|
-
:
|
|
12611
|
+
: emptyState;
|
|
12541
12612
|
const errorBanner = c.error
|
|
12542
12613
|
? '<div class="banner banner-warn">' + escHtml(c.error) + '</div>'
|
|
12543
12614
|
: "";
|
|
12544
|
-
const sendDisabled = c.sending ? ' disabled' : '';
|
|
12545
|
-
const sendLabel = c.sending ? 'Sending...' : 'Send';
|
|
12546
|
-
const chips = CONCIERGE_SUGGESTIONS.map(function (s) {
|
|
12547
|
-
return '<button class="btn chip" data-action="concierge-suggestion" data-suggestion-id="' + escHtml(s.id) + '"' + sendDisabled + '>' + escHtml(s.label) + '</button>';
|
|
12548
|
-
}).join("\n");
|
|
12549
12615
|
const activeChatsPanel = renderActiveChatsPanel();
|
|
12550
12616
|
return [
|
|
12551
|
-
'<
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
'<
|
|
12556
|
-
|
|
12617
|
+
'<div class="concierge-wrap">',
|
|
12618
|
+
'<div class="page-head"><div>',
|
|
12619
|
+
'<p class="eyebrow">Concierge</p>',
|
|
12620
|
+
'<h1>Talk to your fortress.</h1>',
|
|
12621
|
+
'<p class="sub">A direct line to Sanctuary, routed through the substrate you chose. Nothing leaves without your hand on it.</p>',
|
|
12622
|
+
'</div></div>',
|
|
12623
|
+
activeChatsPanel,
|
|
12624
|
+
'<div class="card concierge-card">',
|
|
12625
|
+
'<div class="concierge-header">',
|
|
12626
|
+
'<div class="concierge-persona">',
|
|
12627
|
+
'<div class="glyph-ring"></div>',
|
|
12628
|
+
'<div class="concierge-persona-text"><strong>Sanctuary Fortress concierge</strong><small>read-only over fortress state</small></div>',
|
|
12629
|
+
'</div>',
|
|
12630
|
+
'<div class="concierge-meta">' + badge + '</div>',
|
|
12631
|
+
'</div>',
|
|
12632
|
+
errorBanner,
|
|
12633
|
+
'<div class="concierge-history" id="concierge-history">' + messagesHtml + '</div>',
|
|
12634
|
+
'<form class="concierge-composer" data-action="concierge-submit">',
|
|
12635
|
+
'<div class="input-wrap">',
|
|
12636
|
+
'<input type="text" name="concierge-input" placeholder="Type to Sanctuary. Enter to send." value="' + escHtml(c.composer) + '" data-action="concierge-input"' + sendDisabled + ' autocomplete="off">',
|
|
12637
|
+
'<span class="composer-meta">Enter</span>',
|
|
12638
|
+
'</div>',
|
|
12639
|
+
'<button type="submit" class="btn btn-primary" data-action="concierge-send"' + sendDisabled + '>' + escHtml(sendLabel) + '</button>',
|
|
12640
|
+
'</form>',
|
|
12641
|
+
'<p class="muted concierge-foot">First time? <a href="#intelligence">Pick a substrate</a> to enable concierge replies.</p>',
|
|
12557
12642
|
'</div>',
|
|
12558
|
-
errorBanner,
|
|
12559
|
-
'<div class="concierge-history" id="concierge-history">' + messages + '</div>',
|
|
12560
|
-
'<form class="concierge-composer" data-action="concierge-submit">',
|
|
12561
|
-
'<input type="text" name="concierge-input" placeholder="Ask the concierge about this fortress..." value="' + escHtml(c.composer) + '" data-action="concierge-input"' + sendDisabled + ' autocomplete="off">',
|
|
12562
|
-
'<button type="submit" class="btn btn-primary" data-action="concierge-send"' + sendDisabled + '>' + escHtml(sendLabel) + '</button>',
|
|
12563
|
-
'</form>',
|
|
12564
|
-
'<div class="concierge-chips">' + chips + '</div>',
|
|
12565
|
-
'<p class="muted concierge-foot">First time? <a href="#intelligence">Pick a substrate</a> to enable concierge replies.</p>',
|
|
12566
12643
|
'</div>'
|
|
12567
12644
|
].join("");
|
|
12568
12645
|
}
|
|
12569
12646
|
|
|
12570
12647
|
// ── Render: agents list / detail ───────────────────────────────────────
|
|
12648
|
+
//
|
|
12649
|
+
// Sprint Piece 2 PR 4 polish: empty state uses .agents-empty with the
|
|
12650
|
+
// concentric icon-frame + a terminal-block CTA. Populated state uses the
|
|
12651
|
+
// .agents-layout grid with the .agents-list 4-column table (Agent /
|
|
12652
|
+
// State / Attestation / Last seen). The empty-state branch keeps the
|
|
12653
|
+
// literal '<h1>Agents</h1>' start and the "No wrapped agents yet." copy
|
|
12654
|
+
// because agents-empty-state-canary.test.ts pins both.
|
|
12655
|
+
function agentInitials(agentId) {
|
|
12656
|
+
const tail = String(agentId || "").split(":").pop() || "";
|
|
12657
|
+
const cleaned = tail.replace(/[^a-zA-Z0-9]/g, "");
|
|
12658
|
+
return (cleaned.slice(0, 2) || "??").toUpperCase();
|
|
12659
|
+
}
|
|
12660
|
+
function agentStateClass(status) {
|
|
12661
|
+
if (status === "active") return "live";
|
|
12662
|
+
if (status === "locked_down" || status === "error") return "off";
|
|
12663
|
+
return "idle";
|
|
12664
|
+
}
|
|
12665
|
+
// Per-agent attestation badge (Q1 layer 2). Square chip beside each
|
|
12666
|
+
// agent: a bounded glyph beside a bounded entity. Color and fill pattern
|
|
12667
|
+
// carry meaning together so the badge reads even monochrome. The "locked"
|
|
12668
|
+
// status maps to the unverified visual (rust + hatched mark) since a
|
|
12669
|
+
// locked-down agent has no current attestation; the inspect-pane copy
|
|
12670
|
+
// explains the distinction. Pure visual surface; no state derivation.
|
|
12671
|
+
function renderAgentAttestationBadge(status) {
|
|
12672
|
+
let cls;
|
|
12673
|
+
let label;
|
|
12674
|
+
if (status === "active") { cls = "verified"; label = "verified"; }
|
|
12675
|
+
else if (status === "locked_down") { cls = "unverified"; label = "locked"; }
|
|
12676
|
+
else if (status === "error") { cls = "unverified"; label = "unverified"; }
|
|
12677
|
+
else { cls = "degraded"; label = "degraded"; }
|
|
12678
|
+
return '<span class="att-agent ' + cls + '" title="Agent attestation"><span class="mark"></span>' + escHtml(label) + '</span>';
|
|
12679
|
+
}
|
|
12680
|
+
// Per-action attestation tick (Q1 layer 3). Tiny inline shape on every
|
|
12681
|
+
// timeline row. Two-byte signature fragment is enough at low resolution;
|
|
12682
|
+
// the full signature is one click away. Neutral state shows a circle
|
|
12683
|
+
// instead of a tick when the signer was unreachable; the action is still
|
|
12684
|
+
// recorded. Visual surface only.
|
|
12685
|
+
function renderActionAttestationBadge(stateName, sig) {
|
|
12686
|
+
const valid = stateName === "verified" || stateName === "degraded" || stateName === "unverified" || stateName === "neutral";
|
|
12687
|
+
const cls = valid ? stateName : "neutral";
|
|
12688
|
+
const sigText = sig ? String(sig) : "--";
|
|
12689
|
+
return '<span class="att-action ' + cls + '" title="Action attestation">' +
|
|
12690
|
+
'<span class="tick"></span>' +
|
|
12691
|
+
'<span>' + escHtml(sigText) + '</span>' +
|
|
12692
|
+
'</span>';
|
|
12693
|
+
}
|
|
12694
|
+
// Attestation gallery surface (Q1 four classes: global / per-agent /
|
|
12695
|
+
// per-action / per-transaction custody-provenance stub). Reference for
|
|
12696
|
+
// operators: shows what each badge looks like across verified, degraded,
|
|
12697
|
+
// unverified, and (where applicable) pending or neutral states. Pure
|
|
12698
|
+
// visual; no derivation, no live data. Castle Layer 3 cooperative-MCP UX
|
|
12699
|
+
// surface; Castle Layer 1 enforcement ships in WP-V1.x-CASTLE-WALL.
|
|
12700
|
+
function renderAttestation() {
|
|
12701
|
+
return '<div class="att-gallery">' +
|
|
12702
|
+
'<div class="page-head"><div>' +
|
|
12703
|
+
'<p class="eyebrow">Attestation</p>' +
|
|
12704
|
+
'<h1>Four classes of badge.</h1>' +
|
|
12705
|
+
'<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>' +
|
|
12706
|
+
'</div></div>' +
|
|
12707
|
+
// Global
|
|
12708
|
+
'<div class="att-section">' +
|
|
12709
|
+
'<div class="att-section-head"><div>' +
|
|
12710
|
+
'<h2>Global. The fortress itself.</h2>' +
|
|
12711
|
+
'<p>Lives in the topbar. Visible on every surface. Tells you the fortress identity is currently signed and matches the binary you installed.</p>' +
|
|
12712
|
+
'</div><span class="label">topbar</span></div>' +
|
|
12713
|
+
attRow(renderTopbarAttestationBadge("verified"), "Verified", "Identity matches. Binary matches. Default state for a healthy fortress.") +
|
|
12714
|
+
attRow(renderTopbarAttestationBadge("degraded"), "Degraded", "The signature is older than the staleness window, or one of two co-signers is unreachable. The fortress keeps running.") +
|
|
12715
|
+
attRow(renderTopbarAttestationBadge("unverified"), "Unverified", "The signature did not validate. The surface still works; lockdown is still available; the badge tells you to investigate.") +
|
|
12716
|
+
attRow(renderTopbarAttestationBadge("pending"), "Pending", "First-run state. Fortress is signing for the first time. Settles in seconds.") +
|
|
12717
|
+
'</div>' +
|
|
12718
|
+
// Per-agent
|
|
12719
|
+
'<div class="att-section">' +
|
|
12720
|
+
'<div class="att-section-head"><div>' +
|
|
12721
|
+
'<h2>Per-agent. In the agents list and inspect pane.</h2>' +
|
|
12722
|
+
'<p>A square chip beside each agent. Square because an agent is bounded; the fortress (a circle) contains it.</p>' +
|
|
12723
|
+
'</div><span class="label">agents view</span></div>' +
|
|
12724
|
+
'<div class="att-row">' +
|
|
12725
|
+
'<div class="demo" style="display:flex; gap:8px; flex-wrap:wrap;">' +
|
|
12726
|
+
renderAgentAttestationBadge("active") +
|
|
12727
|
+
renderAgentAttestationBadgeForState("degraded", "degraded") +
|
|
12728
|
+
renderAgentAttestationBadgeForState("unverified", "unverified") +
|
|
12729
|
+
'</div>' +
|
|
12730
|
+
'<div class="desc"><strong>Verified, degraded, unverified</strong>' +
|
|
12731
|
+
'<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>' +
|
|
12732
|
+
'</div>' +
|
|
12733
|
+
'</div>' +
|
|
12734
|
+
'</div>' +
|
|
12735
|
+
// Per-action
|
|
12736
|
+
'<div class="att-section">' +
|
|
12737
|
+
'<div class="att-section-head"><div>' +
|
|
12738
|
+
'<h2>Per-action. Inline in the activity timeline.</h2>' +
|
|
12739
|
+
'<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>' +
|
|
12740
|
+
'</div><span class="label">timeline</span></div>' +
|
|
12741
|
+
'<div class="att-row">' +
|
|
12742
|
+
'<div class="demo" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">' +
|
|
12743
|
+
'<span style="font-size:13px; color:var(--ink-2);">14:22:08 doc-reviewer summarized intake.pdf</span>' +
|
|
12744
|
+
renderActionAttestationBadge("verified", "9c7d..2a") +
|
|
12745
|
+
'</div>' +
|
|
12746
|
+
'<div class="desc"><strong>Verified action</strong>' +
|
|
12747
|
+
'<small>The most common shape. Two-byte signature fragment is enough; the full signature is one click away.</small>' +
|
|
12748
|
+
'</div>' +
|
|
12749
|
+
'</div>' +
|
|
12750
|
+
'<div class="att-row">' +
|
|
12751
|
+
'<div class="demo" style="display:flex; gap:10px; align-items:center;">' +
|
|
12752
|
+
'<span style="font-size:13px; color:var(--ink-2);">14:11:47 privacy filter redacted payload</span>' +
|
|
12753
|
+
renderActionAttestationBadge("degraded", "b440..71") +
|
|
12754
|
+
'</div>' +
|
|
12755
|
+
'<div class="desc"><strong>Degraded action</strong>' +
|
|
12756
|
+
'<small>The action signed, but the signature class was less than the policy preferred. Useful when a substrate is still warming up.</small>' +
|
|
12757
|
+
'</div>' +
|
|
12758
|
+
'</div>' +
|
|
12759
|
+
'<div class="att-row">' +
|
|
12760
|
+
'<div class="demo" style="display:flex; gap:10px; align-items:center;">' +
|
|
12761
|
+
'<span style="font-size:13px; color:var(--ink-2);">14:09:02 agent attempted external link</span>' +
|
|
12762
|
+
renderActionAttestationBadge("neutral", "--") +
|
|
12763
|
+
'</div>' +
|
|
12764
|
+
'<div class="desc"><strong>Neutral. Degrade, not destroy.</strong>' +
|
|
12765
|
+
'<small>The signer was unreachable. Rather than hide the action, the badge becomes neutral and a tooltip explains. The action is still recorded.</small>' +
|
|
12766
|
+
'</div>' +
|
|
12767
|
+
'</div>' +
|
|
12768
|
+
'</div>' +
|
|
12769
|
+
// Custody stub
|
|
12770
|
+
'<div class="att-section">' +
|
|
12771
|
+
'<div class="att-section-head"><div>' +
|
|
12772
|
+
'<h2>Custody. Stub for v1.x.</h2>' +
|
|
12773
|
+
'<p>A fourth class, surfaced conservatively. Reserved for forthcoming custody-provenance signatures (x402 payment receipts, ERC-8004 identity assertions). Visible, dashed, clearly stubbed.</p>' +
|
|
12774
|
+
'</div><span class="label">stub</span></div>' +
|
|
12775
|
+
'<div class="att-row">' +
|
|
12776
|
+
'<div class="demo">' +
|
|
12777
|
+
'<span class="att-custody" title="Custody-provenance, v1.x">' +
|
|
12778
|
+
'<span class="seal-stub"></span>' +
|
|
12779
|
+
'<span class="stub-tag">custody. stub</span>' +
|
|
12780
|
+
'</span>' +
|
|
12781
|
+
'</div>' +
|
|
12782
|
+
'<div class="desc"><strong>Custody. Stub.</strong>' +
|
|
12783
|
+
'<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>' +
|
|
12784
|
+
'</div>' +
|
|
12785
|
+
'</div>' +
|
|
12786
|
+
'</div>' +
|
|
12787
|
+
// Tooltip
|
|
12788
|
+
'<div class="att-section">' +
|
|
12789
|
+
'<div class="att-section-head"><div>' +
|
|
12790
|
+
'<h2>Tooltip on failure.</h2>' +
|
|
12791
|
+
'<p>A failed badge is never silent. The tooltip explains in plain language, suggests one action, and confirms the surface is still working.</p>' +
|
|
12792
|
+
'</div><span class="label">degrade not destroy</span></div>' +
|
|
12793
|
+
'<div class="att-row">' +
|
|
12794
|
+
'<div class="demo">' +
|
|
12795
|
+
'<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>' +
|
|
12796
|
+
'</div>' +
|
|
12797
|
+
'<div class="desc"><strong>Plain-language tooltip</strong>' +
|
|
12798
|
+
'<small>Three lines, in order: what happened, what did not break, what to do. No jargon, no stack trace.</small>' +
|
|
12799
|
+
'</div>' +
|
|
12800
|
+
'</div>' +
|
|
12801
|
+
'</div>' +
|
|
12802
|
+
'</div>';
|
|
12803
|
+
}
|
|
12804
|
+
function attRow(demoHtml, strong, smallText) {
|
|
12805
|
+
return '<div class="att-row">' +
|
|
12806
|
+
'<div class="demo">' + demoHtml + '</div>' +
|
|
12807
|
+
'<div class="desc"><strong>' + escHtml(strong) + '</strong>' +
|
|
12808
|
+
'<small>' + escHtml(smallText) + '</small>' +
|
|
12809
|
+
'</div>' +
|
|
12810
|
+
'</div>';
|
|
12811
|
+
}
|
|
12812
|
+
// Gallery-only variant: render a per-agent badge for a given visual state
|
|
12813
|
+
// (verified / degraded / unverified) without going through the agent
|
|
12814
|
+
// status mapping. Used by renderAttestation to show all three states
|
|
12815
|
+
// side by side as design reference.
|
|
12816
|
+
function renderAgentAttestationBadgeForState(cls, label) {
|
|
12817
|
+
return '<span class="att-agent ' + escHtml(cls) + '" title="Agent attestation"><span class="mark"></span>' + escHtml(label) + '</span>';
|
|
12818
|
+
}
|
|
12819
|
+
function relTimeFromIso(iso) {
|
|
12820
|
+
if (!iso) return "";
|
|
12821
|
+
const d = new Date(iso);
|
|
12822
|
+
if (isNaN(d.getTime())) return iso;
|
|
12823
|
+
const diffMs = Date.now() - d.getTime();
|
|
12824
|
+
const diffSec = Math.max(0, Math.floor(diffMs / 1000));
|
|
12825
|
+
if (diffSec < 60) return diffSec + "s ago";
|
|
12826
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
12827
|
+
if (diffMin < 60) return diffMin + "m ago";
|
|
12828
|
+
const diffHr = Math.floor(diffMin / 60);
|
|
12829
|
+
if (diffHr < 24) return diffHr + "h ago";
|
|
12830
|
+
const diffDay = Math.floor(diffHr / 24);
|
|
12831
|
+
return diffDay + "d ago";
|
|
12832
|
+
}
|
|
12571
12833
|
function renderAgentsList() {
|
|
12572
|
-
if (!state.agents.length) return '<h1>Agents</h1
|
|
12834
|
+
if (!state.agents.length) return '<h1>Agents</h1>' +
|
|
12835
|
+
'<div class="agents-empty">' +
|
|
12836
|
+
'<div class="icon-frame"><div class="core"></div></div>' +
|
|
12837
|
+
'<h2>No wrapped agents yet.</h2>' +
|
|
12838
|
+
'<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>' +
|
|
12839
|
+
'<div class="terminal-block"><span class="cmd"><span class="prompt">$</span>sanctuary wrap</span></div>' +
|
|
12840
|
+
'</div>';
|
|
12841
|
+
const count = state.agents.length;
|
|
12842
|
+
const subCopy = count + ' wrapped. Click one to inspect its activity, policy, and pending approvals.';
|
|
12573
12843
|
const rows = state.agents.map(function (a) {
|
|
12574
12844
|
const map = STATUS_MAP[a.status] || STATUS_MAP.unknown;
|
|
12575
|
-
const
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
'<
|
|
12845
|
+
const dotCls = agentStateClass(a.status);
|
|
12846
|
+
const initials = agentInitials(a.agent_id);
|
|
12847
|
+
const role = escHtml(a.harness) + (a.model_provider && a.model_provider.model_id ? ' · ' + escHtml(a.model_provider.model_id) : '');
|
|
12848
|
+
const isSelected = state.selectedAgentId === a.agent_id;
|
|
12849
|
+
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) + '">' +
|
|
12850
|
+
'<div class="agent-identity">' +
|
|
12851
|
+
'<div class="agent-glyph">' + escHtml(initials) + '</div>' +
|
|
12852
|
+
'<div class="agent-name">' +
|
|
12853
|
+
'<strong>' + escHtml(a.agent_id) + '</strong>' +
|
|
12854
|
+
'<small>' + role + '</small>' +
|
|
12855
|
+
'</div>' +
|
|
12856
|
+
'</div>' +
|
|
12857
|
+
'<span class="agent-state">' +
|
|
12858
|
+
'<span class="state-dot ' + dotCls + '"></span>' +
|
|
12859
|
+
escHtml(map.label) +
|
|
12860
|
+
'</span>' +
|
|
12861
|
+
renderAgentAttestationBadge(a.status) +
|
|
12862
|
+
'<span class="agent-last">' + escHtml(relTimeFromIso(a.last_activity_at)) + '</span>' +
|
|
12581
12863
|
'</div>';
|
|
12582
12864
|
}).join("\n");
|
|
12583
|
-
return '<
|
|
12865
|
+
return '<div class="agents-wrap">' +
|
|
12866
|
+
'<div class="page-head">' +
|
|
12867
|
+
'<div>' +
|
|
12868
|
+
'<p class="eyebrow">Agents</p>' +
|
|
12869
|
+
'<h1>Agents.</h1>' +
|
|
12870
|
+
'<p class="sub">' + escHtml(subCopy) + '</p>' +
|
|
12871
|
+
'</div>' +
|
|
12872
|
+
'</div>' +
|
|
12873
|
+
'<div class="agents-layout">' +
|
|
12874
|
+
'<div class="agents-list">' +
|
|
12875
|
+
'<div class="agents-list-head">' +
|
|
12876
|
+
'<span>Agent</span><span>State</span><span>Attestation</span><span>Last seen</span>' +
|
|
12877
|
+
'</div>' +
|
|
12878
|
+
rows +
|
|
12879
|
+
'</div>' +
|
|
12880
|
+
'</div>' +
|
|
12881
|
+
'</div>';
|
|
12584
12882
|
}
|
|
12585
12883
|
|
|
12586
12884
|
function renderAgentDetail() {
|
|
@@ -12591,7 +12889,10 @@ function renderAgentDetail() {
|
|
|
12591
12889
|
const timeline = events.length
|
|
12592
12890
|
? events.map(function (e) {
|
|
12593
12891
|
const t = renderTemplate(e.display_template_id, e.display_template_args);
|
|
12594
|
-
|
|
12892
|
+
const badgeHtml = e.attestation
|
|
12893
|
+
? ' ' + renderActionAttestationBadge(e.attestation.state, e.attestation.fragment)
|
|
12894
|
+
: '';
|
|
12895
|
+
return '<div class="row"><span class="muted">' + escHtml(shortTime(e.emitted_at)) + '</span><span>' + escHtml(t) + badgeHtml + '</span></div>';
|
|
12595
12896
|
}).join("\n")
|
|
12596
12897
|
: '<p class="muted">No activity yet.</p>';
|
|
12597
12898
|
// WP-V1.2 reshape click-to-inspect surface. Clicking "Open inspect
|
|
@@ -12635,53 +12936,99 @@ function renderAgentInspectPanel(agent) {
|
|
|
12635
12936
|
: "";
|
|
12636
12937
|
|
|
12637
12938
|
// State 2: panel loaded.
|
|
12939
|
+
// Sprint Piece 2 PR 4 polish: outer wrapper combines .card with
|
|
12940
|
+
// .inspect-pane (sticky right rail, internal scroll, sectioned body).
|
|
12941
|
+
// The .card class is preserved so the rendered surface keeps its
|
|
12942
|
+
// shared card chrome; .inspect-pane overrides .card padding so the
|
|
12943
|
+
// inspect-head and inspect-body control their own spacing per design.
|
|
12638
12944
|
if (panel) {
|
|
12945
|
+
const dotCls = agentStateClass(agent.status);
|
|
12946
|
+
const stateMap = STATUS_MAP[agent.status] || STATUS_MAP.unknown;
|
|
12639
12947
|
const activity = (panel.recent_activity || []).slice(0, 20);
|
|
12640
12948
|
const activityHtml = activity.length
|
|
12641
|
-
?
|
|
12949
|
+
? '<div class="timeline">' +
|
|
12950
|
+
activity.map(function (e) {
|
|
12642
12951
|
const t = renderTemplate(e.display_template_id, e.display_template_args);
|
|
12643
|
-
|
|
12644
|
-
|
|
12952
|
+
const badgeHtml = e.attestation
|
|
12953
|
+
? renderActionAttestationBadge(e.attestation.state, e.attestation.fragment)
|
|
12954
|
+
: '';
|
|
12955
|
+
return '<div class="timeline-item ok">' +
|
|
12956
|
+
'<div class="ts">' + escHtml(shortTime(e.emitted_at)) + '</div>' +
|
|
12957
|
+
'<div class="what">' + escHtml(t) + '</div>' +
|
|
12958
|
+
(badgeHtml ? '<div class="att">' + badgeHtml + '</div>' : '') +
|
|
12959
|
+
'</div>';
|
|
12960
|
+
}).join("") +
|
|
12961
|
+
'</div>'
|
|
12645
12962
|
: '<p class="muted">No recent activity for this agent.</p>';
|
|
12646
12963
|
|
|
12647
12964
|
const approvals = panel.pending_approvals || [];
|
|
12648
12965
|
const approvalsHtml = approvals.length
|
|
12649
12966
|
? approvals.map(function (item) {
|
|
12650
12967
|
const promptText = renderTemplate(item.display_template_id, item.display_template_args);
|
|
12651
|
-
return '<div class="row">' +
|
|
12652
|
-
'<
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
'
|
|
12968
|
+
return '<div class="approval-row">' +
|
|
12969
|
+
'<div class="what">' +
|
|
12970
|
+
'<span class="pill tone-degraded">' + escHtml(item.tier || "tier1") + '</span>' +
|
|
12971
|
+
escHtml(promptText) +
|
|
12972
|
+
'</div>' +
|
|
12973
|
+
'<div class="actions">' +
|
|
12974
|
+
'<button class="btn" data-action="inbox-deny" data-item-id="' + escHtml(item.item_id) + '">Deny</button>' +
|
|
12975
|
+
'<button class="btn btn-primary" data-action="inbox-approve" data-item-id="' + escHtml(item.item_id) + '">Approve once</button>' +
|
|
12976
|
+
'</div>' +
|
|
12656
12977
|
'</div>';
|
|
12657
|
-
}).join("
|
|
12978
|
+
}).join("")
|
|
12658
12979
|
: '<p class="muted">No pending approvals routed through this agent.</p>';
|
|
12659
12980
|
|
|
12660
|
-
const
|
|
12661
|
-
? '<
|
|
12981
|
+
const policySection = panel.policy_summary
|
|
12982
|
+
? '<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>' +
|
|
12662
12983
|
(panel.policy_summary.channel_template_id
|
|
12663
|
-
? '<
|
|
12984
|
+
? '<div class="policy-line"><span class="k">Template</span><span class="v">' + escHtml(panel.policy_summary.channel_template_id) + '</span></div>'
|
|
12664
12985
|
: '') +
|
|
12665
|
-
'<
|
|
12666
|
-
: '<
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
'<div class="
|
|
12670
|
-
|
|
12671
|
-
|
|
12672
|
-
|
|
12986
|
+
'<div class="policy-line"><span class="k">Bound</span><span class="v">' + escHtml(shortTime(panel.policy_summary.bound_at)) + '</span></div>'
|
|
12987
|
+
: '<div class="policy-line"><span class="k">Policy</span><span class="v">No bound policy yet.</span></div>';
|
|
12988
|
+
|
|
12989
|
+
const modelLine = agent.model_provider
|
|
12990
|
+
? '<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>'
|
|
12991
|
+
: '';
|
|
12992
|
+
|
|
12993
|
+
return '<div class="card inspect-pane">' +
|
|
12994
|
+
'<div class="inspect-head">' +
|
|
12995
|
+
'<div class="row1">' +
|
|
12996
|
+
'<div class="agent-glyph">' + escHtml(agentInitials(agent.agent_id)) + '</div>' +
|
|
12997
|
+
'<h3>' + escHtml(agent.agent_id) + '</h3>' +
|
|
12998
|
+
'<span style="margin-left:auto;">' + renderAgentAttestationBadge(agent.status) + '</span>' +
|
|
12999
|
+
'</div>' +
|
|
13000
|
+
'<div class="meta">' +
|
|
13001
|
+
'<span class="pill ' + (dotCls === "live" ? "tone-verified" : "tone-degraded") + '"><span class="state-dot ' + dotCls + '" style="margin-right:4px;"></span>' + escHtml(stateMap.label) + '</span>' +
|
|
13002
|
+
'<span class="pill">opened ' + escHtml(shortTime(panel.opened_at)) + '</span>' +
|
|
13003
|
+
'<button class="btn btn-quiet" data-action="agent-inspect-open" data-agent-id="' + escHtml(agent.agent_id) + '" title="Refresh inspect panel">Refresh</button>' +
|
|
13004
|
+
'</div>' +
|
|
13005
|
+
'</div>' +
|
|
13006
|
+
'<div class="inspect-body">' +
|
|
13007
|
+
errorBanner +
|
|
13008
|
+
'<div class="inspect-section">' +
|
|
13009
|
+
'<h4>Pending approvals' + (approvals.length ? ' <span class="count">' + approvals.length + '</span>' : '') + '</h4>' +
|
|
13010
|
+
approvalsHtml +
|
|
13011
|
+
'</div>' +
|
|
13012
|
+
'<div class="inspect-section">' +
|
|
13013
|
+
'<h4>Recent activity</h4>' +
|
|
13014
|
+
activityHtml +
|
|
13015
|
+
'</div>' +
|
|
13016
|
+
'<div class="inspect-section">' +
|
|
13017
|
+
'<h4>Policy summary</h4>' +
|
|
13018
|
+
policySection +
|
|
13019
|
+
'</div>' +
|
|
13020
|
+
'<div class="inspect-section">' +
|
|
13021
|
+
'<h4>Identity</h4>' +
|
|
13022
|
+
'<div class="policy-line"><span class="k">Agent id</span><span class="v">' + escHtml(agent.agent_id) + '</span></div>' +
|
|
13023
|
+
'<div class="policy-line"><span class="k">Harness</span><span class="v">' + escHtml(agent.harness) + '</span></div>' +
|
|
13024
|
+
modelLine +
|
|
13025
|
+
'<div class="policy-line"><span class="k">Wrapped at</span><span class="v">' + escHtml(shortTime(agent.wrapped_at)) + '</span></div>' +
|
|
13026
|
+
'</div>' +
|
|
13027
|
+
'<p class="muted" style="margin-top:10px;font-size:12px;">' +
|
|
13028
|
+
'<a href="#activity?agent=' + escHtml(agent.agent_id) + '">View full activity</a> · ' +
|
|
13029
|
+
'<a href="#policy">Edit policy</a>' +
|
|
13030
|
+
'</p>' +
|
|
12673
13031
|
'</div>' +
|
|
12674
|
-
errorBanner +
|
|
12675
|
-
'<h3>Pending approvals</h3>' +
|
|
12676
|
-
approvalsHtml +
|
|
12677
|
-
'<h3 style="margin-top:14px;">Recent activity</h3>' +
|
|
12678
|
-
activityHtml +
|
|
12679
|
-
'<h3 style="margin-top:14px;">Policy</h3>' +
|
|
12680
|
-
'<dl class="kv">' + policyLine + '</dl>' +
|
|
12681
|
-
'<p class="muted" style="margin-top:10px;font-size:12px;">' +
|
|
12682
|
-
'<a href="#activity?agent=' + escHtml(agent.agent_id) + '">View full activity</a> · ' +
|
|
12683
|
-
'<a href="#policy">Edit policy</a>' +
|
|
12684
|
-
'</p>' +
|
|
12685
13032
|
'</div>';
|
|
12686
13033
|
}
|
|
12687
13034
|
|
|
@@ -12830,6 +13177,16 @@ function statusDotClass(status) {
|
|
|
12830
13177
|
return "red";
|
|
12831
13178
|
}
|
|
12832
13179
|
|
|
13180
|
+
// Card-grid polish (Sprint Piece 2 PR 3) maps the badge dot class onto
|
|
13181
|
+
// the shaped glyph token. Sage circle for ok, ochre triangle for warn,
|
|
13182
|
+
// rust diamond for fail. Keep aligned with .status-glyph rules in
|
|
13183
|
+
// html.ts and the .intel-card-status modifier classes.
|
|
13184
|
+
function statusGlyphClass(dotClass) {
|
|
13185
|
+
if (dotClass === "green") return "ok";
|
|
13186
|
+
if (dotClass === "yellow") return "warn";
|
|
13187
|
+
return "fail";
|
|
13188
|
+
}
|
|
13189
|
+
|
|
12833
13190
|
function statusLabel(health) {
|
|
12834
13191
|
if (health === "ok") return "Working";
|
|
12835
13192
|
if (health === "degraded") return "Degraded";
|
|
@@ -12869,13 +13226,18 @@ function renderIntelligenceCenter() {
|
|
|
12869
13226
|
}
|
|
12870
13227
|
const status = state.intelligence.status;
|
|
12871
13228
|
const config = state.intelligence.config || {};
|
|
12872
|
-
const
|
|
13229
|
+
const surfaceCards = SURFACES_ORDER.map(function (surfaceId) {
|
|
12873
13230
|
const surfaceStatus = (status.surfaces || []).find(function (s) { return s.surface === surfaceId; });
|
|
12874
13231
|
if (!surfaceStatus) {
|
|
12875
|
-
return '<div class="intel-row"
|
|
12876
|
-
'<
|
|
12877
|
-
|
|
12878
|
-
|
|
13232
|
+
return '<div class="intel-row intel-card" data-intel-surface="' + escHtml(surfaceId) + '">' +
|
|
13233
|
+
'<div class="intel-card-head">' +
|
|
13234
|
+
'<div class="intel-card-name">' +
|
|
13235
|
+
'<strong>' + escHtml(SURFACE_LABELS[surfaceId] || surfaceId) + '</strong>' +
|
|
13236
|
+
'<small>' + escHtml(surfaceId) + '</small>' +
|
|
13237
|
+
'</div>' +
|
|
13238
|
+
'</div>' +
|
|
13239
|
+
'<div class="muted">No status reported.</div>' +
|
|
13240
|
+
'</div>';
|
|
12879
13241
|
}
|
|
12880
13242
|
const substrate = surfaceStatus.chosen;
|
|
12881
13243
|
const localPick = (config.local_model_picks || {})[surfaceId];
|
|
@@ -12893,41 +13255,62 @@ function renderIntelligenceCenter() {
|
|
|
12893
13255
|
if (provider) currentBadge = currentBadge + " (" + (FRONTIER_PROVIDER_LABELS[provider] || provider) + ")";
|
|
12894
13256
|
}
|
|
12895
13257
|
const dotClass = statusDotClass((surfaceStatus.badge || {}).status || "red");
|
|
13258
|
+
const glyphClass = statusGlyphClass(dotClass);
|
|
12896
13259
|
const failures = surfaceStatus.recentFailures || [];
|
|
12897
13260
|
const expanded = !!state.intelligence.expandedFailures[surfaceId];
|
|
12898
|
-
|
|
13261
|
+
|
|
13262
|
+
// Card foot. The failures toggle is the load-bearing affordance for
|
|
13263
|
+
// the rc.6 ZZ test (button[data-action="intel-failures-toggle"] with
|
|
13264
|
+
// text "recent failures (N)"). Pluralization is "failures" regardless
|
|
13265
|
+
// of N for backward compatibility with the seeded test contract.
|
|
13266
|
+
// Surface zero-failure state as a quiet mono note so the card still
|
|
13267
|
+
// has visual rhythm in its foot row.
|
|
13268
|
+
let footHtml;
|
|
12899
13269
|
if (failures.length > 0) {
|
|
12900
13270
|
const toggleLabel = (expanded ? "Hide" : "View") + " recent failures (" + failures.length + ")";
|
|
12901
|
-
|
|
12902
|
-
|
|
12903
|
-
|
|
12904
|
-
|
|
12905
|
-
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
'</
|
|
12915
|
-
|
|
13271
|
+
footHtml =
|
|
13272
|
+
'<button class="intel-failures-toggle' + (expanded ? ' open' : '') + '" data-action="intel-failures-toggle" data-intel-surface="' + escHtml(surfaceId) + '">' +
|
|
13273
|
+
'<span class="caret"></span>' +
|
|
13274
|
+
escHtml(toggleLabel) +
|
|
13275
|
+
'</button>';
|
|
13276
|
+
} else {
|
|
13277
|
+
footHtml = '<span class="muted mono" style="font-size: 11px;">no recent failures</span>';
|
|
13278
|
+
}
|
|
13279
|
+
|
|
13280
|
+
let failuresBlock = "";
|
|
13281
|
+
if (failures.length > 0 && expanded) {
|
|
13282
|
+
const rows = failures.slice().reverse().map(function (f) {
|
|
13283
|
+
return '<div class="intel-failure-row">' +
|
|
13284
|
+
'<span class="ts">' + escHtml(shortTime(f.ts)) + '</span>' +
|
|
13285
|
+
'<div>' +
|
|
13286
|
+
'<div class="err-class">' + escHtml(f.failureClass) + '</div>' +
|
|
13287
|
+
'<div>' + escHtml(f.snippet) + '</div>' +
|
|
13288
|
+
'</div>' +
|
|
12916
13289
|
'</div>';
|
|
13290
|
+
}).join("");
|
|
13291
|
+
failuresBlock = '<div class="intel-failures">' + rows + '</div>';
|
|
12917
13292
|
}
|
|
12918
|
-
|
|
12919
|
-
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
|
|
12923
|
-
'<
|
|
12924
|
-
'<span class="pill">' + escHtml(currentBadge) + '</span>' +
|
|
12925
|
-
'<span class="muted mono">' + escHtml(statusLabel(surfaceStatus.health)) + '</span>' +
|
|
13293
|
+
|
|
13294
|
+
return '<div class="intel-row intel-card" data-intel-surface="' + escHtml(surfaceId) + '">' +
|
|
13295
|
+
'<div class="intel-card-head">' +
|
|
13296
|
+
'<div class="intel-card-name">' +
|
|
13297
|
+
'<strong>' + escHtml(SURFACE_LABELS[surfaceId] || surfaceId) + '</strong>' +
|
|
13298
|
+
'<small>' + escHtml(surfaceId) + '</small>' +
|
|
12926
13299
|
'</div>' +
|
|
12927
|
-
'<
|
|
12928
|
-
|
|
13300
|
+
'<span class="intel-card-status ' + glyphClass + '" title="' + escHtml(statusLabel(surfaceStatus.health)) + '">' +
|
|
13301
|
+
'<span class="status-glyph ' + glyphClass + '"></span>' +
|
|
13302
|
+
escHtml(statusLabel(surfaceStatus.health)) +
|
|
13303
|
+
'</span>' +
|
|
12929
13304
|
'</div>' +
|
|
12930
|
-
'<div
|
|
13305
|
+
'<div class="intel-substrate">' +
|
|
13306
|
+
'<div class="sub-line primary">' +
|
|
13307
|
+
'<span>' + escHtml(currentBadge) + '</span>' +
|
|
13308
|
+
'<button class="btn-quiet" data-action="intel-picker-open" data-intel-surface="' + escHtml(surfaceId) + '">Change</button>' +
|
|
13309
|
+
'</div>' +
|
|
13310
|
+
'</div>' +
|
|
13311
|
+
'<div class="intel-row-tradeoff">' + escHtml(substrateTradeoff(substrate)) + '</div>' +
|
|
13312
|
+
'<div class="intel-card-foot">' + footHtml + '</div>' +
|
|
13313
|
+
failuresBlock +
|
|
12931
13314
|
'</div>';
|
|
12932
13315
|
}).join("\n");
|
|
12933
13316
|
|
|
@@ -12939,11 +13322,15 @@ function renderIntelligenceCenter() {
|
|
|
12939
13322
|
|
|
12940
13323
|
const modal = state.intelligence.picker.open ? renderIntelligencePicker() : "";
|
|
12941
13324
|
|
|
12942
|
-
return '<section class="intel-
|
|
12943
|
-
'<
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
|
|
13325
|
+
return '<section class="intel-wrap">' +
|
|
13326
|
+
'<div class="page-head">' +
|
|
13327
|
+
'<div>' +
|
|
13328
|
+
'<p class="eyebrow">Intelligence</p>' +
|
|
13329
|
+
'<h1>Substrate routing.</h1>' +
|
|
13330
|
+
'<p class="sub">Six surfaces, six choices. Each surface picks where its thinking happens. Local for privacy. Hosted for capability. Hybrid for both.</p>' +
|
|
13331
|
+
'</div>' +
|
|
13332
|
+
'</div>' +
|
|
13333
|
+
'<div class="intel-grid">' + surfaceCards + '</div>' +
|
|
12947
13334
|
'<section class="intel-panel"><h2>Host capability</h2>' +
|
|
12948
13335
|
'<dl class="intel-hardware">' +
|
|
12949
13336
|
'<dt>Total RAM</dt><dd>' + escHtml(hardware.totalRamGb || "?") + ' GB</dd>' +
|
|
@@ -13736,6 +14123,13 @@ document.addEventListener("click", function (ev) {
|
|
|
13736
14123
|
const intelLocalModel = tgt.getAttribute("data-intel-local-model");
|
|
13737
14124
|
const intelFrontierProvider = tgt.getAttribute("data-intel-frontier-provider");
|
|
13738
14125
|
if (action === "lockdown") return void onLockdownClick();
|
|
14126
|
+
if (action === "theme-toggle") {
|
|
14127
|
+
const isDark = document.documentElement.getAttribute("data-theme") === "dark";
|
|
14128
|
+
const next = isDark ? "light" : "dark";
|
|
14129
|
+
sessionStorage.setItem(THEME_KEY, next);
|
|
14130
|
+
applyTheme(next);
|
|
14131
|
+
return;
|
|
14132
|
+
}
|
|
13739
14133
|
if (action === "intel-reload") { return void fetchIntelligenceState().then(rerender); }
|
|
13740
14134
|
if (action === "intel-picker-open" && intelSurface) return void onIntelPickerOpen(intelSurface);
|
|
13741
14135
|
if (action === "intel-picker-close") return onIntelPickerClose();
|
|
@@ -13889,12 +14283,30 @@ document.addEventListener("input", function (ev) {
|
|
|
13889
14283
|
// then the dashboard view renders a static welcome card with no form
|
|
13890
14284
|
// inputs that could be confused for a working command surface.
|
|
13891
14285
|
|
|
13892
|
-
// Theme:
|
|
14286
|
+
// Theme: explicit operator preference (sessionStorage) overrides system
|
|
14287
|
+
// pref. The toggle button in the topbar dispatches data-action
|
|
14288
|
+
// "theme-toggle" which writes the chosen theme and updates the
|
|
14289
|
+
// [data-theme] attribute. When no explicit choice exists, fall back to
|
|
14290
|
+
// system preference and track changes so dark-mode-at-sunset behavior
|
|
14291
|
+
// keeps working on macOS / Windows.
|
|
14292
|
+
const THEME_KEY = "sanctuary-v11-theme";
|
|
14293
|
+
function applyTheme(theme) {
|
|
14294
|
+
if (theme === "dark") document.documentElement.setAttribute("data-theme", "dark");
|
|
14295
|
+
else document.documentElement.removeAttribute("data-theme");
|
|
14296
|
+
}
|
|
14297
|
+
const explicitTheme = sessionStorage.getItem(THEME_KEY);
|
|
13893
14298
|
const mq = window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null;
|
|
13894
|
-
if (
|
|
14299
|
+
if (explicitTheme === "dark" || explicitTheme === "light") {
|
|
14300
|
+
applyTheme(explicitTheme);
|
|
14301
|
+
} else if (mq && mq.matches) {
|
|
14302
|
+
applyTheme("dark");
|
|
14303
|
+
}
|
|
13895
14304
|
if (mq) mq.addEventListener("change", function (e) {
|
|
13896
|
-
|
|
13897
|
-
|
|
14305
|
+
// Only honor system pref changes when the operator has not made an
|
|
14306
|
+
// explicit choice. Once they toggle, the choice sticks for the
|
|
14307
|
+
// session.
|
|
14308
|
+
if (sessionStorage.getItem(THEME_KEY)) return;
|
|
14309
|
+
applyTheme(e.matches ? "dark" : "light");
|
|
13898
14310
|
});
|
|
13899
14311
|
|
|
13900
14312
|
// Boot.
|
|
@@ -13930,6 +14342,26 @@ var STYLES = String.raw`:root {
|
|
|
13930
14342
|
--rad: 6px;
|
|
13931
14343
|
--rad-lg: 10px;
|
|
13932
14344
|
--shadow: 0 1px 2px rgba(0,0,0,0.04), 0 0 0 1px rgba(0,0,0,0.02);
|
|
14345
|
+
/* Type scale. Names are size-relative, not semantic, so refactors do
|
|
14346
|
+
not need to invent new names. Existing rules used these literal px
|
|
14347
|
+
values; tokens make future polish a one-line change. */
|
|
14348
|
+
--text-xs: 11px;
|
|
14349
|
+
--text-sm: 12px;
|
|
14350
|
+
--text-base: 13px;
|
|
14351
|
+
--text-md: 14px;
|
|
14352
|
+
--text-lg: 16px;
|
|
14353
|
+
--text-xl: 22px;
|
|
14354
|
+
--text-display: 36px;
|
|
14355
|
+
/* Spacing scale (4px multiples). Layout-specific magic numbers
|
|
14356
|
+
(220px sidebar, 360px fortress rail, concierge-card heights) stay
|
|
14357
|
+
as literals because the token system is for component padding /
|
|
14358
|
+
margin / gap, not grid track sizing. */
|
|
14359
|
+
--space-1: 4px;
|
|
14360
|
+
--space-2: 8px;
|
|
14361
|
+
--space-3: 12px;
|
|
14362
|
+
--space-4: 16px;
|
|
14363
|
+
--space-5: 24px;
|
|
14364
|
+
--space-6: 32px;
|
|
13933
14365
|
}
|
|
13934
14366
|
[data-theme="dark"] {
|
|
13935
14367
|
--paper: #121210;
|
|
@@ -13956,7 +14388,7 @@ var STYLES = String.raw`:root {
|
|
|
13956
14388
|
html, body { margin: 0; padding: 0; }
|
|
13957
14389
|
body {
|
|
13958
14390
|
font-family: var(--sans);
|
|
13959
|
-
font-size:
|
|
14391
|
+
font-size: var(--text-md);
|
|
13960
14392
|
background: var(--paper);
|
|
13961
14393
|
color: var(--ink);
|
|
13962
14394
|
line-height: 1.45;
|
|
@@ -13977,20 +14409,27 @@ body {
|
|
|
13977
14409
|
"sidebar main";
|
|
13978
14410
|
}
|
|
13979
14411
|
.sidebar { grid-area: sidebar; background: var(--paper-2); border-right: 1px solid var(--rule); padding: 12px 8px; }
|
|
13980
|
-
.sidebar h1 { font-family: var(--serif); font-size:
|
|
14412
|
+
.sidebar h1 { font-family: var(--serif); font-size: var(--text-lg); margin: 4px 8px 16px; }
|
|
13981
14413
|
.sidebar nav { display: flex; flex-direction: column; gap: 2px; }
|
|
13982
14414
|
.sidebar nav a {
|
|
13983
|
-
display:
|
|
13984
|
-
|
|
14415
|
+
display: flex; align-items: center; gap: var(--space-2);
|
|
14416
|
+
padding: 6px 10px; border-radius: var(--rad);
|
|
14417
|
+
color: var(--ink-2); text-decoration: none; font-size: var(--text-base);
|
|
14418
|
+
}
|
|
14419
|
+
.sidebar nav a svg {
|
|
14420
|
+
flex-shrink: 0; width: 16px; height: 16px;
|
|
14421
|
+
color: var(--ink-3);
|
|
13985
14422
|
}
|
|
13986
14423
|
.sidebar nav a:hover { background: var(--paper-3); }
|
|
14424
|
+
.sidebar nav a:hover svg { color: var(--ink-2); }
|
|
13987
14425
|
.sidebar nav a.active { background: var(--surface); color: var(--ink); border: 1px solid var(--rule); }
|
|
13988
|
-
.
|
|
13989
|
-
.topbar
|
|
14426
|
+
.sidebar nav a.active svg { color: var(--ink); }
|
|
14427
|
+
.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); }
|
|
14428
|
+
.topbar .brand { font-family: var(--serif); font-size: var(--text-md); }
|
|
13990
14429
|
.topbar .pills { display: flex; gap: 6px; flex: 1; }
|
|
13991
14430
|
.pill {
|
|
13992
14431
|
display: inline-flex; align-items: center; gap: 4px;
|
|
13993
|
-
padding: 2px 8px; border-radius: 12px; font-size:
|
|
14432
|
+
padding: 2px 8px; border-radius: 12px; font-size: var(--text-xs);
|
|
13994
14433
|
font-family: var(--mono); border: 1px solid var(--rule);
|
|
13995
14434
|
background: var(--surface-2); color: var(--ink-2);
|
|
13996
14435
|
}
|
|
@@ -14002,7 +14441,7 @@ body {
|
|
|
14002
14441
|
display: inline-flex; align-items: center; gap: 4px;
|
|
14003
14442
|
padding: 4px 10px; border-radius: var(--rad);
|
|
14004
14443
|
background: var(--surface); border: 1px solid var(--rule);
|
|
14005
|
-
font-family: var(--sans); font-size:
|
|
14444
|
+
font-family: var(--sans); font-size: var(--text-sm); color: var(--ink);
|
|
14006
14445
|
cursor: pointer;
|
|
14007
14446
|
}
|
|
14008
14447
|
.btn:hover:not(:disabled) { background: var(--surface-2); }
|
|
@@ -14012,21 +14451,30 @@ body {
|
|
|
14012
14451
|
.btn.btn-danger { background: var(--rust-bg); color: var(--rust); border-color: var(--rust); }
|
|
14013
14452
|
.btn.tier1-pending { background: var(--ochre-bg); color: var(--ochre); border-color: var(--ochre); }
|
|
14014
14453
|
.btn.tier1-engaged { background: var(--rust-bg); color: var(--rust); border-color: var(--rust); }
|
|
14454
|
+
.btn.btn-icon {
|
|
14455
|
+
padding: 4px 6px;
|
|
14456
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
14457
|
+
color: var(--ink-2);
|
|
14458
|
+
}
|
|
14459
|
+
.btn.btn-icon svg { width: 16px; height: 16px; }
|
|
14460
|
+
.btn.btn-icon .icon-sun { display: none; }
|
|
14461
|
+
[data-theme="dark"] .btn.btn-icon .icon-moon { display: none; }
|
|
14462
|
+
[data-theme="dark"] .btn.btn-icon .icon-sun { display: inline; }
|
|
14015
14463
|
.main { grid-area: main; overflow-y: auto; padding: 16px 24px; }
|
|
14016
|
-
.fortress { grid-area: fortress; overflow-y: auto; border-left: 1px solid var(--rule); background: var(--paper-2); padding:
|
|
14464
|
+
.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; }
|
|
14017
14465
|
.app.route-full .fortress { display: none; }
|
|
14018
14466
|
.card {
|
|
14019
14467
|
background: var(--surface); border: 1px solid var(--rule);
|
|
14020
|
-
border-radius: var(--rad); padding:
|
|
14468
|
+
border-radius: var(--rad); padding: var(--space-3);
|
|
14021
14469
|
}
|
|
14022
|
-
.card h3 { margin: 0 0 8px; font-size:
|
|
14470
|
+
.card h3 { margin: 0 0 8px; font-size: var(--text-base); font-weight: 600; color: var(--ink); }
|
|
14023
14471
|
.muted { color: var(--ink-3); }
|
|
14024
|
-
.mono { font-family: var(--mono); font-size:
|
|
14025
|
-
.row { display: flex; align-items: center; gap:
|
|
14472
|
+
.mono { font-family: var(--mono); font-size: var(--text-sm); }
|
|
14473
|
+
.row { display: flex; align-items: center; gap: var(--space-2); padding: 6px 0; border-bottom: 1px dashed var(--rule); }
|
|
14026
14474
|
.row:last-child { border-bottom: 0; }
|
|
14027
14475
|
.row .grow { flex: 1; min-width: 0; }
|
|
14028
14476
|
.agent-row { flex-direction: column; align-items: stretch; gap: 6px; }
|
|
14029
|
-
.agent-row-head { display: flex; align-items: center; gap:
|
|
14477
|
+
.agent-row-head { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
|
|
14030
14478
|
.agent-row-head .grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
14031
14479
|
.agent-row-actions { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
14032
14480
|
/* Click-to-inspect affordance: the head sub-row of a fortress-column
|
|
@@ -14036,7 +14484,7 @@ body {
|
|
|
14036
14484
|
.agent-row-head[data-action="agent-row-inspect-open"] { cursor: pointer; border-radius: var(--rad); padding: 4px 6px; margin: -4px -6px; }
|
|
14037
14485
|
.agent-row-head[data-action="agent-row-inspect-open"]:hover { background: var(--paper-3); }
|
|
14038
14486
|
.agent-row-head[data-action="agent-row-inspect-open"]:focus-visible { outline: 2px solid var(--ink-3); outline-offset: 1px; }
|
|
14039
|
-
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; font-size:
|
|
14487
|
+
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; font-size: var(--text-sm); }
|
|
14040
14488
|
.kv dt { color: var(--ink-3); }
|
|
14041
14489
|
.kv dd { margin: 0; color: var(--ink); }
|
|
14042
14490
|
.glyph { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--ink-4); }
|
|
@@ -14047,74 +14495,74 @@ body {
|
|
|
14047
14495
|
.toast {
|
|
14048
14496
|
position: fixed; bottom: 16px; right: 16px;
|
|
14049
14497
|
background: var(--ink); color: var(--paper); padding: 8px 12px;
|
|
14050
|
-
border-radius: var(--rad); font-size:
|
|
14498
|
+
border-radius: var(--rad); font-size: var(--text-sm); z-index: 1000;
|
|
14051
14499
|
max-width: 360px;
|
|
14052
14500
|
}
|
|
14053
14501
|
.toast.error { background: var(--rust); color: var(--paper); }
|
|
14054
|
-
.layer-card { background: var(--surface-2); border: 1px solid var(--rule); border-radius: var(--rad); padding:
|
|
14055
|
-
.layer-card h4 { margin: 0 0 4px; font-size:
|
|
14056
|
-
.layer-card p { margin: 0; font-size:
|
|
14057
|
-
.chat-thread { display: flex; flex-direction: column; gap:
|
|
14502
|
+
.layer-card { background: var(--surface-2); border: 1px solid var(--rule); border-radius: var(--rad); padding: var(--space-2); }
|
|
14503
|
+
.layer-card h4 { margin: 0 0 4px; font-size: var(--text-sm); font-weight: 600; }
|
|
14504
|
+
.layer-card p { margin: 0; font-size: var(--text-xs); color: var(--ink-3); }
|
|
14505
|
+
.chat-thread { display: flex; flex-direction: column; gap: var(--space-2); padding-bottom: 12px; }
|
|
14058
14506
|
.chat-msg { padding: 8px 10px; border-radius: var(--rad); border: 1px solid var(--rule); background: var(--surface); max-width: 78%; }
|
|
14059
|
-
.chat-msg.system { background: var(--paper-3); color: var(--ink-3); font-size:
|
|
14507
|
+
.chat-msg.system { background: var(--paper-3); color: var(--ink-3); font-size: var(--text-sm); max-width: 100%; }
|
|
14060
14508
|
.chat-msg.agent { align-self: flex-start; }
|
|
14061
14509
|
.chat-msg.operator { align-self: flex-end; background: var(--ink); color: var(--paper); }
|
|
14062
14510
|
.chat-msg .meta { font-size: 10px; color: var(--ink-4); margin-top: 4px; }
|
|
14063
|
-
.composer { display: flex; gap:
|
|
14511
|
+
.composer { display: flex; gap: var(--space-2); padding: var(--space-2); border-top: 1px solid var(--rule); }
|
|
14064
14512
|
.composer input { flex: 1; padding: 6px 8px; border: 1px solid var(--rule); border-radius: var(--rad); font-family: var(--sans); }
|
|
14065
14513
|
.wizard-step { padding: 10px; border: 1px solid var(--rule); border-radius: var(--rad); margin-bottom: 8px; background: var(--surface); }
|
|
14066
14514
|
.wizard-step.active { border-color: var(--ink); }
|
|
14067
14515
|
.wizard-step.done { background: var(--sage-bg); border-color: var(--sage); }
|
|
14068
|
-
.code-block { font-family: var(--mono); background: var(--paper-3); padding:
|
|
14516
|
+
.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; }
|
|
14069
14517
|
.policy-center { max-width: 980px; margin: 0 auto; }
|
|
14070
|
-
.policy-center .eyebrow { margin: 0 0 6px; color: var(--ink-3); font-family: var(--mono); font-size:
|
|
14071
|
-
.policy-center h1 { font-family: var(--serif); font-size:
|
|
14518
|
+
.policy-center .eyebrow { margin: 0 0 6px; color: var(--ink-3); font-family: var(--mono); font-size: var(--text-sm); letter-spacing: 0; }
|
|
14519
|
+
.policy-center h1 { font-family: var(--serif); font-size: var(--text-display); line-height: 1.08; font-weight: 400; margin: 0 0 10px; }
|
|
14072
14520
|
.policy-subtitle { max-width: 860px; color: var(--ink-2); font-size: 15px; margin: 0 0 24px; }
|
|
14073
14521
|
.policy-panel { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--rad-lg); padding: 20px; margin: 18px 0; }
|
|
14074
|
-
.policy-panel h2 { font-family: var(--serif); font-size:
|
|
14075
|
-
.template-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap:
|
|
14522
|
+
.policy-panel h2 { font-family: var(--serif); font-size: var(--text-xl); font-weight: 400; margin: 0 0 14px; }
|
|
14523
|
+
.template-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3); }
|
|
14076
14524
|
.template-card { background: var(--surface-2); border: 1px solid var(--rule); border-radius: var(--rad); padding: 14px; min-height: 132px; }
|
|
14077
|
-
.template-card-head { display: flex; justify-content: space-between; align-items: center; gap:
|
|
14078
|
-
.severity { border-radius: 999px; padding: 2px 9px; font-family: var(--mono); font-size:
|
|
14525
|
+
.template-card-head { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); margin-bottom: 12px; }
|
|
14526
|
+
.severity { border-radius: 999px; padding: 2px 9px; font-family: var(--mono); font-size: var(--text-xs); font-weight: 700; }
|
|
14079
14527
|
.severity.low { color: var(--sage); background: var(--sage-bg); }
|
|
14080
14528
|
.severity.medium { color: var(--ochre); background: var(--ochre-bg); }
|
|
14081
14529
|
.template-id { background: var(--paper-3); border-radius: var(--rad); padding: 2px 7px; color: var(--ink-3); }
|
|
14082
|
-
.template-card h3 { font-size:
|
|
14083
|
-
.template-card p { color: var(--ink-3); margin: 0; font-size:
|
|
14530
|
+
.template-card h3 { font-size: var(--text-lg); margin: 0 0 6px; }
|
|
14531
|
+
.template-card p { color: var(--ink-3); margin: 0; font-size: var(--text-md); }
|
|
14084
14532
|
.rules-scroll { overflow-x: auto; }
|
|
14085
14533
|
.rules-table { width: 100%; border-collapse: collapse; min-width: 760px; }
|
|
14086
|
-
.rules-table th { text-align: left; color: var(--ink-3); font-family: var(--mono); font-size:
|
|
14534
|
+
.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); }
|
|
14087
14535
|
.rules-table td { padding: 12px 10px; border-bottom: 1px solid var(--rule); vertical-align: top; }
|
|
14088
14536
|
.link-btn, .template-cell { border: 0; background: transparent; color: var(--ink); padding: 0; cursor: pointer; font: inherit; text-align: left; }
|
|
14089
14537
|
.template-cell { font-family: var(--mono); max-width: 180px; overflow-wrap: anywhere; }
|
|
14090
14538
|
.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; }
|
|
14091
14539
|
.template-picker-options { display: grid; gap: 6px; max-height: 320px; overflow-y: auto; }
|
|
14092
|
-
.template-option { display: grid; grid-template-columns: 18px 1fr; gap:
|
|
14540
|
+
.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); }
|
|
14093
14541
|
.template-option small { display: block; color: var(--ink-3); margin-top: 2px; }
|
|
14094
|
-
.template-picker-actions { display: flex; justify-content: flex-end; gap:
|
|
14542
|
+
.template-picker-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: 10px; }
|
|
14095
14543
|
.allow-count { color: var(--sage); font-weight: 700; }
|
|
14096
14544
|
.block-count { color: var(--rust); }
|
|
14097
14545
|
.toggle-on { display: inline-block; width: 28px; height: 16px; border-radius: 999px; background: var(--sage); position: relative; }
|
|
14098
14546
|
.toggle-on::after { content: ""; position: absolute; right: 2px; top: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--surface); }
|
|
14099
14547
|
.error-text { color: var(--rust); margin: 8px 0 0; }
|
|
14100
14548
|
.intel-center { max-width: 980px; margin: 0 auto; }
|
|
14101
|
-
.intel-center .eyebrow { margin: 0 0 6px; color: var(--ink-3); font-family: var(--mono); font-size:
|
|
14102
|
-
.intel-center h1 { font-family: var(--serif); font-size:
|
|
14549
|
+
.intel-center .eyebrow { margin: 0 0 6px; color: var(--ink-3); font-family: var(--mono); font-size: var(--text-sm); letter-spacing: 0; }
|
|
14550
|
+
.intel-center h1 { font-family: var(--serif); font-size: var(--text-display); line-height: 1.08; font-weight: 400; margin: 0 0 10px; }
|
|
14103
14551
|
.intel-subtitle { max-width: 860px; color: var(--ink-2); font-size: 15px; margin: 0 0 24px; }
|
|
14104
14552
|
.intel-panel { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--rad-lg); padding: 20px; margin: 18px 0; }
|
|
14105
|
-
.intel-panel h2 { font-family: var(--serif); font-size:
|
|
14106
|
-
.intel-row { display: grid; grid-template-columns: 200px 1fr auto; gap:
|
|
14553
|
+
.intel-panel h2 { font-family: var(--serif); font-size: var(--text-xl); font-weight: 400; margin: 0 0 14px; }
|
|
14554
|
+
.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; }
|
|
14107
14555
|
.intel-row:last-child { border-bottom: 0; }
|
|
14108
14556
|
.intel-row-name { font-weight: 600; }
|
|
14109
|
-
.intel-row-name small { display: block; color: var(--ink-3); font-weight: 400; font-size:
|
|
14557
|
+
.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); }
|
|
14110
14558
|
.intel-row-body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
14111
|
-
.intel-row-current { display: flex; gap:
|
|
14112
|
-
.intel-row-tradeoff { color: var(--ink-2); font-size:
|
|
14559
|
+
.intel-row-current { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
|
|
14560
|
+
.intel-row-tradeoff { color: var(--ink-2); font-size: var(--text-base); line-height: 1.5; }
|
|
14113
14561
|
.intel-status-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
|
|
14114
14562
|
.intel-status-dot.green { background: var(--sage); }
|
|
14115
14563
|
.intel-status-dot.yellow { background: var(--ochre); }
|
|
14116
14564
|
.intel-status-dot.red { background: var(--rust); }
|
|
14117
|
-
.intel-hardware { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; font-size:
|
|
14565
|
+
.intel-hardware { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; font-size: var(--text-base); }
|
|
14118
14566
|
.intel-hardware dt { color: var(--ink-3); font-family: var(--mono); }
|
|
14119
14567
|
.intel-hardware dd { margin: 0; }
|
|
14120
14568
|
.intel-modal-backdrop {
|
|
@@ -14127,33 +14575,162 @@ body {
|
|
|
14127
14575
|
box-shadow: 0 8px 32px rgba(0,0,0,0.18); padding: 24px;
|
|
14128
14576
|
width: 100%; max-width: 640px;
|
|
14129
14577
|
}
|
|
14130
|
-
.intel-modal h2 { font-family: var(--serif); font-size:
|
|
14131
|
-
.intel-modal-subtitle { color: var(--ink-3); margin: 0 0 18px; font-size:
|
|
14578
|
+
.intel-modal h2 { font-family: var(--serif); font-size: var(--text-xl); font-weight: 400; margin: 0 0 8px; }
|
|
14579
|
+
.intel-modal-subtitle { color: var(--ink-3); margin: 0 0 18px; font-size: var(--text-base); }
|
|
14132
14580
|
.intel-option {
|
|
14133
|
-
border: 1px solid var(--rule); border-radius: var(--rad); padding:
|
|
14581
|
+
border: 1px solid var(--rule); border-radius: var(--rad); padding: var(--space-3);
|
|
14134
14582
|
margin-bottom: 10px; background: var(--surface-2); cursor: pointer;
|
|
14135
14583
|
display: grid; grid-template-columns: 18px 1fr; gap: 10px; align-items: start;
|
|
14136
14584
|
}
|
|
14137
14585
|
.intel-option.selected { border-color: var(--ink); background: var(--surface); }
|
|
14138
|
-
.intel-option-body strong { display: block; font-size:
|
|
14139
|
-
.intel-option-body small { display: block; color: var(--ink-3); font-size:
|
|
14586
|
+
.intel-option-body strong { display: block; font-size: var(--text-md); margin-bottom: 4px; }
|
|
14587
|
+
.intel-option-body small { display: block; color: var(--ink-3); font-size: var(--text-sm); line-height: 1.5; }
|
|
14140
14588
|
.intel-suboptions { margin-top: 10px; padding: 10px; background: var(--paper-3); border-radius: var(--rad); }
|
|
14141
|
-
.intel-suboptions label { display: block; margin: 6px 0; font-size:
|
|
14589
|
+
.intel-suboptions label { display: block; margin: 6px 0; font-size: var(--text-base); }
|
|
14142
14590
|
.intel-suboptions input[type="text"], .intel-suboptions input[type="password"] {
|
|
14143
14591
|
width: 100%; padding: 6px 8px; border: 1px solid var(--rule); border-radius: var(--rad);
|
|
14144
|
-
font-family: var(--mono); font-size:
|
|
14592
|
+
font-family: var(--mono); font-size: var(--text-sm); box-sizing: border-box;
|
|
14593
|
+
}
|
|
14594
|
+
.intel-modal-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: 18px; }
|
|
14595
|
+
/*
|
|
14596
|
+
* Intelligence panel polish (Sprint Piece 2 PR 3). Card grid layout
|
|
14597
|
+
* replaces the legacy 3-col row visual. The legacy .intel-row and
|
|
14598
|
+
* .intel-status-dot rules above are retained for the e2e selector
|
|
14599
|
+
* contract (.intel-row[data-intel-surface="..."]) and as the responsive
|
|
14600
|
+
* fallback. Cards render as a flex column with a substrate inset,
|
|
14601
|
+
* status badge with shaped glyph, and a recent-failures toggle in the
|
|
14602
|
+
* card foot.
|
|
14603
|
+
*/
|
|
14604
|
+
.intel-wrap { max-width: 1000px; margin: 0 auto; }
|
|
14605
|
+
.intel-grid {
|
|
14606
|
+
display: grid;
|
|
14607
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
14608
|
+
gap: 12px;
|
|
14609
|
+
}
|
|
14610
|
+
.intel-card {
|
|
14611
|
+
background: var(--surface);
|
|
14612
|
+
border: 1px solid var(--rule);
|
|
14613
|
+
border-radius: var(--rad-lg);
|
|
14614
|
+
padding: 16px;
|
|
14615
|
+
display: flex; flex-direction: column;
|
|
14616
|
+
gap: 12px;
|
|
14617
|
+
}
|
|
14618
|
+
.intel-card-head {
|
|
14619
|
+
display: flex; align-items: flex-start; justify-content: space-between;
|
|
14620
|
+
gap: 10px;
|
|
14621
|
+
}
|
|
14622
|
+
.intel-card-name {
|
|
14623
|
+
display: flex; flex-direction: column; gap: 2px;
|
|
14624
|
+
min-width: 0;
|
|
14625
|
+
}
|
|
14626
|
+
.intel-card-name strong {
|
|
14627
|
+
font-family: var(--serif); font-weight: 500;
|
|
14628
|
+
font-size: 15px; letter-spacing: 0.005em;
|
|
14629
|
+
}
|
|
14630
|
+
.intel-card-name small {
|
|
14631
|
+
color: var(--ink-3); font-size: 11px; font-family: var(--mono);
|
|
14632
|
+
}
|
|
14633
|
+
.intel-card-status {
|
|
14634
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
14635
|
+
font-family: var(--mono); font-size: 11px;
|
|
14636
|
+
padding: 3px 9px; border-radius: 999px;
|
|
14637
|
+
border: 1px solid var(--rule); background: var(--surface-2);
|
|
14638
|
+
flex-shrink: 0;
|
|
14639
|
+
}
|
|
14640
|
+
.intel-card-status.ok { color: var(--sage); border-color: var(--sage); background: var(--sage-bg); }
|
|
14641
|
+
.intel-card-status.warn { color: var(--ochre); border-color: var(--ochre); background: var(--ochre-bg); }
|
|
14642
|
+
.intel-card-status.fail { color: var(--rust); border-color: var(--rust); background: var(--rust-bg); }
|
|
14643
|
+
.status-glyph {
|
|
14644
|
+
width: 10px; height: 10px;
|
|
14645
|
+
position: relative; flex-shrink: 0;
|
|
14145
14646
|
}
|
|
14146
|
-
.
|
|
14647
|
+
.status-glyph.ok::before {
|
|
14648
|
+
content: ""; position: absolute; inset: 0;
|
|
14649
|
+
border-radius: 50%; background: currentColor;
|
|
14650
|
+
}
|
|
14651
|
+
.status-glyph.warn::before {
|
|
14652
|
+
content: ""; position: absolute; inset: 0;
|
|
14653
|
+
background: currentColor;
|
|
14654
|
+
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
|
|
14655
|
+
}
|
|
14656
|
+
.status-glyph.fail::before {
|
|
14657
|
+
content: ""; position: absolute; inset: 1px;
|
|
14658
|
+
background: currentColor;
|
|
14659
|
+
clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
|
|
14660
|
+
}
|
|
14661
|
+
.intel-substrate {
|
|
14662
|
+
display: flex; flex-direction: column; gap: 4px;
|
|
14663
|
+
padding: 10px 12px;
|
|
14664
|
+
background: var(--paper-2);
|
|
14665
|
+
border: 1px solid var(--rule);
|
|
14666
|
+
border-radius: var(--rad);
|
|
14667
|
+
}
|
|
14668
|
+
.intel-substrate .sub-line {
|
|
14669
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
14670
|
+
gap: 10px; font-size: 12px;
|
|
14671
|
+
}
|
|
14672
|
+
.intel-substrate .sub-line.primary {
|
|
14673
|
+
font-family: var(--mono); font-size: 13px; color: var(--ink);
|
|
14674
|
+
}
|
|
14675
|
+
.intel-substrate .sub-line.secondary { color: var(--ink-3); font-size: 11px; }
|
|
14676
|
+
.intel-card-foot {
|
|
14677
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
14678
|
+
gap: 8px; padding-top: 4px;
|
|
14679
|
+
}
|
|
14680
|
+
.intel-failures-toggle {
|
|
14681
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
14682
|
+
font-size: 12px; font-family: var(--mono);
|
|
14683
|
+
color: var(--ink-3);
|
|
14684
|
+
background: transparent; border: 0; padding: 0;
|
|
14685
|
+
cursor: pointer;
|
|
14686
|
+
}
|
|
14687
|
+
.intel-failures-toggle:hover { color: var(--ink); }
|
|
14688
|
+
.intel-failures-toggle .caret {
|
|
14689
|
+
display: inline-block; width: 0; height: 0;
|
|
14690
|
+
border-left: 4px solid transparent;
|
|
14691
|
+
border-right: 4px solid transparent;
|
|
14692
|
+
border-top: 5px solid currentColor;
|
|
14693
|
+
transition: transform 160ms ease;
|
|
14694
|
+
}
|
|
14695
|
+
.intel-failures-toggle.open .caret { transform: rotate(180deg); }
|
|
14696
|
+
.intel-failures {
|
|
14697
|
+
border-top: 1px solid var(--rule);
|
|
14698
|
+
padding-top: 12px;
|
|
14699
|
+
display: flex; flex-direction: column;
|
|
14700
|
+
gap: 8px;
|
|
14701
|
+
}
|
|
14702
|
+
.intel-failure-row {
|
|
14703
|
+
display: grid; grid-template-columns: 88px 1fr; gap: 12px;
|
|
14704
|
+
font-size: 12px; padding: 8px 10px;
|
|
14705
|
+
border-radius: var(--rad);
|
|
14706
|
+
background: var(--paper-2);
|
|
14707
|
+
border: 1px solid var(--rule);
|
|
14708
|
+
}
|
|
14709
|
+
.intel-failure-row .ts {
|
|
14710
|
+
font-family: var(--mono); font-size: 11px; color: var(--ink-3);
|
|
14711
|
+
}
|
|
14712
|
+
.intel-failure-row .err-class {
|
|
14713
|
+
font-family: var(--mono); font-size: 10px;
|
|
14714
|
+
letter-spacing: 0.04em; text-transform: uppercase;
|
|
14715
|
+
color: var(--rust); margin-bottom: 2px;
|
|
14716
|
+
}
|
|
14717
|
+
.btn-quiet {
|
|
14718
|
+
background: transparent; border: 1px solid var(--rule);
|
|
14719
|
+
padding: 2px 6px; font-size: 11px;
|
|
14720
|
+
border-radius: var(--rad); cursor: pointer;
|
|
14721
|
+
color: var(--ink-2); font-family: var(--sans);
|
|
14722
|
+
}
|
|
14723
|
+
.btn-quiet:hover { background: var(--surface-2); color: var(--ink); }
|
|
14147
14724
|
.banner-warn {
|
|
14148
14725
|
background: var(--ochre-bg); color: var(--ochre); border: 1px solid var(--ochre);
|
|
14149
|
-
border-radius: var(--rad); padding: 8px 12px; margin: 8px 0; font-size:
|
|
14726
|
+
border-radius: var(--rad); padding: 8px 12px; margin: 8px 0; font-size: var(--text-base);
|
|
14150
14727
|
}
|
|
14151
14728
|
.banner-info {
|
|
14152
14729
|
background: var(--indigo-bg); color: var(--indigo); border: 1px solid var(--indigo);
|
|
14153
|
-
border-radius: var(--rad); padding: 8px 12px; margin: 8px 0; font-size:
|
|
14730
|
+
border-radius: var(--rad); padding: 8px 12px; margin: 8px 0; font-size: var(--text-base);
|
|
14154
14731
|
}
|
|
14155
14732
|
.btn.chip {
|
|
14156
|
-
border-radius: 999px; padding: 4px 12px; font-size:
|
|
14733
|
+
border-radius: 999px; padding: 4px 12px; font-size: var(--text-sm);
|
|
14157
14734
|
background: var(--surface-2); border-color: var(--rule);
|
|
14158
14735
|
}
|
|
14159
14736
|
.btn.chip:hover:not(:disabled) { background: var(--paper-3); }
|
|
@@ -14170,85 +14747,210 @@ body {
|
|
|
14170
14747
|
* dynamically, but the input box is below the fold, so I still have
|
|
14171
14748
|
* to scroll." rc.5 closes that with a structural layout fix.
|
|
14172
14749
|
*/
|
|
14750
|
+
/* Sprint Piece 2 PR 2 (2026-05-03): concierge surface polish.
|
|
14751
|
+
* Translates Claude Design references at
|
|
14752
|
+
* server/docs/design-refs/sprint-piece-2/surface-concierge.jsx and the
|
|
14753
|
+
* Surface 1 block of surfaces.css. The bounded-card layout above is
|
|
14754
|
+
* preserved verbatim because rc.5 and the DDD e2e suite depend on it;
|
|
14755
|
+
* the Claude Design reference uses height: 720px for the card, but
|
|
14756
|
+
* production keeps the calc-based bounded height so the card adapts
|
|
14757
|
+
* to the operator viewport. The polish lands the persona glyph-ring,
|
|
14758
|
+
* mono uppercase author labels, paper-2 background for concierge
|
|
14759
|
+
* replies, suggest-grid empty-state cards, and the composer input-wrap
|
|
14760
|
+
* with the keyboard-shortcut pill.
|
|
14761
|
+
*/
|
|
14762
|
+
.concierge-wrap { max-width: 880px; margin: 0 auto; }
|
|
14763
|
+
.page-head {
|
|
14764
|
+
display: flex; align-items: flex-end; justify-content: space-between;
|
|
14765
|
+
gap: var(--space-4);
|
|
14766
|
+
margin-bottom: 18px; padding-bottom: 14px;
|
|
14767
|
+
border-bottom: 1px solid var(--rule);
|
|
14768
|
+
}
|
|
14769
|
+
.page-head .eyebrow {
|
|
14770
|
+
font-family: var(--mono); font-size: var(--text-xs);
|
|
14771
|
+
letter-spacing: 0.08em; text-transform: uppercase;
|
|
14772
|
+
color: var(--ink-3);
|
|
14773
|
+
margin: 0 0 6px;
|
|
14774
|
+
}
|
|
14775
|
+
.page-head h1 {
|
|
14776
|
+
font-family: var(--serif); font-weight: 400;
|
|
14777
|
+
font-size: 28px; letter-spacing: -0.01em;
|
|
14778
|
+
margin: 0 0 4px;
|
|
14779
|
+
}
|
|
14780
|
+
.page-head .sub {
|
|
14781
|
+
color: var(--ink-3); margin: 0;
|
|
14782
|
+
font-size: var(--text-base); max-width: 60ch;
|
|
14783
|
+
}
|
|
14173
14784
|
.concierge-card {
|
|
14174
14785
|
display: flex; flex-direction: column;
|
|
14175
14786
|
height: calc(100vh - 180px);
|
|
14176
14787
|
max-height: calc(100vh - 180px);
|
|
14177
14788
|
min-height: 360px;
|
|
14178
|
-
padding:
|
|
14789
|
+
padding: 18px 22px 14px;
|
|
14179
14790
|
gap: 0;
|
|
14180
14791
|
}
|
|
14181
14792
|
.concierge-header {
|
|
14182
14793
|
display: flex; align-items: center; justify-content: space-between;
|
|
14183
|
-
gap:
|
|
14794
|
+
gap: var(--space-3); padding-bottom: 14px;
|
|
14795
|
+
border-bottom: 1px solid var(--rule);
|
|
14184
14796
|
flex-wrap: wrap;
|
|
14185
14797
|
flex-shrink: 0;
|
|
14186
14798
|
}
|
|
14187
|
-
.concierge-persona {
|
|
14188
|
-
|
|
14189
|
-
|
|
14799
|
+
.concierge-persona { display: flex; align-items: center; gap: 10px; }
|
|
14800
|
+
.concierge-persona .glyph-ring {
|
|
14801
|
+
width: 26px; height: 26px;
|
|
14802
|
+
border: 1.5px solid var(--ink-2);
|
|
14803
|
+
border-radius: 50%;
|
|
14804
|
+
position: relative;
|
|
14805
|
+
flex-shrink: 0;
|
|
14806
|
+
}
|
|
14807
|
+
.concierge-persona .glyph-ring::after {
|
|
14808
|
+
content: ""; position: absolute; inset: 5px;
|
|
14809
|
+
border-radius: 50%; background: var(--ink-2);
|
|
14810
|
+
}
|
|
14811
|
+
.concierge-persona-text { display: flex; flex-direction: column; }
|
|
14812
|
+
.concierge-persona-text strong {
|
|
14813
|
+
font-family: var(--serif); font-weight: 500;
|
|
14814
|
+
font-size: 15px; letter-spacing: 0.005em;
|
|
14815
|
+
}
|
|
14816
|
+
.concierge-persona-text small {
|
|
14817
|
+
color: var(--ink-3); font-size: var(--text-xs);
|
|
14818
|
+
font-family: var(--mono);
|
|
14819
|
+
}
|
|
14820
|
+
.concierge-meta {
|
|
14821
|
+
display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
|
|
14190
14822
|
}
|
|
14191
|
-
.concierge-persona strong { font-size: 14px; }
|
|
14192
14823
|
.concierge-badge { white-space: nowrap; }
|
|
14193
14824
|
.concierge-history {
|
|
14194
14825
|
flex: 1 1 auto;
|
|
14195
14826
|
min-height: 0;
|
|
14196
14827
|
overflow-y: auto;
|
|
14197
|
-
padding:
|
|
14198
|
-
display: flex; flex-direction: column; gap:
|
|
14828
|
+
padding: 18px 4px 6px;
|
|
14829
|
+
display: flex; flex-direction: column; gap: 18px;
|
|
14199
14830
|
}
|
|
14200
14831
|
.concierge-msg {
|
|
14201
|
-
display: flex; flex-direction: column; gap:
|
|
14202
|
-
max-width:
|
|
14832
|
+
display: flex; flex-direction: column; gap: 5px;
|
|
14833
|
+
max-width: 78%;
|
|
14203
14834
|
}
|
|
14204
14835
|
.concierge-msg-author {
|
|
14205
|
-
font-size:
|
|
14836
|
+
font-size: 10px;
|
|
14837
|
+
font-family: var(--mono);
|
|
14838
|
+
letter-spacing: 0.04em;
|
|
14839
|
+
text-transform: uppercase;
|
|
14840
|
+
color: var(--ink-4);
|
|
14206
14841
|
}
|
|
14207
14842
|
.concierge-msg-body {
|
|
14208
|
-
padding:
|
|
14209
|
-
border:
|
|
14210
|
-
|
|
14211
|
-
|
|
14843
|
+
padding: 11px 14px;
|
|
14844
|
+
border-radius: 12px;
|
|
14845
|
+
border: 1px solid var(--rule);
|
|
14846
|
+
background: var(--surface);
|
|
14847
|
+
font-size: var(--text-md);
|
|
14848
|
+
line-height: 1.55;
|
|
14849
|
+
white-space: pre-wrap;
|
|
14850
|
+
word-wrap: break-word;
|
|
14212
14851
|
}
|
|
14213
14852
|
.concierge-msg-concierge { align-self: flex-start; }
|
|
14853
|
+
.concierge-msg-concierge .concierge-msg-body {
|
|
14854
|
+
background: var(--paper-2);
|
|
14855
|
+
}
|
|
14214
14856
|
.concierge-msg-operator { align-self: flex-end; align-items: flex-end; }
|
|
14215
14857
|
.concierge-msg-operator .concierge-msg-body {
|
|
14216
14858
|
background: var(--ink); color: var(--paper); border-color: var(--ink);
|
|
14217
14859
|
}
|
|
14860
|
+
.concierge-msg-meta {
|
|
14861
|
+
display: flex; gap: 6px;
|
|
14862
|
+
font-size: 10px;
|
|
14863
|
+
color: var(--ink-4);
|
|
14864
|
+
font-family: var(--mono);
|
|
14865
|
+
}
|
|
14218
14866
|
.concierge-empty {
|
|
14219
|
-
|
|
14220
|
-
|
|
14867
|
+
flex: 1 1 auto;
|
|
14868
|
+
display: flex; flex-direction: column; gap: 22px;
|
|
14869
|
+
justify-content: center;
|
|
14870
|
+
padding: 24px 12px;
|
|
14871
|
+
}
|
|
14872
|
+
.concierge-empty-headline { max-width: 52ch; }
|
|
14873
|
+
.concierge-empty-headline h2 {
|
|
14874
|
+
font-family: var(--serif); font-weight: 400;
|
|
14875
|
+
font-size: var(--text-xl); margin: 0 0 6px;
|
|
14876
|
+
letter-spacing: -0.005em;
|
|
14877
|
+
}
|
|
14878
|
+
.concierge-empty-headline p {
|
|
14879
|
+
color: var(--ink-3); margin: 0;
|
|
14880
|
+
font-size: var(--text-md); line-height: 1.55;
|
|
14881
|
+
}
|
|
14882
|
+
.concierge-suggest-grid {
|
|
14883
|
+
display: grid;
|
|
14884
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
14885
|
+
gap: 10px;
|
|
14886
|
+
}
|
|
14887
|
+
.concierge-suggest {
|
|
14888
|
+
background: var(--surface);
|
|
14889
|
+
border: 1px solid var(--rule);
|
|
14890
|
+
border-radius: var(--rad);
|
|
14891
|
+
padding: 12px 14px;
|
|
14892
|
+
font-size: var(--text-base);
|
|
14893
|
+
cursor: pointer;
|
|
14894
|
+
display: flex; flex-direction: column;
|
|
14895
|
+
gap: 6px;
|
|
14896
|
+
text-align: left;
|
|
14897
|
+
font-family: var(--sans);
|
|
14898
|
+
color: var(--ink);
|
|
14899
|
+
}
|
|
14900
|
+
.concierge-suggest:hover:not(:disabled) {
|
|
14901
|
+
background: var(--surface-2);
|
|
14902
|
+
border-color: var(--rule-2);
|
|
14903
|
+
}
|
|
14904
|
+
.concierge-suggest:disabled {
|
|
14905
|
+
cursor: not-allowed; color: var(--ink-4); opacity: 0.7;
|
|
14906
|
+
}
|
|
14907
|
+
.concierge-suggest .label {
|
|
14908
|
+
font-family: var(--mono);
|
|
14909
|
+
font-size: 10px;
|
|
14910
|
+
letter-spacing: 0.06em;
|
|
14911
|
+
text-transform: uppercase;
|
|
14912
|
+
color: var(--ink-3);
|
|
14221
14913
|
}
|
|
14222
14914
|
.concierge-composer {
|
|
14223
14915
|
display: flex; gap: 10px; align-items: center;
|
|
14224
|
-
padding: 12px 0
|
|
14916
|
+
padding: 12px 0 4px;
|
|
14225
14917
|
border-top: 1px solid var(--rule);
|
|
14226
14918
|
flex-shrink: 0;
|
|
14227
14919
|
}
|
|
14920
|
+
.concierge-composer .input-wrap {
|
|
14921
|
+
flex: 1;
|
|
14922
|
+
display: flex; align-items: center; gap: var(--space-2);
|
|
14923
|
+
padding: 8px 12px;
|
|
14924
|
+
border: 1px solid var(--rule);
|
|
14925
|
+
border-radius: var(--rad);
|
|
14926
|
+
background: var(--surface);
|
|
14927
|
+
}
|
|
14928
|
+
.concierge-composer .input-wrap:focus-within {
|
|
14929
|
+
border-color: var(--ink-3);
|
|
14930
|
+
}
|
|
14228
14931
|
.concierge-composer input {
|
|
14229
14932
|
flex: 1; min-width: 0;
|
|
14230
|
-
padding:
|
|
14231
|
-
border:
|
|
14232
|
-
|
|
14233
|
-
|
|
14933
|
+
padding: 4px 0;
|
|
14934
|
+
border: 0;
|
|
14935
|
+
background: transparent;
|
|
14936
|
+
color: var(--ink);
|
|
14937
|
+
font-family: var(--sans);
|
|
14938
|
+
font-size: var(--text-md);
|
|
14939
|
+
outline: none;
|
|
14234
14940
|
}
|
|
14235
|
-
.concierge-composer input:
|
|
14236
|
-
|
|
14941
|
+
.concierge-composer input::placeholder { color: var(--ink-4); }
|
|
14942
|
+
.concierge-composer .composer-meta {
|
|
14943
|
+
font-family: var(--mono);
|
|
14944
|
+
font-size: 10px;
|
|
14945
|
+
color: var(--ink-4);
|
|
14946
|
+
letter-spacing: 0.04em;
|
|
14237
14947
|
}
|
|
14238
14948
|
.concierge-composer .btn-primary {
|
|
14239
|
-
padding: 8px 18px; font-size:
|
|
14240
|
-
}
|
|
14241
|
-
.concierge-chips {
|
|
14242
|
-
display: flex; flex-wrap: wrap; gap: 6px;
|
|
14243
|
-
padding: 10px 0 0;
|
|
14244
|
-
}
|
|
14245
|
-
.concierge-chips::before {
|
|
14246
|
-
content: "Try:"; color: var(--ink-3); font-size: 12px;
|
|
14247
|
-
align-self: center; margin-right: 4px;
|
|
14949
|
+
padding: 8px 18px; font-size: var(--text-base); flex-shrink: 0;
|
|
14248
14950
|
}
|
|
14249
14951
|
.concierge-foot {
|
|
14250
14952
|
margin: 12px 0 0; padding-top: 10px; border-top: 1px dashed var(--rule);
|
|
14251
|
-
font-size:
|
|
14953
|
+
font-size: var(--text-sm);
|
|
14252
14954
|
}
|
|
14253
14955
|
.concierge-foot a { color: var(--ink-2); }
|
|
14254
14956
|
.tier1-approval-card {
|
|
@@ -14256,20 +14958,532 @@ body {
|
|
|
14256
14958
|
border-radius: var(--rad); padding: 14px 16px; margin: 12px 0;
|
|
14257
14959
|
}
|
|
14258
14960
|
.tier1-approval-card h3 {
|
|
14259
|
-
margin: 0 0 8px; color: var(--ochre); font-size:
|
|
14961
|
+
margin: 0 0 8px; color: var(--ochre); font-size: var(--text-md);
|
|
14260
14962
|
}
|
|
14261
|
-
.tier1-approval-card p { margin: 0 0 12px; font-size:
|
|
14963
|
+
.tier1-approval-card p { margin: 0 0 12px; font-size: var(--text-base); }
|
|
14262
14964
|
.tier1-approval-card .actions {
|
|
14263
|
-
display: flex; gap:
|
|
14965
|
+
display: flex; gap: var(--space-2); flex-wrap: wrap;
|
|
14966
|
+
}
|
|
14967
|
+
/* Sprint Piece 2 PR 4 (2026-05-04): Agents view + Inspect pane polish.
|
|
14968
|
+
* Translates Claude Design references at
|
|
14969
|
+
* server/docs/design-refs/sprint-piece-2/surface-agents.jsx and the
|
|
14970
|
+
* Surface 3 block of surfaces.css. The fortress-column .agent-row,
|
|
14971
|
+
* .agent-row-head, and .agent-row-actions rules above are kept verbatim
|
|
14972
|
+
* because Finding DD tests pin them; the new Agents-view list scopes
|
|
14973
|
+
* its grid layout under .agents-list (descendant selector) so the
|
|
14974
|
+
* fortress-column rules are unaffected. The inspect pane combines the
|
|
14975
|
+
* existing .card surface with .inspect-pane structure (sticky right
|
|
14976
|
+
* rail, internal scroll, sectioned body) for the agent-detail view.
|
|
14977
|
+
*/
|
|
14978
|
+
.agents-wrap { max-width: 1080px; margin: 0 auto; }
|
|
14979
|
+
.agents-layout {
|
|
14980
|
+
display: grid;
|
|
14981
|
+
grid-template-columns: 1fr 420px;
|
|
14982
|
+
gap: 20px;
|
|
14983
|
+
align-items: start;
|
|
14984
|
+
}
|
|
14985
|
+
.agents-list {
|
|
14986
|
+
background: var(--surface);
|
|
14987
|
+
border: 1px solid var(--rule);
|
|
14988
|
+
border-radius: var(--rad-lg);
|
|
14989
|
+
overflow: hidden;
|
|
14990
|
+
}
|
|
14991
|
+
.agents-list-head {
|
|
14992
|
+
display: grid;
|
|
14993
|
+
grid-template-columns: minmax(0, 1fr) 110px 120px 88px;
|
|
14994
|
+
gap: 12px;
|
|
14995
|
+
padding: 10px 16px;
|
|
14996
|
+
border-bottom: 1px solid var(--rule);
|
|
14997
|
+
background: var(--paper-2);
|
|
14998
|
+
font-family: var(--mono);
|
|
14999
|
+
font-size: 10px;
|
|
15000
|
+
letter-spacing: 0.06em;
|
|
15001
|
+
text-transform: uppercase;
|
|
15002
|
+
color: var(--ink-3);
|
|
15003
|
+
}
|
|
15004
|
+
.agents-list .agent-row {
|
|
15005
|
+
display: grid;
|
|
15006
|
+
grid-template-columns: minmax(0, 1fr) 110px 120px 88px;
|
|
15007
|
+
gap: 12px;
|
|
15008
|
+
padding: 14px 16px;
|
|
15009
|
+
border-bottom: 1px solid var(--rule);
|
|
15010
|
+
align-items: center;
|
|
15011
|
+
cursor: pointer;
|
|
15012
|
+
transition: background 120ms ease;
|
|
15013
|
+
}
|
|
15014
|
+
.agents-list .agent-row:last-child { border-bottom: 0; }
|
|
15015
|
+
.agents-list .agent-row:hover { background: var(--paper-2); }
|
|
15016
|
+
.agents-list .agent-row.selected {
|
|
15017
|
+
background: var(--paper-2);
|
|
15018
|
+
box-shadow: inset 3px 0 0 var(--ink);
|
|
15019
|
+
}
|
|
15020
|
+
.agent-identity { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
15021
|
+
.agent-glyph {
|
|
15022
|
+
width: 28px; height: 28px;
|
|
15023
|
+
border-radius: var(--rad);
|
|
15024
|
+
background: var(--paper-3);
|
|
15025
|
+
border: 1px solid var(--rule);
|
|
15026
|
+
display: grid; place-items: center;
|
|
15027
|
+
flex-shrink: 0;
|
|
15028
|
+
font-family: var(--mono);
|
|
15029
|
+
font-size: 11px;
|
|
15030
|
+
color: var(--ink-2);
|
|
15031
|
+
font-weight: 600;
|
|
15032
|
+
}
|
|
15033
|
+
.agent-name {
|
|
15034
|
+
display: flex; flex-direction: column; min-width: 0;
|
|
15035
|
+
}
|
|
15036
|
+
.agent-name strong {
|
|
15037
|
+
font-size: var(--text-base); font-weight: 500;
|
|
15038
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
15039
|
+
}
|
|
15040
|
+
.agent-name small {
|
|
15041
|
+
font-family: var(--mono); font-size: var(--text-xs); color: var(--ink-3);
|
|
15042
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
15043
|
+
display: block;
|
|
15044
|
+
}
|
|
15045
|
+
.agent-state {
|
|
15046
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
15047
|
+
font-size: var(--text-xs);
|
|
15048
|
+
font-family: var(--mono);
|
|
15049
|
+
}
|
|
15050
|
+
.state-dot {
|
|
15051
|
+
width: 7px; height: 7px; border-radius: 50%;
|
|
15052
|
+
background: var(--ink-4);
|
|
15053
|
+
}
|
|
15054
|
+
.state-dot.live { background: var(--sage); animation: pulse-soft 2.4s ease-in-out infinite; }
|
|
15055
|
+
.state-dot.idle { background: var(--ochre); }
|
|
15056
|
+
.state-dot.off { background: var(--ink-4); }
|
|
15057
|
+
@keyframes pulse-soft {
|
|
15058
|
+
0%, 100% { box-shadow: 0 0 0 0 currentColor; opacity: 1; }
|
|
15059
|
+
50% { box-shadow: 0 0 0 4px transparent; opacity: 0.7; }
|
|
15060
|
+
}
|
|
15061
|
+
.agent-last {
|
|
15062
|
+
font-family: var(--mono); font-size: var(--text-xs); color: var(--ink-3);
|
|
15063
|
+
}
|
|
15064
|
+
/* Inspect pane (combined with .card outer wrapper for the
|
|
15065
|
+
* renderAgentInspectPanel return-shape regex anchored in
|
|
15066
|
+
* dashboard-welcome.test.ts:152). The .inspect-pane modifier overrides
|
|
15067
|
+
* .card padding so internal sections control their own spacing.
|
|
15068
|
+
*/
|
|
15069
|
+
.inspect-pane {
|
|
15070
|
+
padding: 0;
|
|
15071
|
+
display: flex; flex-direction: column;
|
|
15072
|
+
position: sticky;
|
|
15073
|
+
top: 20px;
|
|
15074
|
+
max-height: calc(100vh - 100px);
|
|
15075
|
+
overflow: hidden;
|
|
15076
|
+
}
|
|
15077
|
+
.inspect-head {
|
|
15078
|
+
padding: 16px 18px;
|
|
15079
|
+
border-bottom: 1px solid var(--rule);
|
|
15080
|
+
display: flex; flex-direction: column; gap: 10px;
|
|
15081
|
+
}
|
|
15082
|
+
.inspect-head .row1 {
|
|
15083
|
+
display: flex; align-items: center; gap: 10px;
|
|
15084
|
+
}
|
|
15085
|
+
.inspect-head h3 {
|
|
15086
|
+
font-family: var(--serif); font-weight: 500;
|
|
15087
|
+
font-size: 17px; margin: 0;
|
|
15088
|
+
}
|
|
15089
|
+
.inspect-head .meta {
|
|
15090
|
+
display: flex; gap: 6px; flex-wrap: wrap;
|
|
15091
|
+
}
|
|
15092
|
+
.inspect-body {
|
|
15093
|
+
overflow-y: auto;
|
|
15094
|
+
padding: 4px 18px 18px;
|
|
15095
|
+
}
|
|
15096
|
+
.inspect-section {
|
|
15097
|
+
padding: 14px 0;
|
|
15098
|
+
border-bottom: 1px solid var(--rule);
|
|
15099
|
+
}
|
|
15100
|
+
.inspect-section:last-child { border-bottom: 0; }
|
|
15101
|
+
.inspect-section h4 {
|
|
15102
|
+
font-family: var(--mono);
|
|
15103
|
+
font-size: 10px;
|
|
15104
|
+
letter-spacing: 0.08em;
|
|
15105
|
+
text-transform: uppercase;
|
|
15106
|
+
color: var(--ink-3);
|
|
15107
|
+
margin: 0 0 10px;
|
|
15108
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
15109
|
+
}
|
|
15110
|
+
.inspect-section h4 .count {
|
|
15111
|
+
font-family: var(--mono);
|
|
15112
|
+
background: var(--paper-3);
|
|
15113
|
+
border-radius: 999px;
|
|
15114
|
+
padding: 1px 7px;
|
|
15115
|
+
color: var(--ink-2);
|
|
15116
|
+
font-size: 10px;
|
|
15117
|
+
}
|
|
15118
|
+
.approval-row {
|
|
15119
|
+
background: var(--ochre-bg);
|
|
15120
|
+
border: 1px solid var(--ochre);
|
|
15121
|
+
border-radius: var(--rad);
|
|
15122
|
+
padding: 10px 12px;
|
|
15123
|
+
margin-bottom: 8px;
|
|
15124
|
+
display: flex; flex-direction: column; gap: 8px;
|
|
15125
|
+
}
|
|
15126
|
+
.approval-row .what { font-size: var(--text-base); color: var(--ink); }
|
|
15127
|
+
.approval-row .what .pill { margin-right: 6px; }
|
|
15128
|
+
.approval-row .why {
|
|
15129
|
+
font-size: var(--text-sm); color: var(--ink-2);
|
|
15130
|
+
padding-left: 10px;
|
|
15131
|
+
border-left: 2px solid var(--ochre);
|
|
15132
|
+
}
|
|
15133
|
+
.approval-row .actions { display: flex; gap: 6px; justify-content: flex-end; }
|
|
15134
|
+
.timeline {
|
|
15135
|
+
display: flex; flex-direction: column; gap: 0;
|
|
15136
|
+
position: relative;
|
|
15137
|
+
padding-left: 14px;
|
|
15138
|
+
}
|
|
15139
|
+
.timeline::before {
|
|
15140
|
+
content: "";
|
|
15141
|
+
position: absolute;
|
|
15142
|
+
left: 4px; top: 6px; bottom: 6px;
|
|
15143
|
+
width: 1px;
|
|
15144
|
+
background: var(--rule);
|
|
15145
|
+
}
|
|
15146
|
+
.timeline-item {
|
|
15147
|
+
position: relative;
|
|
15148
|
+
padding: 6px 0 10px;
|
|
15149
|
+
font-size: var(--text-sm);
|
|
15150
|
+
}
|
|
15151
|
+
.timeline-item::before {
|
|
15152
|
+
content: "";
|
|
15153
|
+
position: absolute;
|
|
15154
|
+
left: -14px; top: 11px;
|
|
15155
|
+
width: 8px; height: 8px;
|
|
15156
|
+
border-radius: 50%;
|
|
15157
|
+
background: var(--surface);
|
|
15158
|
+
border: 1.5px solid var(--ink-4);
|
|
15159
|
+
}
|
|
15160
|
+
.timeline-item.ok::before { border-color: var(--sage); }
|
|
15161
|
+
.timeline-item.warn::before { border-color: var(--ochre); }
|
|
15162
|
+
.timeline-item.fail::before { border-color: var(--rust); }
|
|
15163
|
+
.timeline-item .ts {
|
|
15164
|
+
font-family: var(--mono); font-size: 10px;
|
|
15165
|
+
color: var(--ink-3);
|
|
15166
|
+
letter-spacing: 0.02em;
|
|
15167
|
+
}
|
|
15168
|
+
.timeline-item .what {
|
|
15169
|
+
margin-top: 2px; color: var(--ink); font-size: var(--text-base);
|
|
15170
|
+
}
|
|
15171
|
+
.timeline-item .att {
|
|
15172
|
+
margin-top: 4px;
|
|
15173
|
+
display: inline-flex;
|
|
15174
|
+
}
|
|
15175
|
+
.policy-line {
|
|
15176
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
15177
|
+
padding: 5px 0; font-size: var(--text-base);
|
|
15178
|
+
border-bottom: 1px dashed var(--rule);
|
|
15179
|
+
}
|
|
15180
|
+
.policy-line:last-child { border-bottom: 0; }
|
|
15181
|
+
.policy-line .k { color: var(--ink-3); }
|
|
15182
|
+
.policy-line .v { font-family: var(--mono); font-size: var(--text-sm); color: var(--ink); }
|
|
15183
|
+
/* Empty-state block for when no agents are wrapped. The
|
|
15184
|
+
* renderAgentsList empty-state branch begins with the literal
|
|
15185
|
+
* '<h1>Agents</h1>' (regex-pinned in agents-empty-state-canary.test.ts)
|
|
15186
|
+
* and the "No wrapped agents yet." copy is preserved verbatim.
|
|
15187
|
+
*/
|
|
15188
|
+
.agents-empty {
|
|
15189
|
+
background: var(--surface);
|
|
15190
|
+
border: 1px dashed var(--rule-2);
|
|
15191
|
+
border-radius: var(--rad-lg);
|
|
15192
|
+
padding: 56px 40px;
|
|
15193
|
+
text-align: center;
|
|
15194
|
+
max-width: 720px;
|
|
15195
|
+
margin: 32px auto;
|
|
15196
|
+
}
|
|
15197
|
+
.agents-empty .icon-frame {
|
|
15198
|
+
width: 64px; height: 64px;
|
|
15199
|
+
margin: 0 auto 18px;
|
|
15200
|
+
border: 1px solid var(--rule);
|
|
15201
|
+
border-radius: 50%;
|
|
15202
|
+
display: grid; place-items: center;
|
|
15203
|
+
position: relative;
|
|
15204
|
+
}
|
|
15205
|
+
.agents-empty .icon-frame::before,
|
|
15206
|
+
.agents-empty .icon-frame::after {
|
|
15207
|
+
content: "";
|
|
15208
|
+
position: absolute;
|
|
15209
|
+
border: 1px solid var(--rule);
|
|
15210
|
+
border-radius: 50%;
|
|
15211
|
+
}
|
|
15212
|
+
.agents-empty .icon-frame::before { inset: -8px; opacity: 0.6; }
|
|
15213
|
+
.agents-empty .icon-frame::after { inset: -16px; opacity: 0.3; }
|
|
15214
|
+
.agents-empty .icon-frame .core {
|
|
15215
|
+
width: 22px; height: 22px;
|
|
15216
|
+
background: var(--ink);
|
|
15217
|
+
border-radius: 50%;
|
|
15218
|
+
}
|
|
15219
|
+
.agents-empty h2 {
|
|
15220
|
+
font-family: var(--serif);
|
|
15221
|
+
font-weight: 400;
|
|
15222
|
+
font-size: var(--text-xl);
|
|
15223
|
+
margin: 0 0 8px;
|
|
15224
|
+
}
|
|
15225
|
+
.agents-empty p {
|
|
15226
|
+
color: var(--ink-3);
|
|
15227
|
+
margin: 0 0 20px;
|
|
15228
|
+
font-size: var(--text-md);
|
|
15229
|
+
line-height: 1.55;
|
|
15230
|
+
max-width: 50ch;
|
|
15231
|
+
margin-left: auto; margin-right: auto;
|
|
15232
|
+
}
|
|
15233
|
+
.terminal-block {
|
|
15234
|
+
text-align: left;
|
|
15235
|
+
background: var(--paper-3);
|
|
15236
|
+
border: 1px solid var(--rule);
|
|
15237
|
+
border-radius: var(--rad);
|
|
15238
|
+
padding: 14px 16px;
|
|
15239
|
+
font-family: var(--mono);
|
|
15240
|
+
font-size: var(--text-base);
|
|
15241
|
+
margin: 0 auto 16px;
|
|
15242
|
+
max-width: 480px;
|
|
15243
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
15244
|
+
}
|
|
15245
|
+
.terminal-block .cmd { color: var(--ink); }
|
|
15246
|
+
.terminal-block .cmd .prompt { color: var(--ink-3); margin-right: 8px; user-select: none; }
|
|
15247
|
+
.copy-btn {
|
|
15248
|
+
background: transparent; border: 0;
|
|
15249
|
+
color: var(--ink-3); cursor: pointer;
|
|
15250
|
+
font-family: var(--mono); font-size: var(--text-xs);
|
|
15251
|
+
padding: 2px 6px;
|
|
15252
|
+
border-radius: var(--rad);
|
|
15253
|
+
}
|
|
15254
|
+
.copy-btn:hover { color: var(--ink); background: var(--paper-2); }
|
|
15255
|
+
|
|
15256
|
+
/* Surface 5. Attestation badge gallery. */
|
|
15257
|
+
.att-gallery {
|
|
15258
|
+
display: flex; flex-direction: column; gap: 24px;
|
|
15259
|
+
max-width: 1000px;
|
|
15260
|
+
margin: 0 auto;
|
|
14264
15261
|
}
|
|
15262
|
+
.att-section {
|
|
15263
|
+
background: var(--surface);
|
|
15264
|
+
border: 1px solid var(--rule);
|
|
15265
|
+
border-radius: var(--rad-lg);
|
|
15266
|
+
padding: 22px 24px;
|
|
15267
|
+
}
|
|
15268
|
+
.att-section-head {
|
|
15269
|
+
display: flex; justify-content: space-between; align-items: baseline;
|
|
15270
|
+
gap: 12px;
|
|
15271
|
+
margin-bottom: 16px;
|
|
15272
|
+
padding-bottom: 12px;
|
|
15273
|
+
border-bottom: 1px solid var(--rule);
|
|
15274
|
+
}
|
|
15275
|
+
.att-section-head h2 {
|
|
15276
|
+
font-family: var(--serif);
|
|
15277
|
+
font-weight: 400;
|
|
15278
|
+
font-size: 19px;
|
|
15279
|
+
margin: 0 0 4px;
|
|
15280
|
+
}
|
|
15281
|
+
.att-section-head p {
|
|
15282
|
+
color: var(--ink-3);
|
|
15283
|
+
margin: 0;
|
|
15284
|
+
font-size: 13px;
|
|
15285
|
+
line-height: 1.5;
|
|
15286
|
+
max-width: 64ch;
|
|
15287
|
+
}
|
|
15288
|
+
.att-section-head .label {
|
|
15289
|
+
font-family: var(--mono);
|
|
15290
|
+
font-size: 10px;
|
|
15291
|
+
letter-spacing: 0.08em;
|
|
15292
|
+
text-transform: uppercase;
|
|
15293
|
+
color: var(--ink-3);
|
|
15294
|
+
}
|
|
15295
|
+
.att-row {
|
|
15296
|
+
display: grid;
|
|
15297
|
+
grid-template-columns: 240px 1fr;
|
|
15298
|
+
gap: 24px;
|
|
15299
|
+
padding: 14px 0;
|
|
15300
|
+
border-bottom: 1px dashed var(--rule);
|
|
15301
|
+
align-items: center;
|
|
15302
|
+
}
|
|
15303
|
+
.att-row:last-child { border-bottom: 0; }
|
|
15304
|
+
.att-row .demo {
|
|
15305
|
+
display: flex; align-items: center; justify-content: flex-start;
|
|
15306
|
+
padding: 12px 16px;
|
|
15307
|
+
background: var(--paper-2);
|
|
15308
|
+
border: 1px solid var(--rule);
|
|
15309
|
+
border-radius: var(--rad);
|
|
15310
|
+
min-height: 56px;
|
|
15311
|
+
}
|
|
15312
|
+
.att-row .desc strong {
|
|
15313
|
+
font-size: 13px; display: block; margin-bottom: 3px;
|
|
15314
|
+
}
|
|
15315
|
+
.att-row .desc small {
|
|
15316
|
+
color: var(--ink-3); font-size: 12px;
|
|
15317
|
+
line-height: 1.5;
|
|
15318
|
+
}
|
|
15319
|
+
|
|
15320
|
+
/* Global persistent badge. Lives in the topbar across every surface. */
|
|
15321
|
+
.att-global {
|
|
15322
|
+
display: inline-flex; align-items: center;
|
|
15323
|
+
gap: 8px;
|
|
15324
|
+
padding: 4px 10px 4px 6px;
|
|
15325
|
+
border: 1px solid var(--rule);
|
|
15326
|
+
border-radius: 999px;
|
|
15327
|
+
background: var(--surface-2);
|
|
15328
|
+
font-family: var(--mono);
|
|
15329
|
+
font-size: 11px;
|
|
15330
|
+
color: var(--ink-2);
|
|
15331
|
+
}
|
|
15332
|
+
.att-global.verified { border-color: var(--sage); background: var(--sage-bg); color: var(--sage); }
|
|
15333
|
+
.att-global.degraded { border-color: var(--ochre); background: var(--ochre-bg); color: var(--ochre); }
|
|
15334
|
+
.att-global.unverified { border-color: var(--rust); background: var(--rust-bg); color: var(--rust); }
|
|
15335
|
+
.att-global .seal {
|
|
15336
|
+
width: 18px; height: 18px;
|
|
15337
|
+
position: relative;
|
|
15338
|
+
flex-shrink: 0;
|
|
15339
|
+
}
|
|
15340
|
+
.att-global .seal-ring {
|
|
15341
|
+
position: absolute; inset: 0;
|
|
15342
|
+
border: 1.5px solid currentColor;
|
|
15343
|
+
border-radius: 50%;
|
|
15344
|
+
}
|
|
15345
|
+
.att-global .seal-ring.dashed { border-style: dashed; }
|
|
15346
|
+
.att-global .seal-core {
|
|
15347
|
+
position: absolute; inset: 4px;
|
|
15348
|
+
background: currentColor;
|
|
15349
|
+
border-radius: 50%;
|
|
15350
|
+
opacity: 0.85;
|
|
15351
|
+
}
|
|
15352
|
+
.att-global.degraded .seal-core { background: transparent; border: 1px solid currentColor; }
|
|
15353
|
+
.att-global.unverified .seal-core {
|
|
15354
|
+
background: transparent;
|
|
15355
|
+
border: 1px solid currentColor;
|
|
15356
|
+
}
|
|
15357
|
+
.att-global.unverified .seal-core::after {
|
|
15358
|
+
content: ""; position: absolute; inset: 0;
|
|
15359
|
+
background: currentColor; opacity: 0.4;
|
|
15360
|
+
clip-path: polygon(0 0, 100% 100%, 100% 90%, 10% 0);
|
|
15361
|
+
}
|
|
15362
|
+
.att-global .label {
|
|
15363
|
+
font-family: var(--mono);
|
|
15364
|
+
font-size: 11px;
|
|
15365
|
+
letter-spacing: 0.02em;
|
|
15366
|
+
text-transform: uppercase;
|
|
15367
|
+
}
|
|
15368
|
+
.att-global .hash {
|
|
15369
|
+
font-family: var(--mono);
|
|
15370
|
+
font-size: 10px;
|
|
15371
|
+
opacity: 0.7;
|
|
15372
|
+
border-left: 1px solid currentColor;
|
|
15373
|
+
padding-left: 8px;
|
|
15374
|
+
margin-left: 2px;
|
|
15375
|
+
}
|
|
15376
|
+
|
|
15377
|
+
/* Per-agent badge. Square chip beside each agent. */
|
|
15378
|
+
.att-agent {
|
|
15379
|
+
display: inline-flex; align-items: center;
|
|
15380
|
+
gap: 6px;
|
|
15381
|
+
padding: 3px 7px;
|
|
15382
|
+
border-radius: var(--rad);
|
|
15383
|
+
border: 1px solid var(--rule);
|
|
15384
|
+
background: var(--surface);
|
|
15385
|
+
font-family: var(--mono);
|
|
15386
|
+
font-size: 10px;
|
|
15387
|
+
color: var(--ink-2);
|
|
15388
|
+
}
|
|
15389
|
+
.att-agent .mark {
|
|
15390
|
+
width: 10px; height: 10px;
|
|
15391
|
+
border: 1.5px solid currentColor;
|
|
15392
|
+
border-radius: 2px;
|
|
15393
|
+
position: relative;
|
|
15394
|
+
}
|
|
15395
|
+
.att-agent.verified { color: var(--sage); border-color: var(--sage); background: var(--sage-bg); }
|
|
15396
|
+
.att-agent.verified .mark { background: currentColor; }
|
|
15397
|
+
.att-agent.degraded { color: var(--ochre); border-color: var(--ochre); background: var(--ochre-bg); }
|
|
15398
|
+
.att-agent.unverified { color: var(--rust); border-color: var(--rust); background: var(--rust-bg); }
|
|
15399
|
+
.att-agent.unverified .mark {
|
|
15400
|
+
background: repeating-linear-gradient(
|
|
15401
|
+
45deg, currentColor, currentColor 1px,
|
|
15402
|
+
transparent 1px, transparent 3px
|
|
15403
|
+
);
|
|
15404
|
+
}
|
|
15405
|
+
|
|
15406
|
+
/* Per-action badge. Tiny inline tick on timeline rows. */
|
|
15407
|
+
.att-action {
|
|
15408
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
15409
|
+
font-family: var(--mono);
|
|
15410
|
+
font-size: 10px;
|
|
15411
|
+
color: var(--ink-3);
|
|
15412
|
+
padding: 1px 6px;
|
|
15413
|
+
border-radius: 4px;
|
|
15414
|
+
background: var(--paper-3);
|
|
15415
|
+
border: 1px solid transparent;
|
|
15416
|
+
}
|
|
15417
|
+
.att-action .tick {
|
|
15418
|
+
width: 6px; height: 6px;
|
|
15419
|
+
border-radius: 1px;
|
|
15420
|
+
background: currentColor;
|
|
15421
|
+
}
|
|
15422
|
+
.att-action.verified { color: var(--sage); }
|
|
15423
|
+
.att-action.degraded { color: var(--ochre); }
|
|
15424
|
+
.att-action.unverified { color: var(--rust); }
|
|
15425
|
+
.att-action.neutral .tick { background: var(--ink-4); border-radius: 50%; }
|
|
15426
|
+
|
|
15427
|
+
/* Custody-provenance badge stub (v1.x). Visibly stubbed with dashed border. */
|
|
15428
|
+
.att-custody {
|
|
15429
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
15430
|
+
padding: 4px 10px 4px 6px;
|
|
15431
|
+
border-radius: var(--rad);
|
|
15432
|
+
border: 1px dashed var(--rule-2);
|
|
15433
|
+
background: var(--paper-3);
|
|
15434
|
+
color: var(--ink-3);
|
|
15435
|
+
font-family: var(--mono);
|
|
15436
|
+
font-size: 10px;
|
|
15437
|
+
}
|
|
15438
|
+
.att-custody .seal-stub {
|
|
15439
|
+
width: 16px; height: 16px;
|
|
15440
|
+
border: 1px dashed var(--ink-4);
|
|
15441
|
+
border-radius: 50%;
|
|
15442
|
+
position: relative;
|
|
15443
|
+
flex-shrink: 0;
|
|
15444
|
+
}
|
|
15445
|
+
.att-custody .seal-stub::after {
|
|
15446
|
+
content: ""; position: absolute; inset: 4px;
|
|
15447
|
+
border: 1px dashed var(--ink-4);
|
|
15448
|
+
border-radius: 50%;
|
|
15449
|
+
}
|
|
15450
|
+
.att-custody .stub-tag {
|
|
15451
|
+
letter-spacing: 0.06em;
|
|
15452
|
+
text-transform: uppercase;
|
|
15453
|
+
}
|
|
15454
|
+
|
|
15455
|
+
/* Tooltip surface for badges. */
|
|
15456
|
+
.att-tooltip {
|
|
15457
|
+
background: var(--ink);
|
|
15458
|
+
color: var(--paper);
|
|
15459
|
+
font-family: var(--mono);
|
|
15460
|
+
font-size: 11px;
|
|
15461
|
+
padding: 8px 10px;
|
|
15462
|
+
border-radius: var(--rad);
|
|
15463
|
+
max-width: 280px;
|
|
15464
|
+
line-height: 1.5;
|
|
15465
|
+
display: inline-block;
|
|
15466
|
+
}
|
|
15467
|
+
[data-theme="dark"] .att-tooltip {
|
|
15468
|
+
background: var(--paper-3);
|
|
15469
|
+
color: var(--ink);
|
|
15470
|
+
}
|
|
15471
|
+
|
|
14265
15472
|
@media (max-width: 1100px) {
|
|
14266
15473
|
.app, .app.route-full { grid-template-columns: 56px 1fr; grid-template-areas: "sidebar topbar" "sidebar main"; }
|
|
14267
15474
|
.fortress { display: none; }
|
|
14268
15475
|
.sidebar h1, .sidebar nav a span { display: none; }
|
|
15476
|
+
.sidebar nav a { justify-content: center; padding: 8px 6px; }
|
|
14269
15477
|
.template-grid { grid-template-columns: 1fr; }
|
|
14270
15478
|
.policy-center h1 { font-size: 30px; }
|
|
14271
15479
|
.intel-center h1 { font-size: 30px; }
|
|
14272
15480
|
.intel-row { grid-template-columns: 1fr; }
|
|
15481
|
+
.intel-grid { grid-template-columns: 1fr; }
|
|
15482
|
+
.intel-failure-row { grid-template-columns: 1fr; }
|
|
15483
|
+
.agents-layout { grid-template-columns: 1fr; }
|
|
15484
|
+
.agents-list-head, .agents-list .agent-row { grid-template-columns: minmax(0, 1fr) 90px 90px; }
|
|
15485
|
+
.agents-list-head span:nth-child(4), .agents-list .agent-row > .agent-last { display: none; }
|
|
15486
|
+
.inspect-pane { position: static; max-height: none; }
|
|
14273
15487
|
}
|
|
14274
15488
|
`;
|
|
14275
15489
|
var NAV_ITEMS = [
|
|
@@ -14277,11 +15491,26 @@ var NAV_ITEMS = [
|
|
|
14277
15491
|
{ id: "agents", label: "Agents" },
|
|
14278
15492
|
{ id: "policy", label: "Policy" },
|
|
14279
15493
|
{ id: "intelligence", label: "Intelligence" },
|
|
15494
|
+
{ id: "attestation", label: "Attestation" },
|
|
14280
15495
|
{ id: "privacy", label: "Privacy" },
|
|
14281
15496
|
{ id: "coordination", label: "Coordination" },
|
|
14282
15497
|
{ id: "health", label: "Health" },
|
|
14283
15498
|
{ id: "exit-drill", label: "Exit drill" }
|
|
14284
15499
|
];
|
|
15500
|
+
var NAV_ICON_PATHS = {
|
|
15501
|
+
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"/>',
|
|
15502
|
+
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"/>',
|
|
15503
|
+
policy: '<path d="M4 2h5l3 3v9H4z"/><path d="M9 2v3h3"/><path d="M6 9h4M6 11.5h4"/>',
|
|
15504
|
+
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"/>',
|
|
15505
|
+
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"/>',
|
|
15506
|
+
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"/>',
|
|
15507
|
+
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"/>',
|
|
15508
|
+
health: '<path d="M2 8h2.5l1.5-4 3 8 1.5-4H14"/>',
|
|
15509
|
+
"exit-drill": '<path d="M9.5 2H3v12h6.5"/><path d="M11 5l3 3-3 3"/><path d="M14 8H6.5"/>'
|
|
15510
|
+
};
|
|
15511
|
+
var 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">';
|
|
15512
|
+
var 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>';
|
|
15513
|
+
var 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>';
|
|
14285
15514
|
function escHtml2(value) {
|
|
14286
15515
|
if (value == null) return "";
|
|
14287
15516
|
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -14294,9 +15523,11 @@ function renderDashboardV11Html(options = {}) {
|
|
|
14294
15523
|
const fortressId = options.fortressId ?? "fortress";
|
|
14295
15524
|
const sanctuaryVersion = options.sanctuaryVersion ?? SANCTUARY_VERSION;
|
|
14296
15525
|
const embedClient = options.embedClient !== false;
|
|
14297
|
-
const nav = NAV_ITEMS.map(
|
|
14298
|
-
|
|
14299
|
-
|
|
15526
|
+
const nav = NAV_ITEMS.map((n) => {
|
|
15527
|
+
const iconPath = NAV_ICON_PATHS[n.id] ?? "";
|
|
15528
|
+
const icon = iconPath ? SVG_OPEN + iconPath + "</svg>" : "";
|
|
15529
|
+
return `<a href="#${n.id}" data-route="${n.id}">${icon}<span>${escHtml2(n.label)}</span></a>`;
|
|
15530
|
+
}).join("\n ");
|
|
14300
15531
|
const config = JSON.stringify({
|
|
14301
15532
|
authToken,
|
|
14302
15533
|
hubApiBase,
|
|
@@ -14328,8 +15559,12 @@ function renderDashboardV11Html(options = {}) {
|
|
|
14328
15559
|
<span class="pill" data-pill="version">v${escHtml2(sanctuaryVersion)}</span>
|
|
14329
15560
|
<span class="pill" data-pill="deployment">deployment: local</span>
|
|
14330
15561
|
<span class="pill" data-pill="mode">mode: solo</span>
|
|
14331
|
-
<span class="
|
|
15562
|
+
<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>
|
|
14332
15563
|
</div>
|
|
15564
|
+
<button class="btn btn-icon" id="btn-theme-toggle" data-action="theme-toggle" aria-label="Toggle theme" title="Toggle theme">
|
|
15565
|
+
<span class="icon-moon">${THEME_ICON_MOON}</span>
|
|
15566
|
+
<span class="icon-sun">${THEME_ICON_SUN}</span>
|
|
15567
|
+
</button>
|
|
14333
15568
|
<button class="btn btn-danger" id="btn-lockdown" data-action="lockdown">Lockdown</button>
|
|
14334
15569
|
</header>
|
|
14335
15570
|
<main class="main" id="main"><p class="muted">Loading dashboard.</p></main>
|
|
@@ -17662,8 +18897,8 @@ function verifySHR(shr, now) {
|
|
|
17662
18897
|
const errors = [];
|
|
17663
18898
|
const warnings = [];
|
|
17664
18899
|
const currentTime = now ?? /* @__PURE__ */ new Date();
|
|
17665
|
-
if (!shr.body || !shr.signed_by || !shr.signature) {
|
|
17666
|
-
errors.push("Missing required SHR fields (body, signed_by, or signature)");
|
|
18900
|
+
if (!shr.body || !shr.signed_by || !shr.signature || !shr.signature_scheme) {
|
|
18901
|
+
errors.push("Missing required SHR fields (body, signed_by, signature_scheme, or signature)");
|
|
17667
18902
|
return {
|
|
17668
18903
|
valid: false,
|
|
17669
18904
|
errors,
|
|
@@ -17676,6 +18911,9 @@ function verifySHR(shr, now) {
|
|
|
17676
18911
|
if (shr.body.shr_version !== "1.0") {
|
|
17677
18912
|
errors.push(`Unsupported SHR version: ${shr.body.shr_version}`);
|
|
17678
18913
|
}
|
|
18914
|
+
if (shr.signature_scheme !== SIGNATURE_SCHEME_V1) {
|
|
18915
|
+
errors.push(`Unsupported SHR signature_scheme: ${String(shr.signature_scheme)}`);
|
|
18916
|
+
}
|
|
17679
18917
|
const expiresAt = new Date(shr.body.expires_at);
|
|
17680
18918
|
if (isNaN(expiresAt.getTime())) {
|
|
17681
18919
|
errors.push("Invalid expires_at timestamp");
|
|
@@ -28733,8 +29971,6 @@ function aggregateInbox(sources, store) {
|
|
|
28733
29971
|
}
|
|
28734
29972
|
return store.list();
|
|
28735
29973
|
}
|
|
28736
|
-
|
|
28737
|
-
// src/hub/activity-feed.ts
|
|
28738
29974
|
var LIFECYCLE_VERBS = [
|
|
28739
29975
|
"wrap",
|
|
28740
29976
|
"unwrap",
|
|
@@ -28793,18 +30029,30 @@ function extractAgentIdHint(entry) {
|
|
|
28793
30029
|
const value = details.agent_id;
|
|
28794
30030
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
28795
30031
|
}
|
|
30032
|
+
function deriveAttestationFragment(entryId) {
|
|
30033
|
+
const hash2 = createHash("sha256").update(entryId).digest("hex");
|
|
30034
|
+
return `${hash2.slice(0, 4)}..${hash2.slice(4, 6)}`;
|
|
30035
|
+
}
|
|
30036
|
+
function deriveAttestationState(entry) {
|
|
30037
|
+
return entry.result === "success" ? "verified" : "degraded";
|
|
30038
|
+
}
|
|
28796
30039
|
function projectEntry(entry) {
|
|
28797
30040
|
const agentIdHint = extractAgentIdHint(entry);
|
|
28798
30041
|
const category = categorizeOperation(entry.layer, entry.operation);
|
|
30042
|
+
const entryId = `${entry.timestamp}|${entry.operation}|${entry.identity_id}`;
|
|
28799
30043
|
return {
|
|
28800
30044
|
version: "1.1",
|
|
28801
|
-
entry_id:
|
|
30045
|
+
entry_id: entryId,
|
|
28802
30046
|
emitted_at: entry.timestamp,
|
|
28803
30047
|
...agentIdHint ? { agent_id: agentIdHint } : {},
|
|
28804
30048
|
identity_id: entry.identity_id,
|
|
28805
30049
|
category,
|
|
28806
30050
|
display_template_id: templateIdFor(category, entry.operation),
|
|
28807
|
-
display_template_args: buildTemplateArgs(entry, agentIdHint)
|
|
30051
|
+
display_template_args: buildTemplateArgs(entry, agentIdHint),
|
|
30052
|
+
attestation: {
|
|
30053
|
+
state: deriveAttestationState(entry),
|
|
30054
|
+
fragment: deriveAttestationFragment(entryId)
|
|
30055
|
+
}
|
|
28808
30056
|
};
|
|
28809
30057
|
}
|
|
28810
30058
|
async function aggregateActivity(sources, filter = {}) {
|
|
@@ -31753,7 +33001,7 @@ var MemoryStorage = class {
|
|
|
31753
33001
|
};
|
|
31754
33002
|
|
|
31755
33003
|
// src/contracts/v1.1/constants.ts
|
|
31756
|
-
var
|
|
33004
|
+
var SIGNATURE_SCHEME_V12 = "ed25519-v1";
|
|
31757
33005
|
var EXIT_BUNDLE_MANIFEST_VERSION = "SANCTUARY_EXIT_BUNDLE_V1";
|
|
31758
33006
|
var EXIT_BUNDLE_ARTIFACT_KINDS = [
|
|
31759
33007
|
"public_identity",
|
|
@@ -31778,6 +33026,12 @@ var EXIT_BUNDLE_PATH_MAX_BYTES = 256;
|
|
|
31778
33026
|
// src/exit/verifier.ts
|
|
31779
33027
|
init_encoding();
|
|
31780
33028
|
init_hashing();
|
|
33029
|
+
var InvalidExitBundleError = class extends Error {
|
|
33030
|
+
constructor(message) {
|
|
33031
|
+
super(message);
|
|
33032
|
+
this.name = "InvalidExitBundleError";
|
|
33033
|
+
}
|
|
33034
|
+
};
|
|
31781
33035
|
var PRIVATE_MATERIAL_KEYS = /* @__PURE__ */ new Set([
|
|
31782
33036
|
"private_key",
|
|
31783
33037
|
"privatekey",
|
|
@@ -31960,7 +33214,7 @@ function verifyReputationArtifact(reputationArtifact, publicKeysByDid) {
|
|
|
31960
33214
|
unverifiable_attestations: unverifiable
|
|
31961
33215
|
};
|
|
31962
33216
|
}
|
|
31963
|
-
async function verifyExitBundle(bundleDir) {
|
|
33217
|
+
async function verifyExitBundle(bundleDir, options = {}) {
|
|
31964
33218
|
const root = resolve(bundleDir);
|
|
31965
33219
|
let manifest;
|
|
31966
33220
|
let manifestBytes;
|
|
@@ -31969,7 +33223,9 @@ async function verifyExitBundle(bundleDir) {
|
|
|
31969
33223
|
manifestBytes = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
31970
33224
|
manifest = JSON.parse(Buffer.from(raw).toString("utf8"));
|
|
31971
33225
|
} catch {
|
|
31972
|
-
|
|
33226
|
+
throw new InvalidExitBundleError(
|
|
33227
|
+
`Not a valid SANCTUARY_EXIT_BUNDLE_V1 directory: manifest.json missing at ${join(root, "manifest.json")}`
|
|
33228
|
+
);
|
|
31973
33229
|
}
|
|
31974
33230
|
const warnings = [];
|
|
31975
33231
|
const unsupportedArtifacts = [];
|
|
@@ -31977,7 +33233,7 @@ async function verifyExitBundle(bundleDir) {
|
|
|
31977
33233
|
if (!body || body.manifest_version !== EXIT_BUNDLE_MANIFEST_VERSION) {
|
|
31978
33234
|
return fail(root, manifest, "manifest_unknown_version", warnings, unsupportedArtifacts);
|
|
31979
33235
|
}
|
|
31980
|
-
if (body.signature_scheme !==
|
|
33236
|
+
if (body.signature_scheme !== SIGNATURE_SCHEME_V12) {
|
|
31981
33237
|
return fail(
|
|
31982
33238
|
root,
|
|
31983
33239
|
manifest,
|
|
@@ -32137,9 +33393,16 @@ async function verifyExitBundle(bundleDir) {
|
|
|
32137
33393
|
}
|
|
32138
33394
|
const reputationFailed = reputation?.bundle_signature_valid === false || (reputation?.invalid_attestations ?? 0) > 0;
|
|
32139
33395
|
const identityFailed = identity ? !identity.signature_valid : false;
|
|
33396
|
+
const unverifiableCount = reputation?.unverifiable_attestations ?? 0;
|
|
33397
|
+
const unverifiableFailed = unverifiableCount > 0 && !options.acceptUnverifiableAttestations;
|
|
33398
|
+
if (unverifiableFailed) {
|
|
33399
|
+
warnings.push(
|
|
33400
|
+
`${unverifiableCount} reputation attestation(s) have unknown signer public keys; pass --accept-unverifiable-attestations to import anyway`
|
|
33401
|
+
);
|
|
33402
|
+
}
|
|
32140
33403
|
return {
|
|
32141
33404
|
version: "1.1",
|
|
32142
|
-
passed: !reputationFailed && !identityFailed,
|
|
33405
|
+
passed: !reputationFailed && !identityFailed && !unverifiableFailed,
|
|
32143
33406
|
verified_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
32144
33407
|
manifest_path: join(root, "manifest.json"),
|
|
32145
33408
|
manifest_hash: sha256Hex2(manifestBytes),
|
|
@@ -32156,7 +33419,7 @@ async function verifyExitBundle(bundleDir) {
|
|
|
32156
33419
|
identity,
|
|
32157
33420
|
audit,
|
|
32158
33421
|
reputation,
|
|
32159
|
-
failure_class: reputationFailed || identityFailed ? "other" : void 0
|
|
33422
|
+
failure_class: reputationFailed || identityFailed || unverifiableFailed ? "other" : void 0
|
|
32160
33423
|
};
|
|
32161
33424
|
}
|
|
32162
33425
|
|
|
@@ -32169,6 +33432,14 @@ var EXIT_POLICY_SETS_NAMESPACE = "_exit_policy_sets";
|
|
|
32169
33432
|
var EXIT_COMMITMENTS_NAMESPACE = "_exit_commitments";
|
|
32170
33433
|
var EXIT_PLACEHOLDER_METADATA_NAMESPACE = "_exit_placeholder_metadata";
|
|
32171
33434
|
var PRIVACY_PLACEHOLDER_NAMESPACE = "_privacy_placeholder_vault";
|
|
33435
|
+
var ExitBundleImportError = class extends Error {
|
|
33436
|
+
code;
|
|
33437
|
+
constructor(code, message) {
|
|
33438
|
+
super(message);
|
|
33439
|
+
this.name = "ExitBundleImportError";
|
|
33440
|
+
this.code = code;
|
|
33441
|
+
}
|
|
33442
|
+
};
|
|
32172
33443
|
function sha256Hex3(bytes) {
|
|
32173
33444
|
return Array.from(hash(bytes)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
32174
33445
|
}
|
|
@@ -32461,7 +33732,7 @@ async function exportExitBundle(opts) {
|
|
|
32461
33732
|
),
|
|
32462
33733
|
artifacts_aggregate_hash_alg: "sha256",
|
|
32463
33734
|
export_approval_audit_id: exportApprovalAuditId,
|
|
32464
|
-
signature_scheme:
|
|
33735
|
+
signature_scheme: SIGNATURE_SCHEME_V12
|
|
32465
33736
|
};
|
|
32466
33737
|
const signature = sign(
|
|
32467
33738
|
canonicalizeToBytes(body),
|
|
@@ -32646,7 +33917,9 @@ async function stageArtifact(storage, namespace, key, value) {
|
|
|
32646
33917
|
await storage.write(namespace, key, jsonBytes(value));
|
|
32647
33918
|
}
|
|
32648
33919
|
async function importExitBundle(opts) {
|
|
32649
|
-
const verification = await verifyExitBundle(opts.bundleDir
|
|
33920
|
+
const verification = await verifyExitBundle(opts.bundleDir, {
|
|
33921
|
+
acceptUnverifiableAttestations: opts.acceptUnverifiableAttestations
|
|
33922
|
+
});
|
|
32650
33923
|
if (!verification.passed) {
|
|
32651
33924
|
return {
|
|
32652
33925
|
verified: false,
|
|
@@ -32742,6 +34015,23 @@ async function importExitBundle(opts) {
|
|
|
32742
34015
|
unsupported_artifacts: verification.unsupported_artifacts
|
|
32743
34016
|
};
|
|
32744
34017
|
}
|
|
34018
|
+
if (conflicts.public_identity_exists && !opts.forceRebind) {
|
|
34019
|
+
throw new ExitBundleImportError(
|
|
34020
|
+
"IDENTITY_OVERWRITE_REFUSED",
|
|
34021
|
+
"Importing this bundle would overwrite an existing fortress public identity. Pass forceRebind: true (CLI: --force-rebind) to confirm explicit replacement."
|
|
34022
|
+
);
|
|
34023
|
+
}
|
|
34024
|
+
if (conflicts.public_identity_exists && opts.forceRebind && identityArtifact) {
|
|
34025
|
+
opts.auditLog.append(
|
|
34026
|
+
"l1",
|
|
34027
|
+
"exit_bundle_force_rebind",
|
|
34028
|
+
identityArtifact.json.bundle.identity_id,
|
|
34029
|
+
{
|
|
34030
|
+
manifest_version: manifest.body.manifest_version,
|
|
34031
|
+
fortress_id: manifest.body.identity_binding.fortress_id
|
|
34032
|
+
}
|
|
34033
|
+
);
|
|
34034
|
+
}
|
|
32745
34035
|
const importId = importIdForManifest(manifest);
|
|
32746
34036
|
const stagedArtifacts = [];
|
|
32747
34037
|
if (identityArtifact) {
|
|
@@ -32852,7 +34142,7 @@ function exitBundleManifestShape() {
|
|
|
32852
34142
|
manifest_version: EXIT_BUNDLE_MANIFEST_VERSION,
|
|
32853
34143
|
artifacts: [...EXIT_BUNDLE_ARTIFACT_KINDS],
|
|
32854
34144
|
hash_alg: "sha256",
|
|
32855
|
-
signature_scheme:
|
|
34145
|
+
signature_scheme: SIGNATURE_SCHEME_V12,
|
|
32856
34146
|
required_top_level_file: "manifest.json",
|
|
32857
34147
|
artifact_paths: [
|
|
32858
34148
|
"artifacts/public_identity.json",
|
|
@@ -32961,6 +34251,11 @@ Options:
|
|
|
32961
34251
|
--destination-identity-id <id> Destination signer for re-keyed state
|
|
32962
34252
|
--state-namespace <name> Export a namespace; repeatable
|
|
32963
34253
|
--conflict <skip|overwrite|version>
|
|
34254
|
+
--force-rebind On import: explicitly replace an existing fortress
|
|
34255
|
+
public identity (Tier 1 confirmation)
|
|
34256
|
+
--accept-unverifiable-attestations
|
|
34257
|
+
On import: accept reputation attestations whose
|
|
34258
|
+
signer DID is not in the bundle (Tier 1 confirmation)
|
|
32964
34259
|
--json
|
|
32965
34260
|
--yes, -y Explicit non-interactive Tier 1 approval
|
|
32966
34261
|
--help, -h
|
|
@@ -32989,7 +34284,22 @@ async function runExitCommand(args) {
|
|
|
32989
34284
|
write(err, "Usage: sanctuary exit verify <dir>\n");
|
|
32990
34285
|
return 2;
|
|
32991
34286
|
}
|
|
32992
|
-
|
|
34287
|
+
let result;
|
|
34288
|
+
try {
|
|
34289
|
+
result = await verifyExitBundle(dir, {
|
|
34290
|
+
acceptUnverifiableAttestations: hasFlag(
|
|
34291
|
+
argv,
|
|
34292
|
+
"--accept-unverifiable-attestations"
|
|
34293
|
+
)
|
|
34294
|
+
});
|
|
34295
|
+
} catch (e) {
|
|
34296
|
+
if (e instanceof InvalidExitBundleError) {
|
|
34297
|
+
write(err, `Error: ${e.message}
|
|
34298
|
+
`);
|
|
34299
|
+
return 1;
|
|
34300
|
+
}
|
|
34301
|
+
throw e;
|
|
34302
|
+
}
|
|
32993
34303
|
if (json) {
|
|
32994
34304
|
write(out, JSON.stringify(result, null, 2) + "\n");
|
|
32995
34305
|
} else {
|
|
@@ -33067,9 +34377,15 @@ async function runExitCommand(args) {
|
|
|
33067
34377
|
return 2;
|
|
33068
34378
|
}
|
|
33069
34379
|
const activate = hasFlag(argv, "--activate");
|
|
34380
|
+
const forceRebind = hasFlag(argv, "--force-rebind");
|
|
34381
|
+
const acceptUnverifiableAttestations = hasFlag(
|
|
34382
|
+
argv,
|
|
34383
|
+
"--accept-unverifiable-attestations"
|
|
34384
|
+
);
|
|
33070
34385
|
if (activate) {
|
|
34386
|
+
const prompt = 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?";
|
|
33071
34387
|
const approved = await confirmTier1(
|
|
33072
|
-
|
|
34388
|
+
prompt,
|
|
33073
34389
|
hasFlag(argv, "--yes") || hasFlag(argv, "-y"),
|
|
33074
34390
|
stdin,
|
|
33075
34391
|
err
|
|
@@ -33078,6 +34394,18 @@ async function runExitCommand(args) {
|
|
|
33078
34394
|
write(err, "Aborted.\n");
|
|
33079
34395
|
return 1;
|
|
33080
34396
|
}
|
|
34397
|
+
if (acceptUnverifiableAttestations) {
|
|
34398
|
+
const acceptApproved = await confirmTier1(
|
|
34399
|
+
"Tier 1 approval required: accept unverifiable reputation attestations on import?",
|
|
34400
|
+
hasFlag(argv, "--yes") || hasFlag(argv, "-y"),
|
|
34401
|
+
stdin,
|
|
34402
|
+
err
|
|
34403
|
+
);
|
|
34404
|
+
if (!acceptApproved) {
|
|
34405
|
+
write(err, "Aborted.\n");
|
|
34406
|
+
return 1;
|
|
34407
|
+
}
|
|
34408
|
+
}
|
|
33081
34409
|
}
|
|
33082
34410
|
const ctx = await openExitContext(argv, env);
|
|
33083
34411
|
const conflict = flagValue(argv, "--conflict") ?? "skip";
|
|
@@ -33085,19 +34413,31 @@ async function runExitCommand(args) {
|
|
|
33085
34413
|
write(err, "--conflict must be skip, overwrite, or version\n");
|
|
33086
34414
|
return 2;
|
|
33087
34415
|
}
|
|
33088
|
-
|
|
33089
|
-
|
|
33090
|
-
|
|
33091
|
-
|
|
33092
|
-
|
|
33093
|
-
|
|
33094
|
-
|
|
33095
|
-
|
|
33096
|
-
|
|
33097
|
-
|
|
33098
|
-
|
|
33099
|
-
|
|
33100
|
-
|
|
34416
|
+
let result;
|
|
34417
|
+
try {
|
|
34418
|
+
result = await importExitBundle({
|
|
34419
|
+
bundleDir: dir,
|
|
34420
|
+
storage: ctx.storage,
|
|
34421
|
+
masterKey: ctx.masterKey,
|
|
34422
|
+
identityManager: ctx.identityManager,
|
|
34423
|
+
auditLog: ctx.auditLog,
|
|
34424
|
+
reputationStore: ctx.reputationStore,
|
|
34425
|
+
activate,
|
|
34426
|
+
forceRebind,
|
|
34427
|
+
acceptUnverifiableAttestations,
|
|
34428
|
+
conflictResolution: conflict,
|
|
34429
|
+
sourcePassphrase: flagValue(argv, "--source-passphrase"),
|
|
34430
|
+
sourceRecoveryKey: flagValue(argv, "--source-recovery-key"),
|
|
34431
|
+
destinationSignerIdentityId: flagValue(argv, "--destination-identity-id")
|
|
34432
|
+
});
|
|
34433
|
+
} catch (e) {
|
|
34434
|
+
if (e instanceof InvalidExitBundleError) {
|
|
34435
|
+
write(err, `Error: ${e.message}
|
|
34436
|
+
`);
|
|
34437
|
+
return 1;
|
|
34438
|
+
}
|
|
34439
|
+
throw e;
|
|
34440
|
+
}
|
|
33101
34441
|
if (json) write(out, JSON.stringify(result, null, 2) + "\n");
|
|
33102
34442
|
else {
|
|
33103
34443
|
write(out, `verified: ${result.verified}
|
|
@@ -33327,7 +34667,7 @@ async function createSanctuaryServer(options) {
|
|
|
33327
34667
|
const hasKeyParams = existingNamespaces.some((e) => e.key === "key-params");
|
|
33328
34668
|
if (hasKeyParams) {
|
|
33329
34669
|
throw new Error(
|
|
33330
|
-
"Sanctuary:
|
|
34670
|
+
"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."
|
|
33331
34671
|
);
|
|
33332
34672
|
}
|
|
33333
34673
|
masterKey = generateRandomKey();
|
|
@@ -33908,6 +35248,6 @@ Refusing to start the cocoon while the reset-history marker is unreadable.`
|
|
|
33908
35248
|
};
|
|
33909
35249
|
}
|
|
33910
35250
|
|
|
33911
|
-
export { ATTESTATION_VERSION, ApprovalGate, AuditLog, AutoApproveChannel, BaselineTracker, TEMPLATES as CONTEXT_GATE_TEMPLATES, CallbackApprovalChannel, ClientManager, CommitmentStore, ContextGateEnforcer, ContextGatePolicyStore, DashboardApprovalChannel, FederationRegistry, FilesystemStorage, HERO_COPY, InMemoryModelProvenanceStore, InjectionDetector, MODEL_PRESETS, MemoryStorage, PolicyStore, ProxyRouter, ReputationStore, SovereigntyProfileStore, StateStore, StderrApprovalChannel, TIER_WEIGHTS, WebhookApprovalChannel, canonicalize2 as canonicalize, classifyField, completeHandshake, computeWeightedScore, createBridgeCommitment, createDefaultProfile, createPedersenCommitment, createProofOfKnowledge, createRangeProof, createSanctuaryServer, evaluateField, exitBundleManifestShape, exportExitBundle, filterContext, generateAttestation, generateSHR, generateSystemPrompt, getProtectionSnapshot, getTemplate2 as getTemplate, importExitBundle, initiateHandshake, listTemplateIds, loadConfig, loadExitArtifact, loadPrincipalPolicy, readManifest, recommendPolicy, renderDashboardHTML, resolveTier, respondToHandshake, runExitCommand, signPayload, startDashboard, startDashboardServer, tierDistribution, verifyAttestation, verifyBridgeCommitment, verifyCompletion, verifyExitBundle, verifyPedersenCommitment, verifyProofOfKnowledge, verifyRangeProof, verifySHR, verifySignature };
|
|
35251
|
+
export { ATTESTATION_VERSION, ApprovalGate, AuditLog, AutoApproveChannel, BaselineTracker, TEMPLATES as CONTEXT_GATE_TEMPLATES, CallbackApprovalChannel, ClientManager, CommitmentStore, ContextGateEnforcer, ContextGatePolicyStore, DashboardApprovalChannel, ExitBundleImportError, FederationRegistry, FilesystemStorage, HERO_COPY, InMemoryModelProvenanceStore, InjectionDetector, MODEL_PRESETS, MemoryStorage, PolicyStore, ProxyRouter, ReputationStore, SovereigntyProfileStore, StateStore, StderrApprovalChannel, TIER_WEIGHTS, WebhookApprovalChannel, canonicalize2 as canonicalize, classifyField, completeHandshake, computeWeightedScore, createBridgeCommitment, createDefaultProfile, createPedersenCommitment, createProofOfKnowledge, createRangeProof, createSanctuaryServer, evaluateField, exitBundleManifestShape, exportExitBundle, filterContext, generateAttestation, generateSHR, generateSystemPrompt, getProtectionSnapshot, getTemplate2 as getTemplate, importExitBundle, initiateHandshake, listTemplateIds, loadConfig, loadExitArtifact, loadPrincipalPolicy, readManifest, recommendPolicy, renderDashboardHTML, resolveTier, respondToHandshake, runExitCommand, signPayload, startDashboard, startDashboardServer, tierDistribution, verifyAttestation, verifyBridgeCommitment, verifyCompletion, verifyExitBundle, verifyPedersenCommitment, verifyProofOfKnowledge, verifyRangeProof, verifySHR, verifySignature };
|
|
33912
35252
|
//# sourceMappingURL=index.js.map
|
|
33913
35253
|
//# sourceMappingURL=index.js.map
|