fix(desktop): component cleanup + dead code removal + DeerFlow ai-elements

- ChatArea: DeerFlow ai-elements annotations for accessibility
- Conversation: remove unused Context, simplify message rendering
- Delete dead modules: audit-logger.ts, gateway-reconnect.ts
- Replace console.log with structured logger across components
- Add idb dependency for IndexedDB persistence
- Fix kernel-skills type safety improvements
This commit is contained in:
iven
2026-04-03 00:28:58 +08:00
parent 15d578c5bc
commit 5c74e74f2a
19 changed files with 78 additions and 341 deletions

View File

@@ -1,173 +0,0 @@
/**
* audit-logger.ts - 前端审计日志记录工具
*
* 为 ZCLAW 前端操作提供统一的审计日志记录功能。
* 记录关键操作Hand 触发、Agent 创建等)到本地存储。
*
* @reserved This module is reserved for future audit logging integration.
* It is not currently imported by any component. When audit logging is needed,
* import { logAudit, logAuditSuccess, logAuditFailure } from this module.
*/
import { createLogger } from './logger';
const log = createLogger('AuditLogger');
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();
}
const bytes = crypto.getRandomValues(new Uint8Array(6));
const suffix = Array.from(bytes).map(b => b.toString(36).padStart(2, '0')).join('');
return `audit_${Date.now()}_${suffix}`;
}
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 (e) {
log.debug('Failed to parse audit logs from localStorage', { error: e });
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);
log.debug(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

@@ -1,80 +0,0 @@
/**
* gateway-reconnect.ts - Gateway Reconnect Methods
*
* Extracted from gateway-client.ts for modularity.
* Installs reconnect methods onto GatewayClient.prototype via mixin pattern.
*/
import type { GatewayClient } from './gateway-client';
// === Reconnect Constants ===
/** Maximum number of reconnect attempts before giving up */
export const MAX_RECONNECT_ATTEMPTS = 10;
// === Mixin Installer ===
/**
* Install reconnect methods onto GatewayClient.prototype.
*
* These methods access instance properties:
* - this.reconnectAttempts: number
* - this.reconnectInterval: number
* - this.reconnectTimer: number | null
* - this.log(level, message): void
* - this.connect(): Promise<void>
* - this.setState(state): void
* - this.emitEvent(event, payload): void
*/
export function installReconnectMethods(ClientClass: { prototype: GatewayClient }): void {
const proto = ClientClass.prototype as any;
/**
* Schedule a reconnect attempt with exponential backoff.
*/
proto.scheduleReconnect = function (this: GatewayClient): void {
const self = this as any;
if (self.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
self.log('error', `Max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached. Please reconnect manually.`);
self.setState('disconnected');
self.emitEvent('reconnect_failed', {
attempts: self.reconnectAttempts,
maxAttempts: MAX_RECONNECT_ATTEMPTS,
});
return;
}
self.reconnectAttempts++;
self.setState('reconnecting');
const delay = Math.min(self.reconnectInterval * Math.pow(1.5, self.reconnectAttempts - 1), 30000);
self.log('info', `Scheduling reconnect attempt ${self.reconnectAttempts} in ${delay}ms`);
// Emit reconnecting event for UI
self.emitEvent('reconnecting', {
attempt: self.reconnectAttempts,
delay,
maxAttempts: MAX_RECONNECT_ATTEMPTS,
});
self.reconnectTimer = window.setTimeout(async () => {
try {
await self.connect();
} catch (e) {
/* close handler will trigger another reconnect */
self.log('warn', `Reconnect attempt ${self.reconnectAttempts} failed: ${e instanceof Error ? e.message : String(e)}`);
}
}, delay);
};
/**
* Cancel a pending reconnect attempt.
*/
proto.cancelReconnect = function (this: GatewayClient): void {
const self = this as any;
if (self.reconnectTimer !== null) {
clearTimeout(self.reconnectTimer);
self.reconnectTimer = null;
}
};
}

View File

@@ -6,6 +6,7 @@
import { invoke } from '@tauri-apps/api/core';
import type { KernelClient } from './kernel-client';
import { useConversationStore } from '../store/chat/conversationStore';
/** Skill shape returned by list/refresh/create/update operations. */
type SkillItem = {
@@ -107,12 +108,16 @@ export function installSkillMethods(ClientClass: { prototype: KernelClient }): v
error?: string;
durationMs?: number;
}> {
const convStore = useConversationStore.getState();
const agent = convStore.currentAgent;
const sessionKey = convStore.sessionKey;
return invoke('skill_execute', {
id,
context: {
agentId: '',
sessionId: '',
workingDir: '',
agentId: agent?.id || 'zclaw-main',
sessionId: sessionKey || crypto.randomUUID(),
workingDir: null,
},
input: input || {},
});

View File

@@ -98,6 +98,7 @@ import type {
PromptCheckResult,
PromptTemplateInfo,
PromptVersionInfo,
PaginatedResponse,
AgentTemplateAvailable,
AgentTemplateFull,
} from './saas-types';

View File

@@ -277,28 +277,6 @@ function clearLocalStorageBackup(key: string): void {
}
}
// Keep synchronous versions for backward compatibility (deprecated)
function writeLocalStorageBackup(key: string, value: string): void {
try {
if (value) {
localStorage.setItem(key, value);
} else {
localStorage.removeItem(key);
}
} catch (e) {
logger.debug('writeLocalStorageBackup failed', { error: e });
}
}
function readLocalStorageBackup(key: string): string | null {
try {
return localStorage.getItem(key);
} catch (e) {
logger.debug('readLocalStorageBackup failed', { error: e });
return null;
}
}
// === Device Keys Secure Storage ===
/**

View File

@@ -12,7 +12,7 @@
*/
import { useEffect, useRef, useCallback } from 'react';
import { useChatStore } from '../store/chatStore';
import { useConversationStore } from '../store/chat/conversationStore';
import { intelligenceClient, type IdentityChangeProposal } from './intelligence-client';
import { createLogger } from './logger';
@@ -65,7 +65,7 @@ export function useProposalNotifications(): {
pendingCount: number;
refresh: () => Promise<void>;
} {
const { currentAgent } = useChatStore();
const currentAgent = useConversationStore((s) => s.currentAgent);
const agentId = currentAgent?.id;
const pendingCountRef = useRef(0);