@contextecf/guardian-cli 0.1.1 → 0.1.3
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.
|
@@ -17443,6 +17443,15 @@ async function statusGuardian(flags, options) {
|
|
|
17443
17443
|
const releaseEvidence = await buildGuardianReleaseEvidenceStatus(home);
|
|
17444
17444
|
const serviceSupervision = await buildGuardianServiceSupervisionStatus(home);
|
|
17445
17445
|
const osKeyStorage = await buildGuardianOsKeyStorageStatus(home);
|
|
17446
|
+
const privacy = {
|
|
17447
|
+
rawContentCaptureEnabled: profile?.privacy.raw_content_capture_enabled ?? false,
|
|
17448
|
+
cloudSyncEnabled: profile?.privacy.cloud_sync_enabled ?? false,
|
|
17449
|
+
localAppAutopilotEnabled: profile?.privacy.local_app_autopilot_enabled ?? false
|
|
17450
|
+
};
|
|
17451
|
+
const nextCommands = buildGuardianStatusNextCommands({
|
|
17452
|
+
installed: profile !== void 0,
|
|
17453
|
+
daemonStatus: daemonHealth.daemon.status
|
|
17454
|
+
});
|
|
17446
17455
|
const result = {
|
|
17447
17456
|
schema_version: "contextecf/project-guardian-status/v1",
|
|
17448
17457
|
cli_version: GUARDIAN_CLI_VERSION,
|
|
@@ -17456,11 +17465,7 @@ async function statusGuardian(flags, options) {
|
|
|
17456
17465
|
controlTowerUrl: daemonHealth.controlTowerUrl,
|
|
17457
17466
|
controlTowerUrlSource: profile?.control_tower?.source ?? "default_loopback",
|
|
17458
17467
|
daemon: daemonHealth.daemon,
|
|
17459
|
-
privacy
|
|
17460
|
-
rawContentCaptureEnabled: profile?.privacy.raw_content_capture_enabled ?? false,
|
|
17461
|
-
cloudSyncEnabled: profile?.privacy.cloud_sync_enabled ?? false,
|
|
17462
|
-
localAppAutopilotEnabled: profile?.privacy.local_app_autopilot_enabled ?? false
|
|
17463
|
-
},
|
|
17468
|
+
privacy,
|
|
17464
17469
|
preferences: {
|
|
17465
17470
|
promptCoachMode: profile?.prompt_coach_mode ?? "not_installed",
|
|
17466
17471
|
learningGraphEnabled: profile?.learning_graph.enabled ?? false
|
|
@@ -17479,7 +17484,8 @@ async function statusGuardian(flags, options) {
|
|
|
17479
17484
|
releaseEvidence,
|
|
17480
17485
|
serviceSupervision,
|
|
17481
17486
|
osKeyStorage,
|
|
17482
|
-
notProvenProductionGates: GUARDIAN_NOT_PROVEN_PRODUCTION_GATES
|
|
17487
|
+
notProvenProductionGates: GUARDIAN_NOT_PROVEN_PRODUCTION_GATES,
|
|
17488
|
+
nextCommands
|
|
17483
17489
|
};
|
|
17484
17490
|
if (flags.has("json")) {
|
|
17485
17491
|
return `${JSON.stringify(result, null, 2)}
|
|
@@ -17502,11 +17508,36 @@ async function statusGuardian(flags, options) {
|
|
|
17502
17508
|
`Service supervision: ${result.serviceSupervision.status}`,
|
|
17503
17509
|
`OS key storage: ${result.osKeyStorage.status}`,
|
|
17504
17510
|
`Not-proven production gates: ${result.notProvenProductionGates.length}`,
|
|
17505
|
-
|
|
17506
|
-
|
|
17511
|
+
formatGuardianPrivacyStatus(result.privacy),
|
|
17512
|
+
`Next: ${result.nextCommands.join("; ")}`
|
|
17507
17513
|
].filter(Boolean).join("\n")}
|
|
17508
17514
|
`;
|
|
17509
17515
|
}
|
|
17516
|
+
function buildGuardianStatusNextCommands(input2) {
|
|
17517
|
+
if (!input2.installed) {
|
|
17518
|
+
return ["guardian install"];
|
|
17519
|
+
}
|
|
17520
|
+
if (input2.daemonStatus === "reachable") {
|
|
17521
|
+
return ["guardian open", "guardian readiness", "guardian extension open-setup"];
|
|
17522
|
+
}
|
|
17523
|
+
if (input2.daemonStatus === "unauthorized") {
|
|
17524
|
+
return ["guardian stop", "guardian launch --port=4322", "guardian status"];
|
|
17525
|
+
}
|
|
17526
|
+
if (input2.daemonStatus === "token_missing" || input2.daemonStatus === "invalid_control_tower_url") {
|
|
17527
|
+
return ["guardian setup", "guardian launch", "guardian status"];
|
|
17528
|
+
}
|
|
17529
|
+
return ["guardian launch", "guardian status"];
|
|
17530
|
+
}
|
|
17531
|
+
function formatGuardianPrivacyStatus(input2) {
|
|
17532
|
+
return [
|
|
17533
|
+
`Privacy: raw capture ${formatOnOff(input2.rawContentCaptureEnabled)}`,
|
|
17534
|
+
`cloud sync ${formatOnOff(input2.cloudSyncEnabled)}`,
|
|
17535
|
+
`local app autopilot ${formatOnOff(input2.localAppAutopilotEnabled)}`
|
|
17536
|
+
].join(", ");
|
|
17537
|
+
}
|
|
17538
|
+
function formatOnOff(enabled) {
|
|
17539
|
+
return enabled ? "on" : "off";
|
|
17540
|
+
}
|
|
17510
17541
|
async function runControlTowerOpenCommand(flags, options) {
|
|
17511
17542
|
const stdout = options.stdout ?? process.stdout;
|
|
17512
17543
|
const stderr = options.stderr ?? process.stderr;
|
|
@@ -17593,7 +17624,7 @@ async function runStartCommand(flags, options) {
|
|
|
17593
17624
|
const home = resolveGuardianHome(options.env, options.platform);
|
|
17594
17625
|
const profile = await readProfile(home);
|
|
17595
17626
|
if (!profile) {
|
|
17596
|
-
write(stderr, "Guardian is not installed. Run guardian
|
|
17627
|
+
write(stderr, "Guardian is not installed. Run guardian setup first.\n");
|
|
17597
17628
|
return 1;
|
|
17598
17629
|
}
|
|
17599
17630
|
const host = stringFlag(flags, "host") ?? "127.0.0.1";
|
|
@@ -17601,10 +17632,8 @@ async function runStartCommand(flags, options) {
|
|
|
17601
17632
|
write(stderr, "Guardian start only accepts loopback hosts: 127.0.0.1, localhost, or ::1.\n");
|
|
17602
17633
|
return 1;
|
|
17603
17634
|
}
|
|
17604
|
-
const
|
|
17605
|
-
const
|
|
17606
|
-
const requestedControlTowerUrl = `${requestedDaemonUrl}/control-tower`;
|
|
17607
|
-
const controlTowerSource = host === "127.0.0.1" && port === 4317 ? "default_loopback" : "env_or_flag";
|
|
17635
|
+
const explicitPort = numberFlag(flags, "port");
|
|
17636
|
+
const port = explicitPort ?? 4317;
|
|
17608
17637
|
let token;
|
|
17609
17638
|
try {
|
|
17610
17639
|
token = await readRuntimeToken(home);
|
|
@@ -17616,14 +17645,66 @@ async function runStartCommand(flags, options) {
|
|
|
17616
17645
|
);
|
|
17617
17646
|
return 1;
|
|
17618
17647
|
}
|
|
17619
|
-
const
|
|
17620
|
-
|
|
17621
|
-
|
|
17648
|
+
const healthChecker = options.healthDaemon ?? requestGuardianDaemonHealth;
|
|
17649
|
+
const candidates = explicitPort ? [explicitPort] : Array.from({ length: 8 }, (_, index) => port + index);
|
|
17650
|
+
let selected;
|
|
17651
|
+
let firstTokenMismatch;
|
|
17652
|
+
for (const candidatePort of candidates) {
|
|
17653
|
+
const requestedDaemonUrl2 = formatDaemonBaseUrl(host, candidatePort);
|
|
17654
|
+
const requestedControlTowerUrl2 = `${requestedDaemonUrl2}/control-tower`;
|
|
17655
|
+
const controlTowerSource2 = host === "127.0.0.1" && candidatePort === 4317 ? "default_loopback" : "env_or_flag";
|
|
17656
|
+
const healthUrl2 = resolveDaemonHealthUrl(requestedControlTowerUrl2);
|
|
17657
|
+
if (!healthUrl2.ok) {
|
|
17658
|
+
write(stderr, `${healthUrl2.error}
|
|
17622
17659
|
`);
|
|
17660
|
+
return 1;
|
|
17661
|
+
}
|
|
17662
|
+
const existing2 = await healthChecker({ url: healthUrl2.url, token });
|
|
17663
|
+
if (existing2.ok && existing2.reachable) {
|
|
17664
|
+
selected = {
|
|
17665
|
+
port: candidatePort,
|
|
17666
|
+
requestedDaemonUrl: requestedDaemonUrl2,
|
|
17667
|
+
requestedControlTowerUrl: requestedControlTowerUrl2,
|
|
17668
|
+
healthUrl: healthUrl2.url,
|
|
17669
|
+
controlTowerSource: controlTowerSource2,
|
|
17670
|
+
existing: existing2
|
|
17671
|
+
};
|
|
17672
|
+
break;
|
|
17673
|
+
}
|
|
17674
|
+
if (existing2.statusCode === 401) {
|
|
17675
|
+
firstTokenMismatch ??= {
|
|
17676
|
+
requestedDaemonUrl: requestedDaemonUrl2,
|
|
17677
|
+
port: candidatePort,
|
|
17678
|
+
healthUrl: healthUrl2.url,
|
|
17679
|
+
existing: existing2
|
|
17680
|
+
};
|
|
17681
|
+
if (explicitPort) {
|
|
17682
|
+
write(stderr, renderDaemonTokenMismatchMessage(requestedDaemonUrl2, candidatePort));
|
|
17683
|
+
return 1;
|
|
17684
|
+
}
|
|
17685
|
+
continue;
|
|
17686
|
+
}
|
|
17687
|
+
selected = {
|
|
17688
|
+
port: candidatePort,
|
|
17689
|
+
requestedDaemonUrl: requestedDaemonUrl2,
|
|
17690
|
+
requestedControlTowerUrl: requestedControlTowerUrl2,
|
|
17691
|
+
healthUrl: healthUrl2.url,
|
|
17692
|
+
controlTowerSource: controlTowerSource2,
|
|
17693
|
+
existing: existing2
|
|
17694
|
+
};
|
|
17695
|
+
break;
|
|
17696
|
+
}
|
|
17697
|
+
if (!selected) {
|
|
17698
|
+
const mismatch = firstTokenMismatch ?? {
|
|
17699
|
+
requestedDaemonUrl: formatDaemonBaseUrl(host, port),
|
|
17700
|
+
port,
|
|
17701
|
+
healthUrl: `${formatDaemonBaseUrl(host, port)}/v1/guardian/admin/health`,
|
|
17702
|
+
existing: { ok: false, reachable: false, statusCode: 401, error: "unauthorized" }
|
|
17703
|
+
};
|
|
17704
|
+
write(stderr, renderDaemonTokenMismatchMessage(mismatch.requestedDaemonUrl, mismatch.port));
|
|
17623
17705
|
return 1;
|
|
17624
17706
|
}
|
|
17625
|
-
const
|
|
17626
|
-
const existing = await healthChecker({ url: healthUrl.url, token });
|
|
17707
|
+
const { requestedDaemonUrl, requestedControlTowerUrl, healthUrl, controlTowerSource, existing } = selected;
|
|
17627
17708
|
if (existing.ok && existing.reachable) {
|
|
17628
17709
|
profile.runtime.daemon_status = "start_requested";
|
|
17629
17710
|
profile.control_tower = {
|
|
@@ -17637,7 +17718,7 @@ async function runStartCommand(flags, options) {
|
|
|
17637
17718
|
daemonStatus: profile.runtime.daemon_status,
|
|
17638
17719
|
daemonUrl: requestedDaemonUrl,
|
|
17639
17720
|
controlTowerUrl: requestedControlTowerUrl,
|
|
17640
|
-
healthUrl
|
|
17721
|
+
healthUrl,
|
|
17641
17722
|
healthStatus: "reachable",
|
|
17642
17723
|
nextCommands: ["guardian open", "guardian status", "guardian daemon pair --json"]
|
|
17643
17724
|
};
|
|
@@ -17654,20 +17735,12 @@ async function runStartCommand(flags, options) {
|
|
|
17654
17735
|
);
|
|
17655
17736
|
return 0;
|
|
17656
17737
|
}
|
|
17657
|
-
if (existing.statusCode === 401) {
|
|
17658
|
-
write(
|
|
17659
|
-
stderr,
|
|
17660
|
-
`Guardian daemon at ${requestedDaemonUrl} rejected the local runtime token. Run guardian status or guardian stop before starting another daemon.
|
|
17661
|
-
`
|
|
17662
|
-
);
|
|
17663
|
-
return 1;
|
|
17664
|
-
}
|
|
17665
17738
|
if (!options.startDetachedDaemon) {
|
|
17666
17739
|
write(stderr, "Guardian detached daemon launcher is unavailable in this runtime.\n");
|
|
17667
17740
|
return 1;
|
|
17668
17741
|
}
|
|
17669
|
-
const started = await options.startDetachedDaemon({ home, host, port });
|
|
17670
|
-
const daemonUrl = normalizeDaemonBaseUrl(started.url, host, port);
|
|
17742
|
+
const started = await options.startDetachedDaemon({ home, host, port: selected.port });
|
|
17743
|
+
const daemonUrl = normalizeDaemonBaseUrl(started.url, host, selected.port);
|
|
17671
17744
|
const controlTowerUrl = `${daemonUrl}/control-tower`;
|
|
17672
17745
|
profile.runtime.daemon_status = "start_requested";
|
|
17673
17746
|
profile.control_tower = {
|
|
@@ -17682,7 +17755,7 @@ async function runStartCommand(flags, options) {
|
|
|
17682
17755
|
daemonUrl,
|
|
17683
17756
|
controlTowerUrl,
|
|
17684
17757
|
pid: started.pid,
|
|
17685
|
-
healthUrl
|
|
17758
|
+
healthUrl,
|
|
17686
17759
|
healthStatus: existing.error ? "unreachable" : "not_reachable",
|
|
17687
17760
|
nextCommands: ["guardian open", "guardian status", "guardian daemon pair --json"]
|
|
17688
17761
|
};
|
|
@@ -17700,6 +17773,15 @@ async function runStartCommand(flags, options) {
|
|
|
17700
17773
|
);
|
|
17701
17774
|
return 0;
|
|
17702
17775
|
}
|
|
17776
|
+
function renderDaemonTokenMismatchMessage(requestedDaemonUrl, port) {
|
|
17777
|
+
return [
|
|
17778
|
+
`Guardian found another daemon or container at ${requestedDaemonUrl}, but it rejected this profile's local runtime token.`,
|
|
17779
|
+
"This usually means an older Guardian daemon or Docker demo is still using the port.",
|
|
17780
|
+
`Recovery: stop the other process/container, or run Guardian on a different port with guardian launch --port=${port + 1}.`,
|
|
17781
|
+
`Diagnostics: guardian status; docker ps --filter publish=${port}`,
|
|
17782
|
+
""
|
|
17783
|
+
].join("\n");
|
|
17784
|
+
}
|
|
17703
17785
|
async function runStopCommand(flags, options) {
|
|
17704
17786
|
const stdout = options.stdout ?? process.stdout;
|
|
17705
17787
|
const stderr = options.stderr ?? process.stderr;
|
|
@@ -22235,7 +22317,7 @@ var init_runtime = __esm({
|
|
|
22235
22317
|
"use strict";
|
|
22236
22318
|
init_src();
|
|
22237
22319
|
init_src2();
|
|
22238
|
-
GUARDIAN_CLI_VERSION = "0.1.
|
|
22320
|
+
GUARDIAN_CLI_VERSION = "0.1.3";
|
|
22239
22321
|
GUARDIAN_PROFILE_SCHEMA_VERSION = "contextecf/project-guardian-profile/v1";
|
|
22240
22322
|
GUARDIAN_NPM_PACKAGE_NAME = "@contextecf/guardian-cli";
|
|
22241
22323
|
GUARDIAN_GLOBAL_INSTALL_COMMAND = `npm install -g ${GUARDIAN_NPM_PACKAGE_NAME}`;
|
|
@@ -22998,6 +23080,14 @@ function detectRisk(promptManifest, policyPacks) {
|
|
|
22998
23080
|
risks.add("pii");
|
|
22999
23081
|
matchedPolicyRefs.push("guardian:builtin:pii-detection");
|
|
23000
23082
|
}
|
|
23083
|
+
if (containsFullSsn(prompt)) {
|
|
23084
|
+
risks.add("pii");
|
|
23085
|
+
matchedPolicyRefs.push("guardian:builtin:ssn-detection");
|
|
23086
|
+
matchedPolicyRefs.push("guardian:builtin:ssn-full-value");
|
|
23087
|
+
} else if (SSN_CONTEXT_PATTERN.test(prompt)) {
|
|
23088
|
+
risks.add("pii");
|
|
23089
|
+
matchedPolicyRefs.push("guardian:builtin:ssn-detection");
|
|
23090
|
+
}
|
|
23001
23091
|
if (FINANCIAL_PATTERN.test(prompt)) {
|
|
23002
23092
|
risks.add("financial_data");
|
|
23003
23093
|
matchedPolicyRefs.push("guardian:builtin:financial-data");
|
|
@@ -23029,7 +23119,7 @@ function detectRisk(promptManifest, policyPacks) {
|
|
|
23029
23119
|
}
|
|
23030
23120
|
}
|
|
23031
23121
|
const detectedRisks = Array.from(risks);
|
|
23032
|
-
const riskTier = tierForRisks(detectedRisks);
|
|
23122
|
+
const riskTier = hasFullSsnRisk(matchedPolicyRefs) ? "high" : tierForRisks(detectedRisks);
|
|
23033
23123
|
const recommendedDisposition = recommendedDispositionForTier(
|
|
23034
23124
|
riskTier,
|
|
23035
23125
|
detectedRisks,
|
|
@@ -23056,6 +23146,9 @@ function decideDisposition(session, promptManifest, policyPacks, riskReport) {
|
|
|
23056
23146
|
if (riskReport.detected_risks.includes("secret") || riskReport.detected_risks.includes("prompt_injection")) {
|
|
23057
23147
|
return "block";
|
|
23058
23148
|
}
|
|
23149
|
+
if (hasFullSsnRisk(riskReport.matched_policy_refs)) {
|
|
23150
|
+
return promptCoachEnabled(policyPacks) ? "rewrite" : "block";
|
|
23151
|
+
}
|
|
23059
23152
|
const thresholds = effectiveRiskThresholds(policyPacks);
|
|
23060
23153
|
if (riskTierRank(riskReport.risk_tier) >= riskTierRank(thresholds.block_at)) {
|
|
23061
23154
|
return "block";
|
|
@@ -23115,6 +23208,7 @@ function buildFinalPrompt(prompt, disposition, riskReport) {
|
|
|
23115
23208
|
);
|
|
23116
23209
|
}
|
|
23117
23210
|
if (riskReport.detected_risks.includes("pii")) {
|
|
23211
|
+
rewritten = rewritten.replace(SSN_PATTERN, "XXX-XX-$3");
|
|
23118
23212
|
rewritten = rewritten.replace(EMAIL_PATTERN, "[REDACTED_EMAIL]");
|
|
23119
23213
|
}
|
|
23120
23214
|
return rewritten;
|
|
@@ -23128,11 +23222,12 @@ function buildCoachCard(disposition, riskReport, policyPacks, learningSignals) {
|
|
|
23128
23222
|
if (!coachDecision.should_present_card || coachDecision.intervention_level === "none") {
|
|
23129
23223
|
return void 0;
|
|
23130
23224
|
}
|
|
23225
|
+
const fullSsnRisk = hasFullSsnRisk(riskReport.matched_policy_refs);
|
|
23131
23226
|
const card = {
|
|
23132
23227
|
card_id: `coach:${riskReport.risk_report_id}`,
|
|
23133
23228
|
intervention_level: coachDecision.intervention_level,
|
|
23134
|
-
title: disposition === "block" ? "Guardian blocked this prompt" : disposition === "confirm" ? "Guardian needs your confirmation" : coachDecision.intervention_level === "soft_tag_along" ? "Guardian found useful context" : "Guardian can improve this prompt",
|
|
23135
|
-
body: disposition === "confirm" ? "This prompt includes higher-risk content and needs confirmation before it leaves your machine." : coachDecision.intervention_level === "soft_tag_along" ? "This prompt may work better with a little more local context attached first." : "Guardian found context or privacy improvements that may make this prompt safer and more useful.",
|
|
23229
|
+
title: disposition === "block" ? "Guardian blocked this prompt" : disposition === "confirm" ? "Guardian needs your confirmation" : coachDecision.intervention_level === "soft_tag_along" ? "Guardian found useful context" : fullSsnRisk ? "Guardian found an identity number" : "Guardian can improve this prompt",
|
|
23230
|
+
body: disposition === "confirm" ? "This prompt includes higher-risk content and needs confirmation before it leaves your machine." : coachDecision.intervention_level === "soft_tag_along" ? "This prompt may work better with a little more local context attached first." : fullSsnRisk ? "Guardian can mask the Social Security number before anything leaves your machine." : "Guardian found context or privacy improvements that may make this prompt safer and more useful.",
|
|
23136
23231
|
recommended_choice_id: disposition === "confirm" ? "confirm-after-review" : coachDecision.intervention_level === "soft_tag_along" ? "add-context" : "use-guardian-version",
|
|
23137
23232
|
choices: disposition === "confirm" ? [
|
|
23138
23233
|
{
|
|
@@ -23162,6 +23257,20 @@ function buildCoachCard(disposition, riskReport, policyPacks, learningSignals) {
|
|
|
23162
23257
|
description: "Send without adding context.",
|
|
23163
23258
|
resulting_disposition: "allow"
|
|
23164
23259
|
}
|
|
23260
|
+
] : fullSsnRisk ? [
|
|
23261
|
+
{
|
|
23262
|
+
choice_id: "use-guardian-version",
|
|
23263
|
+
label: "Use masked prompt",
|
|
23264
|
+
description: "Replace the Social Security number with XXX-XX-1234 before sending.",
|
|
23265
|
+
recommended: true,
|
|
23266
|
+
resulting_disposition: "rewrite"
|
|
23267
|
+
},
|
|
23268
|
+
{
|
|
23269
|
+
choice_id: "cancel",
|
|
23270
|
+
label: "Cancel",
|
|
23271
|
+
description: "Do not send this prompt.",
|
|
23272
|
+
resulting_disposition: "block"
|
|
23273
|
+
}
|
|
23165
23274
|
] : [
|
|
23166
23275
|
{
|
|
23167
23276
|
choice_id: "use-guardian-version",
|
|
@@ -23261,6 +23370,13 @@ function promptCoachEnabled(policyPacks) {
|
|
|
23261
23370
|
function isDestructiveTool(toolName) {
|
|
23262
23371
|
return /\b(delete|drop|send|publish|pay|write|exec|shell|migration)\b/i.test(toolName);
|
|
23263
23372
|
}
|
|
23373
|
+
function containsFullSsn(prompt) {
|
|
23374
|
+
SSN_PATTERN.lastIndex = 0;
|
|
23375
|
+
return SSN_PATTERN.test(prompt);
|
|
23376
|
+
}
|
|
23377
|
+
function hasFullSsnRisk(policyRefs) {
|
|
23378
|
+
return policyRefs.includes("guardian:builtin:ssn-full-value");
|
|
23379
|
+
}
|
|
23264
23380
|
function createPolicyPackPreset(overrides) {
|
|
23265
23381
|
return {
|
|
23266
23382
|
version: POLICY_PACK_PRESET_VERSION,
|
|
@@ -23356,7 +23472,7 @@ function digest2(value) {
|
|
|
23356
23472
|
function isPresent(value) {
|
|
23357
23473
|
return value !== void 0;
|
|
23358
23474
|
}
|
|
23359
|
-
var DEFAULT_RISK_THRESHOLDS, POLICY_PACK_PRESET_VERSION, HIGH_RISK_TERMS, SECRET_PATTERNS, EMAIL_PATTERN, FINANCIAL_PATTERN, HEALTHCARE_PATTERN, WORK_PATTERN, PROMPT_INJECTION_PATTERN, GUARDIAN_POLICY_PACK_PRESETS;
|
|
23475
|
+
var DEFAULT_RISK_THRESHOLDS, POLICY_PACK_PRESET_VERSION, HIGH_RISK_TERMS, SECRET_PATTERNS, EMAIL_PATTERN, SSN_PATTERN, SSN_CONTEXT_PATTERN, FINANCIAL_PATTERN, HEALTHCARE_PATTERN, WORK_PATTERN, PROMPT_INJECTION_PATTERN, GUARDIAN_POLICY_PACK_PRESETS;
|
|
23360
23476
|
var init_src3 = __esm({
|
|
23361
23477
|
"packages/personal-fabric-runtime/src/index.ts"() {
|
|
23362
23478
|
"use strict";
|
|
@@ -23377,6 +23493,8 @@ var init_src3 = __esm({
|
|
|
23377
23493
|
/\b(password|secret|token)\s*[:=]\s*\S+/i
|
|
23378
23494
|
];
|
|
23379
23495
|
EMAIL_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i;
|
|
23496
|
+
SSN_PATTERN = /\b(?!000|666|9\d\d)(\d{3})[-\s]?(?!00)(\d{2})[-\s]?(?!0000)(\d{4})\b/g;
|
|
23497
|
+
SSN_CONTEXT_PATTERN = /\b(ssn|social security(?: number)?)\b/i;
|
|
23380
23498
|
FINANCIAL_PATTERN = /\b(bank|wire|routing number|account number|payroll|invoice|revenue|forecast|contract value|credit card)\b/i;
|
|
23381
23499
|
HEALTHCARE_PATTERN = /\b(patient|diagnosis|medication|hipaa|medical record|prescription|clinical)\b/i;
|
|
23382
23500
|
WORK_PATTERN = /\b(contract|client|customer|salesforce|pipeline|renewal|board|employee|confidential)\b/i;
|
|
@@ -24810,6 +24928,7 @@ __export(daemon_exports, {
|
|
|
24810
24928
|
buildControlTowerLocalDataStatus: () => buildControlTowerLocalDataStatus,
|
|
24811
24929
|
buildControlTowerReleaseDoctorStatus: () => buildControlTowerReleaseDoctorStatus,
|
|
24812
24930
|
processGuardianBrowserBridgeRequest: () => processGuardianBrowserBridgeRequest,
|
|
24931
|
+
processGuardianControlTowerActivityRequest: () => processGuardianControlTowerActivityRequest,
|
|
24813
24932
|
processGuardianControlTowerAppPermissionRequest: () => processGuardianControlTowerAppPermissionRequest,
|
|
24814
24933
|
processGuardianControlTowerDataRequest: () => processGuardianControlTowerDataRequest,
|
|
24815
24934
|
processGuardianControlTowerPolicyRequest: () => processGuardianControlTowerPolicyRequest,
|
|
@@ -24973,6 +25092,14 @@ async function handleRequest(input2) {
|
|
|
24973
25092
|
);
|
|
24974
25093
|
return;
|
|
24975
25094
|
}
|
|
25095
|
+
const activityUrl = parseGuardianUrl(input2.request.url);
|
|
25096
|
+
if (input2.request.method === "GET" && activityUrl?.pathname === "/v1/guardian/activity") {
|
|
25097
|
+
const filter = parseActivityFilter(activityUrl.searchParams.get("filter") ?? "all");
|
|
25098
|
+
const receipts = (await input2.store.listReceipts()).slice().sort((left, right) => right.created_at.localeCompare(left.created_at)).slice(0, CONTROL_TOWER_RECEIPT_LIMIT);
|
|
25099
|
+
const result2 = processGuardianControlTowerActivityRequest(receipts, filter);
|
|
25100
|
+
writeJson(input2.response, result2.statusCode, result2.body);
|
|
25101
|
+
return;
|
|
25102
|
+
}
|
|
24976
25103
|
if (input2.request.method === "GET" && input2.request.url === "/v1/guardian/admin/health") {
|
|
24977
25104
|
const result2 = processGuardianDaemonHealthRequest({
|
|
24978
25105
|
token: input2.token,
|
|
@@ -25129,6 +25256,20 @@ function processGuardianDaemonHealthRequest(input2) {
|
|
|
25129
25256
|
}
|
|
25130
25257
|
};
|
|
25131
25258
|
}
|
|
25259
|
+
function processGuardianControlTowerActivityRequest(receipts, filterValue) {
|
|
25260
|
+
const filter = parseActivityFilter(filterValue);
|
|
25261
|
+
return {
|
|
25262
|
+
statusCode: 200,
|
|
25263
|
+
body: {
|
|
25264
|
+
ok: true,
|
|
25265
|
+
schema_version: "contextecf/project-guardian-control-tower-activity/v1",
|
|
25266
|
+
filter,
|
|
25267
|
+
raw_content_included: false,
|
|
25268
|
+
token_printed: false,
|
|
25269
|
+
activities: buildActivityItems(receipts, filter)
|
|
25270
|
+
}
|
|
25271
|
+
};
|
|
25272
|
+
}
|
|
25132
25273
|
function processGuardianDaemonStopRequest(input2) {
|
|
25133
25274
|
if (!isAuthorized(input2.token, input2.suppliedToken)) {
|
|
25134
25275
|
return {
|
|
@@ -25919,12 +26060,31 @@ function closeGuardianDaemonServer(server) {
|
|
|
25919
26060
|
} catch {
|
|
25920
26061
|
}
|
|
25921
26062
|
}
|
|
26063
|
+
function parseGuardianUrl(url2) {
|
|
26064
|
+
if (!url2) {
|
|
26065
|
+
return void 0;
|
|
26066
|
+
}
|
|
26067
|
+
try {
|
|
26068
|
+
return new URL(url2, "http://127.0.0.1");
|
|
26069
|
+
} catch {
|
|
26070
|
+
return void 0;
|
|
26071
|
+
}
|
|
26072
|
+
}
|
|
25922
26073
|
function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { nodes: [], edges: [] }, learningEvents = [], localDataStatus = createEmptyControlTowerLocalDataStatus(profile), releaseDoctorStatus = createEmptyControlTowerReleaseDoctorStatus(
|
|
25923
26074
|
profile
|
|
25924
26075
|
)) {
|
|
25925
26076
|
const policyPacks = profile.policy_packs.available.length;
|
|
25926
26077
|
const activePolicyPacks = profile.policy_packs.active.join(", ") || "none";
|
|
25927
26078
|
const recentReceipts = receipts.slice().sort((left, right) => right.created_at.localeCompare(left.created_at)).slice(0, CONTROL_TOWER_RECEIPT_LIMIT);
|
|
26079
|
+
const blockedReceipts = recentReceipts.filter(
|
|
26080
|
+
(receipt) => receipt.disposition === "block"
|
|
26081
|
+
).length;
|
|
26082
|
+
const riskyReceipts = recentReceipts.filter(
|
|
26083
|
+
(receipt) => receipt.disposition === "warn" || receipt.disposition === "confirm"
|
|
26084
|
+
).length;
|
|
26085
|
+
const allowedReceipts = recentReceipts.filter(
|
|
26086
|
+
(receipt) => receipt.disposition === "allow"
|
|
26087
|
+
).length;
|
|
25928
26088
|
const receiptRows = renderReceiptRows(recentReceipts);
|
|
25929
26089
|
const graphNodes = searchGraph.nodes.slice(0, CONTROL_TOWER_GRAPH_NODE_LIMIT);
|
|
25930
26090
|
const graphEdges = searchGraph.edges.slice(0, CONTROL_TOWER_GRAPH_EDGE_LIMIT);
|
|
@@ -26004,6 +26164,92 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
|
|
|
26004
26164
|
).join("\n");
|
|
26005
26165
|
const privacyDefaults = profile.privacy.privacy_mode_enabled && !profile.privacy.raw_content_capture_enabled && !profile.privacy.cloud_sync_enabled && !profile.privacy.local_app_autopilot_enabled ? "on" : "needs review";
|
|
26006
26166
|
const promptCoachBudget = profile.learning_graph.prompt_coach_interruption_budget;
|
|
26167
|
+
const protectedState = releaseDoctorStatus.doctor.checkCounts.fail === 0 && privacyDefaults === "on";
|
|
26168
|
+
const protectedStateLabel = protectedState ? "You're Protected" : "Needs Review";
|
|
26169
|
+
const protectedStateDetail = protectedState ? "Guardian is running locally with metadata-only privacy defaults." : "Guardian is running, but one or more install checks need attention.";
|
|
26170
|
+
const protectionRingClass = protectedState ? "protection-ring is-protected" : "protection-ring";
|
|
26171
|
+
const heroMetrics = [
|
|
26172
|
+
{
|
|
26173
|
+
label: "Threats blocked",
|
|
26174
|
+
value: String(blockedReceipts),
|
|
26175
|
+
detail: "Recent metadata receipts",
|
|
26176
|
+
tone: "blocked"
|
|
26177
|
+
},
|
|
26178
|
+
{
|
|
26179
|
+
label: "Risky prompts flagged",
|
|
26180
|
+
value: String(riskyReceipts),
|
|
26181
|
+
detail: "Warn or confirmation decisions",
|
|
26182
|
+
tone: "flagged"
|
|
26183
|
+
},
|
|
26184
|
+
{
|
|
26185
|
+
label: "Safe actions approved",
|
|
26186
|
+
value: String(allowedReceipts),
|
|
26187
|
+
detail: "Allowed recent decisions",
|
|
26188
|
+
tone: "allowed"
|
|
26189
|
+
},
|
|
26190
|
+
{
|
|
26191
|
+
label: "AI tools watched",
|
|
26192
|
+
value: String(providerPermissionRows.length),
|
|
26193
|
+
detail: `${appPermissionRows.length} local app permission surface(s)`,
|
|
26194
|
+
tone: "watched"
|
|
26195
|
+
}
|
|
26196
|
+
];
|
|
26197
|
+
const heroMetricCards = heroMetrics.map(
|
|
26198
|
+
(metric) => ` <article class="metric-card metric-${escapeHtml(metric.tone)}">
|
|
26199
|
+
<div class="metric-value">${escapeHtml(metric.value)}</div>
|
|
26200
|
+
<div class="metric-label">${escapeHtml(metric.label)}</div>
|
|
26201
|
+
<div class="metric-detail">${escapeHtml(metric.detail)}</div>
|
|
26202
|
+
</article>`
|
|
26203
|
+
).join("\n");
|
|
26204
|
+
const activityFeedHtml = renderActivityFeed(recentReceipts);
|
|
26205
|
+
const postureCardsHtml = GUARDIAN_POSTURE_PROFILE_CATALOG.map((postureProfile) => {
|
|
26206
|
+
const selected = profile.posture_profile.selected === postureProfile.id;
|
|
26207
|
+
const icon = postureProfile.id === "calm" ? "☾" : postureProfile.id === "balanced" ? "⚖" : postureProfile.id === "guided" ? "🎓" : postureProfile.id === "high_assurance" ? "🔒" : "⚙";
|
|
26208
|
+
return ` <form class="posture-card${selected ? " is-active" : ""}" method="post" action="/v1/guardian/admin/preferences">
|
|
26209
|
+
<input type="hidden" name="postureProfileId" value="${escapeHtml(postureProfile.id)}">
|
|
26210
|
+
<button type="submit" class="posture-button" ${selected || profile.posture_profile.managed ? "disabled" : ""}>
|
|
26211
|
+
<span class="posture-icon">${icon}</span>
|
|
26212
|
+
<span class="posture-title">${escapeHtml(postureProfile.displayName)}</span>
|
|
26213
|
+
<span class="posture-summary">${escapeHtml(postureProfile.summary)}</span>
|
|
26214
|
+
${selected ? '<span class="posture-state">ACTIVE</span>' : '<span class="posture-state">Use this setting</span>'}
|
|
26215
|
+
</button>
|
|
26216
|
+
</form>`;
|
|
26217
|
+
}).join("\n");
|
|
26218
|
+
const privacySettingRowsHtml = [
|
|
26219
|
+
{
|
|
26220
|
+
label: "Privacy mode",
|
|
26221
|
+
detail: "Never stores your actual prompts",
|
|
26222
|
+
enabled: profile.privacy.privacy_mode_enabled,
|
|
26223
|
+
control: renderOutcomePrivacyModeControl(profile.privacy.privacy_mode_enabled)
|
|
26224
|
+
},
|
|
26225
|
+
{
|
|
26226
|
+
label: "Prompt coaching",
|
|
26227
|
+
detail: "Tips to improve your AI requests",
|
|
26228
|
+
enabled: profile.prompt_coach_mode !== "off",
|
|
26229
|
+
control: renderOutcomePromptCoachControl(profile.prompt_coach_mode)
|
|
26230
|
+
},
|
|
26231
|
+
{
|
|
26232
|
+
label: "Stays on this device",
|
|
26233
|
+
detail: "All data kept local, never synced",
|
|
26234
|
+
enabled: !profile.privacy.cloud_sync_enabled,
|
|
26235
|
+
control: '<span class="toggle is-on" aria-label="Stays on this device on"><span></span></span>'
|
|
26236
|
+
}
|
|
26237
|
+
].map(
|
|
26238
|
+
(setting) => ` <div class="setting-row">
|
|
26239
|
+
<div>
|
|
26240
|
+
<h3>${escapeHtml(setting.label)}</h3>
|
|
26241
|
+
<p>${escapeHtml(setting.detail)}</p>
|
|
26242
|
+
</div>
|
|
26243
|
+
${setting.control}
|
|
26244
|
+
</div>`
|
|
26245
|
+
).join("\n");
|
|
26246
|
+
const providerPillsHtml = providerPermissionRows.filter((row) => row.state !== "revoked").map(
|
|
26247
|
+
(row) => ` <form method="post" action="/v1/guardian/admin/provider-permission" class="provider-pill-form">
|
|
26248
|
+
<input type="hidden" name="action" value="revoke">
|
|
26249
|
+
<input type="hidden" name="providerId" value="${escapeHtml(row.id)}">
|
|
26250
|
+
<button type="submit" class="provider-pill" aria-label="Revoke ${escapeHtml(providerDisplayName(row.id))}"><span></span>${escapeHtml(providerDisplayName(row.id))}</button>
|
|
26251
|
+
</form>`
|
|
26252
|
+
).join("\n");
|
|
26007
26253
|
const rows = [
|
|
26008
26254
|
["Profile", profile.profile_dir],
|
|
26009
26255
|
["Daemon", profile.runtime.daemon_status],
|
|
@@ -26056,53 +26302,466 @@ function renderControlTowerStatusPage(profile, receipts = [], searchGraph = { no
|
|
|
26056
26302
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
26057
26303
|
<title>Project Guardian Control Tower</title>
|
|
26058
26304
|
<style>
|
|
26059
|
-
:root {
|
|
26060
|
-
|
|
26061
|
-
|
|
26062
|
-
|
|
26063
|
-
|
|
26064
|
-
|
|
26065
|
-
|
|
26066
|
-
|
|
26067
|
-
|
|
26068
|
-
|
|
26069
|
-
|
|
26305
|
+
:root {
|
|
26306
|
+
color-scheme: dark;
|
|
26307
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
26308
|
+
background: #070c16;
|
|
26309
|
+
color: #f6f8ff;
|
|
26310
|
+
}
|
|
26311
|
+
* { box-sizing: border-box; }
|
|
26312
|
+
body {
|
|
26313
|
+
min-height: 100vh;
|
|
26314
|
+
margin: 0;
|
|
26315
|
+
background:
|
|
26316
|
+
radial-gradient(circle at 18% 12%, rgba(46, 184, 122, 0.16), transparent 30%),
|
|
26317
|
+
radial-gradient(circle at 85% 18%, rgba(79, 126, 255, 0.12), transparent 32%),
|
|
26318
|
+
linear-gradient(180deg, #0a111f 0%, #070c16 52%, #050812 100%);
|
|
26319
|
+
color: #f6f8ff;
|
|
26320
|
+
}
|
|
26321
|
+
body::before {
|
|
26322
|
+
position: fixed;
|
|
26323
|
+
inset: 0;
|
|
26324
|
+
z-index: -1;
|
|
26325
|
+
content: "";
|
|
26326
|
+
background-image:
|
|
26327
|
+
linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
|
|
26328
|
+
linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
|
|
26329
|
+
background-size: 44px 44px;
|
|
26330
|
+
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.55), transparent 72%);
|
|
26331
|
+
}
|
|
26332
|
+
main { max-width: 1180px; margin: 0 auto; padding: 104px 20px 56px; }
|
|
26333
|
+
h1 { margin: 0 0 10px; font-size: clamp(34px, 5vw, 64px); line-height: 1; letter-spacing: 0; }
|
|
26334
|
+
h2 { margin: 0 0 10px; font-size: 22px; line-height: 1.2; letter-spacing: 0; }
|
|
26335
|
+
p { margin: 0 0 20px; color: #aab4c5; }
|
|
26336
|
+
section {
|
|
26337
|
+
margin-top: 24px;
|
|
26338
|
+
padding: 22px;
|
|
26339
|
+
border: 1px solid rgba(148, 163, 184, 0.18);
|
|
26340
|
+
border-radius: 8px;
|
|
26341
|
+
background: rgba(12, 20, 34, 0.82);
|
|
26342
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.22);
|
|
26343
|
+
}
|
|
26344
|
+
table { width: 100%; border-collapse: collapse; overflow: hidden; border: 1px solid rgba(148, 163, 184, 0.15); border-radius: 8px; background: rgba(6, 12, 24, 0.62); }
|
|
26345
|
+
th, td { padding: 12px 14px; border-bottom: 1px solid rgba(148, 163, 184, 0.13); text-align: left; vertical-align: top; }
|
|
26346
|
+
th { width: 210px; color: #d8e1f0; font-weight: 650; }
|
|
26347
|
+
td { color: #c8d2e3; overflow-wrap: anywhere; }
|
|
26348
|
+
code { padding: 2px 5px; border-radius: 6px; background: rgba(15, 23, 42, 0.9); color: #d8f8e7; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; }
|
|
26070
26349
|
form { margin: 0; }
|
|
26071
|
-
button { min-width: 96px; min-height: 34px; border: 1px solid
|
|
26072
|
-
button:hover { background:
|
|
26350
|
+
button { min-width: 96px; min-height: 34px; border: 1px solid rgba(69, 201, 126, 0.44); border-radius: 8px; background: rgba(24, 96, 62, 0.62); color: #ecfff4; font: inherit; font-weight: 650; cursor: pointer; }
|
|
26351
|
+
button:hover { background: rgba(39, 132, 82, 0.72); }
|
|
26073
26352
|
button[disabled] { cursor: default; opacity: 0.56; }
|
|
26353
|
+
input {
|
|
26354
|
+
min-height: 34px;
|
|
26355
|
+
max-width: 100%;
|
|
26356
|
+
border: 1px solid rgba(148, 163, 184, 0.24);
|
|
26357
|
+
border-radius: 8px;
|
|
26358
|
+
background: rgba(8, 13, 24, 0.92);
|
|
26359
|
+
color: #f6f8ff;
|
|
26360
|
+
padding: 7px 9px;
|
|
26361
|
+
}
|
|
26074
26362
|
details { max-width: 100%; }
|
|
26075
|
-
summary { cursor: pointer; font-weight: 650; }
|
|
26363
|
+
summary { cursor: pointer; font-weight: 650; color: #eff6ff; }
|
|
26076
26364
|
dl { display: grid; grid-template-columns: minmax(120px, 180px) 1fr; gap: 8px 14px; margin: 12px 0 0; }
|
|
26077
|
-
dt { color: #
|
|
26078
|
-
dd { margin: 0; overflow-wrap: anywhere; }
|
|
26365
|
+
dt { color: #8fa0b7; font-weight: 650; }
|
|
26366
|
+
dd { margin: 0; overflow-wrap: anywhere; color: #c8d2e3; }
|
|
26079
26367
|
tr:last-child th, tr:last-child td { border-bottom: 0; }
|
|
26080
|
-
.
|
|
26081
|
-
|
|
26082
|
-
|
|
26083
|
-
|
|
26084
|
-
|
|
26085
|
-
|
|
26086
|
-
|
|
26087
|
-
|
|
26088
|
-
|
|
26089
|
-
|
|
26090
|
-
|
|
26368
|
+
.topbar {
|
|
26369
|
+
position: fixed;
|
|
26370
|
+
top: 0;
|
|
26371
|
+
right: 0;
|
|
26372
|
+
left: 0;
|
|
26373
|
+
z-index: 10;
|
|
26374
|
+
display: flex;
|
|
26375
|
+
align-items: center;
|
|
26376
|
+
justify-content: space-between;
|
|
26377
|
+
min-height: 68px;
|
|
26378
|
+
padding: 0 24px;
|
|
26379
|
+
border-bottom: 1px solid rgba(148, 163, 184, 0.13);
|
|
26380
|
+
background: rgba(7, 12, 22, 0.78);
|
|
26381
|
+
backdrop-filter: blur(18px);
|
|
26382
|
+
}
|
|
26383
|
+
.brand, .topbar-actions, .status-pill, .activity-heading, .filter-chips {
|
|
26384
|
+
display: flex;
|
|
26385
|
+
align-items: center;
|
|
26386
|
+
}
|
|
26387
|
+
.brand { gap: 10px; color: #f8fbff; font-size: 18px; font-weight: 750; }
|
|
26388
|
+
.brand-mark {
|
|
26389
|
+
display: inline-grid;
|
|
26390
|
+
place-items: center;
|
|
26391
|
+
width: 34px;
|
|
26392
|
+
height: 34px;
|
|
26393
|
+
border-radius: 8px;
|
|
26394
|
+
background: linear-gradient(145deg, #22c55e, #128452);
|
|
26395
|
+
color: #02120b;
|
|
26396
|
+
font-weight: 900;
|
|
26397
|
+
box-shadow: 0 0 22px rgba(34, 197, 94, 0.34);
|
|
26398
|
+
}
|
|
26399
|
+
.topbar-actions { gap: 10px; }
|
|
26400
|
+
.status-pill {
|
|
26401
|
+
gap: 8px;
|
|
26402
|
+
min-height: 34px;
|
|
26403
|
+
padding: 0 12px;
|
|
26404
|
+
border: 1px solid rgba(34, 197, 94, 0.26);
|
|
26405
|
+
border-radius: 999px;
|
|
26406
|
+
background: rgba(10, 40, 28, 0.86);
|
|
26407
|
+
color: #c9f8d8;
|
|
26408
|
+
font-size: 14px;
|
|
26409
|
+
font-weight: 650;
|
|
26410
|
+
}
|
|
26411
|
+
.status-dot { width: 8px; height: 8px; border-radius: 999px; background: #22c55e; box-shadow: 0 0 14px rgba(34, 197, 94, 0.9); }
|
|
26412
|
+
.icon-button {
|
|
26413
|
+
display: inline-grid;
|
|
26414
|
+
place-items: center;
|
|
26415
|
+
width: 34px;
|
|
26416
|
+
height: 34px;
|
|
26417
|
+
border: 1px solid rgba(148, 163, 184, 0.2);
|
|
26418
|
+
border-radius: 8px;
|
|
26419
|
+
color: #d8e1f0;
|
|
26420
|
+
background: rgba(15, 23, 42, 0.78);
|
|
26421
|
+
font-size: 16px;
|
|
26422
|
+
}
|
|
26423
|
+
.eyebrow {
|
|
26424
|
+
width: fit-content;
|
|
26425
|
+
margin-bottom: 16px;
|
|
26426
|
+
padding: 6px 10px;
|
|
26427
|
+
border: 1px solid rgba(46, 184, 122, 0.35);
|
|
26428
|
+
border-radius: 999px;
|
|
26429
|
+
color: #9cf2bf;
|
|
26430
|
+
background: rgba(16, 82, 55, 0.45);
|
|
26431
|
+
font-size: 13px;
|
|
26432
|
+
font-weight: 700;
|
|
26433
|
+
}
|
|
26434
|
+
.intro { max-width: 720px; font-size: 17px; line-height: 1.55; }
|
|
26435
|
+
.hero-grid {
|
|
26436
|
+
display: grid;
|
|
26437
|
+
grid-template-columns: minmax(280px, 1.05fr) minmax(320px, 0.95fr);
|
|
26438
|
+
gap: 18px;
|
|
26439
|
+
margin-top: 28px;
|
|
26440
|
+
}
|
|
26441
|
+
.protection-card, .metric-card {
|
|
26442
|
+
border: 1px solid rgba(148, 163, 184, 0.17);
|
|
26443
|
+
border-radius: 8px;
|
|
26444
|
+
background: linear-gradient(180deg, rgba(13, 24, 40, 0.92), rgba(8, 14, 26, 0.94));
|
|
26445
|
+
box-shadow: 0 22px 80px rgba(0, 0, 0, 0.28);
|
|
26446
|
+
}
|
|
26447
|
+
.protection-card {
|
|
26448
|
+
display: grid;
|
|
26449
|
+
justify-items: center;
|
|
26450
|
+
align-content: center;
|
|
26451
|
+
min-height: 372px;
|
|
26452
|
+
padding: 38px 24px;
|
|
26453
|
+
text-align: center;
|
|
26454
|
+
}
|
|
26455
|
+
.protection-card h2 { margin-top: 22px; font-size: 30px; }
|
|
26456
|
+
.protection-card p { max-width: 420px; margin-bottom: 0; }
|
|
26457
|
+
.protection-ring {
|
|
26458
|
+
display: grid;
|
|
26459
|
+
place-items: center;
|
|
26460
|
+
width: min(56vw, 218px);
|
|
26461
|
+
aspect-ratio: 1;
|
|
26462
|
+
border: 1px solid rgba(245, 158, 11, 0.48);
|
|
26463
|
+
border-radius: 999px;
|
|
26464
|
+
background:
|
|
26465
|
+
radial-gradient(circle, rgba(245, 158, 11, 0.22) 0%, rgba(245, 158, 11, 0.06) 52%, transparent 70%),
|
|
26466
|
+
conic-gradient(from 20deg, rgba(245, 158, 11, 0.08), rgba(245, 158, 11, 0.8), rgba(245, 158, 11, 0.08));
|
|
26467
|
+
box-shadow: 0 0 64px rgba(245, 158, 11, 0.22);
|
|
26468
|
+
}
|
|
26469
|
+
.protection-ring.is-protected {
|
|
26470
|
+
border-color: rgba(34, 197, 94, 0.48);
|
|
26471
|
+
background:
|
|
26472
|
+
radial-gradient(circle, rgba(34, 197, 94, 0.22) 0%, rgba(34, 197, 94, 0.06) 52%, transparent 70%),
|
|
26473
|
+
conic-gradient(from 20deg, rgba(34, 197, 94, 0.08), rgba(34, 197, 94, 0.86), rgba(34, 197, 94, 0.08));
|
|
26474
|
+
box-shadow: 0 0 72px rgba(34, 197, 94, 0.32);
|
|
26475
|
+
}
|
|
26476
|
+
.ring-core {
|
|
26477
|
+
display: grid;
|
|
26478
|
+
place-items: center;
|
|
26479
|
+
width: 84px;
|
|
26480
|
+
aspect-ratio: 1;
|
|
26481
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
26482
|
+
border-radius: 999px;
|
|
26483
|
+
background: linear-gradient(145deg, #22c55e, #0f8b57);
|
|
26484
|
+
color: #03140b;
|
|
26485
|
+
font-size: 34px;
|
|
26486
|
+
font-weight: 950;
|
|
26487
|
+
}
|
|
26488
|
+
.metric-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
|
|
26489
|
+
.metric-card { min-height: 176px; padding: 22px; }
|
|
26490
|
+
.metric-value { margin-bottom: 8px; color: #e6fff0; font-size: 46px; line-height: 1; font-weight: 850; }
|
|
26491
|
+
.metric-label { color: #f5f7fb; font-size: 16px; font-weight: 760; }
|
|
26492
|
+
.metric-detail { margin-top: 8px; color: #91a1b8; font-size: 13px; line-height: 1.35; }
|
|
26493
|
+
.metric-blocked .metric-value { color: #ff8b8b; }
|
|
26494
|
+
.metric-flagged .metric-value { color: #ffd166; }
|
|
26495
|
+
.metric-allowed .metric-value { color: #70f0a3; }
|
|
26496
|
+
.metric-watched .metric-value { color: #8cc7ff; }
|
|
26497
|
+
.activity-list { display: grid; gap: 14px; }
|
|
26498
|
+
.activity-card {
|
|
26499
|
+
display: grid;
|
|
26500
|
+
grid-template-columns: 64px 1fr auto;
|
|
26501
|
+
gap: 18px;
|
|
26502
|
+
align-items: center;
|
|
26503
|
+
min-height: 96px;
|
|
26504
|
+
padding: 18px 22px;
|
|
26505
|
+
border: 1px solid rgba(148, 163, 184, 0.16);
|
|
26506
|
+
border-radius: 8px;
|
|
26507
|
+
background: rgba(10, 18, 32, 0.82);
|
|
26508
|
+
}
|
|
26509
|
+
.activity-card.block { border-color: rgba(244, 63, 94, 0.26); background: rgba(43, 12, 32, 0.7); }
|
|
26510
|
+
.activity-card.warn, .activity-card.confirm { border-color: rgba(245, 158, 11, 0.28); background: rgba(42, 34, 22, 0.62); }
|
|
26511
|
+
.activity-card.allow, .activity-card.rewrite { border-color: rgba(34, 197, 94, 0.24); background: rgba(7, 36, 32, 0.64); }
|
|
26512
|
+
.activity-icon {
|
|
26513
|
+
display: grid;
|
|
26514
|
+
place-items: center;
|
|
26515
|
+
width: 48px;
|
|
26516
|
+
height: 48px;
|
|
26517
|
+
border-radius: 8px;
|
|
26518
|
+
background: rgba(255, 255, 255, 0.06);
|
|
26519
|
+
font-size: 24px;
|
|
26520
|
+
}
|
|
26521
|
+
.activity-title { color: #f5f7fb; font-size: 17px; font-weight: 780; }
|
|
26522
|
+
.activity-meta { margin-top: 6px; color: #91a1b8; font-size: 14px; font-weight: 650; }
|
|
26523
|
+
.activity-badge {
|
|
26524
|
+
padding: 7px 12px;
|
|
26525
|
+
border: 1px solid rgba(148, 163, 184, 0.22);
|
|
26526
|
+
border-radius: 999px;
|
|
26527
|
+
color: #d8e1f0;
|
|
26528
|
+
font-size: 13px;
|
|
26529
|
+
font-weight: 850;
|
|
26530
|
+
}
|
|
26531
|
+
.activity-badge.block { border-color: rgba(244, 63, 94, 0.42); color: #ff5c7a; background: rgba(244, 63, 94, 0.12); }
|
|
26532
|
+
.activity-badge.warn, .activity-badge.confirm { border-color: rgba(245, 158, 11, 0.42); color: #ffb020; background: rgba(245, 158, 11, 0.12); }
|
|
26533
|
+
.activity-badge.allow, .activity-badge.rewrite { border-color: rgba(34, 197, 94, 0.36); color: #38e678; background: rgba(34, 197, 94, 0.11); }
|
|
26534
|
+
.watch-grid { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(300px, 0.65fr); gap: 22px; }
|
|
26535
|
+
.posture-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
|
|
26536
|
+
.posture-card, .settings-card {
|
|
26537
|
+
min-height: 178px;
|
|
26538
|
+
padding: 22px;
|
|
26539
|
+
border: 1px solid rgba(148, 163, 184, 0.18);
|
|
26540
|
+
border-radius: 8px;
|
|
26541
|
+
background: rgba(11, 24, 40, 0.86);
|
|
26542
|
+
}
|
|
26543
|
+
.posture-card.is-active { border-color: rgba(34, 197, 94, 0.48); background: rgba(6, 45, 37, 0.82); }
|
|
26544
|
+
.posture-button {
|
|
26545
|
+
display: grid;
|
|
26546
|
+
width: 100%;
|
|
26547
|
+
min-width: 0;
|
|
26548
|
+
min-height: 0;
|
|
26549
|
+
padding: 0;
|
|
26550
|
+
border: 0;
|
|
26551
|
+
border-radius: 0;
|
|
26552
|
+
background: transparent;
|
|
26553
|
+
color: inherit;
|
|
26554
|
+
text-align: left;
|
|
26555
|
+
cursor: pointer;
|
|
26556
|
+
}
|
|
26557
|
+
.posture-button:hover { background: transparent; }
|
|
26558
|
+
.posture-button[disabled] { opacity: 1; }
|
|
26559
|
+
.posture-icon { min-height: 32px; color: #e5eefb; font-size: 28px; }
|
|
26560
|
+
.posture-title, .settings-card h3 { margin: 10px 0 8px; color: #f6f8ff; font-size: 19px; font-weight: 820; }
|
|
26561
|
+
.posture-summary, .settings-card p { margin: 0; color: #aab4c5; line-height: 1.5; }
|
|
26562
|
+
.posture-state { margin-top: 14px; color: #38e678; font-size: 13px; font-weight: 900; }
|
|
26563
|
+
.settings-stack { display: grid; gap: 16px; }
|
|
26564
|
+
.setting-row { display: flex; align-items: center; justify-content: space-between; gap: 18px; }
|
|
26565
|
+
.setting-row h3 { margin: 0 0 4px; }
|
|
26566
|
+
.toggle-form { margin: 0; }
|
|
26567
|
+
.toggle {
|
|
26568
|
+
display: inline-flex;
|
|
26569
|
+
align-items: center;
|
|
26570
|
+
width: 56px;
|
|
26571
|
+
height: 32px;
|
|
26572
|
+
min-width: 56px;
|
|
26573
|
+
min-height: 32px;
|
|
26574
|
+
padding: 4px;
|
|
26575
|
+
border: 0;
|
|
26576
|
+
border-radius: 999px;
|
|
26577
|
+
background: rgba(71, 85, 105, 0.55);
|
|
26578
|
+
cursor: pointer;
|
|
26579
|
+
}
|
|
26580
|
+
.toggle:hover { background: rgba(71, 85, 105, 0.7); }
|
|
26581
|
+
.toggle span { width: 24px; height: 24px; border-radius: 999px; background: #d8e1f0; }
|
|
26582
|
+
.toggle.is-on { justify-content: flex-end; background: #22c55e; box-shadow: 0 0 24px rgba(34, 197, 94, 0.28); }
|
|
26583
|
+
.toggle.is-on:hover { background: #27d768; }
|
|
26584
|
+
.toggle[disabled] { cursor: default; opacity: 1; }
|
|
26585
|
+
.provider-list { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }
|
|
26586
|
+
.provider-pill-form { margin: 0; }
|
|
26587
|
+
.provider-pill {
|
|
26588
|
+
display: inline-flex;
|
|
26589
|
+
align-items: center;
|
|
26590
|
+
gap: 8px;
|
|
26591
|
+
min-width: 0;
|
|
26592
|
+
min-height: 0;
|
|
26593
|
+
padding: 8px 12px;
|
|
26594
|
+
border: 1px solid rgba(148, 163, 184, 0.22);
|
|
26595
|
+
border-radius: 999px;
|
|
26596
|
+
color: #d8e1f0;
|
|
26597
|
+
background: rgba(15, 23, 42, 0.52);
|
|
26598
|
+
font: inherit;
|
|
26599
|
+
font-weight: 750;
|
|
26600
|
+
cursor: pointer;
|
|
26601
|
+
}
|
|
26602
|
+
.provider-pill:hover { background: rgba(30, 41, 59, 0.78); }
|
|
26603
|
+
.provider-pill span { width: 8px; height: 8px; border-radius: 999px; background: #22c55e; }
|
|
26604
|
+
.activity-heading { justify-content: space-between; gap: 16px; margin-bottom: 12px; }
|
|
26605
|
+
.activity-heading h2 { margin: 0; }
|
|
26606
|
+
.filter-chips { gap: 8px; flex-wrap: wrap; }
|
|
26607
|
+
.activity-filter-status { margin: 0 0 12px; color: #91a1b8; font-size: 14px; font-weight: 650; }
|
|
26608
|
+
.chip {
|
|
26609
|
+
min-width: auto;
|
|
26610
|
+
min-height: 36px;
|
|
26611
|
+
padding: 6px 10px;
|
|
26612
|
+
border: 1px solid rgba(148, 163, 184, 0.19);
|
|
26613
|
+
border-radius: 999px;
|
|
26614
|
+
color: #aab4c5;
|
|
26615
|
+
background: rgba(15, 23, 42, 0.64);
|
|
26616
|
+
font: inherit;
|
|
26617
|
+
font-size: 13px;
|
|
26618
|
+
font-weight: 700;
|
|
26619
|
+
cursor: pointer;
|
|
26620
|
+
}
|
|
26621
|
+
.chip:hover { color: #f5f7fb; background: rgba(30, 41, 59, 0.78); }
|
|
26622
|
+
.chip.is-active, .chip[aria-pressed="true"] { border-color: rgba(34, 197, 94, 0.38); color: #d7ffe5; background: rgba(21, 128, 61, 0.22); }
|
|
26623
|
+
.activity-card[hidden], .empty-filter-state[hidden] { display: none; }
|
|
26624
|
+
.empty-filter-state {
|
|
26625
|
+
padding: 22px;
|
|
26626
|
+
border: 1px dashed rgba(148, 163, 184, 0.24);
|
|
26627
|
+
border-radius: 8px;
|
|
26628
|
+
color: #aab4c5;
|
|
26629
|
+
background: rgba(15, 23, 42, 0.42);
|
|
26630
|
+
}
|
|
26631
|
+
.detail-panel {
|
|
26632
|
+
margin-top: 24px;
|
|
26633
|
+
border: 1px solid rgba(148, 163, 184, 0.18);
|
|
26634
|
+
border-radius: 8px;
|
|
26635
|
+
background: rgba(12, 20, 34, 0.72);
|
|
26636
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
|
26637
|
+
}
|
|
26638
|
+
.detail-panel > summary {
|
|
26639
|
+
display: flex;
|
|
26640
|
+
align-items: center;
|
|
26641
|
+
justify-content: space-between;
|
|
26642
|
+
gap: 16px;
|
|
26643
|
+
padding: 18px 20px;
|
|
26644
|
+
list-style: none;
|
|
26645
|
+
}
|
|
26646
|
+
.detail-panel > summary::-webkit-details-marker { display: none; }
|
|
26647
|
+
.detail-panel > summary span { color: #f6f8ff; font-size: 18px; font-weight: 820; }
|
|
26648
|
+
.detail-panel > summary small { color: #91a1b8; font-size: 13px; line-height: 1.35; text-align: right; }
|
|
26649
|
+
.detail-panel > summary::after {
|
|
26650
|
+
content: "+";
|
|
26651
|
+
display: grid;
|
|
26652
|
+
place-items: center;
|
|
26653
|
+
width: 28px;
|
|
26654
|
+
height: 28px;
|
|
26655
|
+
flex: 0 0 auto;
|
|
26656
|
+
border: 1px solid rgba(148, 163, 184, 0.2);
|
|
26657
|
+
border-radius: 8px;
|
|
26658
|
+
color: #d8e1f0;
|
|
26659
|
+
}
|
|
26660
|
+
.detail-panel[open] > summary::after { content: "-"; }
|
|
26661
|
+
.panel-body { padding: 0 18px 18px; }
|
|
26662
|
+
.panel-body section:first-child { margin-top: 0; }
|
|
26663
|
+
.visually-hidden {
|
|
26664
|
+
position: absolute;
|
|
26665
|
+
width: 1px;
|
|
26666
|
+
height: 1px;
|
|
26667
|
+
padding: 0;
|
|
26668
|
+
margin: -1px;
|
|
26669
|
+
overflow: hidden;
|
|
26670
|
+
clip: rect(0, 0, 0, 0);
|
|
26671
|
+
white-space: nowrap;
|
|
26672
|
+
border: 0;
|
|
26673
|
+
}
|
|
26674
|
+
@media (max-width: 820px) {
|
|
26675
|
+
main { padding: 92px 14px 40px; }
|
|
26676
|
+
.topbar { padding: 0 14px; }
|
|
26677
|
+
.hero-grid, .metric-grid, .watch-grid, .posture-grid { grid-template-columns: 1fr; }
|
|
26678
|
+
.protection-card { min-height: 320px; }
|
|
26679
|
+
.activity-card { grid-template-columns: 48px 1fr; }
|
|
26680
|
+
.activity-badge { grid-column: 2; width: fit-content; }
|
|
26681
|
+
section { padding: 16px; overflow-x: auto; }
|
|
26682
|
+
table { min-width: 720px; }
|
|
26683
|
+
.activity-heading { align-items: flex-start; flex-direction: column; }
|
|
26684
|
+
.detail-panel > summary { align-items: flex-start; flex-direction: column; }
|
|
26685
|
+
.detail-panel > summary small { text-align: left; }
|
|
26091
26686
|
}
|
|
26092
26687
|
</style>
|
|
26093
26688
|
</head>
|
|
26094
26689
|
<body>
|
|
26690
|
+
<header class="topbar">
|
|
26691
|
+
<div class="brand"><span class="brand-mark">G</span><span>Guardian</span></div>
|
|
26692
|
+
<div class="topbar-actions">
|
|
26693
|
+
<span class="status-pill"><span class="status-dot"></span>${escapeHtml(profile.runtime.daemon_status)}</span>
|
|
26694
|
+
<span class="icon-button" aria-label="Settings">⚙</span>
|
|
26695
|
+
</div>
|
|
26696
|
+
</header>
|
|
26095
26697
|
<main>
|
|
26096
|
-
<div class="
|
|
26698
|
+
<div class="eyebrow">Local-first status</div>
|
|
26097
26699
|
<h1>Project Guardian Control Tower</h1>
|
|
26098
|
-
<p>This
|
|
26099
|
-
<
|
|
26100
|
-
<
|
|
26700
|
+
<p class="intro">This local page confirms Guardian is watching your AI surface with privacy-first, metadata-only governance. It never exposes the runtime token, raw prompts, raw responses, or local action payloads.</p>
|
|
26701
|
+
<div class="hero-grid" aria-label="Guardian protection overview">
|
|
26702
|
+
<article class="protection-card">
|
|
26703
|
+
<div class="${protectionRingClass}"><div class="ring-core">G</div></div>
|
|
26704
|
+
<h2>${escapeHtml(protectedStateLabel)}</h2>
|
|
26705
|
+
<p>${escapeHtml(protectedStateDetail)}</p>
|
|
26706
|
+
</article>
|
|
26707
|
+
<div class="metric-grid">
|
|
26708
|
+
${heroMetricCards}
|
|
26709
|
+
</div>
|
|
26710
|
+
</div>
|
|
26711
|
+
<section aria-labelledby="receipt-feed">
|
|
26712
|
+
<div class="activity-heading">
|
|
26713
|
+
<h2 id="receipt-feed">What Guardian did today</h2>
|
|
26714
|
+
<div class="filter-chips" aria-label="Activity filters">
|
|
26715
|
+
<button type="button" class="chip is-active" data-activity-filter="all" aria-pressed="true">All</button>
|
|
26716
|
+
<button type="button" class="chip" data-activity-filter="blocked" aria-pressed="false">Blocked</button>
|
|
26717
|
+
<button type="button" class="chip" data-activity-filter="flagged" aria-pressed="false">Flagged</button>
|
|
26718
|
+
<button type="button" class="chip" data-activity-filter="allowed" aria-pressed="false">Allowed</button>
|
|
26719
|
+
</div>
|
|
26720
|
+
</div>
|
|
26721
|
+
<p class="activity-filter-status" data-activity-filter-status aria-live="polite">Showing all local Guardian activity.</p>
|
|
26722
|
+
<div class="activity-list" data-activity-list>
|
|
26723
|
+
${activityFeedHtml}
|
|
26724
|
+
<div class="empty-filter-state" data-activity-empty hidden>No activity matches this filter yet.</div>
|
|
26725
|
+
</div>
|
|
26726
|
+
</section>
|
|
26727
|
+
<section aria-labelledby="watch-level">
|
|
26728
|
+
<h2 id="watch-level">How closely should Guardian watch?</h2>
|
|
26729
|
+
<div class="watch-grid">
|
|
26730
|
+
<div class="posture-grid">
|
|
26731
|
+
${postureCardsHtml}
|
|
26732
|
+
</div>
|
|
26733
|
+
<div class="settings-stack">
|
|
26734
|
+
<article class="settings-card">
|
|
26735
|
+
<h2>Your privacy settings</h2>
|
|
26736
|
+
${privacySettingRowsHtml}
|
|
26737
|
+
</article>
|
|
26738
|
+
<article class="settings-card">
|
|
26739
|
+
<h2>AI tools being watched</h2>
|
|
26740
|
+
<p>Guardian monitors these in real time.</p>
|
|
26741
|
+
<div class="provider-list">
|
|
26742
|
+
${providerPillsHtml}
|
|
26743
|
+
</div>
|
|
26744
|
+
</article>
|
|
26745
|
+
</div>
|
|
26746
|
+
</div>
|
|
26747
|
+
</section>
|
|
26748
|
+
<details class="detail-panel" aria-labelledby="technical-details-summary">
|
|
26749
|
+
<summary id="technical-details-summary">
|
|
26750
|
+
<span>Technical details</span>
|
|
26751
|
+
<small>Runtime status, policy packs, permissions, local data, release evidence, receipts, Search Graph, and Learning Graph.</small>
|
|
26752
|
+
</summary>
|
|
26753
|
+
<div class="panel-body">
|
|
26754
|
+
<section aria-labelledby="install-snapshot">
|
|
26755
|
+
<h2 id="install-snapshot">Install Snapshot</h2>
|
|
26756
|
+
<p>Quick local runtime, privacy, policy, release, and graph status.</p>
|
|
26757
|
+
<table aria-label="Guardian status">
|
|
26758
|
+
<tbody>
|
|
26101
26759
|
${rows.map(
|
|
26102
26760
|
([label, value]) => ` <tr><th scope="row">${escapeHtml(label)}</th><td>${escapeHtml(value)}</td></tr>`
|
|
26103
26761
|
).join("\n")}
|
|
26104
|
-
|
|
26105
|
-
|
|
26762
|
+
</tbody>
|
|
26763
|
+
</table>
|
|
26764
|
+
</section>
|
|
26106
26765
|
<section aria-labelledby="privacy-posture">
|
|
26107
26766
|
<h2 id="privacy-posture">Privacy Posture</h2>
|
|
26108
26767
|
<p>Review Guardian's local-first privacy defaults and pending privacy controls without exposing runtime tokens, raw prompts, raw responses, or local action payloads.</p>
|
|
@@ -26229,8 +26888,16 @@ ${releaseDoctorRows}
|
|
|
26229
26888
|
</table>
|
|
26230
26889
|
</section>
|
|
26231
26890
|
<section aria-labelledby="receipt-drilldowns">
|
|
26232
|
-
<
|
|
26233
|
-
|
|
26891
|
+
<div class="activity-heading">
|
|
26892
|
+
<h2 id="receipt-drilldowns">What Guardian did today</h2>
|
|
26893
|
+
<div class="filter-chips" aria-label="Activity filters">
|
|
26894
|
+
<span class="chip is-active">All</span>
|
|
26895
|
+
<span class="chip">Blocked</span>
|
|
26896
|
+
<span class="chip">Flagged</span>
|
|
26897
|
+
<span class="chip">Allowed</span>
|
|
26898
|
+
</div>
|
|
26899
|
+
</div>
|
|
26900
|
+
<p><span class="visually-hidden">Activity & Receipts.</span>Inspect recent Guardian decisions without raw prompt, response, or local-action payload content.</p>
|
|
26234
26901
|
<table aria-label="Guardian receipt drilldowns">
|
|
26235
26902
|
<thead>
|
|
26236
26903
|
<tr><th scope="col">Receipt</th><th scope="col">Type</th><th scope="col">Decision</th><th scope="col">Details</th></tr>
|
|
@@ -26308,7 +26975,104 @@ ${learningEventRows}
|
|
|
26308
26975
|
</tbody>
|
|
26309
26976
|
</table>
|
|
26310
26977
|
</section>
|
|
26978
|
+
</div>
|
|
26979
|
+
</details>
|
|
26311
26980
|
</main>
|
|
26981
|
+
<script>
|
|
26982
|
+
(() => {
|
|
26983
|
+
const filters = Array.from(document.querySelectorAll('[data-activity-filter]'));
|
|
26984
|
+
const list = document.querySelector('[data-activity-list]');
|
|
26985
|
+
const emptyState = document.querySelector('[data-activity-empty]');
|
|
26986
|
+
const filterStatus = document.querySelector('[data-activity-filter-status]');
|
|
26987
|
+
const filterCopy = {
|
|
26988
|
+
all: {
|
|
26989
|
+
status: 'Showing all local Guardian activity.',
|
|
26990
|
+
empty: 'No Guardian activity recorded yet.',
|
|
26991
|
+
},
|
|
26992
|
+
blocked: {
|
|
26993
|
+
status: 'Showing blocked Guardian decisions.',
|
|
26994
|
+
empty: 'No blocked activity recorded yet.',
|
|
26995
|
+
},
|
|
26996
|
+
flagged: {
|
|
26997
|
+
status: 'Showing flagged Guardian decisions.',
|
|
26998
|
+
empty: 'No flagged activity recorded yet.',
|
|
26999
|
+
},
|
|
27000
|
+
allowed: {
|
|
27001
|
+
status: 'Showing allowed Guardian decisions.',
|
|
27002
|
+
empty: 'No allowed activity recorded yet.',
|
|
27003
|
+
},
|
|
27004
|
+
};
|
|
27005
|
+
const setFilterCopy = (selectedFilter) => {
|
|
27006
|
+
const copy = filterCopy[selectedFilter] || filterCopy.all;
|
|
27007
|
+
if (filterStatus) filterStatus.textContent = copy.status;
|
|
27008
|
+
if (emptyState) emptyState.textContent = copy.empty;
|
|
27009
|
+
};
|
|
27010
|
+
const setSelectedFilter = (selectedFilter) => {
|
|
27011
|
+
for (const filter of filters) {
|
|
27012
|
+
const selected = filter.getAttribute('data-activity-filter') === selectedFilter;
|
|
27013
|
+
filter.classList.toggle('is-active', selected);
|
|
27014
|
+
filter.setAttribute('aria-pressed', selected ? 'true' : 'false');
|
|
27015
|
+
}
|
|
27016
|
+
setFilterCopy(selectedFilter);
|
|
27017
|
+
};
|
|
27018
|
+
const createActivityCard = (activity) => {
|
|
27019
|
+
const card = document.createElement('article');
|
|
27020
|
+
card.className = 'activity-card ' + activity.tone;
|
|
27021
|
+
card.setAttribute('data-activity-state', activity.state);
|
|
27022
|
+
const icon = document.createElement('div');
|
|
27023
|
+
icon.className = 'activity-icon';
|
|
27024
|
+
icon.textContent = activity.icon;
|
|
27025
|
+
const body = document.createElement('div');
|
|
27026
|
+
const title = document.createElement('div');
|
|
27027
|
+
title.className = 'activity-title';
|
|
27028
|
+
title.textContent = activity.title;
|
|
27029
|
+
const meta = document.createElement('div');
|
|
27030
|
+
meta.className = 'activity-meta';
|
|
27031
|
+
meta.textContent = activity.meta;
|
|
27032
|
+
body.append(title, meta);
|
|
27033
|
+
const badge = document.createElement('div');
|
|
27034
|
+
badge.className = 'activity-badge ' + activity.tone;
|
|
27035
|
+
badge.textContent = activity.label;
|
|
27036
|
+
card.append(icon, body, badge);
|
|
27037
|
+
return card;
|
|
27038
|
+
};
|
|
27039
|
+
const renderActivities = (activities) => {
|
|
27040
|
+
if (!list) return;
|
|
27041
|
+
for (const card of Array.from(list.querySelectorAll('[data-activity-state]'))) {
|
|
27042
|
+
card.remove();
|
|
27043
|
+
}
|
|
27044
|
+
for (const activity of activities) {
|
|
27045
|
+
list.insertBefore(createActivityCard(activity), emptyState);
|
|
27046
|
+
}
|
|
27047
|
+
if (emptyState) emptyState.hidden = activities.length > 0;
|
|
27048
|
+
};
|
|
27049
|
+
const applyFilter = async (selectedFilter) => {
|
|
27050
|
+
setSelectedFilter(selectedFilter);
|
|
27051
|
+
const controller = new AbortController();
|
|
27052
|
+
const timeoutId = window.setTimeout(() => controller.abort(), 2500);
|
|
27053
|
+
try {
|
|
27054
|
+
const response = await fetch('/v1/guardian/activity?filter=' + encodeURIComponent(selectedFilter), {
|
|
27055
|
+
headers: { accept: 'application/json' },
|
|
27056
|
+
signal: controller.signal,
|
|
27057
|
+
});
|
|
27058
|
+
if (!response.ok) return;
|
|
27059
|
+
const body = await response.json();
|
|
27060
|
+
if (body && body.ok && Array.isArray(body.activities)) {
|
|
27061
|
+
renderActivities(body.activities);
|
|
27062
|
+
}
|
|
27063
|
+
} catch {
|
|
27064
|
+
// Keep the server-rendered activity feed if a local refresh is unavailable.
|
|
27065
|
+
} finally {
|
|
27066
|
+
window.clearTimeout(timeoutId);
|
|
27067
|
+
}
|
|
27068
|
+
};
|
|
27069
|
+
for (const filter of filters) {
|
|
27070
|
+
filter.addEventListener('click', () => {
|
|
27071
|
+
applyFilter(filter.getAttribute('data-activity-filter') || 'all');
|
|
27072
|
+
});
|
|
27073
|
+
}
|
|
27074
|
+
})();
|
|
27075
|
+
</script>
|
|
26312
27076
|
</body>
|
|
26313
27077
|
</html>
|
|
26314
27078
|
`;
|
|
@@ -26322,6 +27086,24 @@ function renderDataPathForm(action, pathName, label) {
|
|
|
26322
27086
|
"</form>"
|
|
26323
27087
|
].join("");
|
|
26324
27088
|
}
|
|
27089
|
+
function renderOutcomePrivacyModeControl(enabled) {
|
|
27090
|
+
return [
|
|
27091
|
+
'<form method="post" action="/v1/guardian/admin/preferences" class="toggle-form">',
|
|
27092
|
+
' <input type="hidden" name="privacyModeEnabled" value="on">',
|
|
27093
|
+
` <button type="submit" class="toggle${enabled ? " is-on" : ""}" aria-label="Privacy mode ${enabled ? "on" : "off"}" ${enabled ? "disabled" : ""}><span></span></button>`,
|
|
27094
|
+
"</form>"
|
|
27095
|
+
].join("");
|
|
27096
|
+
}
|
|
27097
|
+
function renderOutcomePromptCoachControl(mode) {
|
|
27098
|
+
const enabled = mode !== "off";
|
|
27099
|
+
const nextMode = enabled ? "off" : "balanced";
|
|
27100
|
+
return [
|
|
27101
|
+
'<form method="post" action="/v1/guardian/admin/preferences" class="toggle-form">',
|
|
27102
|
+
` <input type="hidden" name="promptCoachMode" value="${escapeHtml(nextMode)}">`,
|
|
27103
|
+
` <button type="submit" class="toggle${enabled ? " is-on" : ""}" aria-label="Prompt coaching ${enabled ? "on" : "off"}"><span></span></button>`,
|
|
27104
|
+
"</form>"
|
|
27105
|
+
].join("");
|
|
27106
|
+
}
|
|
26325
27107
|
function renderDataConfirmForm(action, pathName, confirmation, label) {
|
|
26326
27108
|
return [
|
|
26327
27109
|
'<form method="post" action="/v1/guardian/admin/data">',
|
|
@@ -26660,10 +27442,105 @@ function renderReceiptRows(receipts) {
|
|
|
26660
27442
|
}
|
|
26661
27443
|
return receipts.map((receipt) => renderReceiptRow(receipt)).join("\n");
|
|
26662
27444
|
}
|
|
27445
|
+
function renderActivityFeed(receipts) {
|
|
27446
|
+
if (receipts.length === 0) {
|
|
27447
|
+
return ` <article class="activity-card allow" data-activity-state="allowed">
|
|
27448
|
+
<div class="activity-icon">✅</div>
|
|
27449
|
+
<div>
|
|
27450
|
+
<div class="activity-title">No risky activity recorded yet</div>
|
|
27451
|
+
<div class="activity-meta">Guardian is watching locally</div>
|
|
27452
|
+
</div>
|
|
27453
|
+
<div class="activity-badge allow">READY</div>
|
|
27454
|
+
</article>`;
|
|
27455
|
+
}
|
|
27456
|
+
return receipts.map((receipt) => renderActivityCard(receipt)).join("\n");
|
|
27457
|
+
}
|
|
27458
|
+
function parseActivityFilter(value) {
|
|
27459
|
+
if (value === "blocked" || value === "flagged" || value === "allowed") {
|
|
27460
|
+
return value;
|
|
27461
|
+
}
|
|
27462
|
+
return "all";
|
|
27463
|
+
}
|
|
27464
|
+
function buildActivityItems(receipts, filter) {
|
|
27465
|
+
return receipts.filter((receipt) => {
|
|
27466
|
+
const group = activityFilterGroup(receipt.disposition);
|
|
27467
|
+
return filter === "all" || group === filter;
|
|
27468
|
+
}).map((receipt) => ({
|
|
27469
|
+
id: receipt.receipt_id,
|
|
27470
|
+
state: activityFilterGroup(receipt.disposition),
|
|
27471
|
+
tone: activityTone(receipt.disposition),
|
|
27472
|
+
icon: activityPlainIcon(receipt.disposition),
|
|
27473
|
+
title: receipt.summary,
|
|
27474
|
+
meta: `${formatActivityTime(receipt.created_at)} \xB7 ${receipt.receipt_type}`,
|
|
27475
|
+
label: activityLabel(receipt.disposition),
|
|
27476
|
+
raw_content_included: false
|
|
27477
|
+
}));
|
|
27478
|
+
}
|
|
27479
|
+
function renderActivityCard(receipt) {
|
|
27480
|
+
const tone = activityTone(receipt.disposition);
|
|
27481
|
+
const filterGroup = activityFilterGroup(receipt.disposition);
|
|
27482
|
+
return ` <article class="activity-card ${tone}" data-activity-state="${escapeHtml(filterGroup)}">
|
|
27483
|
+
<div class="activity-icon">${activityIcon(receipt.disposition)}</div>
|
|
27484
|
+
<div>
|
|
27485
|
+
<div class="activity-title">${escapeHtml(receipt.summary)}</div>
|
|
27486
|
+
<div class="activity-meta">${escapeHtml(formatActivityTime(receipt.created_at))} \xB7 ${escapeHtml(receipt.receipt_type)}</div>
|
|
27487
|
+
</div>
|
|
27488
|
+
<div class="activity-badge ${tone}">${escapeHtml(activityLabel(receipt.disposition))}</div>
|
|
27489
|
+
</article>`;
|
|
27490
|
+
}
|
|
26663
27491
|
function renderReceiptRow(receipt) {
|
|
26664
27492
|
const policyBasis = receipt.policy_basis.length > 0 ? receipt.policy_basis.join(", ") : "none recorded";
|
|
26665
27493
|
return ` <tr><th scope="row"><code>${escapeHtml(receipt.receipt_id)}</code></th><td>${escapeHtml(receipt.receipt_type)}</td><td>${escapeHtml(receipt.disposition)}</td><td><details><summary>${escapeHtml(receipt.summary)}</summary><dl><dt>Transaction</dt><dd><code>${escapeHtml(receipt.transaction_id)}</code></dd><dt>Created</dt><dd>${escapeHtml(receipt.created_at)}</dd><dt>Policy basis</dt><dd>${escapeHtml(policyBasis)}</dd><dt>Receipt hash</dt><dd><code>${escapeHtml(receipt.receipt_hash)}</code></dd><dt>Raw content</dt><dd>${receipt.raw_content_included ? "included" : "metadata-only"}</dd></dl></details></td></tr>`;
|
|
26666
27494
|
}
|
|
27495
|
+
function activityTone(disposition) {
|
|
27496
|
+
if (disposition === "block") return "block";
|
|
27497
|
+
if (disposition === "warn" || disposition === "confirm") return disposition;
|
|
27498
|
+
return disposition === "rewrite" ? "rewrite" : "allow";
|
|
27499
|
+
}
|
|
27500
|
+
function activityIcon(disposition) {
|
|
27501
|
+
if (disposition === "block") return "🛡";
|
|
27502
|
+
if (disposition === "warn" || disposition === "confirm") return "⚠";
|
|
27503
|
+
return "✅";
|
|
27504
|
+
}
|
|
27505
|
+
function activityPlainIcon(disposition) {
|
|
27506
|
+
if (disposition === "block") return "SHIELD";
|
|
27507
|
+
if (disposition === "warn" || disposition === "confirm") return "!";
|
|
27508
|
+
return "OK";
|
|
27509
|
+
}
|
|
27510
|
+
function activityLabel(disposition) {
|
|
27511
|
+
if (disposition === "block") return "BLOCKED";
|
|
27512
|
+
if (disposition === "warn") return "FLAGGED";
|
|
27513
|
+
if (disposition === "confirm") return "REVIEW";
|
|
27514
|
+
if (disposition === "rewrite") return "MASKED";
|
|
27515
|
+
return "ALLOWED";
|
|
27516
|
+
}
|
|
27517
|
+
function activityFilterGroup(disposition) {
|
|
27518
|
+
if (disposition === "block") return "blocked";
|
|
27519
|
+
if (disposition === "warn" || disposition === "confirm") return "flagged";
|
|
27520
|
+
return "allowed";
|
|
27521
|
+
}
|
|
27522
|
+
function formatActivityTime(createdAt) {
|
|
27523
|
+
const parsed = Date.parse(createdAt);
|
|
27524
|
+
if (Number.isNaN(parsed)) {
|
|
27525
|
+
return createdAt;
|
|
27526
|
+
}
|
|
27527
|
+
return new Date(parsed).toLocaleString("en-US", {
|
|
27528
|
+
dateStyle: "medium",
|
|
27529
|
+
timeStyle: "short",
|
|
27530
|
+
timeZone: "UTC"
|
|
27531
|
+
});
|
|
27532
|
+
}
|
|
27533
|
+
function providerDisplayName(providerId) {
|
|
27534
|
+
if (providerId === "chatgpt") return "ChatGPT";
|
|
27535
|
+
if (providerId === "claude") return "Claude";
|
|
27536
|
+
if (providerId === "gemini") return "Gemini";
|
|
27537
|
+
if (providerId === "perplexity") return "Perplexity";
|
|
27538
|
+
if (providerId === "cursor") return "Cursor";
|
|
27539
|
+
if (providerId === "windsurf") return "Windsurf";
|
|
27540
|
+
if (providerId === "openrouter") return "OpenRouter";
|
|
27541
|
+
if (providerId === "local_model") return "Local AI";
|
|
27542
|
+
return providerId;
|
|
27543
|
+
}
|
|
26667
27544
|
function buildControlTowerProviderPermissionRows(profile) {
|
|
26668
27545
|
const revokedProviders = new Set(profile.permissions?.revoked_provider_ids ?? []);
|
|
26669
27546
|
return GUARDIAN_PROVIDER_IDS.map((providerId) => ({
|