@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.cjs
CHANGED
|
@@ -710,19 +710,40 @@ function assertSanctuaryConfigShape(c) {
|
|
|
710
710
|
|
|
711
711
|
// src/storage/filesystem.ts
|
|
712
712
|
init_random();
|
|
713
|
+
var SAFE_CHARS = /[^A-Za-z0-9_.\-]/g;
|
|
714
|
+
function bijectiveEncode(name) {
|
|
715
|
+
return name.replace(
|
|
716
|
+
SAFE_CHARS,
|
|
717
|
+
(ch) => "!" + ch.charCodeAt(0).toString(16).padStart(2, "0").toUpperCase()
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
function legacyNamespaceSanitize(name) {
|
|
721
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
722
|
+
}
|
|
723
|
+
function legacyKeySanitize(name) {
|
|
724
|
+
return name.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
725
|
+
}
|
|
713
726
|
var FilesystemStorage = class {
|
|
714
727
|
basePath;
|
|
715
728
|
constructor(basePath) {
|
|
716
729
|
this.basePath = basePath;
|
|
717
730
|
}
|
|
718
731
|
entryPath(namespace, key) {
|
|
719
|
-
const safeNamespace = namespace
|
|
720
|
-
const safeKey = key
|
|
732
|
+
const safeNamespace = bijectiveEncode(namespace);
|
|
733
|
+
const safeKey = bijectiveEncode(key);
|
|
721
734
|
return path.join(this.basePath, safeNamespace, `${safeKey}.enc`);
|
|
722
735
|
}
|
|
723
736
|
namespacePath(namespace) {
|
|
724
|
-
|
|
725
|
-
|
|
737
|
+
return path.join(this.basePath, bijectiveEncode(namespace));
|
|
738
|
+
}
|
|
739
|
+
// Legacy on-disk paths produced by the pre-#41 sanitizer. Returned for
|
|
740
|
+
// ENOENT-fallback in read/exists/delete; never written to.
|
|
741
|
+
legacyEntryPath(namespace, key) {
|
|
742
|
+
return path.join(
|
|
743
|
+
this.basePath,
|
|
744
|
+
legacyNamespaceSanitize(namespace),
|
|
745
|
+
`${legacyKeySanitize(key)}.enc`
|
|
746
|
+
);
|
|
726
747
|
}
|
|
727
748
|
async write(namespace, key, data) {
|
|
728
749
|
const dirPath = this.namespacePath(namespace);
|
|
@@ -731,7 +752,13 @@ var FilesystemStorage = class {
|
|
|
731
752
|
await promises.writeFile(filePath, data, { mode: 384 });
|
|
732
753
|
}
|
|
733
754
|
async read(namespace, key) {
|
|
734
|
-
const
|
|
755
|
+
const buf = await this.readAtPath(this.entryPath(namespace, key));
|
|
756
|
+
if (buf !== null) return buf;
|
|
757
|
+
const legacy = this.legacyEntryPath(namespace, key);
|
|
758
|
+
if (legacy === this.entryPath(namespace, key)) return null;
|
|
759
|
+
return this.readAtPath(legacy);
|
|
760
|
+
}
|
|
761
|
+
async readAtPath(filePath) {
|
|
735
762
|
try {
|
|
736
763
|
const buf = await promises.readFile(filePath);
|
|
737
764
|
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
@@ -743,7 +770,13 @@ var FilesystemStorage = class {
|
|
|
743
770
|
}
|
|
744
771
|
}
|
|
745
772
|
async delete(namespace, key, secureOverwrite = true) {
|
|
746
|
-
const
|
|
773
|
+
const newPath = this.entryPath(namespace, key);
|
|
774
|
+
if (await this.deleteAtPath(newPath, secureOverwrite)) return true;
|
|
775
|
+
const legacy = this.legacyEntryPath(namespace, key);
|
|
776
|
+
if (legacy === newPath) return false;
|
|
777
|
+
return this.deleteAtPath(legacy, secureOverwrite);
|
|
778
|
+
}
|
|
779
|
+
async deleteAtPath(filePath, secureOverwrite) {
|
|
747
780
|
try {
|
|
748
781
|
if (secureOverwrite) {
|
|
749
782
|
const fileStat = await promises.stat(filePath);
|
|
@@ -789,12 +822,19 @@ var FilesystemStorage = class {
|
|
|
789
822
|
}
|
|
790
823
|
}
|
|
791
824
|
async exists(namespace, key) {
|
|
792
|
-
const
|
|
825
|
+
const newPath = this.entryPath(namespace, key);
|
|
793
826
|
try {
|
|
794
|
-
await promises.stat(
|
|
827
|
+
await promises.stat(newPath);
|
|
795
828
|
return true;
|
|
796
829
|
} catch {
|
|
797
|
-
|
|
830
|
+
const legacy = this.legacyEntryPath(namespace, key);
|
|
831
|
+
if (legacy === newPath) return false;
|
|
832
|
+
try {
|
|
833
|
+
await promises.stat(legacy);
|
|
834
|
+
return true;
|
|
835
|
+
} catch {
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
798
838
|
}
|
|
799
839
|
}
|
|
800
840
|
async totalSize() {
|
|
@@ -4780,6 +4820,32 @@ function canonicalizeForSigning(body) {
|
|
|
4780
4820
|
// src/shr/generator.ts
|
|
4781
4821
|
init_identity();
|
|
4782
4822
|
init_encoding();
|
|
4823
|
+
|
|
4824
|
+
// src/mesh/constants.ts
|
|
4825
|
+
var PROTOCOL_VERSION = "0.1";
|
|
4826
|
+
var SIGNATURE_SCHEME_V1 = "ed25519-v1";
|
|
4827
|
+
var RESERVED_EVENT_TYPE_PREFIXES = [
|
|
4828
|
+
"EXTENSION_",
|
|
4829
|
+
"cross_fortress_",
|
|
4830
|
+
"multi_master_"
|
|
4831
|
+
];
|
|
4832
|
+
function isReservedEventType(s) {
|
|
4833
|
+
return RESERVED_EVENT_TYPE_PREFIXES.some((p) => s.startsWith(p));
|
|
4834
|
+
}
|
|
4835
|
+
var RESERVED_EXTENSION_ENVELOPE_KEYS = [
|
|
4836
|
+
"cross_fortress_read_grant",
|
|
4837
|
+
"cross_fortress_read_query",
|
|
4838
|
+
"cross_fortress_read_response",
|
|
4839
|
+
"multi_master_policy_merge",
|
|
4840
|
+
"audit_replication_full_n_way",
|
|
4841
|
+
"auto_promote_canonical_audit",
|
|
4842
|
+
"agent_live_migration"
|
|
4843
|
+
];
|
|
4844
|
+
function isReservedExtensionKey(k) {
|
|
4845
|
+
return RESERVED_EXTENSION_ENVELOPE_KEYS.includes(k);
|
|
4846
|
+
}
|
|
4847
|
+
|
|
4848
|
+
// src/shr/generator.ts
|
|
4783
4849
|
var DEFAULT_VALIDITY_MS = 60 * 60 * 1e3;
|
|
4784
4850
|
var DEFAULT_FRESHNESS_WINDOW_DAYS = 30;
|
|
4785
4851
|
var DEFAULT_LOW_TIER_DOMINANCE_THRESHOLD = 0.6;
|
|
@@ -4930,6 +4996,7 @@ function generateSHR(identityId, opts) {
|
|
|
4930
4996
|
return {
|
|
4931
4997
|
body,
|
|
4932
4998
|
signed_by: identity.public_key,
|
|
4999
|
+
signature_scheme: SIGNATURE_SCHEME_V1,
|
|
4933
5000
|
signature: toBase64url(signatureBytes)
|
|
4934
5001
|
};
|
|
4935
5002
|
}
|
|
@@ -7166,14 +7233,14 @@ function generateDashboardHTML(options) {
|
|
|
7166
7233
|
// cookie (set by /auth/session and sent automatically by the
|
|
7167
7234
|
// browser) or as a ?session= query parameter, both of which Stack
|
|
7168
7235
|
// A's checkAuth honours. Loopback callers also bypass auth via the
|
|
7169
|
-
// v0.10.2 _autoAuthLocalhost path, which is the path
|
|
7236
|
+
// v0.10.2 _autoAuthLocalhost path, which is the path Mini1
|
|
7170
7237
|
// hits when the dashboard is auto-opened on 127.0.0.1.
|
|
7171
7238
|
//
|
|
7172
7239
|
// The endpoint itself is /events \u2014 Stack A's route table mounts it
|
|
7173
7240
|
// there, and the previous /api/events URL was a 404 in every real
|
|
7174
7241
|
// boot from v0.10.0 through v0.10.4. The retry loop that result
|
|
7175
7242
|
// produced is exactly the "status bar flashing blue continuously"
|
|
7176
|
-
//
|
|
7243
|
+
// Mini1 reported on v0.10.4.
|
|
7177
7244
|
const eventSource = new EventSource(API_BASE + '/events');
|
|
7178
7245
|
|
|
7179
7246
|
eventSource.addEventListener('init', (e) => {
|
|
@@ -9985,10 +10052,8 @@ var CHANNEL_TEMPLATE_IDS = [
|
|
|
9985
10052
|
"read-then-report",
|
|
9986
10053
|
"scheduled-digest",
|
|
9987
10054
|
"plan-draft-only",
|
|
9988
|
-
"fortress-relay"
|
|
9989
|
-
"concierge-loop"
|
|
10055
|
+
"fortress-relay"
|
|
9990
10056
|
];
|
|
9991
|
-
var COUNTERPARTY_WILDCARD = "*";
|
|
9992
10057
|
var BUDGET_UNITS = ["tokens", "usd"];
|
|
9993
10058
|
|
|
9994
10059
|
// src/templates/registry.ts
|
|
@@ -10156,8 +10221,12 @@ function lintOnboarding(_name, content) {
|
|
|
10156
10221
|
function resolveTemplatesDir() {
|
|
10157
10222
|
const thisFile = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
10158
10223
|
const thisDir = path.dirname(thisFile);
|
|
10159
|
-
if (thisDir.includes("/dist
|
|
10160
|
-
|
|
10224
|
+
if (thisDir.includes("/dist")) {
|
|
10225
|
+
const templatesSubdir = path.join(thisDir, "templates");
|
|
10226
|
+
const candidateDir = fs.existsSync(path.join(thisDir, TEMPLATE_NAMES[0])) ? thisDir : fs.existsSync(path.join(templatesSubdir, TEMPLATE_NAMES[0])) ? templatesSubdir : null;
|
|
10227
|
+
if (candidateDir) return candidateDir;
|
|
10228
|
+
const srcFallback = thisDir.endsWith("/templates") ? thisDir.replace("/dist/templates", "/src/templates") : path.join(thisDir.replace("/dist", "/src"), "templates");
|
|
10229
|
+
return srcFallback;
|
|
10161
10230
|
}
|
|
10162
10231
|
return thisDir;
|
|
10163
10232
|
}
|
|
@@ -10426,23 +10495,6 @@ var fortressRelay = (params) => {
|
|
|
10426
10495
|
setRetentionDays(p, 90);
|
|
10427
10496
|
return p;
|
|
10428
10497
|
};
|
|
10429
|
-
var conciergeLoop = (params) => {
|
|
10430
|
-
const p = basePolicy(params);
|
|
10431
|
-
p.source_english = "Bidirectional Q&A with the operator. Reads local fortress state; never writes outward.";
|
|
10432
|
-
grantOn(p, "memory", {
|
|
10433
|
-
counterparty: params.counterparty,
|
|
10434
|
-
action: "read",
|
|
10435
|
-
scope: { local_fortress_state_only: true, ...params.scope ?? {} }
|
|
10436
|
-
});
|
|
10437
|
-
grantOn(p, "outputs", {
|
|
10438
|
-
counterparty: params.counterparty || COUNTERPARTY_WILDCARD,
|
|
10439
|
-
action: "read",
|
|
10440
|
-
scope: { operator_chat_only: true, ...params.scope ?? {} }
|
|
10441
|
-
});
|
|
10442
|
-
p.egress = { allowlist: [] };
|
|
10443
|
-
setRetentionDays(p, 14);
|
|
10444
|
-
return p;
|
|
10445
|
-
};
|
|
10446
10498
|
function allowedHostsFromScope(scope) {
|
|
10447
10499
|
const raw = scope?.allowed_hosts;
|
|
10448
10500
|
if (!Array.isArray(raw)) return [];
|
|
@@ -10483,13 +10535,6 @@ var REGISTRY = {
|
|
|
10483
10535
|
label: "Fortress relay",
|
|
10484
10536
|
description: "Routes signed events between peer fortresses. Commits bind only when both sides sign.",
|
|
10485
10537
|
factory: fortressRelay
|
|
10486
|
-
},
|
|
10487
|
-
"concierge-loop": {
|
|
10488
|
-
id: "concierge-loop",
|
|
10489
|
-
severity: "LOW",
|
|
10490
|
-
label: "Concierge loop",
|
|
10491
|
-
description: "Bidirectional Q&A with the operator. Reads local fortress state; never writes outward.",
|
|
10492
|
-
factory: conciergeLoop
|
|
10493
10538
|
}
|
|
10494
10539
|
};
|
|
10495
10540
|
function applyChannelTemplate(id, params) {
|
|
@@ -10563,8 +10608,14 @@ function encode(value) {
|
|
|
10563
10608
|
}
|
|
10564
10609
|
function encodeArray(arr) {
|
|
10565
10610
|
const parts = [];
|
|
10566
|
-
for (
|
|
10567
|
-
|
|
10611
|
+
for (let i = 0; i < arr.length; i++) {
|
|
10612
|
+
const item = arr[i];
|
|
10613
|
+
if (item === void 0) {
|
|
10614
|
+
throw new MeshCanonicalJsonError(
|
|
10615
|
+
`canonicalize(): undefined is not a valid JSON value at array index ${i}`
|
|
10616
|
+
);
|
|
10617
|
+
}
|
|
10618
|
+
parts.push(encode(item));
|
|
10568
10619
|
}
|
|
10569
10620
|
return "[" + parts.join(",") + "]";
|
|
10570
10621
|
}
|
|
@@ -10869,29 +10920,6 @@ function encodePolicyBlob(policy) {
|
|
|
10869
10920
|
init_encoding();
|
|
10870
10921
|
init_random();
|
|
10871
10922
|
|
|
10872
|
-
// src/mesh/constants.ts
|
|
10873
|
-
var PROTOCOL_VERSION = "0.1";
|
|
10874
|
-
var RESERVED_EVENT_TYPE_PREFIXES = [
|
|
10875
|
-
"EXTENSION_",
|
|
10876
|
-
"cross_fortress_",
|
|
10877
|
-
"multi_master_"
|
|
10878
|
-
];
|
|
10879
|
-
function isReservedEventType(s) {
|
|
10880
|
-
return RESERVED_EVENT_TYPE_PREFIXES.some((p) => s.startsWith(p));
|
|
10881
|
-
}
|
|
10882
|
-
var RESERVED_EXTENSION_ENVELOPE_KEYS = [
|
|
10883
|
-
"cross_fortress_read_grant",
|
|
10884
|
-
"cross_fortress_read_query",
|
|
10885
|
-
"cross_fortress_read_response",
|
|
10886
|
-
"multi_master_policy_merge",
|
|
10887
|
-
"audit_replication_full_n_way",
|
|
10888
|
-
"auto_promote_canonical_audit",
|
|
10889
|
-
"agent_live_migration"
|
|
10890
|
-
];
|
|
10891
|
-
function isReservedExtensionKey(k) {
|
|
10892
|
-
return RESERVED_EXTENSION_ENVELOPE_KEYS.includes(k);
|
|
10893
|
-
}
|
|
10894
|
-
|
|
10895
10923
|
// src/mesh/trust-root.ts
|
|
10896
10924
|
init_encoding();
|
|
10897
10925
|
init_identity();
|
|
@@ -12112,7 +12140,7 @@ async function api(path, opts) {
|
|
|
12112
12140
|
// /policies, /activity responses on subsequent GETs even when the
|
|
12113
12141
|
// server-side state has changed (e.g. recent-failures buffer cleared
|
|
12114
12142
|
// on substrate flip). The pre-rc.5 client used bare fetch with no
|
|
12115
|
-
// cache control, which on
|
|
12143
|
+
// cache control, which on Mini1 Safari produced a stale view of
|
|
12116
12144
|
// server state and made the operator-visible badge color stick to
|
|
12117
12145
|
// its prior value across substrate changes. Belt + suspenders:
|
|
12118
12146
|
// cache: "no-store" turns off the response cache; the _t query
|
|
@@ -12274,12 +12302,6 @@ const CHANNEL_TEMPLATES = [
|
|
|
12274
12302
|
severity: "MEDIUM",
|
|
12275
12303
|
title: "Fortress relay",
|
|
12276
12304
|
description: "Routes signed events between peer fortresses. Commits bind only when both sides sign."
|
|
12277
|
-
},
|
|
12278
|
-
{
|
|
12279
|
-
id: "concierge-loop",
|
|
12280
|
-
severity: "LOW",
|
|
12281
|
-
title: "Concierge loop",
|
|
12282
|
-
description: "Bidirectional Q&A with the operator. Reads local fortress state; never writes outward."
|
|
12283
12305
|
}
|
|
12284
12306
|
];
|
|
12285
12307
|
|
|
@@ -12326,6 +12348,24 @@ function setRoute(route) {
|
|
|
12326
12348
|
renderFortress();
|
|
12327
12349
|
}
|
|
12328
12350
|
|
|
12351
|
+
// Renders the global attestation badge (Q1 layer 1, persistent across
|
|
12352
|
+
// surfaces). Tone is driven by state.topbarPills.attestation. Pending
|
|
12353
|
+
// state shows a dashed seal ring; verified shows solid; degraded shows
|
|
12354
|
+
// outlined core; unverified shows the broken-seal mark. Observation
|
|
12355
|
+
// language only; Castle Layer 1 enforcement ships in WP-V1.x-CASTLE-WALL.
|
|
12356
|
+
function renderTopbarAttestationBadge(stateName) {
|
|
12357
|
+
const valid = stateName === "verified" || stateName === "degraded" || stateName === "unverified" || stateName === "pending";
|
|
12358
|
+
const cls = valid ? stateName : "pending";
|
|
12359
|
+
const ringDashed = cls === "pending" ? " dashed" : "";
|
|
12360
|
+
return '<span class="att-global ' + cls + '" data-pill="attestation" title="Fortress attestation">' +
|
|
12361
|
+
'<span class="seal">' +
|
|
12362
|
+
'<span class="seal-ring' + ringDashed + '"></span>' +
|
|
12363
|
+
'<span class="seal-core"></span>' +
|
|
12364
|
+
'</span>' +
|
|
12365
|
+
'<span class="label">' + escHtml(cls) + '</span>' +
|
|
12366
|
+
'</span>';
|
|
12367
|
+
}
|
|
12368
|
+
|
|
12329
12369
|
function renderTopbar() {
|
|
12330
12370
|
const pillEl = document.getElementById("topbar-pills");
|
|
12331
12371
|
if (!pillEl) return;
|
|
@@ -12342,7 +12382,7 @@ function renderTopbar() {
|
|
|
12342
12382
|
versionPill,
|
|
12343
12383
|
'<span class="pill" data-pill="deployment">deployment: ' + escHtml(state.topbarPills.deployment) + '</span>',
|
|
12344
12384
|
'<span class="pill" data-pill="mode">mode: ' + escHtml(state.topbarPills.mode) + '</span>',
|
|
12345
|
-
|
|
12385
|
+
renderTopbarAttestationBadge(state.topbarPills.attestation)
|
|
12346
12386
|
].join("");
|
|
12347
12387
|
// Lockdown button three-state UX (binding addendum 3).
|
|
12348
12388
|
const btn = document.getElementById("btn-lockdown");
|
|
@@ -12438,6 +12478,7 @@ function renderMain() {
|
|
|
12438
12478
|
case "agent-detail": nextHtml = renderAgentDetail(); break;
|
|
12439
12479
|
case "policy": nextHtml = renderPolicyCenter(); break;
|
|
12440
12480
|
case "intelligence": nextHtml = renderIntelligenceCenter(); break;
|
|
12481
|
+
case "attestation": nextHtml = renderAttestation(); break;
|
|
12441
12482
|
case "privacy": nextHtml = renderPrivacyPage(); break;
|
|
12442
12483
|
case "coordination": nextHtml = renderCoordinationPage(); break;
|
|
12443
12484
|
case "health": nextHtml = renderHealthPage(); break;
|
|
@@ -12516,9 +12557,9 @@ function renderMain() {
|
|
|
12516
12557
|
// "Concierge unavailable; substrate not configured") sourced from the
|
|
12517
12558
|
// last response's served_by + display_label.
|
|
12518
12559
|
const CONCIERGE_SUGGESTIONS = [
|
|
12519
|
-
{ id: "summarize-hour", label: "summarize the last hour", query: "Summarize what happened in this fortress in the last hour." },
|
|
12520
|
-
{ id: "agent-touched", label: "what has each agent touched today", query: "What has each wrapped agent done today? Group by agent." },
|
|
12521
|
-
{ id: "open-approvals", label: "any open approvals?", query: "Are there any open Tier 1 approvals or pending inbox items I should look at?" }
|
|
12560
|
+
{ id: "summarize-hour", category: "Summarize", label: "summarize the last hour", query: "Summarize what happened in this fortress in the last hour." },
|
|
12561
|
+
{ id: "agent-touched", category: "Inspect", label: "what has each agent touched today", query: "What has each wrapped agent done today? Group by agent." },
|
|
12562
|
+
{ 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?" }
|
|
12522
12563
|
];
|
|
12523
12564
|
|
|
12524
12565
|
// Direct-agent chat surface was removed in the v1.2 reshape; the
|
|
@@ -12535,59 +12576,316 @@ function renderDashboardConcierge() {
|
|
|
12535
12576
|
const badge = c.badge && c.badge.displayLabel
|
|
12536
12577
|
? '<span class="pill mono concierge-badge" title="Substrate that served the most recent response">' + escHtml(c.badge.displayLabel) + '</span>'
|
|
12537
12578
|
: '<span class="pill muted concierge-badge">Concierge: substrate not yet contacted</span>';
|
|
12538
|
-
const
|
|
12579
|
+
const sendDisabled = c.sending ? ' disabled' : '';
|
|
12580
|
+
const sendLabel = c.sending ? 'Sending...' : 'Send';
|
|
12581
|
+
// Sprint Piece 2 PR 2: empty state lives INSIDE the concierge-history
|
|
12582
|
+
// container so the DDD e2e selector .concierge-history matches both
|
|
12583
|
+
// empty and active state. The container's flex layout hosts a single
|
|
12584
|
+
// .concierge-empty child that fills the available height with a serif
|
|
12585
|
+
// headline and a 3-up suggest grid; the grid replaces the v1.2 bottom
|
|
12586
|
+
// chip row, which is retired with this polish.
|
|
12587
|
+
const emptyState =
|
|
12588
|
+
'<div class="concierge-empty">' +
|
|
12589
|
+
'<div class="concierge-empty-headline">' +
|
|
12590
|
+
'<h2>Where would you like to begin.</h2>' +
|
|
12591
|
+
'<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>' +
|
|
12592
|
+
'</div>' +
|
|
12593
|
+
'<div class="concierge-suggest-grid">' +
|
|
12594
|
+
CONCIERGE_SUGGESTIONS.map(function (s) {
|
|
12595
|
+
return '<button class="concierge-suggest" data-action="concierge-suggestion" data-suggestion-id="' + escHtml(s.id) + '"' + sendDisabled + '>' +
|
|
12596
|
+
'<span class="label">' + escHtml(s.category || '') + '</span>' +
|
|
12597
|
+
escHtml(s.label) +
|
|
12598
|
+
'</button>';
|
|
12599
|
+
}).join("") +
|
|
12600
|
+
'</div>' +
|
|
12601
|
+
'</div>';
|
|
12602
|
+
const messagesHtml = c.messages.length
|
|
12539
12603
|
? c.messages.map(function (m) {
|
|
12540
12604
|
const cls = m.role === "operator" ? "concierge-msg-operator" : "concierge-msg-concierge";
|
|
12541
|
-
const
|
|
12605
|
+
const authorLabel = m.role === "operator" ? "you" : "sanctuary";
|
|
12606
|
+
const metaParts = [];
|
|
12607
|
+
if (m.created_at) metaParts.push(escHtml(shortTime(m.created_at)));
|
|
12608
|
+
if (m.role === "concierge" && m.served_by) metaParts.push('substrate: ' + escHtml(m.served_by));
|
|
12609
|
+
const meta = metaParts.length
|
|
12610
|
+
? '<div class="concierge-msg-meta"><span>' + metaParts.join(' · ') + '</span></div>'
|
|
12611
|
+
: '';
|
|
12542
12612
|
return '<div class="concierge-msg ' + cls + '">' +
|
|
12543
|
-
'<
|
|
12613
|
+
'<span class="concierge-msg-author">' + escHtml(authorLabel) + '</span>' +
|
|
12544
12614
|
'<div class="concierge-msg-body">' + escHtml(m.body) + '</div>' +
|
|
12615
|
+
meta +
|
|
12545
12616
|
'</div>';
|
|
12546
12617
|
}).join("\n")
|
|
12547
|
-
:
|
|
12618
|
+
: emptyState;
|
|
12548
12619
|
const errorBanner = c.error
|
|
12549
12620
|
? '<div class="banner banner-warn">' + escHtml(c.error) + '</div>'
|
|
12550
12621
|
: "";
|
|
12551
|
-
const sendDisabled = c.sending ? ' disabled' : '';
|
|
12552
|
-
const sendLabel = c.sending ? 'Sending...' : 'Send';
|
|
12553
|
-
const chips = CONCIERGE_SUGGESTIONS.map(function (s) {
|
|
12554
|
-
return '<button class="btn chip" data-action="concierge-suggestion" data-suggestion-id="' + escHtml(s.id) + '"' + sendDisabled + '>' + escHtml(s.label) + '</button>';
|
|
12555
|
-
}).join("\n");
|
|
12556
12622
|
const activeChatsPanel = renderActiveChatsPanel();
|
|
12557
12623
|
return [
|
|
12558
|
-
'<
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
'<
|
|
12563
|
-
|
|
12624
|
+
'<div class="concierge-wrap">',
|
|
12625
|
+
'<div class="page-head"><div>',
|
|
12626
|
+
'<p class="eyebrow">Concierge</p>',
|
|
12627
|
+
'<h1>Talk to your fortress.</h1>',
|
|
12628
|
+
'<p class="sub">A direct line to Sanctuary, routed through the substrate you chose. Nothing leaves without your hand on it.</p>',
|
|
12629
|
+
'</div></div>',
|
|
12630
|
+
activeChatsPanel,
|
|
12631
|
+
'<div class="card concierge-card">',
|
|
12632
|
+
'<div class="concierge-header">',
|
|
12633
|
+
'<div class="concierge-persona">',
|
|
12634
|
+
'<div class="glyph-ring"></div>',
|
|
12635
|
+
'<div class="concierge-persona-text"><strong>Sanctuary Fortress concierge</strong><small>read-only over fortress state</small></div>',
|
|
12636
|
+
'</div>',
|
|
12637
|
+
'<div class="concierge-meta">' + badge + '</div>',
|
|
12638
|
+
'</div>',
|
|
12639
|
+
errorBanner,
|
|
12640
|
+
'<div class="concierge-history" id="concierge-history">' + messagesHtml + '</div>',
|
|
12641
|
+
'<form class="concierge-composer" data-action="concierge-submit">',
|
|
12642
|
+
'<div class="input-wrap">',
|
|
12643
|
+
'<input type="text" name="concierge-input" placeholder="Type to Sanctuary. Enter to send." value="' + escHtml(c.composer) + '" data-action="concierge-input"' + sendDisabled + ' autocomplete="off">',
|
|
12644
|
+
'<span class="composer-meta">Enter</span>',
|
|
12645
|
+
'</div>',
|
|
12646
|
+
'<button type="submit" class="btn btn-primary" data-action="concierge-send"' + sendDisabled + '>' + escHtml(sendLabel) + '</button>',
|
|
12647
|
+
'</form>',
|
|
12648
|
+
'<p class="muted concierge-foot">First time? <a href="#intelligence">Pick a substrate</a> to enable concierge replies.</p>',
|
|
12564
12649
|
'</div>',
|
|
12565
|
-
errorBanner,
|
|
12566
|
-
'<div class="concierge-history" id="concierge-history">' + messages + '</div>',
|
|
12567
|
-
'<form class="concierge-composer" data-action="concierge-submit">',
|
|
12568
|
-
'<input type="text" name="concierge-input" placeholder="Ask the concierge about this fortress..." value="' + escHtml(c.composer) + '" data-action="concierge-input"' + sendDisabled + ' autocomplete="off">',
|
|
12569
|
-
'<button type="submit" class="btn btn-primary" data-action="concierge-send"' + sendDisabled + '>' + escHtml(sendLabel) + '</button>',
|
|
12570
|
-
'</form>',
|
|
12571
|
-
'<div class="concierge-chips">' + chips + '</div>',
|
|
12572
|
-
'<p class="muted concierge-foot">First time? <a href="#intelligence">Pick a substrate</a> to enable concierge replies.</p>',
|
|
12573
12650
|
'</div>'
|
|
12574
12651
|
].join("");
|
|
12575
12652
|
}
|
|
12576
12653
|
|
|
12577
12654
|
// ── Render: agents list / detail ───────────────────────────────────────
|
|
12655
|
+
//
|
|
12656
|
+
// Sprint Piece 2 PR 4 polish: empty state uses .agents-empty with the
|
|
12657
|
+
// concentric icon-frame + a terminal-block CTA. Populated state uses the
|
|
12658
|
+
// .agents-layout grid with the .agents-list 4-column table (Agent /
|
|
12659
|
+
// State / Attestation / Last seen). The empty-state branch keeps the
|
|
12660
|
+
// literal '<h1>Agents</h1>' start and the "No wrapped agents yet." copy
|
|
12661
|
+
// because agents-empty-state-canary.test.ts pins both.
|
|
12662
|
+
function agentInitials(agentId) {
|
|
12663
|
+
const tail = String(agentId || "").split(":").pop() || "";
|
|
12664
|
+
const cleaned = tail.replace(/[^a-zA-Z0-9]/g, "");
|
|
12665
|
+
return (cleaned.slice(0, 2) || "??").toUpperCase();
|
|
12666
|
+
}
|
|
12667
|
+
function agentStateClass(status) {
|
|
12668
|
+
if (status === "active") return "live";
|
|
12669
|
+
if (status === "locked_down" || status === "error") return "off";
|
|
12670
|
+
return "idle";
|
|
12671
|
+
}
|
|
12672
|
+
// Per-agent attestation badge (Q1 layer 2). Square chip beside each
|
|
12673
|
+
// agent: a bounded glyph beside a bounded entity. Color and fill pattern
|
|
12674
|
+
// carry meaning together so the badge reads even monochrome. The "locked"
|
|
12675
|
+
// status maps to the unverified visual (rust + hatched mark) since a
|
|
12676
|
+
// locked-down agent has no current attestation; the inspect-pane copy
|
|
12677
|
+
// explains the distinction. Pure visual surface; no state derivation.
|
|
12678
|
+
function renderAgentAttestationBadge(status) {
|
|
12679
|
+
let cls;
|
|
12680
|
+
let label;
|
|
12681
|
+
if (status === "active") { cls = "verified"; label = "verified"; }
|
|
12682
|
+
else if (status === "locked_down") { cls = "unverified"; label = "locked"; }
|
|
12683
|
+
else if (status === "error") { cls = "unverified"; label = "unverified"; }
|
|
12684
|
+
else { cls = "degraded"; label = "degraded"; }
|
|
12685
|
+
return '<span class="att-agent ' + cls + '" title="Agent attestation"><span class="mark"></span>' + escHtml(label) + '</span>';
|
|
12686
|
+
}
|
|
12687
|
+
// Per-action attestation tick (Q1 layer 3). Tiny inline shape on every
|
|
12688
|
+
// timeline row. Two-byte signature fragment is enough at low resolution;
|
|
12689
|
+
// the full signature is one click away. Neutral state shows a circle
|
|
12690
|
+
// instead of a tick when the signer was unreachable; the action is still
|
|
12691
|
+
// recorded. Visual surface only.
|
|
12692
|
+
function renderActionAttestationBadge(stateName, sig) {
|
|
12693
|
+
const valid = stateName === "verified" || stateName === "degraded" || stateName === "unverified" || stateName === "neutral";
|
|
12694
|
+
const cls = valid ? stateName : "neutral";
|
|
12695
|
+
const sigText = sig ? String(sig) : "--";
|
|
12696
|
+
return '<span class="att-action ' + cls + '" title="Action attestation">' +
|
|
12697
|
+
'<span class="tick"></span>' +
|
|
12698
|
+
'<span>' + escHtml(sigText) + '</span>' +
|
|
12699
|
+
'</span>';
|
|
12700
|
+
}
|
|
12701
|
+
// Attestation gallery surface (Q1 four classes: global / per-agent /
|
|
12702
|
+
// per-action / per-transaction custody-provenance stub). Reference for
|
|
12703
|
+
// operators: shows what each badge looks like across verified, degraded,
|
|
12704
|
+
// unverified, and (where applicable) pending or neutral states. Pure
|
|
12705
|
+
// visual; no derivation, no live data. Castle Layer 3 cooperative-MCP UX
|
|
12706
|
+
// surface; Castle Layer 1 enforcement ships in WP-V1.x-CASTLE-WALL.
|
|
12707
|
+
function renderAttestation() {
|
|
12708
|
+
return '<div class="att-gallery">' +
|
|
12709
|
+
'<div class="page-head"><div>' +
|
|
12710
|
+
'<p class="eyebrow">Attestation</p>' +
|
|
12711
|
+
'<h1>Four classes of badge.</h1>' +
|
|
12712
|
+
'<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>' +
|
|
12713
|
+
'</div></div>' +
|
|
12714
|
+
// Global
|
|
12715
|
+
'<div class="att-section">' +
|
|
12716
|
+
'<div class="att-section-head"><div>' +
|
|
12717
|
+
'<h2>Global. The fortress itself.</h2>' +
|
|
12718
|
+
'<p>Lives in the topbar. Visible on every surface. Tells you the fortress identity is currently signed and matches the binary you installed.</p>' +
|
|
12719
|
+
'</div><span class="label">topbar</span></div>' +
|
|
12720
|
+
attRow(renderTopbarAttestationBadge("verified"), "Verified", "Identity matches. Binary matches. Default state for a healthy fortress.") +
|
|
12721
|
+
attRow(renderTopbarAttestationBadge("degraded"), "Degraded", "The signature is older than the staleness window, or one of two co-signers is unreachable. The fortress keeps running.") +
|
|
12722
|
+
attRow(renderTopbarAttestationBadge("unverified"), "Unverified", "The signature did not validate. The surface still works; lockdown is still available; the badge tells you to investigate.") +
|
|
12723
|
+
attRow(renderTopbarAttestationBadge("pending"), "Pending", "First-run state. Fortress is signing for the first time. Settles in seconds.") +
|
|
12724
|
+
'</div>' +
|
|
12725
|
+
// Per-agent
|
|
12726
|
+
'<div class="att-section">' +
|
|
12727
|
+
'<div class="att-section-head"><div>' +
|
|
12728
|
+
'<h2>Per-agent. In the agents list and inspect pane.</h2>' +
|
|
12729
|
+
'<p>A square chip beside each agent. Square because an agent is bounded; the fortress (a circle) contains it.</p>' +
|
|
12730
|
+
'</div><span class="label">agents view</span></div>' +
|
|
12731
|
+
'<div class="att-row">' +
|
|
12732
|
+
'<div class="demo" style="display:flex; gap:8px; flex-wrap:wrap;">' +
|
|
12733
|
+
renderAgentAttestationBadge("active") +
|
|
12734
|
+
renderAgentAttestationBadgeForState("degraded", "degraded") +
|
|
12735
|
+
renderAgentAttestationBadgeForState("unverified", "unverified") +
|
|
12736
|
+
'</div>' +
|
|
12737
|
+
'<div class="desc"><strong>Verified, degraded, unverified</strong>' +
|
|
12738
|
+
'<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>' +
|
|
12739
|
+
'</div>' +
|
|
12740
|
+
'</div>' +
|
|
12741
|
+
'</div>' +
|
|
12742
|
+
// Per-action
|
|
12743
|
+
'<div class="att-section">' +
|
|
12744
|
+
'<div class="att-section-head"><div>' +
|
|
12745
|
+
'<h2>Per-action. Inline in the activity timeline.</h2>' +
|
|
12746
|
+
'<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>' +
|
|
12747
|
+
'</div><span class="label">timeline</span></div>' +
|
|
12748
|
+
'<div class="att-row">' +
|
|
12749
|
+
'<div class="demo" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">' +
|
|
12750
|
+
'<span style="font-size:13px; color:var(--ink-2);">14:22:08 doc-reviewer summarized intake.pdf</span>' +
|
|
12751
|
+
renderActionAttestationBadge("verified", "9c7d..2a") +
|
|
12752
|
+
'</div>' +
|
|
12753
|
+
'<div class="desc"><strong>Verified action</strong>' +
|
|
12754
|
+
'<small>The most common shape. Two-byte signature fragment is enough; the full signature is one click away.</small>' +
|
|
12755
|
+
'</div>' +
|
|
12756
|
+
'</div>' +
|
|
12757
|
+
'<div class="att-row">' +
|
|
12758
|
+
'<div class="demo" style="display:flex; gap:10px; align-items:center;">' +
|
|
12759
|
+
'<span style="font-size:13px; color:var(--ink-2);">14:11:47 privacy filter redacted payload</span>' +
|
|
12760
|
+
renderActionAttestationBadge("degraded", "b440..71") +
|
|
12761
|
+
'</div>' +
|
|
12762
|
+
'<div class="desc"><strong>Degraded action</strong>' +
|
|
12763
|
+
'<small>The action signed, but the signature class was less than the policy preferred. Useful when a substrate is still warming up.</small>' +
|
|
12764
|
+
'</div>' +
|
|
12765
|
+
'</div>' +
|
|
12766
|
+
'<div class="att-row">' +
|
|
12767
|
+
'<div class="demo" style="display:flex; gap:10px; align-items:center;">' +
|
|
12768
|
+
'<span style="font-size:13px; color:var(--ink-2);">14:09:02 agent attempted external link</span>' +
|
|
12769
|
+
renderActionAttestationBadge("neutral", "--") +
|
|
12770
|
+
'</div>' +
|
|
12771
|
+
'<div class="desc"><strong>Neutral. Degrade, not destroy.</strong>' +
|
|
12772
|
+
'<small>The signer was unreachable. Rather than hide the action, the badge becomes neutral and a tooltip explains. The action is still recorded.</small>' +
|
|
12773
|
+
'</div>' +
|
|
12774
|
+
'</div>' +
|
|
12775
|
+
'</div>' +
|
|
12776
|
+
// Custody stub
|
|
12777
|
+
'<div class="att-section">' +
|
|
12778
|
+
'<div class="att-section-head"><div>' +
|
|
12779
|
+
'<h2>Custody. Stub for v1.x.</h2>' +
|
|
12780
|
+
'<p>A fourth class, surfaced conservatively. Reserved for forthcoming custody-provenance signatures (x402 payment receipts, ERC-8004 identity assertions). Visible, dashed, clearly stubbed.</p>' +
|
|
12781
|
+
'</div><span class="label">stub</span></div>' +
|
|
12782
|
+
'<div class="att-row">' +
|
|
12783
|
+
'<div class="demo">' +
|
|
12784
|
+
'<span class="att-custody" title="Custody-provenance, v1.x">' +
|
|
12785
|
+
'<span class="seal-stub"></span>' +
|
|
12786
|
+
'<span class="stub-tag">custody. stub</span>' +
|
|
12787
|
+
'</span>' +
|
|
12788
|
+
'</div>' +
|
|
12789
|
+
'<div class="desc"><strong>Custody. Stub.</strong>' +
|
|
12790
|
+
'<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>' +
|
|
12791
|
+
'</div>' +
|
|
12792
|
+
'</div>' +
|
|
12793
|
+
'</div>' +
|
|
12794
|
+
// Tooltip
|
|
12795
|
+
'<div class="att-section">' +
|
|
12796
|
+
'<div class="att-section-head"><div>' +
|
|
12797
|
+
'<h2>Tooltip on failure.</h2>' +
|
|
12798
|
+
'<p>A failed badge is never silent. The tooltip explains in plain language, suggests one action, and confirms the surface is still working.</p>' +
|
|
12799
|
+
'</div><span class="label">degrade not destroy</span></div>' +
|
|
12800
|
+
'<div class="att-row">' +
|
|
12801
|
+
'<div class="demo">' +
|
|
12802
|
+
'<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>' +
|
|
12803
|
+
'</div>' +
|
|
12804
|
+
'<div class="desc"><strong>Plain-language tooltip</strong>' +
|
|
12805
|
+
'<small>Three lines, in order: what happened, what did not break, what to do. No jargon, no stack trace.</small>' +
|
|
12806
|
+
'</div>' +
|
|
12807
|
+
'</div>' +
|
|
12808
|
+
'</div>' +
|
|
12809
|
+
'</div>';
|
|
12810
|
+
}
|
|
12811
|
+
function attRow(demoHtml, strong, smallText) {
|
|
12812
|
+
return '<div class="att-row">' +
|
|
12813
|
+
'<div class="demo">' + demoHtml + '</div>' +
|
|
12814
|
+
'<div class="desc"><strong>' + escHtml(strong) + '</strong>' +
|
|
12815
|
+
'<small>' + escHtml(smallText) + '</small>' +
|
|
12816
|
+
'</div>' +
|
|
12817
|
+
'</div>';
|
|
12818
|
+
}
|
|
12819
|
+
// Gallery-only variant: render a per-agent badge for a given visual state
|
|
12820
|
+
// (verified / degraded / unverified) without going through the agent
|
|
12821
|
+
// status mapping. Used by renderAttestation to show all three states
|
|
12822
|
+
// side by side as design reference.
|
|
12823
|
+
function renderAgentAttestationBadgeForState(cls, label) {
|
|
12824
|
+
return '<span class="att-agent ' + escHtml(cls) + '" title="Agent attestation"><span class="mark"></span>' + escHtml(label) + '</span>';
|
|
12825
|
+
}
|
|
12826
|
+
function relTimeFromIso(iso) {
|
|
12827
|
+
if (!iso) return "";
|
|
12828
|
+
const d = new Date(iso);
|
|
12829
|
+
if (isNaN(d.getTime())) return iso;
|
|
12830
|
+
const diffMs = Date.now() - d.getTime();
|
|
12831
|
+
const diffSec = Math.max(0, Math.floor(diffMs / 1000));
|
|
12832
|
+
if (diffSec < 60) return diffSec + "s ago";
|
|
12833
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
12834
|
+
if (diffMin < 60) return diffMin + "m ago";
|
|
12835
|
+
const diffHr = Math.floor(diffMin / 60);
|
|
12836
|
+
if (diffHr < 24) return diffHr + "h ago";
|
|
12837
|
+
const diffDay = Math.floor(diffHr / 24);
|
|
12838
|
+
return diffDay + "d ago";
|
|
12839
|
+
}
|
|
12578
12840
|
function renderAgentsList() {
|
|
12579
|
-
if (!state.agents.length) return '<h1>Agents</h1
|
|
12841
|
+
if (!state.agents.length) return '<h1>Agents</h1>' +
|
|
12842
|
+
'<div class="agents-empty">' +
|
|
12843
|
+
'<div class="icon-frame"><div class="core"></div></div>' +
|
|
12844
|
+
'<h2>No wrapped agents yet.</h2>' +
|
|
12845
|
+
'<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>' +
|
|
12846
|
+
'<div class="terminal-block"><span class="cmd"><span class="prompt">$</span>sanctuary wrap</span></div>' +
|
|
12847
|
+
'</div>';
|
|
12848
|
+
const count = state.agents.length;
|
|
12849
|
+
const subCopy = count + ' wrapped. Click one to inspect its activity, policy, and pending approvals.';
|
|
12580
12850
|
const rows = state.agents.map(function (a) {
|
|
12581
12851
|
const map = STATUS_MAP[a.status] || STATUS_MAP.unknown;
|
|
12582
|
-
const
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
'<
|
|
12852
|
+
const dotCls = agentStateClass(a.status);
|
|
12853
|
+
const initials = agentInitials(a.agent_id);
|
|
12854
|
+
const role = escHtml(a.harness) + (a.model_provider && a.model_provider.model_id ? ' · ' + escHtml(a.model_provider.model_id) : '');
|
|
12855
|
+
const isSelected = state.selectedAgentId === a.agent_id;
|
|
12856
|
+
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) + '">' +
|
|
12857
|
+
'<div class="agent-identity">' +
|
|
12858
|
+
'<div class="agent-glyph">' + escHtml(initials) + '</div>' +
|
|
12859
|
+
'<div class="agent-name">' +
|
|
12860
|
+
'<strong>' + escHtml(a.agent_id) + '</strong>' +
|
|
12861
|
+
'<small>' + role + '</small>' +
|
|
12862
|
+
'</div>' +
|
|
12863
|
+
'</div>' +
|
|
12864
|
+
'<span class="agent-state">' +
|
|
12865
|
+
'<span class="state-dot ' + dotCls + '"></span>' +
|
|
12866
|
+
escHtml(map.label) +
|
|
12867
|
+
'</span>' +
|
|
12868
|
+
renderAgentAttestationBadge(a.status) +
|
|
12869
|
+
'<span class="agent-last">' + escHtml(relTimeFromIso(a.last_activity_at)) + '</span>' +
|
|
12588
12870
|
'</div>';
|
|
12589
12871
|
}).join("\n");
|
|
12590
|
-
return '<
|
|
12872
|
+
return '<div class="agents-wrap">' +
|
|
12873
|
+
'<div class="page-head">' +
|
|
12874
|
+
'<div>' +
|
|
12875
|
+
'<p class="eyebrow">Agents</p>' +
|
|
12876
|
+
'<h1>Agents.</h1>' +
|
|
12877
|
+
'<p class="sub">' + escHtml(subCopy) + '</p>' +
|
|
12878
|
+
'</div>' +
|
|
12879
|
+
'</div>' +
|
|
12880
|
+
'<div class="agents-layout">' +
|
|
12881
|
+
'<div class="agents-list">' +
|
|
12882
|
+
'<div class="agents-list-head">' +
|
|
12883
|
+
'<span>Agent</span><span>State</span><span>Attestation</span><span>Last seen</span>' +
|
|
12884
|
+
'</div>' +
|
|
12885
|
+
rows +
|
|
12886
|
+
'</div>' +
|
|
12887
|
+
'</div>' +
|
|
12888
|
+
'</div>';
|
|
12591
12889
|
}
|
|
12592
12890
|
|
|
12593
12891
|
function renderAgentDetail() {
|
|
@@ -12598,7 +12896,10 @@ function renderAgentDetail() {
|
|
|
12598
12896
|
const timeline = events.length
|
|
12599
12897
|
? events.map(function (e) {
|
|
12600
12898
|
const t = renderTemplate(e.display_template_id, e.display_template_args);
|
|
12601
|
-
|
|
12899
|
+
const badgeHtml = e.attestation
|
|
12900
|
+
? ' ' + renderActionAttestationBadge(e.attestation.state, e.attestation.fragment)
|
|
12901
|
+
: '';
|
|
12902
|
+
return '<div class="row"><span class="muted">' + escHtml(shortTime(e.emitted_at)) + '</span><span>' + escHtml(t) + badgeHtml + '</span></div>';
|
|
12602
12903
|
}).join("\n")
|
|
12603
12904
|
: '<p class="muted">No activity yet.</p>';
|
|
12604
12905
|
// WP-V1.2 reshape click-to-inspect surface. Clicking "Open inspect
|
|
@@ -12642,53 +12943,99 @@ function renderAgentInspectPanel(agent) {
|
|
|
12642
12943
|
: "";
|
|
12643
12944
|
|
|
12644
12945
|
// State 2: panel loaded.
|
|
12946
|
+
// Sprint Piece 2 PR 4 polish: outer wrapper combines .card with
|
|
12947
|
+
// .inspect-pane (sticky right rail, internal scroll, sectioned body).
|
|
12948
|
+
// The .card class is preserved so the rendered surface keeps its
|
|
12949
|
+
// shared card chrome; .inspect-pane overrides .card padding so the
|
|
12950
|
+
// inspect-head and inspect-body control their own spacing per design.
|
|
12645
12951
|
if (panel) {
|
|
12952
|
+
const dotCls = agentStateClass(agent.status);
|
|
12953
|
+
const stateMap = STATUS_MAP[agent.status] || STATUS_MAP.unknown;
|
|
12646
12954
|
const activity = (panel.recent_activity || []).slice(0, 20);
|
|
12647
12955
|
const activityHtml = activity.length
|
|
12648
|
-
?
|
|
12956
|
+
? '<div class="timeline">' +
|
|
12957
|
+
activity.map(function (e) {
|
|
12649
12958
|
const t = renderTemplate(e.display_template_id, e.display_template_args);
|
|
12650
|
-
|
|
12651
|
-
|
|
12959
|
+
const badgeHtml = e.attestation
|
|
12960
|
+
? renderActionAttestationBadge(e.attestation.state, e.attestation.fragment)
|
|
12961
|
+
: '';
|
|
12962
|
+
return '<div class="timeline-item ok">' +
|
|
12963
|
+
'<div class="ts">' + escHtml(shortTime(e.emitted_at)) + '</div>' +
|
|
12964
|
+
'<div class="what">' + escHtml(t) + '</div>' +
|
|
12965
|
+
(badgeHtml ? '<div class="att">' + badgeHtml + '</div>' : '') +
|
|
12966
|
+
'</div>';
|
|
12967
|
+
}).join("") +
|
|
12968
|
+
'</div>'
|
|
12652
12969
|
: '<p class="muted">No recent activity for this agent.</p>';
|
|
12653
12970
|
|
|
12654
12971
|
const approvals = panel.pending_approvals || [];
|
|
12655
12972
|
const approvalsHtml = approvals.length
|
|
12656
12973
|
? approvals.map(function (item) {
|
|
12657
12974
|
const promptText = renderTemplate(item.display_template_id, item.display_template_args);
|
|
12658
|
-
return '<div class="row">' +
|
|
12659
|
-
'<
|
|
12660
|
-
|
|
12661
|
-
|
|
12662
|
-
'
|
|
12975
|
+
return '<div class="approval-row">' +
|
|
12976
|
+
'<div class="what">' +
|
|
12977
|
+
'<span class="pill tone-degraded">' + escHtml(item.tier || "tier1") + '</span>' +
|
|
12978
|
+
escHtml(promptText) +
|
|
12979
|
+
'</div>' +
|
|
12980
|
+
'<div class="actions">' +
|
|
12981
|
+
'<button class="btn" data-action="inbox-deny" data-item-id="' + escHtml(item.item_id) + '">Deny</button>' +
|
|
12982
|
+
'<button class="btn btn-primary" data-action="inbox-approve" data-item-id="' + escHtml(item.item_id) + '">Approve once</button>' +
|
|
12983
|
+
'</div>' +
|
|
12663
12984
|
'</div>';
|
|
12664
|
-
}).join("
|
|
12985
|
+
}).join("")
|
|
12665
12986
|
: '<p class="muted">No pending approvals routed through this agent.</p>';
|
|
12666
12987
|
|
|
12667
|
-
const
|
|
12668
|
-
? '<
|
|
12988
|
+
const policySection = panel.policy_summary
|
|
12989
|
+
? '<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>' +
|
|
12669
12990
|
(panel.policy_summary.channel_template_id
|
|
12670
|
-
? '<
|
|
12991
|
+
? '<div class="policy-line"><span class="k">Template</span><span class="v">' + escHtml(panel.policy_summary.channel_template_id) + '</span></div>'
|
|
12671
12992
|
: '') +
|
|
12672
|
-
'<
|
|
12673
|
-
: '<
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
'<div class="
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12993
|
+
'<div class="policy-line"><span class="k">Bound</span><span class="v">' + escHtml(shortTime(panel.policy_summary.bound_at)) + '</span></div>'
|
|
12994
|
+
: '<div class="policy-line"><span class="k">Policy</span><span class="v">No bound policy yet.</span></div>';
|
|
12995
|
+
|
|
12996
|
+
const modelLine = agent.model_provider
|
|
12997
|
+
? '<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>'
|
|
12998
|
+
: '';
|
|
12999
|
+
|
|
13000
|
+
return '<div class="card inspect-pane">' +
|
|
13001
|
+
'<div class="inspect-head">' +
|
|
13002
|
+
'<div class="row1">' +
|
|
13003
|
+
'<div class="agent-glyph">' + escHtml(agentInitials(agent.agent_id)) + '</div>' +
|
|
13004
|
+
'<h3>' + escHtml(agent.agent_id) + '</h3>' +
|
|
13005
|
+
'<span style="margin-left:auto;">' + renderAgentAttestationBadge(agent.status) + '</span>' +
|
|
13006
|
+
'</div>' +
|
|
13007
|
+
'<div class="meta">' +
|
|
13008
|
+
'<span class="pill ' + (dotCls === "live" ? "tone-verified" : "tone-degraded") + '"><span class="state-dot ' + dotCls + '" style="margin-right:4px;"></span>' + escHtml(stateMap.label) + '</span>' +
|
|
13009
|
+
'<span class="pill">opened ' + escHtml(shortTime(panel.opened_at)) + '</span>' +
|
|
13010
|
+
'<button class="btn btn-quiet" data-action="agent-inspect-open" data-agent-id="' + escHtml(agent.agent_id) + '" title="Refresh inspect panel">Refresh</button>' +
|
|
13011
|
+
'</div>' +
|
|
13012
|
+
'</div>' +
|
|
13013
|
+
'<div class="inspect-body">' +
|
|
13014
|
+
errorBanner +
|
|
13015
|
+
'<div class="inspect-section">' +
|
|
13016
|
+
'<h4>Pending approvals' + (approvals.length ? ' <span class="count">' + approvals.length + '</span>' : '') + '</h4>' +
|
|
13017
|
+
approvalsHtml +
|
|
13018
|
+
'</div>' +
|
|
13019
|
+
'<div class="inspect-section">' +
|
|
13020
|
+
'<h4>Recent activity</h4>' +
|
|
13021
|
+
activityHtml +
|
|
13022
|
+
'</div>' +
|
|
13023
|
+
'<div class="inspect-section">' +
|
|
13024
|
+
'<h4>Policy summary</h4>' +
|
|
13025
|
+
policySection +
|
|
13026
|
+
'</div>' +
|
|
13027
|
+
'<div class="inspect-section">' +
|
|
13028
|
+
'<h4>Identity</h4>' +
|
|
13029
|
+
'<div class="policy-line"><span class="k">Agent id</span><span class="v">' + escHtml(agent.agent_id) + '</span></div>' +
|
|
13030
|
+
'<div class="policy-line"><span class="k">Harness</span><span class="v">' + escHtml(agent.harness) + '</span></div>' +
|
|
13031
|
+
modelLine +
|
|
13032
|
+
'<div class="policy-line"><span class="k">Wrapped at</span><span class="v">' + escHtml(shortTime(agent.wrapped_at)) + '</span></div>' +
|
|
13033
|
+
'</div>' +
|
|
13034
|
+
'<p class="muted" style="margin-top:10px;font-size:12px;">' +
|
|
13035
|
+
'<a href="#activity?agent=' + escHtml(agent.agent_id) + '">View full activity</a> · ' +
|
|
13036
|
+
'<a href="#policy">Edit policy</a>' +
|
|
13037
|
+
'</p>' +
|
|
12680
13038
|
'</div>' +
|
|
12681
|
-
errorBanner +
|
|
12682
|
-
'<h3>Pending approvals</h3>' +
|
|
12683
|
-
approvalsHtml +
|
|
12684
|
-
'<h3 style="margin-top:14px;">Recent activity</h3>' +
|
|
12685
|
-
activityHtml +
|
|
12686
|
-
'<h3 style="margin-top:14px;">Policy</h3>' +
|
|
12687
|
-
'<dl class="kv">' + policyLine + '</dl>' +
|
|
12688
|
-
'<p class="muted" style="margin-top:10px;font-size:12px;">' +
|
|
12689
|
-
'<a href="#activity?agent=' + escHtml(agent.agent_id) + '">View full activity</a> · ' +
|
|
12690
|
-
'<a href="#policy">Edit policy</a>' +
|
|
12691
|
-
'</p>' +
|
|
12692
13039
|
'</div>';
|
|
12693
13040
|
}
|
|
12694
13041
|
|
|
@@ -12837,6 +13184,16 @@ function statusDotClass(status) {
|
|
|
12837
13184
|
return "red";
|
|
12838
13185
|
}
|
|
12839
13186
|
|
|
13187
|
+
// Card-grid polish (Sprint Piece 2 PR 3) maps the badge dot class onto
|
|
13188
|
+
// the shaped glyph token. Sage circle for ok, ochre triangle for warn,
|
|
13189
|
+
// rust diamond for fail. Keep aligned with .status-glyph rules in
|
|
13190
|
+
// html.ts and the .intel-card-status modifier classes.
|
|
13191
|
+
function statusGlyphClass(dotClass) {
|
|
13192
|
+
if (dotClass === "green") return "ok";
|
|
13193
|
+
if (dotClass === "yellow") return "warn";
|
|
13194
|
+
return "fail";
|
|
13195
|
+
}
|
|
13196
|
+
|
|
12840
13197
|
function statusLabel(health) {
|
|
12841
13198
|
if (health === "ok") return "Working";
|
|
12842
13199
|
if (health === "degraded") return "Degraded";
|
|
@@ -12876,13 +13233,18 @@ function renderIntelligenceCenter() {
|
|
|
12876
13233
|
}
|
|
12877
13234
|
const status = state.intelligence.status;
|
|
12878
13235
|
const config = state.intelligence.config || {};
|
|
12879
|
-
const
|
|
13236
|
+
const surfaceCards = SURFACES_ORDER.map(function (surfaceId) {
|
|
12880
13237
|
const surfaceStatus = (status.surfaces || []).find(function (s) { return s.surface === surfaceId; });
|
|
12881
13238
|
if (!surfaceStatus) {
|
|
12882
|
-
return '<div class="intel-row"
|
|
12883
|
-
'<
|
|
12884
|
-
|
|
12885
|
-
|
|
13239
|
+
return '<div class="intel-row intel-card" data-intel-surface="' + escHtml(surfaceId) + '">' +
|
|
13240
|
+
'<div class="intel-card-head">' +
|
|
13241
|
+
'<div class="intel-card-name">' +
|
|
13242
|
+
'<strong>' + escHtml(SURFACE_LABELS[surfaceId] || surfaceId) + '</strong>' +
|
|
13243
|
+
'<small>' + escHtml(surfaceId) + '</small>' +
|
|
13244
|
+
'</div>' +
|
|
13245
|
+
'</div>' +
|
|
13246
|
+
'<div class="muted">No status reported.</div>' +
|
|
13247
|
+
'</div>';
|
|
12886
13248
|
}
|
|
12887
13249
|
const substrate = surfaceStatus.chosen;
|
|
12888
13250
|
const localPick = (config.local_model_picks || {})[surfaceId];
|
|
@@ -12900,41 +13262,62 @@ function renderIntelligenceCenter() {
|
|
|
12900
13262
|
if (provider) currentBadge = currentBadge + " (" + (FRONTIER_PROVIDER_LABELS[provider] || provider) + ")";
|
|
12901
13263
|
}
|
|
12902
13264
|
const dotClass = statusDotClass((surfaceStatus.badge || {}).status || "red");
|
|
13265
|
+
const glyphClass = statusGlyphClass(dotClass);
|
|
12903
13266
|
const failures = surfaceStatus.recentFailures || [];
|
|
12904
13267
|
const expanded = !!state.intelligence.expandedFailures[surfaceId];
|
|
12905
|
-
|
|
13268
|
+
|
|
13269
|
+
// Card foot. The failures toggle is the load-bearing affordance for
|
|
13270
|
+
// the rc.6 ZZ test (button[data-action="intel-failures-toggle"] with
|
|
13271
|
+
// text "recent failures (N)"). Pluralization is "failures" regardless
|
|
13272
|
+
// of N for backward compatibility with the seeded test contract.
|
|
13273
|
+
// Surface zero-failure state as a quiet mono note so the card still
|
|
13274
|
+
// has visual rhythm in its foot row.
|
|
13275
|
+
let footHtml;
|
|
12906
13276
|
if (failures.length > 0) {
|
|
12907
13277
|
const toggleLabel = (expanded ? "Hide" : "View") + " recent failures (" + failures.length + ")";
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12915
|
-
|
|
12916
|
-
|
|
12917
|
-
|
|
12918
|
-
|
|
12919
|
-
|
|
12920
|
-
|
|
12921
|
-
'</
|
|
12922
|
-
|
|
13278
|
+
footHtml =
|
|
13279
|
+
'<button class="intel-failures-toggle' + (expanded ? ' open' : '') + '" data-action="intel-failures-toggle" data-intel-surface="' + escHtml(surfaceId) + '">' +
|
|
13280
|
+
'<span class="caret"></span>' +
|
|
13281
|
+
escHtml(toggleLabel) +
|
|
13282
|
+
'</button>';
|
|
13283
|
+
} else {
|
|
13284
|
+
footHtml = '<span class="muted mono" style="font-size: 11px;">no recent failures</span>';
|
|
13285
|
+
}
|
|
13286
|
+
|
|
13287
|
+
let failuresBlock = "";
|
|
13288
|
+
if (failures.length > 0 && expanded) {
|
|
13289
|
+
const rows = failures.slice().reverse().map(function (f) {
|
|
13290
|
+
return '<div class="intel-failure-row">' +
|
|
13291
|
+
'<span class="ts">' + escHtml(shortTime(f.ts)) + '</span>' +
|
|
13292
|
+
'<div>' +
|
|
13293
|
+
'<div class="err-class">' + escHtml(f.failureClass) + '</div>' +
|
|
13294
|
+
'<div>' + escHtml(f.snippet) + '</div>' +
|
|
13295
|
+
'</div>' +
|
|
12923
13296
|
'</div>';
|
|
13297
|
+
}).join("");
|
|
13298
|
+
failuresBlock = '<div class="intel-failures">' + rows + '</div>';
|
|
12924
13299
|
}
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
12929
|
-
|
|
12930
|
-
'<
|
|
12931
|
-
'<span class="pill">' + escHtml(currentBadge) + '</span>' +
|
|
12932
|
-
'<span class="muted mono">' + escHtml(statusLabel(surfaceStatus.health)) + '</span>' +
|
|
13300
|
+
|
|
13301
|
+
return '<div class="intel-row intel-card" data-intel-surface="' + escHtml(surfaceId) + '">' +
|
|
13302
|
+
'<div class="intel-card-head">' +
|
|
13303
|
+
'<div class="intel-card-name">' +
|
|
13304
|
+
'<strong>' + escHtml(SURFACE_LABELS[surfaceId] || surfaceId) + '</strong>' +
|
|
13305
|
+
'<small>' + escHtml(surfaceId) + '</small>' +
|
|
12933
13306
|
'</div>' +
|
|
12934
|
-
'<
|
|
12935
|
-
|
|
13307
|
+
'<span class="intel-card-status ' + glyphClass + '" title="' + escHtml(statusLabel(surfaceStatus.health)) + '">' +
|
|
13308
|
+
'<span class="status-glyph ' + glyphClass + '"></span>' +
|
|
13309
|
+
escHtml(statusLabel(surfaceStatus.health)) +
|
|
13310
|
+
'</span>' +
|
|
12936
13311
|
'</div>' +
|
|
12937
|
-
'<div
|
|
13312
|
+
'<div class="intel-substrate">' +
|
|
13313
|
+
'<div class="sub-line primary">' +
|
|
13314
|
+
'<span>' + escHtml(currentBadge) + '</span>' +
|
|
13315
|
+
'<button class="btn-quiet" data-action="intel-picker-open" data-intel-surface="' + escHtml(surfaceId) + '">Change</button>' +
|
|
13316
|
+
'</div>' +
|
|
13317
|
+
'</div>' +
|
|
13318
|
+
'<div class="intel-row-tradeoff">' + escHtml(substrateTradeoff(substrate)) + '</div>' +
|
|
13319
|
+
'<div class="intel-card-foot">' + footHtml + '</div>' +
|
|
13320
|
+
failuresBlock +
|
|
12938
13321
|
'</div>';
|
|
12939
13322
|
}).join("\n");
|
|
12940
13323
|
|
|
@@ -12946,11 +13329,15 @@ function renderIntelligenceCenter() {
|
|
|
12946
13329
|
|
|
12947
13330
|
const modal = state.intelligence.picker.open ? renderIntelligencePicker() : "";
|
|
12948
13331
|
|
|
12949
|
-
return '<section class="intel-
|
|
12950
|
-
'<
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
13332
|
+
return '<section class="intel-wrap">' +
|
|
13333
|
+
'<div class="page-head">' +
|
|
13334
|
+
'<div>' +
|
|
13335
|
+
'<p class="eyebrow">Intelligence</p>' +
|
|
13336
|
+
'<h1>Substrate routing.</h1>' +
|
|
13337
|
+
'<p class="sub">Six surfaces, six choices. Each surface picks where its thinking happens. Local for privacy. Hosted for capability. Hybrid for both.</p>' +
|
|
13338
|
+
'</div>' +
|
|
13339
|
+
'</div>' +
|
|
13340
|
+
'<div class="intel-grid">' + surfaceCards + '</div>' +
|
|
12954
13341
|
'<section class="intel-panel"><h2>Host capability</h2>' +
|
|
12955
13342
|
'<dl class="intel-hardware">' +
|
|
12956
13343
|
'<dt>Total RAM</dt><dd>' + escHtml(hardware.totalRamGb || "?") + ' GB</dd>' +
|
|
@@ -13743,6 +14130,13 @@ document.addEventListener("click", function (ev) {
|
|
|
13743
14130
|
const intelLocalModel = tgt.getAttribute("data-intel-local-model");
|
|
13744
14131
|
const intelFrontierProvider = tgt.getAttribute("data-intel-frontier-provider");
|
|
13745
14132
|
if (action === "lockdown") return void onLockdownClick();
|
|
14133
|
+
if (action === "theme-toggle") {
|
|
14134
|
+
const isDark = document.documentElement.getAttribute("data-theme") === "dark";
|
|
14135
|
+
const next = isDark ? "light" : "dark";
|
|
14136
|
+
sessionStorage.setItem(THEME_KEY, next);
|
|
14137
|
+
applyTheme(next);
|
|
14138
|
+
return;
|
|
14139
|
+
}
|
|
13746
14140
|
if (action === "intel-reload") { return void fetchIntelligenceState().then(rerender); }
|
|
13747
14141
|
if (action === "intel-picker-open" && intelSurface) return void onIntelPickerOpen(intelSurface);
|
|
13748
14142
|
if (action === "intel-picker-close") return onIntelPickerClose();
|
|
@@ -13896,12 +14290,30 @@ document.addEventListener("input", function (ev) {
|
|
|
13896
14290
|
// then the dashboard view renders a static welcome card with no form
|
|
13897
14291
|
// inputs that could be confused for a working command surface.
|
|
13898
14292
|
|
|
13899
|
-
// Theme:
|
|
14293
|
+
// Theme: explicit operator preference (sessionStorage) overrides system
|
|
14294
|
+
// pref. The toggle button in the topbar dispatches data-action
|
|
14295
|
+
// "theme-toggle" which writes the chosen theme and updates the
|
|
14296
|
+
// [data-theme] attribute. When no explicit choice exists, fall back to
|
|
14297
|
+
// system preference and track changes so dark-mode-at-sunset behavior
|
|
14298
|
+
// keeps working on macOS / Windows.
|
|
14299
|
+
const THEME_KEY = "sanctuary-v11-theme";
|
|
14300
|
+
function applyTheme(theme) {
|
|
14301
|
+
if (theme === "dark") document.documentElement.setAttribute("data-theme", "dark");
|
|
14302
|
+
else document.documentElement.removeAttribute("data-theme");
|
|
14303
|
+
}
|
|
14304
|
+
const explicitTheme = sessionStorage.getItem(THEME_KEY);
|
|
13900
14305
|
const mq = window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null;
|
|
13901
|
-
if (
|
|
14306
|
+
if (explicitTheme === "dark" || explicitTheme === "light") {
|
|
14307
|
+
applyTheme(explicitTheme);
|
|
14308
|
+
} else if (mq && mq.matches) {
|
|
14309
|
+
applyTheme("dark");
|
|
14310
|
+
}
|
|
13902
14311
|
if (mq) mq.addEventListener("change", function (e) {
|
|
13903
|
-
|
|
13904
|
-
|
|
14312
|
+
// Only honor system pref changes when the operator has not made an
|
|
14313
|
+
// explicit choice. Once they toggle, the choice sticks for the
|
|
14314
|
+
// session.
|
|
14315
|
+
if (sessionStorage.getItem(THEME_KEY)) return;
|
|
14316
|
+
applyTheme(e.matches ? "dark" : "light");
|
|
13905
14317
|
});
|
|
13906
14318
|
|
|
13907
14319
|
// Boot.
|
|
@@ -13937,6 +14349,26 @@ var STYLES = String.raw`:root {
|
|
|
13937
14349
|
--rad: 6px;
|
|
13938
14350
|
--rad-lg: 10px;
|
|
13939
14351
|
--shadow: 0 1px 2px rgba(0,0,0,0.04), 0 0 0 1px rgba(0,0,0,0.02);
|
|
14352
|
+
/* Type scale. Names are size-relative, not semantic, so refactors do
|
|
14353
|
+
not need to invent new names. Existing rules used these literal px
|
|
14354
|
+
values; tokens make future polish a one-line change. */
|
|
14355
|
+
--text-xs: 11px;
|
|
14356
|
+
--text-sm: 12px;
|
|
14357
|
+
--text-base: 13px;
|
|
14358
|
+
--text-md: 14px;
|
|
14359
|
+
--text-lg: 16px;
|
|
14360
|
+
--text-xl: 22px;
|
|
14361
|
+
--text-display: 36px;
|
|
14362
|
+
/* Spacing scale (4px multiples). Layout-specific magic numbers
|
|
14363
|
+
(220px sidebar, 360px fortress rail, concierge-card heights) stay
|
|
14364
|
+
as literals because the token system is for component padding /
|
|
14365
|
+
margin / gap, not grid track sizing. */
|
|
14366
|
+
--space-1: 4px;
|
|
14367
|
+
--space-2: 8px;
|
|
14368
|
+
--space-3: 12px;
|
|
14369
|
+
--space-4: 16px;
|
|
14370
|
+
--space-5: 24px;
|
|
14371
|
+
--space-6: 32px;
|
|
13940
14372
|
}
|
|
13941
14373
|
[data-theme="dark"] {
|
|
13942
14374
|
--paper: #121210;
|
|
@@ -13963,7 +14395,7 @@ var STYLES = String.raw`:root {
|
|
|
13963
14395
|
html, body { margin: 0; padding: 0; }
|
|
13964
14396
|
body {
|
|
13965
14397
|
font-family: var(--sans);
|
|
13966
|
-
font-size:
|
|
14398
|
+
font-size: var(--text-md);
|
|
13967
14399
|
background: var(--paper);
|
|
13968
14400
|
color: var(--ink);
|
|
13969
14401
|
line-height: 1.45;
|
|
@@ -13984,20 +14416,27 @@ body {
|
|
|
13984
14416
|
"sidebar main";
|
|
13985
14417
|
}
|
|
13986
14418
|
.sidebar { grid-area: sidebar; background: var(--paper-2); border-right: 1px solid var(--rule); padding: 12px 8px; }
|
|
13987
|
-
.sidebar h1 { font-family: var(--serif); font-size:
|
|
14419
|
+
.sidebar h1 { font-family: var(--serif); font-size: var(--text-lg); margin: 4px 8px 16px; }
|
|
13988
14420
|
.sidebar nav { display: flex; flex-direction: column; gap: 2px; }
|
|
13989
14421
|
.sidebar nav a {
|
|
13990
|
-
display:
|
|
13991
|
-
|
|
14422
|
+
display: flex; align-items: center; gap: var(--space-2);
|
|
14423
|
+
padding: 6px 10px; border-radius: var(--rad);
|
|
14424
|
+
color: var(--ink-2); text-decoration: none; font-size: var(--text-base);
|
|
14425
|
+
}
|
|
14426
|
+
.sidebar nav a svg {
|
|
14427
|
+
flex-shrink: 0; width: 16px; height: 16px;
|
|
14428
|
+
color: var(--ink-3);
|
|
13992
14429
|
}
|
|
13993
14430
|
.sidebar nav a:hover { background: var(--paper-3); }
|
|
14431
|
+
.sidebar nav a:hover svg { color: var(--ink-2); }
|
|
13994
14432
|
.sidebar nav a.active { background: var(--surface); color: var(--ink); border: 1px solid var(--rule); }
|
|
13995
|
-
.
|
|
13996
|
-
.topbar
|
|
14433
|
+
.sidebar nav a.active svg { color: var(--ink); }
|
|
14434
|
+
.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); }
|
|
14435
|
+
.topbar .brand { font-family: var(--serif); font-size: var(--text-md); }
|
|
13997
14436
|
.topbar .pills { display: flex; gap: 6px; flex: 1; }
|
|
13998
14437
|
.pill {
|
|
13999
14438
|
display: inline-flex; align-items: center; gap: 4px;
|
|
14000
|
-
padding: 2px 8px; border-radius: 12px; font-size:
|
|
14439
|
+
padding: 2px 8px; border-radius: 12px; font-size: var(--text-xs);
|
|
14001
14440
|
font-family: var(--mono); border: 1px solid var(--rule);
|
|
14002
14441
|
background: var(--surface-2); color: var(--ink-2);
|
|
14003
14442
|
}
|
|
@@ -14009,7 +14448,7 @@ body {
|
|
|
14009
14448
|
display: inline-flex; align-items: center; gap: 4px;
|
|
14010
14449
|
padding: 4px 10px; border-radius: var(--rad);
|
|
14011
14450
|
background: var(--surface); border: 1px solid var(--rule);
|
|
14012
|
-
font-family: var(--sans); font-size:
|
|
14451
|
+
font-family: var(--sans); font-size: var(--text-sm); color: var(--ink);
|
|
14013
14452
|
cursor: pointer;
|
|
14014
14453
|
}
|
|
14015
14454
|
.btn:hover:not(:disabled) { background: var(--surface-2); }
|
|
@@ -14019,21 +14458,30 @@ body {
|
|
|
14019
14458
|
.btn.btn-danger { background: var(--rust-bg); color: var(--rust); border-color: var(--rust); }
|
|
14020
14459
|
.btn.tier1-pending { background: var(--ochre-bg); color: var(--ochre); border-color: var(--ochre); }
|
|
14021
14460
|
.btn.tier1-engaged { background: var(--rust-bg); color: var(--rust); border-color: var(--rust); }
|
|
14461
|
+
.btn.btn-icon {
|
|
14462
|
+
padding: 4px 6px;
|
|
14463
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
14464
|
+
color: var(--ink-2);
|
|
14465
|
+
}
|
|
14466
|
+
.btn.btn-icon svg { width: 16px; height: 16px; }
|
|
14467
|
+
.btn.btn-icon .icon-sun { display: none; }
|
|
14468
|
+
[data-theme="dark"] .btn.btn-icon .icon-moon { display: none; }
|
|
14469
|
+
[data-theme="dark"] .btn.btn-icon .icon-sun { display: inline; }
|
|
14022
14470
|
.main { grid-area: main; overflow-y: auto; padding: 16px 24px; }
|
|
14023
|
-
.fortress { grid-area: fortress; overflow-y: auto; border-left: 1px solid var(--rule); background: var(--paper-2); padding:
|
|
14471
|
+
.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; }
|
|
14024
14472
|
.app.route-full .fortress { display: none; }
|
|
14025
14473
|
.card {
|
|
14026
14474
|
background: var(--surface); border: 1px solid var(--rule);
|
|
14027
|
-
border-radius: var(--rad); padding:
|
|
14475
|
+
border-radius: var(--rad); padding: var(--space-3);
|
|
14028
14476
|
}
|
|
14029
|
-
.card h3 { margin: 0 0 8px; font-size:
|
|
14477
|
+
.card h3 { margin: 0 0 8px; font-size: var(--text-base); font-weight: 600; color: var(--ink); }
|
|
14030
14478
|
.muted { color: var(--ink-3); }
|
|
14031
|
-
.mono { font-family: var(--mono); font-size:
|
|
14032
|
-
.row { display: flex; align-items: center; gap:
|
|
14479
|
+
.mono { font-family: var(--mono); font-size: var(--text-sm); }
|
|
14480
|
+
.row { display: flex; align-items: center; gap: var(--space-2); padding: 6px 0; border-bottom: 1px dashed var(--rule); }
|
|
14033
14481
|
.row:last-child { border-bottom: 0; }
|
|
14034
14482
|
.row .grow { flex: 1; min-width: 0; }
|
|
14035
14483
|
.agent-row { flex-direction: column; align-items: stretch; gap: 6px; }
|
|
14036
|
-
.agent-row-head { display: flex; align-items: center; gap:
|
|
14484
|
+
.agent-row-head { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
|
|
14037
14485
|
.agent-row-head .grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
14038
14486
|
.agent-row-actions { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
14039
14487
|
/* Click-to-inspect affordance: the head sub-row of a fortress-column
|
|
@@ -14043,7 +14491,7 @@ body {
|
|
|
14043
14491
|
.agent-row-head[data-action="agent-row-inspect-open"] { cursor: pointer; border-radius: var(--rad); padding: 4px 6px; margin: -4px -6px; }
|
|
14044
14492
|
.agent-row-head[data-action="agent-row-inspect-open"]:hover { background: var(--paper-3); }
|
|
14045
14493
|
.agent-row-head[data-action="agent-row-inspect-open"]:focus-visible { outline: 2px solid var(--ink-3); outline-offset: 1px; }
|
|
14046
|
-
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; font-size:
|
|
14494
|
+
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; font-size: var(--text-sm); }
|
|
14047
14495
|
.kv dt { color: var(--ink-3); }
|
|
14048
14496
|
.kv dd { margin: 0; color: var(--ink); }
|
|
14049
14497
|
.glyph { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--ink-4); }
|
|
@@ -14054,74 +14502,74 @@ body {
|
|
|
14054
14502
|
.toast {
|
|
14055
14503
|
position: fixed; bottom: 16px; right: 16px;
|
|
14056
14504
|
background: var(--ink); color: var(--paper); padding: 8px 12px;
|
|
14057
|
-
border-radius: var(--rad); font-size:
|
|
14505
|
+
border-radius: var(--rad); font-size: var(--text-sm); z-index: 1000;
|
|
14058
14506
|
max-width: 360px;
|
|
14059
14507
|
}
|
|
14060
14508
|
.toast.error { background: var(--rust); color: var(--paper); }
|
|
14061
|
-
.layer-card { background: var(--surface-2); border: 1px solid var(--rule); border-radius: var(--rad); padding:
|
|
14062
|
-
.layer-card h4 { margin: 0 0 4px; font-size:
|
|
14063
|
-
.layer-card p { margin: 0; font-size:
|
|
14064
|
-
.chat-thread { display: flex; flex-direction: column; gap:
|
|
14509
|
+
.layer-card { background: var(--surface-2); border: 1px solid var(--rule); border-radius: var(--rad); padding: var(--space-2); }
|
|
14510
|
+
.layer-card h4 { margin: 0 0 4px; font-size: var(--text-sm); font-weight: 600; }
|
|
14511
|
+
.layer-card p { margin: 0; font-size: var(--text-xs); color: var(--ink-3); }
|
|
14512
|
+
.chat-thread { display: flex; flex-direction: column; gap: var(--space-2); padding-bottom: 12px; }
|
|
14065
14513
|
.chat-msg { padding: 8px 10px; border-radius: var(--rad); border: 1px solid var(--rule); background: var(--surface); max-width: 78%; }
|
|
14066
|
-
.chat-msg.system { background: var(--paper-3); color: var(--ink-3); font-size:
|
|
14514
|
+
.chat-msg.system { background: var(--paper-3); color: var(--ink-3); font-size: var(--text-sm); max-width: 100%; }
|
|
14067
14515
|
.chat-msg.agent { align-self: flex-start; }
|
|
14068
14516
|
.chat-msg.operator { align-self: flex-end; background: var(--ink); color: var(--paper); }
|
|
14069
14517
|
.chat-msg .meta { font-size: 10px; color: var(--ink-4); margin-top: 4px; }
|
|
14070
|
-
.composer { display: flex; gap:
|
|
14518
|
+
.composer { display: flex; gap: var(--space-2); padding: var(--space-2); border-top: 1px solid var(--rule); }
|
|
14071
14519
|
.composer input { flex: 1; padding: 6px 8px; border: 1px solid var(--rule); border-radius: var(--rad); font-family: var(--sans); }
|
|
14072
14520
|
.wizard-step { padding: 10px; border: 1px solid var(--rule); border-radius: var(--rad); margin-bottom: 8px; background: var(--surface); }
|
|
14073
14521
|
.wizard-step.active { border-color: var(--ink); }
|
|
14074
14522
|
.wizard-step.done { background: var(--sage-bg); border-color: var(--sage); }
|
|
14075
|
-
.code-block { font-family: var(--mono); background: var(--paper-3); padding:
|
|
14523
|
+
.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; }
|
|
14076
14524
|
.policy-center { max-width: 980px; margin: 0 auto; }
|
|
14077
|
-
.policy-center .eyebrow { margin: 0 0 6px; color: var(--ink-3); font-family: var(--mono); font-size:
|
|
14078
|
-
.policy-center h1 { font-family: var(--serif); font-size:
|
|
14525
|
+
.policy-center .eyebrow { margin: 0 0 6px; color: var(--ink-3); font-family: var(--mono); font-size: var(--text-sm); letter-spacing: 0; }
|
|
14526
|
+
.policy-center h1 { font-family: var(--serif); font-size: var(--text-display); line-height: 1.08; font-weight: 400; margin: 0 0 10px; }
|
|
14079
14527
|
.policy-subtitle { max-width: 860px; color: var(--ink-2); font-size: 15px; margin: 0 0 24px; }
|
|
14080
14528
|
.policy-panel { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--rad-lg); padding: 20px; margin: 18px 0; }
|
|
14081
|
-
.policy-panel h2 { font-family: var(--serif); font-size:
|
|
14082
|
-
.template-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap:
|
|
14529
|
+
.policy-panel h2 { font-family: var(--serif); font-size: var(--text-xl); font-weight: 400; margin: 0 0 14px; }
|
|
14530
|
+
.template-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3); }
|
|
14083
14531
|
.template-card { background: var(--surface-2); border: 1px solid var(--rule); border-radius: var(--rad); padding: 14px; min-height: 132px; }
|
|
14084
|
-
.template-card-head { display: flex; justify-content: space-between; align-items: center; gap:
|
|
14085
|
-
.severity { border-radius: 999px; padding: 2px 9px; font-family: var(--mono); font-size:
|
|
14532
|
+
.template-card-head { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); margin-bottom: 12px; }
|
|
14533
|
+
.severity { border-radius: 999px; padding: 2px 9px; font-family: var(--mono); font-size: var(--text-xs); font-weight: 700; }
|
|
14086
14534
|
.severity.low { color: var(--sage); background: var(--sage-bg); }
|
|
14087
14535
|
.severity.medium { color: var(--ochre); background: var(--ochre-bg); }
|
|
14088
14536
|
.template-id { background: var(--paper-3); border-radius: var(--rad); padding: 2px 7px; color: var(--ink-3); }
|
|
14089
|
-
.template-card h3 { font-size:
|
|
14090
|
-
.template-card p { color: var(--ink-3); margin: 0; font-size:
|
|
14537
|
+
.template-card h3 { font-size: var(--text-lg); margin: 0 0 6px; }
|
|
14538
|
+
.template-card p { color: var(--ink-3); margin: 0; font-size: var(--text-md); }
|
|
14091
14539
|
.rules-scroll { overflow-x: auto; }
|
|
14092
14540
|
.rules-table { width: 100%; border-collapse: collapse; min-width: 760px; }
|
|
14093
|
-
.rules-table th { text-align: left; color: var(--ink-3); font-family: var(--mono); font-size:
|
|
14541
|
+
.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); }
|
|
14094
14542
|
.rules-table td { padding: 12px 10px; border-bottom: 1px solid var(--rule); vertical-align: top; }
|
|
14095
14543
|
.link-btn, .template-cell { border: 0; background: transparent; color: var(--ink); padding: 0; cursor: pointer; font: inherit; text-align: left; }
|
|
14096
14544
|
.template-cell { font-family: var(--mono); max-width: 180px; overflow-wrap: anywhere; }
|
|
14097
14545
|
.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; }
|
|
14098
14546
|
.template-picker-options { display: grid; gap: 6px; max-height: 320px; overflow-y: auto; }
|
|
14099
|
-
.template-option { display: grid; grid-template-columns: 18px 1fr; gap:
|
|
14547
|
+
.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); }
|
|
14100
14548
|
.template-option small { display: block; color: var(--ink-3); margin-top: 2px; }
|
|
14101
|
-
.template-picker-actions { display: flex; justify-content: flex-end; gap:
|
|
14549
|
+
.template-picker-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: 10px; }
|
|
14102
14550
|
.allow-count { color: var(--sage); font-weight: 700; }
|
|
14103
14551
|
.block-count { color: var(--rust); }
|
|
14104
14552
|
.toggle-on { display: inline-block; width: 28px; height: 16px; border-radius: 999px; background: var(--sage); position: relative; }
|
|
14105
14553
|
.toggle-on::after { content: ""; position: absolute; right: 2px; top: 2px; width: 12px; height: 12px; border-radius: 50%; background: var(--surface); }
|
|
14106
14554
|
.error-text { color: var(--rust); margin: 8px 0 0; }
|
|
14107
14555
|
.intel-center { max-width: 980px; margin: 0 auto; }
|
|
14108
|
-
.intel-center .eyebrow { margin: 0 0 6px; color: var(--ink-3); font-family: var(--mono); font-size:
|
|
14109
|
-
.intel-center h1 { font-family: var(--serif); font-size:
|
|
14556
|
+
.intel-center .eyebrow { margin: 0 0 6px; color: var(--ink-3); font-family: var(--mono); font-size: var(--text-sm); letter-spacing: 0; }
|
|
14557
|
+
.intel-center h1 { font-family: var(--serif); font-size: var(--text-display); line-height: 1.08; font-weight: 400; margin: 0 0 10px; }
|
|
14110
14558
|
.intel-subtitle { max-width: 860px; color: var(--ink-2); font-size: 15px; margin: 0 0 24px; }
|
|
14111
14559
|
.intel-panel { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--rad-lg); padding: 20px; margin: 18px 0; }
|
|
14112
|
-
.intel-panel h2 { font-family: var(--serif); font-size:
|
|
14113
|
-
.intel-row { display: grid; grid-template-columns: 200px 1fr auto; gap:
|
|
14560
|
+
.intel-panel h2 { font-family: var(--serif); font-size: var(--text-xl); font-weight: 400; margin: 0 0 14px; }
|
|
14561
|
+
.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; }
|
|
14114
14562
|
.intel-row:last-child { border-bottom: 0; }
|
|
14115
14563
|
.intel-row-name { font-weight: 600; }
|
|
14116
|
-
.intel-row-name small { display: block; color: var(--ink-3); font-weight: 400; font-size:
|
|
14564
|
+
.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); }
|
|
14117
14565
|
.intel-row-body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
14118
|
-
.intel-row-current { display: flex; gap:
|
|
14119
|
-
.intel-row-tradeoff { color: var(--ink-2); font-size:
|
|
14566
|
+
.intel-row-current { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
|
|
14567
|
+
.intel-row-tradeoff { color: var(--ink-2); font-size: var(--text-base); line-height: 1.5; }
|
|
14120
14568
|
.intel-status-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
|
|
14121
14569
|
.intel-status-dot.green { background: var(--sage); }
|
|
14122
14570
|
.intel-status-dot.yellow { background: var(--ochre); }
|
|
14123
14571
|
.intel-status-dot.red { background: var(--rust); }
|
|
14124
|
-
.intel-hardware { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; font-size:
|
|
14572
|
+
.intel-hardware { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; font-size: var(--text-base); }
|
|
14125
14573
|
.intel-hardware dt { color: var(--ink-3); font-family: var(--mono); }
|
|
14126
14574
|
.intel-hardware dd { margin: 0; }
|
|
14127
14575
|
.intel-modal-backdrop {
|
|
@@ -14134,33 +14582,162 @@ body {
|
|
|
14134
14582
|
box-shadow: 0 8px 32px rgba(0,0,0,0.18); padding: 24px;
|
|
14135
14583
|
width: 100%; max-width: 640px;
|
|
14136
14584
|
}
|
|
14137
|
-
.intel-modal h2 { font-family: var(--serif); font-size:
|
|
14138
|
-
.intel-modal-subtitle { color: var(--ink-3); margin: 0 0 18px; font-size:
|
|
14585
|
+
.intel-modal h2 { font-family: var(--serif); font-size: var(--text-xl); font-weight: 400; margin: 0 0 8px; }
|
|
14586
|
+
.intel-modal-subtitle { color: var(--ink-3); margin: 0 0 18px; font-size: var(--text-base); }
|
|
14139
14587
|
.intel-option {
|
|
14140
|
-
border: 1px solid var(--rule); border-radius: var(--rad); padding:
|
|
14588
|
+
border: 1px solid var(--rule); border-radius: var(--rad); padding: var(--space-3);
|
|
14141
14589
|
margin-bottom: 10px; background: var(--surface-2); cursor: pointer;
|
|
14142
14590
|
display: grid; grid-template-columns: 18px 1fr; gap: 10px; align-items: start;
|
|
14143
14591
|
}
|
|
14144
14592
|
.intel-option.selected { border-color: var(--ink); background: var(--surface); }
|
|
14145
|
-
.intel-option-body strong { display: block; font-size:
|
|
14146
|
-
.intel-option-body small { display: block; color: var(--ink-3); font-size:
|
|
14593
|
+
.intel-option-body strong { display: block; font-size: var(--text-md); margin-bottom: 4px; }
|
|
14594
|
+
.intel-option-body small { display: block; color: var(--ink-3); font-size: var(--text-sm); line-height: 1.5; }
|
|
14147
14595
|
.intel-suboptions { margin-top: 10px; padding: 10px; background: var(--paper-3); border-radius: var(--rad); }
|
|
14148
|
-
.intel-suboptions label { display: block; margin: 6px 0; font-size:
|
|
14596
|
+
.intel-suboptions label { display: block; margin: 6px 0; font-size: var(--text-base); }
|
|
14149
14597
|
.intel-suboptions input[type="text"], .intel-suboptions input[type="password"] {
|
|
14150
14598
|
width: 100%; padding: 6px 8px; border: 1px solid var(--rule); border-radius: var(--rad);
|
|
14151
|
-
font-family: var(--mono); font-size:
|
|
14599
|
+
font-family: var(--mono); font-size: var(--text-sm); box-sizing: border-box;
|
|
14600
|
+
}
|
|
14601
|
+
.intel-modal-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: 18px; }
|
|
14602
|
+
/*
|
|
14603
|
+
* Intelligence panel polish (Sprint Piece 2 PR 3). Card grid layout
|
|
14604
|
+
* replaces the legacy 3-col row visual. The legacy .intel-row and
|
|
14605
|
+
* .intel-status-dot rules above are retained for the e2e selector
|
|
14606
|
+
* contract (.intel-row[data-intel-surface="..."]) and as the responsive
|
|
14607
|
+
* fallback. Cards render as a flex column with a substrate inset,
|
|
14608
|
+
* status badge with shaped glyph, and a recent-failures toggle in the
|
|
14609
|
+
* card foot.
|
|
14610
|
+
*/
|
|
14611
|
+
.intel-wrap { max-width: 1000px; margin: 0 auto; }
|
|
14612
|
+
.intel-grid {
|
|
14613
|
+
display: grid;
|
|
14614
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
14615
|
+
gap: 12px;
|
|
14616
|
+
}
|
|
14617
|
+
.intel-card {
|
|
14618
|
+
background: var(--surface);
|
|
14619
|
+
border: 1px solid var(--rule);
|
|
14620
|
+
border-radius: var(--rad-lg);
|
|
14621
|
+
padding: 16px;
|
|
14622
|
+
display: flex; flex-direction: column;
|
|
14623
|
+
gap: 12px;
|
|
14624
|
+
}
|
|
14625
|
+
.intel-card-head {
|
|
14626
|
+
display: flex; align-items: flex-start; justify-content: space-between;
|
|
14627
|
+
gap: 10px;
|
|
14628
|
+
}
|
|
14629
|
+
.intel-card-name {
|
|
14630
|
+
display: flex; flex-direction: column; gap: 2px;
|
|
14631
|
+
min-width: 0;
|
|
14632
|
+
}
|
|
14633
|
+
.intel-card-name strong {
|
|
14634
|
+
font-family: var(--serif); font-weight: 500;
|
|
14635
|
+
font-size: 15px; letter-spacing: 0.005em;
|
|
14636
|
+
}
|
|
14637
|
+
.intel-card-name small {
|
|
14638
|
+
color: var(--ink-3); font-size: 11px; font-family: var(--mono);
|
|
14639
|
+
}
|
|
14640
|
+
.intel-card-status {
|
|
14641
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
14642
|
+
font-family: var(--mono); font-size: 11px;
|
|
14643
|
+
padding: 3px 9px; border-radius: 999px;
|
|
14644
|
+
border: 1px solid var(--rule); background: var(--surface-2);
|
|
14645
|
+
flex-shrink: 0;
|
|
14646
|
+
}
|
|
14647
|
+
.intel-card-status.ok { color: var(--sage); border-color: var(--sage); background: var(--sage-bg); }
|
|
14648
|
+
.intel-card-status.warn { color: var(--ochre); border-color: var(--ochre); background: var(--ochre-bg); }
|
|
14649
|
+
.intel-card-status.fail { color: var(--rust); border-color: var(--rust); background: var(--rust-bg); }
|
|
14650
|
+
.status-glyph {
|
|
14651
|
+
width: 10px; height: 10px;
|
|
14652
|
+
position: relative; flex-shrink: 0;
|
|
14152
14653
|
}
|
|
14153
|
-
.
|
|
14654
|
+
.status-glyph.ok::before {
|
|
14655
|
+
content: ""; position: absolute; inset: 0;
|
|
14656
|
+
border-radius: 50%; background: currentColor;
|
|
14657
|
+
}
|
|
14658
|
+
.status-glyph.warn::before {
|
|
14659
|
+
content: ""; position: absolute; inset: 0;
|
|
14660
|
+
background: currentColor;
|
|
14661
|
+
clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
|
|
14662
|
+
}
|
|
14663
|
+
.status-glyph.fail::before {
|
|
14664
|
+
content: ""; position: absolute; inset: 1px;
|
|
14665
|
+
background: currentColor;
|
|
14666
|
+
clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
|
|
14667
|
+
}
|
|
14668
|
+
.intel-substrate {
|
|
14669
|
+
display: flex; flex-direction: column; gap: 4px;
|
|
14670
|
+
padding: 10px 12px;
|
|
14671
|
+
background: var(--paper-2);
|
|
14672
|
+
border: 1px solid var(--rule);
|
|
14673
|
+
border-radius: var(--rad);
|
|
14674
|
+
}
|
|
14675
|
+
.intel-substrate .sub-line {
|
|
14676
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
14677
|
+
gap: 10px; font-size: 12px;
|
|
14678
|
+
}
|
|
14679
|
+
.intel-substrate .sub-line.primary {
|
|
14680
|
+
font-family: var(--mono); font-size: 13px; color: var(--ink);
|
|
14681
|
+
}
|
|
14682
|
+
.intel-substrate .sub-line.secondary { color: var(--ink-3); font-size: 11px; }
|
|
14683
|
+
.intel-card-foot {
|
|
14684
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
14685
|
+
gap: 8px; padding-top: 4px;
|
|
14686
|
+
}
|
|
14687
|
+
.intel-failures-toggle {
|
|
14688
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
14689
|
+
font-size: 12px; font-family: var(--mono);
|
|
14690
|
+
color: var(--ink-3);
|
|
14691
|
+
background: transparent; border: 0; padding: 0;
|
|
14692
|
+
cursor: pointer;
|
|
14693
|
+
}
|
|
14694
|
+
.intel-failures-toggle:hover { color: var(--ink); }
|
|
14695
|
+
.intel-failures-toggle .caret {
|
|
14696
|
+
display: inline-block; width: 0; height: 0;
|
|
14697
|
+
border-left: 4px solid transparent;
|
|
14698
|
+
border-right: 4px solid transparent;
|
|
14699
|
+
border-top: 5px solid currentColor;
|
|
14700
|
+
transition: transform 160ms ease;
|
|
14701
|
+
}
|
|
14702
|
+
.intel-failures-toggle.open .caret { transform: rotate(180deg); }
|
|
14703
|
+
.intel-failures {
|
|
14704
|
+
border-top: 1px solid var(--rule);
|
|
14705
|
+
padding-top: 12px;
|
|
14706
|
+
display: flex; flex-direction: column;
|
|
14707
|
+
gap: 8px;
|
|
14708
|
+
}
|
|
14709
|
+
.intel-failure-row {
|
|
14710
|
+
display: grid; grid-template-columns: 88px 1fr; gap: 12px;
|
|
14711
|
+
font-size: 12px; padding: 8px 10px;
|
|
14712
|
+
border-radius: var(--rad);
|
|
14713
|
+
background: var(--paper-2);
|
|
14714
|
+
border: 1px solid var(--rule);
|
|
14715
|
+
}
|
|
14716
|
+
.intel-failure-row .ts {
|
|
14717
|
+
font-family: var(--mono); font-size: 11px; color: var(--ink-3);
|
|
14718
|
+
}
|
|
14719
|
+
.intel-failure-row .err-class {
|
|
14720
|
+
font-family: var(--mono); font-size: 10px;
|
|
14721
|
+
letter-spacing: 0.04em; text-transform: uppercase;
|
|
14722
|
+
color: var(--rust); margin-bottom: 2px;
|
|
14723
|
+
}
|
|
14724
|
+
.btn-quiet {
|
|
14725
|
+
background: transparent; border: 1px solid var(--rule);
|
|
14726
|
+
padding: 2px 6px; font-size: 11px;
|
|
14727
|
+
border-radius: var(--rad); cursor: pointer;
|
|
14728
|
+
color: var(--ink-2); font-family: var(--sans);
|
|
14729
|
+
}
|
|
14730
|
+
.btn-quiet:hover { background: var(--surface-2); color: var(--ink); }
|
|
14154
14731
|
.banner-warn {
|
|
14155
14732
|
background: var(--ochre-bg); color: var(--ochre); border: 1px solid var(--ochre);
|
|
14156
|
-
border-radius: var(--rad); padding: 8px 12px; margin: 8px 0; font-size:
|
|
14733
|
+
border-radius: var(--rad); padding: 8px 12px; margin: 8px 0; font-size: var(--text-base);
|
|
14157
14734
|
}
|
|
14158
14735
|
.banner-info {
|
|
14159
14736
|
background: var(--indigo-bg); color: var(--indigo); border: 1px solid var(--indigo);
|
|
14160
|
-
border-radius: var(--rad); padding: 8px 12px; margin: 8px 0; font-size:
|
|
14737
|
+
border-radius: var(--rad); padding: 8px 12px; margin: 8px 0; font-size: var(--text-base);
|
|
14161
14738
|
}
|
|
14162
14739
|
.btn.chip {
|
|
14163
|
-
border-radius: 999px; padding: 4px 12px; font-size:
|
|
14740
|
+
border-radius: 999px; padding: 4px 12px; font-size: var(--text-sm);
|
|
14164
14741
|
background: var(--surface-2); border-color: var(--rule);
|
|
14165
14742
|
}
|
|
14166
14743
|
.btn.chip:hover:not(:disabled) { background: var(--paper-3); }
|
|
@@ -14177,85 +14754,210 @@ body {
|
|
|
14177
14754
|
* dynamically, but the input box is below the fold, so I still have
|
|
14178
14755
|
* to scroll." rc.5 closes that with a structural layout fix.
|
|
14179
14756
|
*/
|
|
14757
|
+
/* Sprint Piece 2 PR 2 (2026-05-03): concierge surface polish.
|
|
14758
|
+
* Translates Claude Design references at
|
|
14759
|
+
* server/docs/design-refs/sprint-piece-2/surface-concierge.jsx and the
|
|
14760
|
+
* Surface 1 block of surfaces.css. The bounded-card layout above is
|
|
14761
|
+
* preserved verbatim because rc.5 and the DDD e2e suite depend on it;
|
|
14762
|
+
* the Claude Design reference uses height: 720px for the card, but
|
|
14763
|
+
* production keeps the calc-based bounded height so the card adapts
|
|
14764
|
+
* to the operator viewport. The polish lands the persona glyph-ring,
|
|
14765
|
+
* mono uppercase author labels, paper-2 background for concierge
|
|
14766
|
+
* replies, suggest-grid empty-state cards, and the composer input-wrap
|
|
14767
|
+
* with the keyboard-shortcut pill.
|
|
14768
|
+
*/
|
|
14769
|
+
.concierge-wrap { max-width: 880px; margin: 0 auto; }
|
|
14770
|
+
.page-head {
|
|
14771
|
+
display: flex; align-items: flex-end; justify-content: space-between;
|
|
14772
|
+
gap: var(--space-4);
|
|
14773
|
+
margin-bottom: 18px; padding-bottom: 14px;
|
|
14774
|
+
border-bottom: 1px solid var(--rule);
|
|
14775
|
+
}
|
|
14776
|
+
.page-head .eyebrow {
|
|
14777
|
+
font-family: var(--mono); font-size: var(--text-xs);
|
|
14778
|
+
letter-spacing: 0.08em; text-transform: uppercase;
|
|
14779
|
+
color: var(--ink-3);
|
|
14780
|
+
margin: 0 0 6px;
|
|
14781
|
+
}
|
|
14782
|
+
.page-head h1 {
|
|
14783
|
+
font-family: var(--serif); font-weight: 400;
|
|
14784
|
+
font-size: 28px; letter-spacing: -0.01em;
|
|
14785
|
+
margin: 0 0 4px;
|
|
14786
|
+
}
|
|
14787
|
+
.page-head .sub {
|
|
14788
|
+
color: var(--ink-3); margin: 0;
|
|
14789
|
+
font-size: var(--text-base); max-width: 60ch;
|
|
14790
|
+
}
|
|
14180
14791
|
.concierge-card {
|
|
14181
14792
|
display: flex; flex-direction: column;
|
|
14182
14793
|
height: calc(100vh - 180px);
|
|
14183
14794
|
max-height: calc(100vh - 180px);
|
|
14184
14795
|
min-height: 360px;
|
|
14185
|
-
padding:
|
|
14796
|
+
padding: 18px 22px 14px;
|
|
14186
14797
|
gap: 0;
|
|
14187
14798
|
}
|
|
14188
14799
|
.concierge-header {
|
|
14189
14800
|
display: flex; align-items: center; justify-content: space-between;
|
|
14190
|
-
gap:
|
|
14801
|
+
gap: var(--space-3); padding-bottom: 14px;
|
|
14802
|
+
border-bottom: 1px solid var(--rule);
|
|
14191
14803
|
flex-wrap: wrap;
|
|
14192
14804
|
flex-shrink: 0;
|
|
14193
14805
|
}
|
|
14194
|
-
.concierge-persona {
|
|
14195
|
-
|
|
14196
|
-
|
|
14806
|
+
.concierge-persona { display: flex; align-items: center; gap: 10px; }
|
|
14807
|
+
.concierge-persona .glyph-ring {
|
|
14808
|
+
width: 26px; height: 26px;
|
|
14809
|
+
border: 1.5px solid var(--ink-2);
|
|
14810
|
+
border-radius: 50%;
|
|
14811
|
+
position: relative;
|
|
14812
|
+
flex-shrink: 0;
|
|
14813
|
+
}
|
|
14814
|
+
.concierge-persona .glyph-ring::after {
|
|
14815
|
+
content: ""; position: absolute; inset: 5px;
|
|
14816
|
+
border-radius: 50%; background: var(--ink-2);
|
|
14817
|
+
}
|
|
14818
|
+
.concierge-persona-text { display: flex; flex-direction: column; }
|
|
14819
|
+
.concierge-persona-text strong {
|
|
14820
|
+
font-family: var(--serif); font-weight: 500;
|
|
14821
|
+
font-size: 15px; letter-spacing: 0.005em;
|
|
14822
|
+
}
|
|
14823
|
+
.concierge-persona-text small {
|
|
14824
|
+
color: var(--ink-3); font-size: var(--text-xs);
|
|
14825
|
+
font-family: var(--mono);
|
|
14826
|
+
}
|
|
14827
|
+
.concierge-meta {
|
|
14828
|
+
display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
|
|
14197
14829
|
}
|
|
14198
|
-
.concierge-persona strong { font-size: 14px; }
|
|
14199
14830
|
.concierge-badge { white-space: nowrap; }
|
|
14200
14831
|
.concierge-history {
|
|
14201
14832
|
flex: 1 1 auto;
|
|
14202
14833
|
min-height: 0;
|
|
14203
14834
|
overflow-y: auto;
|
|
14204
|
-
padding:
|
|
14205
|
-
display: flex; flex-direction: column; gap:
|
|
14835
|
+
padding: 18px 4px 6px;
|
|
14836
|
+
display: flex; flex-direction: column; gap: 18px;
|
|
14206
14837
|
}
|
|
14207
14838
|
.concierge-msg {
|
|
14208
|
-
display: flex; flex-direction: column; gap:
|
|
14209
|
-
max-width:
|
|
14839
|
+
display: flex; flex-direction: column; gap: 5px;
|
|
14840
|
+
max-width: 78%;
|
|
14210
14841
|
}
|
|
14211
14842
|
.concierge-msg-author {
|
|
14212
|
-
font-size:
|
|
14843
|
+
font-size: 10px;
|
|
14844
|
+
font-family: var(--mono);
|
|
14845
|
+
letter-spacing: 0.04em;
|
|
14846
|
+
text-transform: uppercase;
|
|
14847
|
+
color: var(--ink-4);
|
|
14213
14848
|
}
|
|
14214
14849
|
.concierge-msg-body {
|
|
14215
|
-
padding:
|
|
14216
|
-
border:
|
|
14217
|
-
|
|
14218
|
-
|
|
14850
|
+
padding: 11px 14px;
|
|
14851
|
+
border-radius: 12px;
|
|
14852
|
+
border: 1px solid var(--rule);
|
|
14853
|
+
background: var(--surface);
|
|
14854
|
+
font-size: var(--text-md);
|
|
14855
|
+
line-height: 1.55;
|
|
14856
|
+
white-space: pre-wrap;
|
|
14857
|
+
word-wrap: break-word;
|
|
14219
14858
|
}
|
|
14220
14859
|
.concierge-msg-concierge { align-self: flex-start; }
|
|
14860
|
+
.concierge-msg-concierge .concierge-msg-body {
|
|
14861
|
+
background: var(--paper-2);
|
|
14862
|
+
}
|
|
14221
14863
|
.concierge-msg-operator { align-self: flex-end; align-items: flex-end; }
|
|
14222
14864
|
.concierge-msg-operator .concierge-msg-body {
|
|
14223
14865
|
background: var(--ink); color: var(--paper); border-color: var(--ink);
|
|
14224
14866
|
}
|
|
14867
|
+
.concierge-msg-meta {
|
|
14868
|
+
display: flex; gap: 6px;
|
|
14869
|
+
font-size: 10px;
|
|
14870
|
+
color: var(--ink-4);
|
|
14871
|
+
font-family: var(--mono);
|
|
14872
|
+
}
|
|
14225
14873
|
.concierge-empty {
|
|
14226
|
-
|
|
14227
|
-
|
|
14874
|
+
flex: 1 1 auto;
|
|
14875
|
+
display: flex; flex-direction: column; gap: 22px;
|
|
14876
|
+
justify-content: center;
|
|
14877
|
+
padding: 24px 12px;
|
|
14878
|
+
}
|
|
14879
|
+
.concierge-empty-headline { max-width: 52ch; }
|
|
14880
|
+
.concierge-empty-headline h2 {
|
|
14881
|
+
font-family: var(--serif); font-weight: 400;
|
|
14882
|
+
font-size: var(--text-xl); margin: 0 0 6px;
|
|
14883
|
+
letter-spacing: -0.005em;
|
|
14884
|
+
}
|
|
14885
|
+
.concierge-empty-headline p {
|
|
14886
|
+
color: var(--ink-3); margin: 0;
|
|
14887
|
+
font-size: var(--text-md); line-height: 1.55;
|
|
14888
|
+
}
|
|
14889
|
+
.concierge-suggest-grid {
|
|
14890
|
+
display: grid;
|
|
14891
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
14892
|
+
gap: 10px;
|
|
14893
|
+
}
|
|
14894
|
+
.concierge-suggest {
|
|
14895
|
+
background: var(--surface);
|
|
14896
|
+
border: 1px solid var(--rule);
|
|
14897
|
+
border-radius: var(--rad);
|
|
14898
|
+
padding: 12px 14px;
|
|
14899
|
+
font-size: var(--text-base);
|
|
14900
|
+
cursor: pointer;
|
|
14901
|
+
display: flex; flex-direction: column;
|
|
14902
|
+
gap: 6px;
|
|
14903
|
+
text-align: left;
|
|
14904
|
+
font-family: var(--sans);
|
|
14905
|
+
color: var(--ink);
|
|
14906
|
+
}
|
|
14907
|
+
.concierge-suggest:hover:not(:disabled) {
|
|
14908
|
+
background: var(--surface-2);
|
|
14909
|
+
border-color: var(--rule-2);
|
|
14910
|
+
}
|
|
14911
|
+
.concierge-suggest:disabled {
|
|
14912
|
+
cursor: not-allowed; color: var(--ink-4); opacity: 0.7;
|
|
14913
|
+
}
|
|
14914
|
+
.concierge-suggest .label {
|
|
14915
|
+
font-family: var(--mono);
|
|
14916
|
+
font-size: 10px;
|
|
14917
|
+
letter-spacing: 0.06em;
|
|
14918
|
+
text-transform: uppercase;
|
|
14919
|
+
color: var(--ink-3);
|
|
14228
14920
|
}
|
|
14229
14921
|
.concierge-composer {
|
|
14230
14922
|
display: flex; gap: 10px; align-items: center;
|
|
14231
|
-
padding: 12px 0
|
|
14923
|
+
padding: 12px 0 4px;
|
|
14232
14924
|
border-top: 1px solid var(--rule);
|
|
14233
14925
|
flex-shrink: 0;
|
|
14234
14926
|
}
|
|
14927
|
+
.concierge-composer .input-wrap {
|
|
14928
|
+
flex: 1;
|
|
14929
|
+
display: flex; align-items: center; gap: var(--space-2);
|
|
14930
|
+
padding: 8px 12px;
|
|
14931
|
+
border: 1px solid var(--rule);
|
|
14932
|
+
border-radius: var(--rad);
|
|
14933
|
+
background: var(--surface);
|
|
14934
|
+
}
|
|
14935
|
+
.concierge-composer .input-wrap:focus-within {
|
|
14936
|
+
border-color: var(--ink-3);
|
|
14937
|
+
}
|
|
14235
14938
|
.concierge-composer input {
|
|
14236
14939
|
flex: 1; min-width: 0;
|
|
14237
|
-
padding:
|
|
14238
|
-
border:
|
|
14239
|
-
|
|
14240
|
-
|
|
14940
|
+
padding: 4px 0;
|
|
14941
|
+
border: 0;
|
|
14942
|
+
background: transparent;
|
|
14943
|
+
color: var(--ink);
|
|
14944
|
+
font-family: var(--sans);
|
|
14945
|
+
font-size: var(--text-md);
|
|
14946
|
+
outline: none;
|
|
14241
14947
|
}
|
|
14242
|
-
.concierge-composer input:
|
|
14243
|
-
|
|
14948
|
+
.concierge-composer input::placeholder { color: var(--ink-4); }
|
|
14949
|
+
.concierge-composer .composer-meta {
|
|
14950
|
+
font-family: var(--mono);
|
|
14951
|
+
font-size: 10px;
|
|
14952
|
+
color: var(--ink-4);
|
|
14953
|
+
letter-spacing: 0.04em;
|
|
14244
14954
|
}
|
|
14245
14955
|
.concierge-composer .btn-primary {
|
|
14246
|
-
padding: 8px 18px; font-size:
|
|
14247
|
-
}
|
|
14248
|
-
.concierge-chips {
|
|
14249
|
-
display: flex; flex-wrap: wrap; gap: 6px;
|
|
14250
|
-
padding: 10px 0 0;
|
|
14251
|
-
}
|
|
14252
|
-
.concierge-chips::before {
|
|
14253
|
-
content: "Try:"; color: var(--ink-3); font-size: 12px;
|
|
14254
|
-
align-self: center; margin-right: 4px;
|
|
14956
|
+
padding: 8px 18px; font-size: var(--text-base); flex-shrink: 0;
|
|
14255
14957
|
}
|
|
14256
14958
|
.concierge-foot {
|
|
14257
14959
|
margin: 12px 0 0; padding-top: 10px; border-top: 1px dashed var(--rule);
|
|
14258
|
-
font-size:
|
|
14960
|
+
font-size: var(--text-sm);
|
|
14259
14961
|
}
|
|
14260
14962
|
.concierge-foot a { color: var(--ink-2); }
|
|
14261
14963
|
.tier1-approval-card {
|
|
@@ -14263,20 +14965,532 @@ body {
|
|
|
14263
14965
|
border-radius: var(--rad); padding: 14px 16px; margin: 12px 0;
|
|
14264
14966
|
}
|
|
14265
14967
|
.tier1-approval-card h3 {
|
|
14266
|
-
margin: 0 0 8px; color: var(--ochre); font-size:
|
|
14968
|
+
margin: 0 0 8px; color: var(--ochre); font-size: var(--text-md);
|
|
14267
14969
|
}
|
|
14268
|
-
.tier1-approval-card p { margin: 0 0 12px; font-size:
|
|
14970
|
+
.tier1-approval-card p { margin: 0 0 12px; font-size: var(--text-base); }
|
|
14269
14971
|
.tier1-approval-card .actions {
|
|
14270
|
-
display: flex; gap:
|
|
14972
|
+
display: flex; gap: var(--space-2); flex-wrap: wrap;
|
|
14973
|
+
}
|
|
14974
|
+
/* Sprint Piece 2 PR 4 (2026-05-04): Agents view + Inspect pane polish.
|
|
14975
|
+
* Translates Claude Design references at
|
|
14976
|
+
* server/docs/design-refs/sprint-piece-2/surface-agents.jsx and the
|
|
14977
|
+
* Surface 3 block of surfaces.css. The fortress-column .agent-row,
|
|
14978
|
+
* .agent-row-head, and .agent-row-actions rules above are kept verbatim
|
|
14979
|
+
* because Finding DD tests pin them; the new Agents-view list scopes
|
|
14980
|
+
* its grid layout under .agents-list (descendant selector) so the
|
|
14981
|
+
* fortress-column rules are unaffected. The inspect pane combines the
|
|
14982
|
+
* existing .card surface with .inspect-pane structure (sticky right
|
|
14983
|
+
* rail, internal scroll, sectioned body) for the agent-detail view.
|
|
14984
|
+
*/
|
|
14985
|
+
.agents-wrap { max-width: 1080px; margin: 0 auto; }
|
|
14986
|
+
.agents-layout {
|
|
14987
|
+
display: grid;
|
|
14988
|
+
grid-template-columns: 1fr 420px;
|
|
14989
|
+
gap: 20px;
|
|
14990
|
+
align-items: start;
|
|
14991
|
+
}
|
|
14992
|
+
.agents-list {
|
|
14993
|
+
background: var(--surface);
|
|
14994
|
+
border: 1px solid var(--rule);
|
|
14995
|
+
border-radius: var(--rad-lg);
|
|
14996
|
+
overflow: hidden;
|
|
14997
|
+
}
|
|
14998
|
+
.agents-list-head {
|
|
14999
|
+
display: grid;
|
|
15000
|
+
grid-template-columns: minmax(0, 1fr) 110px 120px 88px;
|
|
15001
|
+
gap: 12px;
|
|
15002
|
+
padding: 10px 16px;
|
|
15003
|
+
border-bottom: 1px solid var(--rule);
|
|
15004
|
+
background: var(--paper-2);
|
|
15005
|
+
font-family: var(--mono);
|
|
15006
|
+
font-size: 10px;
|
|
15007
|
+
letter-spacing: 0.06em;
|
|
15008
|
+
text-transform: uppercase;
|
|
15009
|
+
color: var(--ink-3);
|
|
15010
|
+
}
|
|
15011
|
+
.agents-list .agent-row {
|
|
15012
|
+
display: grid;
|
|
15013
|
+
grid-template-columns: minmax(0, 1fr) 110px 120px 88px;
|
|
15014
|
+
gap: 12px;
|
|
15015
|
+
padding: 14px 16px;
|
|
15016
|
+
border-bottom: 1px solid var(--rule);
|
|
15017
|
+
align-items: center;
|
|
15018
|
+
cursor: pointer;
|
|
15019
|
+
transition: background 120ms ease;
|
|
15020
|
+
}
|
|
15021
|
+
.agents-list .agent-row:last-child { border-bottom: 0; }
|
|
15022
|
+
.agents-list .agent-row:hover { background: var(--paper-2); }
|
|
15023
|
+
.agents-list .agent-row.selected {
|
|
15024
|
+
background: var(--paper-2);
|
|
15025
|
+
box-shadow: inset 3px 0 0 var(--ink);
|
|
15026
|
+
}
|
|
15027
|
+
.agent-identity { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
15028
|
+
.agent-glyph {
|
|
15029
|
+
width: 28px; height: 28px;
|
|
15030
|
+
border-radius: var(--rad);
|
|
15031
|
+
background: var(--paper-3);
|
|
15032
|
+
border: 1px solid var(--rule);
|
|
15033
|
+
display: grid; place-items: center;
|
|
15034
|
+
flex-shrink: 0;
|
|
15035
|
+
font-family: var(--mono);
|
|
15036
|
+
font-size: 11px;
|
|
15037
|
+
color: var(--ink-2);
|
|
15038
|
+
font-weight: 600;
|
|
15039
|
+
}
|
|
15040
|
+
.agent-name {
|
|
15041
|
+
display: flex; flex-direction: column; min-width: 0;
|
|
15042
|
+
}
|
|
15043
|
+
.agent-name strong {
|
|
15044
|
+
font-size: var(--text-base); font-weight: 500;
|
|
15045
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
15046
|
+
}
|
|
15047
|
+
.agent-name small {
|
|
15048
|
+
font-family: var(--mono); font-size: var(--text-xs); color: var(--ink-3);
|
|
15049
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
15050
|
+
display: block;
|
|
15051
|
+
}
|
|
15052
|
+
.agent-state {
|
|
15053
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
15054
|
+
font-size: var(--text-xs);
|
|
15055
|
+
font-family: var(--mono);
|
|
15056
|
+
}
|
|
15057
|
+
.state-dot {
|
|
15058
|
+
width: 7px; height: 7px; border-radius: 50%;
|
|
15059
|
+
background: var(--ink-4);
|
|
15060
|
+
}
|
|
15061
|
+
.state-dot.live { background: var(--sage); animation: pulse-soft 2.4s ease-in-out infinite; }
|
|
15062
|
+
.state-dot.idle { background: var(--ochre); }
|
|
15063
|
+
.state-dot.off { background: var(--ink-4); }
|
|
15064
|
+
@keyframes pulse-soft {
|
|
15065
|
+
0%, 100% { box-shadow: 0 0 0 0 currentColor; opacity: 1; }
|
|
15066
|
+
50% { box-shadow: 0 0 0 4px transparent; opacity: 0.7; }
|
|
15067
|
+
}
|
|
15068
|
+
.agent-last {
|
|
15069
|
+
font-family: var(--mono); font-size: var(--text-xs); color: var(--ink-3);
|
|
15070
|
+
}
|
|
15071
|
+
/* Inspect pane (combined with .card outer wrapper for the
|
|
15072
|
+
* renderAgentInspectPanel return-shape regex anchored in
|
|
15073
|
+
* dashboard-welcome.test.ts:152). The .inspect-pane modifier overrides
|
|
15074
|
+
* .card padding so internal sections control their own spacing.
|
|
15075
|
+
*/
|
|
15076
|
+
.inspect-pane {
|
|
15077
|
+
padding: 0;
|
|
15078
|
+
display: flex; flex-direction: column;
|
|
15079
|
+
position: sticky;
|
|
15080
|
+
top: 20px;
|
|
15081
|
+
max-height: calc(100vh - 100px);
|
|
15082
|
+
overflow: hidden;
|
|
15083
|
+
}
|
|
15084
|
+
.inspect-head {
|
|
15085
|
+
padding: 16px 18px;
|
|
15086
|
+
border-bottom: 1px solid var(--rule);
|
|
15087
|
+
display: flex; flex-direction: column; gap: 10px;
|
|
15088
|
+
}
|
|
15089
|
+
.inspect-head .row1 {
|
|
15090
|
+
display: flex; align-items: center; gap: 10px;
|
|
15091
|
+
}
|
|
15092
|
+
.inspect-head h3 {
|
|
15093
|
+
font-family: var(--serif); font-weight: 500;
|
|
15094
|
+
font-size: 17px; margin: 0;
|
|
15095
|
+
}
|
|
15096
|
+
.inspect-head .meta {
|
|
15097
|
+
display: flex; gap: 6px; flex-wrap: wrap;
|
|
15098
|
+
}
|
|
15099
|
+
.inspect-body {
|
|
15100
|
+
overflow-y: auto;
|
|
15101
|
+
padding: 4px 18px 18px;
|
|
15102
|
+
}
|
|
15103
|
+
.inspect-section {
|
|
15104
|
+
padding: 14px 0;
|
|
15105
|
+
border-bottom: 1px solid var(--rule);
|
|
15106
|
+
}
|
|
15107
|
+
.inspect-section:last-child { border-bottom: 0; }
|
|
15108
|
+
.inspect-section h4 {
|
|
15109
|
+
font-family: var(--mono);
|
|
15110
|
+
font-size: 10px;
|
|
15111
|
+
letter-spacing: 0.08em;
|
|
15112
|
+
text-transform: uppercase;
|
|
15113
|
+
color: var(--ink-3);
|
|
15114
|
+
margin: 0 0 10px;
|
|
15115
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
15116
|
+
}
|
|
15117
|
+
.inspect-section h4 .count {
|
|
15118
|
+
font-family: var(--mono);
|
|
15119
|
+
background: var(--paper-3);
|
|
15120
|
+
border-radius: 999px;
|
|
15121
|
+
padding: 1px 7px;
|
|
15122
|
+
color: var(--ink-2);
|
|
15123
|
+
font-size: 10px;
|
|
15124
|
+
}
|
|
15125
|
+
.approval-row {
|
|
15126
|
+
background: var(--ochre-bg);
|
|
15127
|
+
border: 1px solid var(--ochre);
|
|
15128
|
+
border-radius: var(--rad);
|
|
15129
|
+
padding: 10px 12px;
|
|
15130
|
+
margin-bottom: 8px;
|
|
15131
|
+
display: flex; flex-direction: column; gap: 8px;
|
|
15132
|
+
}
|
|
15133
|
+
.approval-row .what { font-size: var(--text-base); color: var(--ink); }
|
|
15134
|
+
.approval-row .what .pill { margin-right: 6px; }
|
|
15135
|
+
.approval-row .why {
|
|
15136
|
+
font-size: var(--text-sm); color: var(--ink-2);
|
|
15137
|
+
padding-left: 10px;
|
|
15138
|
+
border-left: 2px solid var(--ochre);
|
|
15139
|
+
}
|
|
15140
|
+
.approval-row .actions { display: flex; gap: 6px; justify-content: flex-end; }
|
|
15141
|
+
.timeline {
|
|
15142
|
+
display: flex; flex-direction: column; gap: 0;
|
|
15143
|
+
position: relative;
|
|
15144
|
+
padding-left: 14px;
|
|
15145
|
+
}
|
|
15146
|
+
.timeline::before {
|
|
15147
|
+
content: "";
|
|
15148
|
+
position: absolute;
|
|
15149
|
+
left: 4px; top: 6px; bottom: 6px;
|
|
15150
|
+
width: 1px;
|
|
15151
|
+
background: var(--rule);
|
|
15152
|
+
}
|
|
15153
|
+
.timeline-item {
|
|
15154
|
+
position: relative;
|
|
15155
|
+
padding: 6px 0 10px;
|
|
15156
|
+
font-size: var(--text-sm);
|
|
15157
|
+
}
|
|
15158
|
+
.timeline-item::before {
|
|
15159
|
+
content: "";
|
|
15160
|
+
position: absolute;
|
|
15161
|
+
left: -14px; top: 11px;
|
|
15162
|
+
width: 8px; height: 8px;
|
|
15163
|
+
border-radius: 50%;
|
|
15164
|
+
background: var(--surface);
|
|
15165
|
+
border: 1.5px solid var(--ink-4);
|
|
15166
|
+
}
|
|
15167
|
+
.timeline-item.ok::before { border-color: var(--sage); }
|
|
15168
|
+
.timeline-item.warn::before { border-color: var(--ochre); }
|
|
15169
|
+
.timeline-item.fail::before { border-color: var(--rust); }
|
|
15170
|
+
.timeline-item .ts {
|
|
15171
|
+
font-family: var(--mono); font-size: 10px;
|
|
15172
|
+
color: var(--ink-3);
|
|
15173
|
+
letter-spacing: 0.02em;
|
|
15174
|
+
}
|
|
15175
|
+
.timeline-item .what {
|
|
15176
|
+
margin-top: 2px; color: var(--ink); font-size: var(--text-base);
|
|
15177
|
+
}
|
|
15178
|
+
.timeline-item .att {
|
|
15179
|
+
margin-top: 4px;
|
|
15180
|
+
display: inline-flex;
|
|
15181
|
+
}
|
|
15182
|
+
.policy-line {
|
|
15183
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
15184
|
+
padding: 5px 0; font-size: var(--text-base);
|
|
15185
|
+
border-bottom: 1px dashed var(--rule);
|
|
15186
|
+
}
|
|
15187
|
+
.policy-line:last-child { border-bottom: 0; }
|
|
15188
|
+
.policy-line .k { color: var(--ink-3); }
|
|
15189
|
+
.policy-line .v { font-family: var(--mono); font-size: var(--text-sm); color: var(--ink); }
|
|
15190
|
+
/* Empty-state block for when no agents are wrapped. The
|
|
15191
|
+
* renderAgentsList empty-state branch begins with the literal
|
|
15192
|
+
* '<h1>Agents</h1>' (regex-pinned in agents-empty-state-canary.test.ts)
|
|
15193
|
+
* and the "No wrapped agents yet." copy is preserved verbatim.
|
|
15194
|
+
*/
|
|
15195
|
+
.agents-empty {
|
|
15196
|
+
background: var(--surface);
|
|
15197
|
+
border: 1px dashed var(--rule-2);
|
|
15198
|
+
border-radius: var(--rad-lg);
|
|
15199
|
+
padding: 56px 40px;
|
|
15200
|
+
text-align: center;
|
|
15201
|
+
max-width: 720px;
|
|
15202
|
+
margin: 32px auto;
|
|
15203
|
+
}
|
|
15204
|
+
.agents-empty .icon-frame {
|
|
15205
|
+
width: 64px; height: 64px;
|
|
15206
|
+
margin: 0 auto 18px;
|
|
15207
|
+
border: 1px solid var(--rule);
|
|
15208
|
+
border-radius: 50%;
|
|
15209
|
+
display: grid; place-items: center;
|
|
15210
|
+
position: relative;
|
|
15211
|
+
}
|
|
15212
|
+
.agents-empty .icon-frame::before,
|
|
15213
|
+
.agents-empty .icon-frame::after {
|
|
15214
|
+
content: "";
|
|
15215
|
+
position: absolute;
|
|
15216
|
+
border: 1px solid var(--rule);
|
|
15217
|
+
border-radius: 50%;
|
|
15218
|
+
}
|
|
15219
|
+
.agents-empty .icon-frame::before { inset: -8px; opacity: 0.6; }
|
|
15220
|
+
.agents-empty .icon-frame::after { inset: -16px; opacity: 0.3; }
|
|
15221
|
+
.agents-empty .icon-frame .core {
|
|
15222
|
+
width: 22px; height: 22px;
|
|
15223
|
+
background: var(--ink);
|
|
15224
|
+
border-radius: 50%;
|
|
15225
|
+
}
|
|
15226
|
+
.agents-empty h2 {
|
|
15227
|
+
font-family: var(--serif);
|
|
15228
|
+
font-weight: 400;
|
|
15229
|
+
font-size: var(--text-xl);
|
|
15230
|
+
margin: 0 0 8px;
|
|
15231
|
+
}
|
|
15232
|
+
.agents-empty p {
|
|
15233
|
+
color: var(--ink-3);
|
|
15234
|
+
margin: 0 0 20px;
|
|
15235
|
+
font-size: var(--text-md);
|
|
15236
|
+
line-height: 1.55;
|
|
15237
|
+
max-width: 50ch;
|
|
15238
|
+
margin-left: auto; margin-right: auto;
|
|
15239
|
+
}
|
|
15240
|
+
.terminal-block {
|
|
15241
|
+
text-align: left;
|
|
15242
|
+
background: var(--paper-3);
|
|
15243
|
+
border: 1px solid var(--rule);
|
|
15244
|
+
border-radius: var(--rad);
|
|
15245
|
+
padding: 14px 16px;
|
|
15246
|
+
font-family: var(--mono);
|
|
15247
|
+
font-size: var(--text-base);
|
|
15248
|
+
margin: 0 auto 16px;
|
|
15249
|
+
max-width: 480px;
|
|
15250
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
15251
|
+
}
|
|
15252
|
+
.terminal-block .cmd { color: var(--ink); }
|
|
15253
|
+
.terminal-block .cmd .prompt { color: var(--ink-3); margin-right: 8px; user-select: none; }
|
|
15254
|
+
.copy-btn {
|
|
15255
|
+
background: transparent; border: 0;
|
|
15256
|
+
color: var(--ink-3); cursor: pointer;
|
|
15257
|
+
font-family: var(--mono); font-size: var(--text-xs);
|
|
15258
|
+
padding: 2px 6px;
|
|
15259
|
+
border-radius: var(--rad);
|
|
15260
|
+
}
|
|
15261
|
+
.copy-btn:hover { color: var(--ink); background: var(--paper-2); }
|
|
15262
|
+
|
|
15263
|
+
/* Surface 5. Attestation badge gallery. */
|
|
15264
|
+
.att-gallery {
|
|
15265
|
+
display: flex; flex-direction: column; gap: 24px;
|
|
15266
|
+
max-width: 1000px;
|
|
15267
|
+
margin: 0 auto;
|
|
14271
15268
|
}
|
|
15269
|
+
.att-section {
|
|
15270
|
+
background: var(--surface);
|
|
15271
|
+
border: 1px solid var(--rule);
|
|
15272
|
+
border-radius: var(--rad-lg);
|
|
15273
|
+
padding: 22px 24px;
|
|
15274
|
+
}
|
|
15275
|
+
.att-section-head {
|
|
15276
|
+
display: flex; justify-content: space-between; align-items: baseline;
|
|
15277
|
+
gap: 12px;
|
|
15278
|
+
margin-bottom: 16px;
|
|
15279
|
+
padding-bottom: 12px;
|
|
15280
|
+
border-bottom: 1px solid var(--rule);
|
|
15281
|
+
}
|
|
15282
|
+
.att-section-head h2 {
|
|
15283
|
+
font-family: var(--serif);
|
|
15284
|
+
font-weight: 400;
|
|
15285
|
+
font-size: 19px;
|
|
15286
|
+
margin: 0 0 4px;
|
|
15287
|
+
}
|
|
15288
|
+
.att-section-head p {
|
|
15289
|
+
color: var(--ink-3);
|
|
15290
|
+
margin: 0;
|
|
15291
|
+
font-size: 13px;
|
|
15292
|
+
line-height: 1.5;
|
|
15293
|
+
max-width: 64ch;
|
|
15294
|
+
}
|
|
15295
|
+
.att-section-head .label {
|
|
15296
|
+
font-family: var(--mono);
|
|
15297
|
+
font-size: 10px;
|
|
15298
|
+
letter-spacing: 0.08em;
|
|
15299
|
+
text-transform: uppercase;
|
|
15300
|
+
color: var(--ink-3);
|
|
15301
|
+
}
|
|
15302
|
+
.att-row {
|
|
15303
|
+
display: grid;
|
|
15304
|
+
grid-template-columns: 240px 1fr;
|
|
15305
|
+
gap: 24px;
|
|
15306
|
+
padding: 14px 0;
|
|
15307
|
+
border-bottom: 1px dashed var(--rule);
|
|
15308
|
+
align-items: center;
|
|
15309
|
+
}
|
|
15310
|
+
.att-row:last-child { border-bottom: 0; }
|
|
15311
|
+
.att-row .demo {
|
|
15312
|
+
display: flex; align-items: center; justify-content: flex-start;
|
|
15313
|
+
padding: 12px 16px;
|
|
15314
|
+
background: var(--paper-2);
|
|
15315
|
+
border: 1px solid var(--rule);
|
|
15316
|
+
border-radius: var(--rad);
|
|
15317
|
+
min-height: 56px;
|
|
15318
|
+
}
|
|
15319
|
+
.att-row .desc strong {
|
|
15320
|
+
font-size: 13px; display: block; margin-bottom: 3px;
|
|
15321
|
+
}
|
|
15322
|
+
.att-row .desc small {
|
|
15323
|
+
color: var(--ink-3); font-size: 12px;
|
|
15324
|
+
line-height: 1.5;
|
|
15325
|
+
}
|
|
15326
|
+
|
|
15327
|
+
/* Global persistent badge. Lives in the topbar across every surface. */
|
|
15328
|
+
.att-global {
|
|
15329
|
+
display: inline-flex; align-items: center;
|
|
15330
|
+
gap: 8px;
|
|
15331
|
+
padding: 4px 10px 4px 6px;
|
|
15332
|
+
border: 1px solid var(--rule);
|
|
15333
|
+
border-radius: 999px;
|
|
15334
|
+
background: var(--surface-2);
|
|
15335
|
+
font-family: var(--mono);
|
|
15336
|
+
font-size: 11px;
|
|
15337
|
+
color: var(--ink-2);
|
|
15338
|
+
}
|
|
15339
|
+
.att-global.verified { border-color: var(--sage); background: var(--sage-bg); color: var(--sage); }
|
|
15340
|
+
.att-global.degraded { border-color: var(--ochre); background: var(--ochre-bg); color: var(--ochre); }
|
|
15341
|
+
.att-global.unverified { border-color: var(--rust); background: var(--rust-bg); color: var(--rust); }
|
|
15342
|
+
.att-global .seal {
|
|
15343
|
+
width: 18px; height: 18px;
|
|
15344
|
+
position: relative;
|
|
15345
|
+
flex-shrink: 0;
|
|
15346
|
+
}
|
|
15347
|
+
.att-global .seal-ring {
|
|
15348
|
+
position: absolute; inset: 0;
|
|
15349
|
+
border: 1.5px solid currentColor;
|
|
15350
|
+
border-radius: 50%;
|
|
15351
|
+
}
|
|
15352
|
+
.att-global .seal-ring.dashed { border-style: dashed; }
|
|
15353
|
+
.att-global .seal-core {
|
|
15354
|
+
position: absolute; inset: 4px;
|
|
15355
|
+
background: currentColor;
|
|
15356
|
+
border-radius: 50%;
|
|
15357
|
+
opacity: 0.85;
|
|
15358
|
+
}
|
|
15359
|
+
.att-global.degraded .seal-core { background: transparent; border: 1px solid currentColor; }
|
|
15360
|
+
.att-global.unverified .seal-core {
|
|
15361
|
+
background: transparent;
|
|
15362
|
+
border: 1px solid currentColor;
|
|
15363
|
+
}
|
|
15364
|
+
.att-global.unverified .seal-core::after {
|
|
15365
|
+
content: ""; position: absolute; inset: 0;
|
|
15366
|
+
background: currentColor; opacity: 0.4;
|
|
15367
|
+
clip-path: polygon(0 0, 100% 100%, 100% 90%, 10% 0);
|
|
15368
|
+
}
|
|
15369
|
+
.att-global .label {
|
|
15370
|
+
font-family: var(--mono);
|
|
15371
|
+
font-size: 11px;
|
|
15372
|
+
letter-spacing: 0.02em;
|
|
15373
|
+
text-transform: uppercase;
|
|
15374
|
+
}
|
|
15375
|
+
.att-global .hash {
|
|
15376
|
+
font-family: var(--mono);
|
|
15377
|
+
font-size: 10px;
|
|
15378
|
+
opacity: 0.7;
|
|
15379
|
+
border-left: 1px solid currentColor;
|
|
15380
|
+
padding-left: 8px;
|
|
15381
|
+
margin-left: 2px;
|
|
15382
|
+
}
|
|
15383
|
+
|
|
15384
|
+
/* Per-agent badge. Square chip beside each agent. */
|
|
15385
|
+
.att-agent {
|
|
15386
|
+
display: inline-flex; align-items: center;
|
|
15387
|
+
gap: 6px;
|
|
15388
|
+
padding: 3px 7px;
|
|
15389
|
+
border-radius: var(--rad);
|
|
15390
|
+
border: 1px solid var(--rule);
|
|
15391
|
+
background: var(--surface);
|
|
15392
|
+
font-family: var(--mono);
|
|
15393
|
+
font-size: 10px;
|
|
15394
|
+
color: var(--ink-2);
|
|
15395
|
+
}
|
|
15396
|
+
.att-agent .mark {
|
|
15397
|
+
width: 10px; height: 10px;
|
|
15398
|
+
border: 1.5px solid currentColor;
|
|
15399
|
+
border-radius: 2px;
|
|
15400
|
+
position: relative;
|
|
15401
|
+
}
|
|
15402
|
+
.att-agent.verified { color: var(--sage); border-color: var(--sage); background: var(--sage-bg); }
|
|
15403
|
+
.att-agent.verified .mark { background: currentColor; }
|
|
15404
|
+
.att-agent.degraded { color: var(--ochre); border-color: var(--ochre); background: var(--ochre-bg); }
|
|
15405
|
+
.att-agent.unverified { color: var(--rust); border-color: var(--rust); background: var(--rust-bg); }
|
|
15406
|
+
.att-agent.unverified .mark {
|
|
15407
|
+
background: repeating-linear-gradient(
|
|
15408
|
+
45deg, currentColor, currentColor 1px,
|
|
15409
|
+
transparent 1px, transparent 3px
|
|
15410
|
+
);
|
|
15411
|
+
}
|
|
15412
|
+
|
|
15413
|
+
/* Per-action badge. Tiny inline tick on timeline rows. */
|
|
15414
|
+
.att-action {
|
|
15415
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
15416
|
+
font-family: var(--mono);
|
|
15417
|
+
font-size: 10px;
|
|
15418
|
+
color: var(--ink-3);
|
|
15419
|
+
padding: 1px 6px;
|
|
15420
|
+
border-radius: 4px;
|
|
15421
|
+
background: var(--paper-3);
|
|
15422
|
+
border: 1px solid transparent;
|
|
15423
|
+
}
|
|
15424
|
+
.att-action .tick {
|
|
15425
|
+
width: 6px; height: 6px;
|
|
15426
|
+
border-radius: 1px;
|
|
15427
|
+
background: currentColor;
|
|
15428
|
+
}
|
|
15429
|
+
.att-action.verified { color: var(--sage); }
|
|
15430
|
+
.att-action.degraded { color: var(--ochre); }
|
|
15431
|
+
.att-action.unverified { color: var(--rust); }
|
|
15432
|
+
.att-action.neutral .tick { background: var(--ink-4); border-radius: 50%; }
|
|
15433
|
+
|
|
15434
|
+
/* Custody-provenance badge stub (v1.x). Visibly stubbed with dashed border. */
|
|
15435
|
+
.att-custody {
|
|
15436
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
15437
|
+
padding: 4px 10px 4px 6px;
|
|
15438
|
+
border-radius: var(--rad);
|
|
15439
|
+
border: 1px dashed var(--rule-2);
|
|
15440
|
+
background: var(--paper-3);
|
|
15441
|
+
color: var(--ink-3);
|
|
15442
|
+
font-family: var(--mono);
|
|
15443
|
+
font-size: 10px;
|
|
15444
|
+
}
|
|
15445
|
+
.att-custody .seal-stub {
|
|
15446
|
+
width: 16px; height: 16px;
|
|
15447
|
+
border: 1px dashed var(--ink-4);
|
|
15448
|
+
border-radius: 50%;
|
|
15449
|
+
position: relative;
|
|
15450
|
+
flex-shrink: 0;
|
|
15451
|
+
}
|
|
15452
|
+
.att-custody .seal-stub::after {
|
|
15453
|
+
content: ""; position: absolute; inset: 4px;
|
|
15454
|
+
border: 1px dashed var(--ink-4);
|
|
15455
|
+
border-radius: 50%;
|
|
15456
|
+
}
|
|
15457
|
+
.att-custody .stub-tag {
|
|
15458
|
+
letter-spacing: 0.06em;
|
|
15459
|
+
text-transform: uppercase;
|
|
15460
|
+
}
|
|
15461
|
+
|
|
15462
|
+
/* Tooltip surface for badges. */
|
|
15463
|
+
.att-tooltip {
|
|
15464
|
+
background: var(--ink);
|
|
15465
|
+
color: var(--paper);
|
|
15466
|
+
font-family: var(--mono);
|
|
15467
|
+
font-size: 11px;
|
|
15468
|
+
padding: 8px 10px;
|
|
15469
|
+
border-radius: var(--rad);
|
|
15470
|
+
max-width: 280px;
|
|
15471
|
+
line-height: 1.5;
|
|
15472
|
+
display: inline-block;
|
|
15473
|
+
}
|
|
15474
|
+
[data-theme="dark"] .att-tooltip {
|
|
15475
|
+
background: var(--paper-3);
|
|
15476
|
+
color: var(--ink);
|
|
15477
|
+
}
|
|
15478
|
+
|
|
14272
15479
|
@media (max-width: 1100px) {
|
|
14273
15480
|
.app, .app.route-full { grid-template-columns: 56px 1fr; grid-template-areas: "sidebar topbar" "sidebar main"; }
|
|
14274
15481
|
.fortress { display: none; }
|
|
14275
15482
|
.sidebar h1, .sidebar nav a span { display: none; }
|
|
15483
|
+
.sidebar nav a { justify-content: center; padding: 8px 6px; }
|
|
14276
15484
|
.template-grid { grid-template-columns: 1fr; }
|
|
14277
15485
|
.policy-center h1 { font-size: 30px; }
|
|
14278
15486
|
.intel-center h1 { font-size: 30px; }
|
|
14279
15487
|
.intel-row { grid-template-columns: 1fr; }
|
|
15488
|
+
.intel-grid { grid-template-columns: 1fr; }
|
|
15489
|
+
.intel-failure-row { grid-template-columns: 1fr; }
|
|
15490
|
+
.agents-layout { grid-template-columns: 1fr; }
|
|
15491
|
+
.agents-list-head, .agents-list .agent-row { grid-template-columns: minmax(0, 1fr) 90px 90px; }
|
|
15492
|
+
.agents-list-head span:nth-child(4), .agents-list .agent-row > .agent-last { display: none; }
|
|
15493
|
+
.inspect-pane { position: static; max-height: none; }
|
|
14280
15494
|
}
|
|
14281
15495
|
`;
|
|
14282
15496
|
var NAV_ITEMS = [
|
|
@@ -14284,11 +15498,26 @@ var NAV_ITEMS = [
|
|
|
14284
15498
|
{ id: "agents", label: "Agents" },
|
|
14285
15499
|
{ id: "policy", label: "Policy" },
|
|
14286
15500
|
{ id: "intelligence", label: "Intelligence" },
|
|
15501
|
+
{ id: "attestation", label: "Attestation" },
|
|
14287
15502
|
{ id: "privacy", label: "Privacy" },
|
|
14288
15503
|
{ id: "coordination", label: "Coordination" },
|
|
14289
15504
|
{ id: "health", label: "Health" },
|
|
14290
15505
|
{ id: "exit-drill", label: "Exit drill" }
|
|
14291
15506
|
];
|
|
15507
|
+
var NAV_ICON_PATHS = {
|
|
15508
|
+
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"/>',
|
|
15509
|
+
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"/>',
|
|
15510
|
+
policy: '<path d="M4 2h5l3 3v9H4z"/><path d="M9 2v3h3"/><path d="M6 9h4M6 11.5h4"/>',
|
|
15511
|
+
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"/>',
|
|
15512
|
+
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"/>',
|
|
15513
|
+
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"/>',
|
|
15514
|
+
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"/>',
|
|
15515
|
+
health: '<path d="M2 8h2.5l1.5-4 3 8 1.5-4H14"/>',
|
|
15516
|
+
"exit-drill": '<path d="M9.5 2H3v12h6.5"/><path d="M11 5l3 3-3 3"/><path d="M14 8H6.5"/>'
|
|
15517
|
+
};
|
|
15518
|
+
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">';
|
|
15519
|
+
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>';
|
|
15520
|
+
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>';
|
|
14292
15521
|
function escHtml2(value) {
|
|
14293
15522
|
if (value == null) return "";
|
|
14294
15523
|
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -14301,9 +15530,11 @@ function renderDashboardV11Html(options = {}) {
|
|
|
14301
15530
|
const fortressId = options.fortressId ?? "fortress";
|
|
14302
15531
|
const sanctuaryVersion = options.sanctuaryVersion ?? SANCTUARY_VERSION;
|
|
14303
15532
|
const embedClient = options.embedClient !== false;
|
|
14304
|
-
const nav = NAV_ITEMS.map(
|
|
14305
|
-
|
|
14306
|
-
|
|
15533
|
+
const nav = NAV_ITEMS.map((n) => {
|
|
15534
|
+
const iconPath = NAV_ICON_PATHS[n.id] ?? "";
|
|
15535
|
+
const icon = iconPath ? SVG_OPEN + iconPath + "</svg>" : "";
|
|
15536
|
+
return `<a href="#${n.id}" data-route="${n.id}">${icon}<span>${escHtml2(n.label)}</span></a>`;
|
|
15537
|
+
}).join("\n ");
|
|
14307
15538
|
const config = JSON.stringify({
|
|
14308
15539
|
authToken,
|
|
14309
15540
|
hubApiBase,
|
|
@@ -14335,8 +15566,12 @@ function renderDashboardV11Html(options = {}) {
|
|
|
14335
15566
|
<span class="pill" data-pill="version">v${escHtml2(sanctuaryVersion)}</span>
|
|
14336
15567
|
<span class="pill" data-pill="deployment">deployment: local</span>
|
|
14337
15568
|
<span class="pill" data-pill="mode">mode: solo</span>
|
|
14338
|
-
<span class="
|
|
15569
|
+
<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>
|
|
14339
15570
|
</div>
|
|
15571
|
+
<button class="btn btn-icon" id="btn-theme-toggle" data-action="theme-toggle" aria-label="Toggle theme" title="Toggle theme">
|
|
15572
|
+
<span class="icon-moon">${THEME_ICON_MOON}</span>
|
|
15573
|
+
<span class="icon-sun">${THEME_ICON_SUN}</span>
|
|
15574
|
+
</button>
|
|
14340
15575
|
<button class="btn btn-danger" id="btn-lockdown" data-action="lockdown">Lockdown</button>
|
|
14341
15576
|
</header>
|
|
14342
15577
|
<main class="main" id="main"><p class="muted">Loading dashboard.</p></main>
|
|
@@ -17669,8 +18904,8 @@ function verifySHR(shr, now) {
|
|
|
17669
18904
|
const errors = [];
|
|
17670
18905
|
const warnings = [];
|
|
17671
18906
|
const currentTime = now ?? /* @__PURE__ */ new Date();
|
|
17672
|
-
if (!shr.body || !shr.signed_by || !shr.signature) {
|
|
17673
|
-
errors.push("Missing required SHR fields (body, signed_by, or signature)");
|
|
18907
|
+
if (!shr.body || !shr.signed_by || !shr.signature || !shr.signature_scheme) {
|
|
18908
|
+
errors.push("Missing required SHR fields (body, signed_by, signature_scheme, or signature)");
|
|
17674
18909
|
return {
|
|
17675
18910
|
valid: false,
|
|
17676
18911
|
errors,
|
|
@@ -17683,6 +18918,9 @@ function verifySHR(shr, now) {
|
|
|
17683
18918
|
if (shr.body.shr_version !== "1.0") {
|
|
17684
18919
|
errors.push(`Unsupported SHR version: ${shr.body.shr_version}`);
|
|
17685
18920
|
}
|
|
18921
|
+
if (shr.signature_scheme !== SIGNATURE_SCHEME_V1) {
|
|
18922
|
+
errors.push(`Unsupported SHR signature_scheme: ${String(shr.signature_scheme)}`);
|
|
18923
|
+
}
|
|
17686
18924
|
const expiresAt = new Date(shr.body.expires_at);
|
|
17687
18925
|
if (isNaN(expiresAt.getTime())) {
|
|
17688
18926
|
errors.push("Invalid expires_at timestamp");
|
|
@@ -28740,8 +29978,6 @@ function aggregateInbox(sources, store) {
|
|
|
28740
29978
|
}
|
|
28741
29979
|
return store.list();
|
|
28742
29980
|
}
|
|
28743
|
-
|
|
28744
|
-
// src/hub/activity-feed.ts
|
|
28745
29981
|
var LIFECYCLE_VERBS = [
|
|
28746
29982
|
"wrap",
|
|
28747
29983
|
"unwrap",
|
|
@@ -28800,18 +30036,30 @@ function extractAgentIdHint(entry) {
|
|
|
28800
30036
|
const value = details.agent_id;
|
|
28801
30037
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
28802
30038
|
}
|
|
30039
|
+
function deriveAttestationFragment(entryId) {
|
|
30040
|
+
const hash2 = crypto.createHash("sha256").update(entryId).digest("hex");
|
|
30041
|
+
return `${hash2.slice(0, 4)}..${hash2.slice(4, 6)}`;
|
|
30042
|
+
}
|
|
30043
|
+
function deriveAttestationState(entry) {
|
|
30044
|
+
return entry.result === "success" ? "verified" : "degraded";
|
|
30045
|
+
}
|
|
28803
30046
|
function projectEntry(entry) {
|
|
28804
30047
|
const agentIdHint = extractAgentIdHint(entry);
|
|
28805
30048
|
const category = categorizeOperation(entry.layer, entry.operation);
|
|
30049
|
+
const entryId = `${entry.timestamp}|${entry.operation}|${entry.identity_id}`;
|
|
28806
30050
|
return {
|
|
28807
30051
|
version: "1.1",
|
|
28808
|
-
entry_id:
|
|
30052
|
+
entry_id: entryId,
|
|
28809
30053
|
emitted_at: entry.timestamp,
|
|
28810
30054
|
...agentIdHint ? { agent_id: agentIdHint } : {},
|
|
28811
30055
|
identity_id: entry.identity_id,
|
|
28812
30056
|
category,
|
|
28813
30057
|
display_template_id: templateIdFor(category, entry.operation),
|
|
28814
|
-
display_template_args: buildTemplateArgs(entry, agentIdHint)
|
|
30058
|
+
display_template_args: buildTemplateArgs(entry, agentIdHint),
|
|
30059
|
+
attestation: {
|
|
30060
|
+
state: deriveAttestationState(entry),
|
|
30061
|
+
fragment: deriveAttestationFragment(entryId)
|
|
30062
|
+
}
|
|
28815
30063
|
};
|
|
28816
30064
|
}
|
|
28817
30065
|
async function aggregateActivity(sources, filter = {}) {
|
|
@@ -31760,7 +33008,7 @@ var MemoryStorage = class {
|
|
|
31760
33008
|
};
|
|
31761
33009
|
|
|
31762
33010
|
// src/contracts/v1.1/constants.ts
|
|
31763
|
-
var
|
|
33011
|
+
var SIGNATURE_SCHEME_V12 = "ed25519-v1";
|
|
31764
33012
|
var EXIT_BUNDLE_MANIFEST_VERSION = "SANCTUARY_EXIT_BUNDLE_V1";
|
|
31765
33013
|
var EXIT_BUNDLE_ARTIFACT_KINDS = [
|
|
31766
33014
|
"public_identity",
|
|
@@ -31785,6 +33033,12 @@ var EXIT_BUNDLE_PATH_MAX_BYTES = 256;
|
|
|
31785
33033
|
// src/exit/verifier.ts
|
|
31786
33034
|
init_encoding();
|
|
31787
33035
|
init_hashing();
|
|
33036
|
+
var InvalidExitBundleError = class extends Error {
|
|
33037
|
+
constructor(message) {
|
|
33038
|
+
super(message);
|
|
33039
|
+
this.name = "InvalidExitBundleError";
|
|
33040
|
+
}
|
|
33041
|
+
};
|
|
31788
33042
|
var PRIVATE_MATERIAL_KEYS = /* @__PURE__ */ new Set([
|
|
31789
33043
|
"private_key",
|
|
31790
33044
|
"privatekey",
|
|
@@ -31967,7 +33221,7 @@ function verifyReputationArtifact(reputationArtifact, publicKeysByDid) {
|
|
|
31967
33221
|
unverifiable_attestations: unverifiable
|
|
31968
33222
|
};
|
|
31969
33223
|
}
|
|
31970
|
-
async function verifyExitBundle(bundleDir) {
|
|
33224
|
+
async function verifyExitBundle(bundleDir, options = {}) {
|
|
31971
33225
|
const root = path.resolve(bundleDir);
|
|
31972
33226
|
let manifest;
|
|
31973
33227
|
let manifestBytes;
|
|
@@ -31976,7 +33230,9 @@ async function verifyExitBundle(bundleDir) {
|
|
|
31976
33230
|
manifestBytes = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
31977
33231
|
manifest = JSON.parse(Buffer.from(raw).toString("utf8"));
|
|
31978
33232
|
} catch {
|
|
31979
|
-
|
|
33233
|
+
throw new InvalidExitBundleError(
|
|
33234
|
+
`Not a valid SANCTUARY_EXIT_BUNDLE_V1 directory: manifest.json missing at ${path.join(root, "manifest.json")}`
|
|
33235
|
+
);
|
|
31980
33236
|
}
|
|
31981
33237
|
const warnings = [];
|
|
31982
33238
|
const unsupportedArtifacts = [];
|
|
@@ -31984,7 +33240,7 @@ async function verifyExitBundle(bundleDir) {
|
|
|
31984
33240
|
if (!body || body.manifest_version !== EXIT_BUNDLE_MANIFEST_VERSION) {
|
|
31985
33241
|
return fail(root, manifest, "manifest_unknown_version", warnings, unsupportedArtifacts);
|
|
31986
33242
|
}
|
|
31987
|
-
if (body.signature_scheme !==
|
|
33243
|
+
if (body.signature_scheme !== SIGNATURE_SCHEME_V12) {
|
|
31988
33244
|
return fail(
|
|
31989
33245
|
root,
|
|
31990
33246
|
manifest,
|
|
@@ -32144,9 +33400,16 @@ async function verifyExitBundle(bundleDir) {
|
|
|
32144
33400
|
}
|
|
32145
33401
|
const reputationFailed = reputation?.bundle_signature_valid === false || (reputation?.invalid_attestations ?? 0) > 0;
|
|
32146
33402
|
const identityFailed = identity ? !identity.signature_valid : false;
|
|
33403
|
+
const unverifiableCount = reputation?.unverifiable_attestations ?? 0;
|
|
33404
|
+
const unverifiableFailed = unverifiableCount > 0 && !options.acceptUnverifiableAttestations;
|
|
33405
|
+
if (unverifiableFailed) {
|
|
33406
|
+
warnings.push(
|
|
33407
|
+
`${unverifiableCount} reputation attestation(s) have unknown signer public keys; pass --accept-unverifiable-attestations to import anyway`
|
|
33408
|
+
);
|
|
33409
|
+
}
|
|
32147
33410
|
return {
|
|
32148
33411
|
version: "1.1",
|
|
32149
|
-
passed: !reputationFailed && !identityFailed,
|
|
33412
|
+
passed: !reputationFailed && !identityFailed && !unverifiableFailed,
|
|
32150
33413
|
verified_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
32151
33414
|
manifest_path: path.join(root, "manifest.json"),
|
|
32152
33415
|
manifest_hash: sha256Hex2(manifestBytes),
|
|
@@ -32163,7 +33426,7 @@ async function verifyExitBundle(bundleDir) {
|
|
|
32163
33426
|
identity,
|
|
32164
33427
|
audit,
|
|
32165
33428
|
reputation,
|
|
32166
|
-
failure_class: reputationFailed || identityFailed ? "other" : void 0
|
|
33429
|
+
failure_class: reputationFailed || identityFailed || unverifiableFailed ? "other" : void 0
|
|
32167
33430
|
};
|
|
32168
33431
|
}
|
|
32169
33432
|
|
|
@@ -32176,6 +33439,14 @@ var EXIT_POLICY_SETS_NAMESPACE = "_exit_policy_sets";
|
|
|
32176
33439
|
var EXIT_COMMITMENTS_NAMESPACE = "_exit_commitments";
|
|
32177
33440
|
var EXIT_PLACEHOLDER_METADATA_NAMESPACE = "_exit_placeholder_metadata";
|
|
32178
33441
|
var PRIVACY_PLACEHOLDER_NAMESPACE = "_privacy_placeholder_vault";
|
|
33442
|
+
var ExitBundleImportError = class extends Error {
|
|
33443
|
+
code;
|
|
33444
|
+
constructor(code, message) {
|
|
33445
|
+
super(message);
|
|
33446
|
+
this.name = "ExitBundleImportError";
|
|
33447
|
+
this.code = code;
|
|
33448
|
+
}
|
|
33449
|
+
};
|
|
32179
33450
|
function sha256Hex3(bytes) {
|
|
32180
33451
|
return Array.from(hash(bytes)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
32181
33452
|
}
|
|
@@ -32468,7 +33739,7 @@ async function exportExitBundle(opts) {
|
|
|
32468
33739
|
),
|
|
32469
33740
|
artifacts_aggregate_hash_alg: "sha256",
|
|
32470
33741
|
export_approval_audit_id: exportApprovalAuditId,
|
|
32471
|
-
signature_scheme:
|
|
33742
|
+
signature_scheme: SIGNATURE_SCHEME_V12
|
|
32472
33743
|
};
|
|
32473
33744
|
const signature = sign(
|
|
32474
33745
|
canonicalizeToBytes(body),
|
|
@@ -32653,7 +33924,9 @@ async function stageArtifact(storage, namespace, key, value) {
|
|
|
32653
33924
|
await storage.write(namespace, key, jsonBytes(value));
|
|
32654
33925
|
}
|
|
32655
33926
|
async function importExitBundle(opts) {
|
|
32656
|
-
const verification = await verifyExitBundle(opts.bundleDir
|
|
33927
|
+
const verification = await verifyExitBundle(opts.bundleDir, {
|
|
33928
|
+
acceptUnverifiableAttestations: opts.acceptUnverifiableAttestations
|
|
33929
|
+
});
|
|
32657
33930
|
if (!verification.passed) {
|
|
32658
33931
|
return {
|
|
32659
33932
|
verified: false,
|
|
@@ -32749,6 +34022,23 @@ async function importExitBundle(opts) {
|
|
|
32749
34022
|
unsupported_artifacts: verification.unsupported_artifacts
|
|
32750
34023
|
};
|
|
32751
34024
|
}
|
|
34025
|
+
if (conflicts.public_identity_exists && !opts.forceRebind) {
|
|
34026
|
+
throw new ExitBundleImportError(
|
|
34027
|
+
"IDENTITY_OVERWRITE_REFUSED",
|
|
34028
|
+
"Importing this bundle would overwrite an existing fortress public identity. Pass forceRebind: true (CLI: --force-rebind) to confirm explicit replacement."
|
|
34029
|
+
);
|
|
34030
|
+
}
|
|
34031
|
+
if (conflicts.public_identity_exists && opts.forceRebind && identityArtifact) {
|
|
34032
|
+
opts.auditLog.append(
|
|
34033
|
+
"l1",
|
|
34034
|
+
"exit_bundle_force_rebind",
|
|
34035
|
+
identityArtifact.json.bundle.identity_id,
|
|
34036
|
+
{
|
|
34037
|
+
manifest_version: manifest.body.manifest_version,
|
|
34038
|
+
fortress_id: manifest.body.identity_binding.fortress_id
|
|
34039
|
+
}
|
|
34040
|
+
);
|
|
34041
|
+
}
|
|
32752
34042
|
const importId = importIdForManifest(manifest);
|
|
32753
34043
|
const stagedArtifacts = [];
|
|
32754
34044
|
if (identityArtifact) {
|
|
@@ -32859,7 +34149,7 @@ function exitBundleManifestShape() {
|
|
|
32859
34149
|
manifest_version: EXIT_BUNDLE_MANIFEST_VERSION,
|
|
32860
34150
|
artifacts: [...EXIT_BUNDLE_ARTIFACT_KINDS],
|
|
32861
34151
|
hash_alg: "sha256",
|
|
32862
|
-
signature_scheme:
|
|
34152
|
+
signature_scheme: SIGNATURE_SCHEME_V12,
|
|
32863
34153
|
required_top_level_file: "manifest.json",
|
|
32864
34154
|
artifact_paths: [
|
|
32865
34155
|
"artifacts/public_identity.json",
|
|
@@ -32968,6 +34258,11 @@ Options:
|
|
|
32968
34258
|
--destination-identity-id <id> Destination signer for re-keyed state
|
|
32969
34259
|
--state-namespace <name> Export a namespace; repeatable
|
|
32970
34260
|
--conflict <skip|overwrite|version>
|
|
34261
|
+
--force-rebind On import: explicitly replace an existing fortress
|
|
34262
|
+
public identity (Tier 1 confirmation)
|
|
34263
|
+
--accept-unverifiable-attestations
|
|
34264
|
+
On import: accept reputation attestations whose
|
|
34265
|
+
signer DID is not in the bundle (Tier 1 confirmation)
|
|
32971
34266
|
--json
|
|
32972
34267
|
--yes, -y Explicit non-interactive Tier 1 approval
|
|
32973
34268
|
--help, -h
|
|
@@ -32996,7 +34291,22 @@ async function runExitCommand(args) {
|
|
|
32996
34291
|
write(err, "Usage: sanctuary exit verify <dir>\n");
|
|
32997
34292
|
return 2;
|
|
32998
34293
|
}
|
|
32999
|
-
|
|
34294
|
+
let result;
|
|
34295
|
+
try {
|
|
34296
|
+
result = await verifyExitBundle(dir, {
|
|
34297
|
+
acceptUnverifiableAttestations: hasFlag(
|
|
34298
|
+
argv,
|
|
34299
|
+
"--accept-unverifiable-attestations"
|
|
34300
|
+
)
|
|
34301
|
+
});
|
|
34302
|
+
} catch (e) {
|
|
34303
|
+
if (e instanceof InvalidExitBundleError) {
|
|
34304
|
+
write(err, `Error: ${e.message}
|
|
34305
|
+
`);
|
|
34306
|
+
return 1;
|
|
34307
|
+
}
|
|
34308
|
+
throw e;
|
|
34309
|
+
}
|
|
33000
34310
|
if (json) {
|
|
33001
34311
|
write(out, JSON.stringify(result, null, 2) + "\n");
|
|
33002
34312
|
} else {
|
|
@@ -33074,9 +34384,15 @@ async function runExitCommand(args) {
|
|
|
33074
34384
|
return 2;
|
|
33075
34385
|
}
|
|
33076
34386
|
const activate = hasFlag(argv, "--activate");
|
|
34387
|
+
const forceRebind = hasFlag(argv, "--force-rebind");
|
|
34388
|
+
const acceptUnverifiableAttestations = hasFlag(
|
|
34389
|
+
argv,
|
|
34390
|
+
"--accept-unverifiable-attestations"
|
|
34391
|
+
);
|
|
33077
34392
|
if (activate) {
|
|
34393
|
+
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?";
|
|
33078
34394
|
const approved = await confirmTier1(
|
|
33079
|
-
|
|
34395
|
+
prompt,
|
|
33080
34396
|
hasFlag(argv, "--yes") || hasFlag(argv, "-y"),
|
|
33081
34397
|
stdin,
|
|
33082
34398
|
err
|
|
@@ -33085,6 +34401,18 @@ async function runExitCommand(args) {
|
|
|
33085
34401
|
write(err, "Aborted.\n");
|
|
33086
34402
|
return 1;
|
|
33087
34403
|
}
|
|
34404
|
+
if (acceptUnverifiableAttestations) {
|
|
34405
|
+
const acceptApproved = await confirmTier1(
|
|
34406
|
+
"Tier 1 approval required: accept unverifiable reputation attestations on import?",
|
|
34407
|
+
hasFlag(argv, "--yes") || hasFlag(argv, "-y"),
|
|
34408
|
+
stdin,
|
|
34409
|
+
err
|
|
34410
|
+
);
|
|
34411
|
+
if (!acceptApproved) {
|
|
34412
|
+
write(err, "Aborted.\n");
|
|
34413
|
+
return 1;
|
|
34414
|
+
}
|
|
34415
|
+
}
|
|
33088
34416
|
}
|
|
33089
34417
|
const ctx = await openExitContext(argv, env);
|
|
33090
34418
|
const conflict = flagValue(argv, "--conflict") ?? "skip";
|
|
@@ -33092,19 +34420,31 @@ async function runExitCommand(args) {
|
|
|
33092
34420
|
write(err, "--conflict must be skip, overwrite, or version\n");
|
|
33093
34421
|
return 2;
|
|
33094
34422
|
}
|
|
33095
|
-
|
|
33096
|
-
|
|
33097
|
-
|
|
33098
|
-
|
|
33099
|
-
|
|
33100
|
-
|
|
33101
|
-
|
|
33102
|
-
|
|
33103
|
-
|
|
33104
|
-
|
|
33105
|
-
|
|
33106
|
-
|
|
33107
|
-
|
|
34423
|
+
let result;
|
|
34424
|
+
try {
|
|
34425
|
+
result = await importExitBundle({
|
|
34426
|
+
bundleDir: dir,
|
|
34427
|
+
storage: ctx.storage,
|
|
34428
|
+
masterKey: ctx.masterKey,
|
|
34429
|
+
identityManager: ctx.identityManager,
|
|
34430
|
+
auditLog: ctx.auditLog,
|
|
34431
|
+
reputationStore: ctx.reputationStore,
|
|
34432
|
+
activate,
|
|
34433
|
+
forceRebind,
|
|
34434
|
+
acceptUnverifiableAttestations,
|
|
34435
|
+
conflictResolution: conflict,
|
|
34436
|
+
sourcePassphrase: flagValue(argv, "--source-passphrase"),
|
|
34437
|
+
sourceRecoveryKey: flagValue(argv, "--source-recovery-key"),
|
|
34438
|
+
destinationSignerIdentityId: flagValue(argv, "--destination-identity-id")
|
|
34439
|
+
});
|
|
34440
|
+
} catch (e) {
|
|
34441
|
+
if (e instanceof InvalidExitBundleError) {
|
|
34442
|
+
write(err, `Error: ${e.message}
|
|
34443
|
+
`);
|
|
34444
|
+
return 1;
|
|
34445
|
+
}
|
|
34446
|
+
throw e;
|
|
34447
|
+
}
|
|
33108
34448
|
if (json) write(out, JSON.stringify(result, null, 2) + "\n");
|
|
33109
34449
|
else {
|
|
33110
34450
|
write(out, `verified: ${result.verified}
|
|
@@ -33334,7 +34674,7 @@ async function createSanctuaryServer(options) {
|
|
|
33334
34674
|
const hasKeyParams = existingNamespaces.some((e) => e.key === "key-params");
|
|
33335
34675
|
if (hasKeyParams) {
|
|
33336
34676
|
throw new Error(
|
|
33337
|
-
"Sanctuary:
|
|
34677
|
+
"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."
|
|
33338
34678
|
);
|
|
33339
34679
|
}
|
|
33340
34680
|
masterKey = generateRandomKey();
|
|
@@ -33927,6 +35267,7 @@ exports.CommitmentStore = CommitmentStore;
|
|
|
33927
35267
|
exports.ContextGateEnforcer = ContextGateEnforcer;
|
|
33928
35268
|
exports.ContextGatePolicyStore = ContextGatePolicyStore;
|
|
33929
35269
|
exports.DashboardApprovalChannel = DashboardApprovalChannel;
|
|
35270
|
+
exports.ExitBundleImportError = ExitBundleImportError;
|
|
33930
35271
|
exports.FederationRegistry = FederationRegistry;
|
|
33931
35272
|
exports.FilesystemStorage = FilesystemStorage;
|
|
33932
35273
|
exports.HERO_COPY = HERO_COPY;
|