feat: complete Phase 1-3 architecture optimization

Phase 1 - Security:
- Add AES-GCM encryption for localStorage fallback
- Enforce WSS protocol for non-localhost WebSocket connections
- Add URL sanitization to prevent XSS in markdown links

Phase 2 - Domain Reorganization:
- Create Intelligence Domain with Valtio store and caching
- Add unified intelligence-client for Rust backend integration
- Migrate from legacy agent-memory, heartbeat, reflection modules

Phase 3 - Core Optimization:
- Add virtual scrolling for ChatArea with react-window
- Implement LRU cache with TTL for intelligence operations
- Add message virtualization utilities

Additional:
- Add OpenFang compatibility test suite
- Update E2E test fixtures
- Add audit logging infrastructure
- Update project documentation and plans

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-21 22:11:50 +08:00
parent 815c56326b
commit ce562e8bfc
36 changed files with 5241 additions and 201 deletions

View File

@@ -0,0 +1,162 @@
/**
* audit-logger.ts - 前端审计日志记录工具
*
* 为 ZCLAW 前端操作提供统一的审计日志记录功能。
* 记录关键操作Hand 触发、Agent 创建等)到本地存储。
*/
export type AuditAction =
| 'hand.trigger'
| 'hand.approve'
| 'hand.cancel'
| 'agent.create'
| 'agent.update'
| 'agent.delete';
export type AuditResult = 'success' | 'failure' | 'pending';
export interface FrontendAuditEntry {
id: string;
timestamp: string;
action: AuditAction;
target: string;
result: AuditResult;
actor?: string;
details?: Record<string, unknown>;
error?: string;
}
export interface AuditLogOptions {
action: AuditAction;
target: string;
result: AuditResult;
actor?: string;
details?: Record<string, unknown>;
error?: string;
}
const STORAGE_KEY = 'zclaw-audit-logs';
const MAX_LOCAL_LOGS = 500;
function generateId(): string {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
return `audit_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
}
function getTimestamp(): string {
return new Date().toISOString();
}
function loadLocalLogs(): FrontendAuditEntry[] {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (!stored) return [];
const logs = JSON.parse(stored) as FrontendAuditEntry[];
return Array.isArray(logs) ? logs : [];
} catch {
return [];
}
}
function saveLocalLogs(logs: FrontendAuditEntry[]): void {
try {
const trimmedLogs = logs.slice(-MAX_LOCAL_LOGS);
localStorage.setItem(STORAGE_KEY, JSON.stringify(trimmedLogs));
} catch (err) {
console.error('[AuditLogger] Failed to save logs to localStorage:', err);
}
}
class AuditLogger {
private logs: FrontendAuditEntry[] = [];
private initialized = false;
constructor() {
this.init();
}
private init(): void {
if (this.initialized) return;
this.logs = loadLocalLogs();
this.initialized = true;
}
async log(options: AuditLogOptions): Promise<FrontendAuditEntry> {
const entry: FrontendAuditEntry = {
id: generateId(),
timestamp: getTimestamp(),
action: options.action,
target: options.target,
result: options.result,
actor: options.actor,
details: options.details,
error: options.error,
};
this.logs.push(entry);
saveLocalLogs(this.logs);
console.log('[AuditLogger]', entry.action, entry.target, entry.result, entry.details || '');
return entry;
}
async logSuccess(
action: AuditAction,
target: string,
details?: Record<string, unknown>
): Promise<FrontendAuditEntry> {
return this.log({ action, target, result: 'success', details });
}
async logFailure(
action: AuditAction,
target: string,
error: string,
details?: Record<string, unknown>
): Promise<FrontendAuditEntry> {
return this.log({ action, target, result: 'failure', error, details });
}
getLogs(): FrontendAuditEntry[] {
return [...this.logs];
}
getLogsByAction(action: AuditAction): FrontendAuditEntry[] {
return this.logs.filter(log => log.action === action);
}
clearLogs(): void {
this.logs = [];
localStorage.removeItem(STORAGE_KEY);
}
exportLogs(): string {
return JSON.stringify(this.logs, null, 2);
}
}
export const auditLogger = new AuditLogger();
export function logAudit(options: AuditLogOptions): Promise<FrontendAuditEntry> {
return auditLogger.log(options);
}
export function logAuditSuccess(
action: AuditAction,
target: string,
details?: Record<string, unknown>
): Promise<FrontendAuditEntry> {
return auditLogger.logSuccess(action, target, details);
}
export function logAuditFailure(
action: AuditAction,
target: string,
error: string,
details?: Record<string, unknown>
): Promise<FrontendAuditEntry> {
return auditLogger.logFailure(action, target, error, details);
}

View File

@@ -14,8 +14,6 @@
import { invoke } from '@tauri-apps/api/core';
import { isTauriRuntime } from './tauri-gateway';
import {
arrayToBase64,
base64ToArray,
deriveKey,
encrypt,
decrypt,