Skip to content

Commit 39433f1

Browse files
MythologIQclaude
andcommitted
feat(vscode): add detail panels, browser executive dashboard, and security hardening
Add 5 dedicated detail webview panels (Kernel Debugger, Memory Browser, Safety Stats, Audit Log, Active Policies) so all 8 sidebar panels now open their own full-panel React views instead of falling back to the Governance Hub. Redesign the browser dashboard as an executive governance experience: - Single-screen grid layout with all panels visible simultaneously - 6 premium themes (Corporate Slate, Midnight Blue, Onyx, Azure Mist, High Contrast Dark, High Contrast Light) persisted via localStorage - Policy editor with 5 governance templates (Strict Security, SOC 2, GDPR, Development, Rate Limiting), validation, test scenarios, and file download/import - Real-time filtering on Audit Log (by severity) and Policies (by action) - SLO trend arrows showing delta between updates - Full data broadcast (SLO, topology, audit, policies) over WebSocket Security hardening: - Fix CSP mismatch between HTTP header and HTML meta tag (blob: for downloads) - Escape all data values in browser innerHTML with esc() including numerics - Sanitize CSS class attribute injection via regex whitelist - Fix pre-existing XSS in CMVK results panel (escHtml on external API data) Sidebar fixes: - Panel settings modal now fully opaque (was 5% transparent) - Browser launch button added to sidebar header - PROMOTE_COMMANDS correctly routes all 8 panels Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent 8022eaa commit 39433f1

31 files changed

+2307
-316
lines changed

packages/agent-os-vscode/esbuild.webview.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ const config = {
2020
'src/webviews/sloDetail/main.tsx',
2121
'src/webviews/topologyDetail/main.tsx',
2222
'src/webviews/hubDetail/main.tsx',
23+
'src/webviews/kernelDetail/main.tsx',
24+
'src/webviews/memoryDetail/main.tsx',
25+
'src/webviews/statsDetail/main.tsx',
26+
'src/webviews/auditDetail/main.tsx',
27+
'src/webviews/policyDetail/main.tsx',
2328
],
2429
bundle: true,
2530
outdir: 'out/webviews',

packages/agent-os-vscode/package.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,31 @@
164164
"title": "Agent OS: Open Governance Hub",
165165
"icon": "$(dashboard)"
166166
},
167+
{
168+
"command": "agent-os.showKernelDebugger",
169+
"title": "Agent OS: Open Kernel Debugger",
170+
"icon": "$(debug-alt)"
171+
},
172+
{
173+
"command": "agent-os.showMemoryBrowser",
174+
"title": "Agent OS: Open Memory Browser",
175+
"icon": "$(database)"
176+
},
177+
{
178+
"command": "agent-os.showSafetyStats",
179+
"title": "Agent OS: Open Safety Stats",
180+
"icon": "$(shield)"
181+
},
182+
{
183+
"command": "agent-os.showAuditDetail",
184+
"title": "Agent OS: Open Audit Log Detail",
185+
"icon": "$(list-unordered)"
186+
},
187+
{
188+
"command": "agent-os.showPolicyDetail",
189+
"title": "Agent OS: Open Active Policies Detail",
190+
"icon": "$(law)"
191+
},
167192
{
168193
"command": "agent-os.openSLOInBrowser",
169194
"title": "Agent OS: Open SLO Dashboard in Browser",

packages/agent-os-vscode/src/extension.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ import { GovernanceStatusBar } from './governanceStatusBar';
3838
import { showSLODetail } from './webviews/sloDetail/SLODetailPanel';
3939
import { showTopologyDetail } from './webviews/topologyDetail/TopologyDetailPanel';
4040
import { showHubDetail } from './webviews/hubDetail/HubDetailPanel';
41+
import { showKernelDetail } from './webviews/kernelDetail/KernelDetailPanel';
42+
import { showMemoryDetail } from './webviews/memoryDetail/MemoryDetailPanel';
43+
import { showStatsDetail } from './webviews/statsDetail/StatsDetailPanel';
44+
import { showAuditDetail } from './webviews/auditDetail/AuditDetailPanel';
45+
import { showPolicyDetail } from './webviews/policyDetail/PolicyDetailPanel';
4146

4247
// 3-Slot Sidebar (Sidebar Redesign)
4348
import { SidebarProvider } from './webviews/sidebar/SidebarProvider';
@@ -438,6 +443,26 @@ safe_query = "SELECT * FROM users WHERE id = ?"
438443
showHubDetail(context.extensionUri, governanceStore);
439444
});
440445

446+
const showKernelDebuggerCmd = vscode.commands.registerCommand('agent-os.showKernelDebugger', () => {
447+
showKernelDetail(context.extensionUri, governanceStore);
448+
});
449+
450+
const showMemoryBrowserCmd = vscode.commands.registerCommand('agent-os.showMemoryBrowser', () => {
451+
showMemoryDetail(context.extensionUri, governanceStore);
452+
});
453+
454+
const showSafetyStatsCmd = vscode.commands.registerCommand('agent-os.showSafetyStats', () => {
455+
showStatsDetail(context.extensionUri, governanceStore);
456+
});
457+
458+
const showAuditDetailCmd = vscode.commands.registerCommand('agent-os.showAuditDetail', () => {
459+
showAuditDetail(context.extensionUri, governanceStore);
460+
});
461+
462+
const showPolicyDetailCmd = vscode.commands.registerCommand('agent-os.showPolicyDetail', () => {
463+
showPolicyDetail(context.extensionUri, governanceStore);
464+
});
465+
441466
// Agent Drill-Down Command (Dashboard Feature Completeness - Phase 2)
442467
const showAgentDetailsCmd = vscode.commands.registerCommand(
443468
'agent-os.showAgentDetails',
@@ -516,7 +541,8 @@ safe_query = "SELECT * FROM users WHERE id = ?"
516541
governanceServer = GovernanceServer.getInstance(
517542
sloDataProvider,
518543
agentTopologyDataProvider,
519-
auditLogger
544+
auditLogger,
545+
policyDataProvider,
520546
);
521547
const port = await governanceServer.start();
522548
const url = `http://localhost:${port}`;
@@ -530,7 +556,8 @@ safe_query = "SELECT * FROM users WHERE id = ?"
530556
governanceServer = GovernanceServer.getInstance(
531557
sloDataProvider,
532558
agentTopologyDataProvider,
533-
auditLogger
559+
auditLogger,
560+
policyDataProvider,
534561
);
535562
const port = await governanceServer.start();
536563
const url = `http://localhost:${port}/#slo`;
@@ -544,7 +571,8 @@ safe_query = "SELECT * FROM users WHERE id = ?"
544571
governanceServer = GovernanceServer.getInstance(
545572
sloDataProvider,
546573
agentTopologyDataProvider,
547-
auditLogger
574+
auditLogger,
575+
policyDataProvider,
548576
);
549577
const port = await governanceServer.start();
550578
const url = `http://localhost:${port}/#topology`;
@@ -699,6 +727,11 @@ safe_query = "SELECT * FROM users WHERE id = ?"
699727
governanceStatusBar,
700728
// Governance Hub & Browser Experience
701729
showGovernanceHubCmd,
730+
showKernelDebuggerCmd,
731+
showMemoryBrowserCmd,
732+
showSafetyStatsCmd,
733+
showAuditDetailCmd,
734+
showPolicyDetailCmd,
702735
showAgentDetailsCmd,
703736
exportAuditCSVCmd,
704737
openGovernanceInBrowserCmd,
@@ -891,13 +924,13 @@ function generateCMVKResultsHTML(result: any, webview: vscode.Webview): string {
891924
const modelRows = result.modelResults.map((m: any) => `
892925
<tr>
893926
<td>${m.passed ? '✅' : '⚠️'}</td>
894-
<td><strong>${m.model}</strong></td>
895-
<td>${m.summary}</td>
927+
<td><strong>${escHtml(String(m.model))}</strong></td>
928+
<td>${escHtml(String(m.summary))}</td>
896929
</tr>
897930
`).join('');
898931

899-
const issuesList = result.issues.length > 0
900-
? `<ul>${result.issues.map((i: string) => `<li>${i}</li>`).join('')}</ul>`
932+
const issuesList = result.issues.length > 0
933+
? `<ul>${result.issues.map((i: string) => `<li>${escHtml(i)}</li>`).join('')}</ul>`
901934
: '<p>No issues detected</p>';
902935

903936
return `
@@ -942,7 +975,7 @@ function generateCMVKResultsHTML(result: any, webview: vscode.Webview): string {
942975
<div class="section">
943976
<h2>Recommendations</h2>
944977
<div class="recommendation">
945-
${result.recommendations}
978+
${escHtml(String(result.recommendations))}
946979
</div>
947980
</div>
948981
` : ''}

packages/agent-os-vscode/src/server/GovernanceServer.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import * as http from 'http';
1212
import * as path from 'path';
1313
import { SLODataProvider } from '../views/sloTypes';
1414
import { AgentTopologyDataProvider } from '../views/topologyTypes';
15+
import { PolicyDataProvider } from '../views/policyTypes';
1516
import { AuditLogger } from '../auditLogger';
1617
import { ServerState, ClientConnection, ServerMessage, ServerMessageType } from './serverTypes';
1718
import { renderBrowserDashboard } from './browserTemplate';
@@ -51,20 +52,23 @@ export class GovernanceServer {
5152
private constructor(
5253
private readonly _sloProvider: SLODataProvider,
5354
private readonly _topologyProvider: AgentTopologyDataProvider,
54-
private readonly _auditLogger: AuditLogger
55+
private readonly _auditLogger: AuditLogger,
56+
private readonly _policyProvider?: PolicyDataProvider,
5557
) {}
5658

5759
/** Get or create the singleton server instance. */
5860
public static getInstance(
5961
sloProvider: SLODataProvider,
6062
topologyProvider: AgentTopologyDataProvider,
61-
auditLogger: AuditLogger
63+
auditLogger: AuditLogger,
64+
policyProvider?: PolicyDataProvider,
6265
): GovernanceServer {
6366
if (!GovernanceServer._instance) {
6467
GovernanceServer._instance = new GovernanceServer(
6568
sloProvider,
6669
topologyProvider,
67-
auditLogger
70+
auditLogger,
71+
policyProvider,
6872
);
6973
}
7074
return GovernanceServer._instance;
@@ -178,7 +182,7 @@ export class GovernanceServer {
178182
/** Apply security headers to all HTTP responses. */
179183
private _setSecurityHeaders(res: http.ServerResponse, nonce: string): void {
180184
res.setHeader('Content-Security-Policy',
181-
"default-src 'self'; " +
185+
"default-src 'self' blob:; " +
182186
`script-src 'nonce-${nonce}'; ` +
183187
"style-src 'self' 'unsafe-inline'; " +
184188
"connect-src 'self' ws://127.0.0.1:*");
@@ -237,6 +241,10 @@ export class GovernanceServer {
237241
ws.send(JSON.stringify({ type: 'sloUpdate', data: slo }));
238242
ws.send(JSON.stringify({ type: 'topologyUpdate', data: topology }));
239243
ws.send(JSON.stringify({ type: 'auditUpdate', data: audit }));
244+
if (this._policyProvider) {
245+
const policy = await this._policyProvider.getSnapshot();
246+
ws.send(JSON.stringify({ type: 'policyUpdate', data: policy }));
247+
}
240248
} catch {
241249
// Provider may not be ready yet; client will receive data on next broadcast cycle
242250
}
@@ -247,6 +255,17 @@ export class GovernanceServer {
247255
try {
248256
const slo = await this._sloProvider.getSnapshot();
249257
this.broadcast('sloUpdate', slo);
258+
const topology = {
259+
agents: this._topologyProvider.getAgents(),
260+
bridges: this._topologyProvider.getBridges(),
261+
delegations: this._topologyProvider.getDelegations(),
262+
};
263+
this.broadcast('topologyUpdate', topology);
264+
this.broadcast('auditUpdate', this._auditLogger.getRecent(50));
265+
if (this._policyProvider) {
266+
const policy = await this._policyProvider.getSnapshot();
267+
this.broadcast('policyUpdate', policy);
268+
}
250269
} catch {
251270
// Non-critical: broadcast failure retries automatically on next 10s interval
252271
}

0 commit comments

Comments
 (0)