v0.15.0: Agent ReAct Loop 核心引擎深度审计与生产级增强 (R1-R50)

核心引擎健壮性(R1-R10)、上下文管理优化(R11-R20)、工具安全与验证(R21-R30)、UI渲染性能(R31-R40)、基础设施与监控(R41-R50)、版本号升级
This commit is contained in:
thzxx
2026-07-11 20:56:48 +08:00
parent e04320d454
commit cb2ce48eb7
15 changed files with 2012 additions and 170 deletions
+303
View File
@@ -0,0 +1,303 @@
/**
* Infrastructure Service - 基础设施服务 (R41-R50)
* 内存泄漏防护、全局错误处理、性能监控、配置验证、健康检查
*/
import { logInfo, logWarn, logError } from './log-service.js';
// ══════════════════════════════════════════════
// R41: 内存泄漏防护 — 事件监听器管理
// ══════════════════════════════════════════════
/** R41: 已注册的事件监听器追踪表 */
const _trackedListeners = new Map<string, { target: EventTarget; type: string; listener: EventListenerOrEventListenerObject; options?: boolean | AddEventListenerOptions }>();
let _listenerIdCounter = 0;
/**
* R41: 注册并追踪事件监听器,便于统一清理
* @returns 监听器 ID,可用于单独移除
*/
export function trackEventListener(
target: EventTarget,
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
): string {
const id = `listener_${++_listenerIdCounter}`;
_trackedListeners.set(id, { target, type, listener, options });
target.addEventListener(type, listener, options);
return id;
}
/** R41: 移除单个事件监听器 */
export function removeTrackedListener(id: string): void {
const entry = _trackedListeners.get(id);
if (entry) {
entry.target.removeEventListener(entry.type, entry.listener, entry.options);
_trackedListeners.delete(id);
}
}
/** R41: 清理所有已追踪的事件监听器(用于页面卸载或会话切换时) */
export function cleanupAllListeners(): void {
let count = 0;
for (const [id, entry] of _trackedListeners) {
try {
entry.target.removeEventListener(entry.type, entry.listener, entry.options);
count++;
} catch { /* ignore */ }
_trackedListeners.delete(id);
}
if (count > 0) {
logInfo(`R41: 已清理 ${count} 个事件监听器`);
}
}
/** R41: 获取当前追踪的监听器数量(供调试用) */
export function getTrackedListenerCount(): number {
return _trackedListeners.size;
}
// ══════════════════════════════════════════════
// R42: 全局错误边界 — 捕获未处理的异常
// ══════════════════════════════════════════════
/** R42: 初始化全局错误处理 */
export function initGlobalErrorHandler(): void {
// 捕获未处理的 JS 错误
window.addEventListener('error', (e) => {
logError('R42: 未捕获错误', `${e.message} @ ${e.filename}:${e.lineno}:${e.colno}`);
// 阻止默认的错误处理(避免弹出丑陋的错误对话框)
e.preventDefault();
});
// 捕获未处理的 Promise rejection
window.addEventListener('unhandledrejection', (e) => {
const reason = e.reason;
const msg = reason instanceof Error ? reason.message : String(reason);
logError('R42: 未处理的 Promise Rejection', msg);
e.preventDefault();
});
logInfo('R42: 全局错误处理器已初始化');
}
// ══════════════════════════════════════════════
// R43: 性能监控 — 关键操作耗时追踪
// ══════════════════════════════════════════════
export interface PerfMetric {
name: string;
duration: number;
timestamp: number;
metadata?: Record<string, unknown>;
}
const _perfMetrics: PerfMetric[] = [];
const MAX_PERF_METRICS = 200;
const _perfTimers = new Map<string, number>();
/** R43: 开始性能计时 */
export function perfStart(name: string): void {
_perfTimers.set(name, performance.now());
}
/** R43: 结束性能计时并记录 */
export function perfEnd(name: string, metadata?: Record<string, unknown>): number {
const startTime = _perfTimers.get(name);
if (startTime === undefined) {
logWarn(`R43: perfEnd 未找到对应的 perfStart: ${name}`);
return 0;
}
const duration = performance.now() - startTime;
_perfTimers.delete(name);
_perfMetrics.push({ name, duration, timestamp: Date.now(), metadata });
// 超过上限时移除最早的
if (_perfMetrics.length > MAX_PERF_METRICS) {
_perfMetrics.shift();
}
// 慢操作警告(超过 1 秒)
if (duration > 1000) {
logWarn(`R43: 慢操作: ${name} 耗时 ${duration.toFixed(0)}ms`);
}
return duration;
}
/** R43: 获取性能指标 */
export function getPerfMetrics(): PerfMetric[] {
return [..._perfMetrics];
}
/** R43: 获取平均性能指标 */
export function getAvgPerfMetric(name: string): number {
const metrics = _perfMetrics.filter(m => m.name === name);
if (metrics.length === 0) return 0;
return metrics.reduce((sum, m) => sum + m.duration, 0) / metrics.length;
}
/** R43: 清空性能指标 */
export function clearPerfMetrics(): void {
_perfMetrics.length = 0;
_perfTimers.clear();
}
// ══════════════════════════════════════════════
// R48: 配置验证 — 启动时验证关键配置
// ══════════════════════════════════════════════
export interface ConfigValidationResult {
valid: boolean;
warnings: string[];
errors: string[];
}
/** R48: 验证应用配置 */
export function validateConfig(config: Record<string, unknown>): ConfigValidationResult {
const warnings: string[] = [];
const errors: string[] = [];
// 验证 numCtx
const numCtx = config.numCtx as number;
if (numCtx !== undefined) {
if (numCtx < 2048) {
warnings.push(`numCtx=${numCtx} 过小,可能导致上下文截断。建议至少 4096。`);
}
if (numCtx > 131072) {
warnings.push(`numCtx=${numCtx} 过大,可能导致内存不足。建议不超过 131072。`);
}
}
// 验证 temperature
const temperature = config.temperature as number;
if (temperature !== undefined) {
if (temperature < 0 || temperature > 2) {
errors.push(`temperature=${temperature} 超出有效范围 [0, 2]`);
}
}
// 验证 maxTurns
const maxTurns = config.maxTurns as number;
if (maxTurns !== undefined) {
if (maxTurns < 1) {
errors.push(`maxTurns=${maxTurns} 不能小于 1`);
}
if (maxTurns > 50) {
warnings.push(`maxTurns=${maxTurns} 过大,可能导致长时间运行。建议不超过 20。`);
}
}
// 验证 streamTimeout
const streamTimeout = config.streamTimeout as number;
if (streamTimeout !== undefined) {
if (streamTimeout < 10000) {
warnings.push(`streamTimeout=${streamTimeout} 过短,可能导致大模型生成被中断。建议至少 30000ms。`);
}
}
// 验证 subAgentTimeout
const subAgentTimeout = config.subAgentTimeout as number;
if (subAgentTimeout !== undefined && subAgentTimeout < 5000) {
warnings.push(`subAgentTimeout=${subAgentTimeout} 过短,子代理可能无法完成任务。`);
}
return {
valid: errors.length === 0,
warnings,
errors,
};
}
// ══════════════════════════════════════════════
// R50: 健康检查 — 系统健康监控
// ══════════════════════════════════════════════
export interface HealthCheckResult {
status: 'healthy' | 'degraded' | 'unhealthy';
checks: Array<{ name: string; status: 'pass' | 'fail' | 'warn'; message: string }>;
timestamp: number;
}
/** R50: 执行系统健康检查 */
export async function runHealthCheck(): Promise<HealthCheckResult> {
const checks: Array<{ name: string; status: 'pass' | 'fail' | 'warn'; message: string }> = [];
// 检查 1: 桌面 API 可用性
const bridge = window.metonaDesktop;
if (bridge?.isDesktop) {
checks.push({ name: '桌面 API', status: 'pass', message: '桌面 API 可用' });
} else {
checks.push({ name: '桌面 API', status: 'fail', message: '桌面 API 不可用(Web 模式)' });
}
// 检查 2: 数据库可用性
if (bridge?.db) {
try {
const sessions = await bridge.db.getAllSessions();
checks.push({ name: '数据库', status: 'pass', message: `数据库正常(${sessions.length} 个会话)` });
} catch (err) {
checks.push({ name: '数据库', status: 'fail', message: `数据库访问失败: ${(err as Error).message}` });
}
} else {
checks.push({ name: '数据库', status: 'warn', message: '数据库 API 不可用' });
}
// 检查 3: 内存使用
const memInfo = (performance as any).memory;
if (memInfo) {
const usedMB = (memInfo.usedJSHeapSize / 1024 / 1024).toFixed(0);
const limitMB = (memInfo.jsHeapSizeLimit / 1024 / 1024).toFixed(0);
const usageRatio = memInfo.usedJSHeapSize / memInfo.jsHeapSizeLimit;
if (usageRatio > 0.8) {
checks.push({ name: '内存', status: 'warn', message: `内存使用较高: ${usedMB}/${limitMB}MB (${(usageRatio * 100).toFixed(0)}%)` });
} else {
checks.push({ name: '内存', status: 'pass', message: `内存使用正常: ${usedMB}/${limitMB}MB` });
}
} else {
checks.push({ name: '内存', status: 'pass', message: '内存监控不可用(非 Chromium' });
}
// 检查 4: 工作空间可用性
if (bridge?.workspace) {
try {
const result = await bridge.workspace.getDir();
if (result.dir) {
checks.push({ name: '工作空间', status: 'pass', message: `工作空间: ${result.dir}` });
} else {
checks.push({ name: '工作空间', status: 'warn', message: '工作空间未设置' });
}
} catch {
checks.push({ name: '工作空间', status: 'warn', message: '工作空间访问失败' });
}
}
// 检查 5: 事件监听器数量(内存泄漏检测)
const listenerCount = _trackedListeners.size;
if (listenerCount > 100) {
checks.push({ name: '事件监听器', status: 'warn', message: `追踪的事件监听器较多: ${listenerCount} 个,可能存在内存泄漏` });
} else {
checks.push({ name: '事件监听器', status: 'pass', message: `事件监听器数量正常: ${listenerCount}` });
}
// 确定整体状态
const hasFail = checks.some(c => c.status === 'fail');
const hasWarn = checks.some(c => c.status === 'warn');
const status: 'healthy' | 'degraded' | 'unhealthy' = hasFail ? 'unhealthy' : hasWarn ? 'degraded' : 'healthy';
return { status, checks, timestamp: Date.now() };
}
/** R50: 格式化健康检查结果为可读字符串 */
export function formatHealthCheck(result: HealthCheckResult): string {
const statusIcon = result.status === 'healthy' ? '✅' : result.status === 'degraded' ? '⚠️' : '❌';
const lines = [`${statusIcon} 系统健康检查 — ${result.status.toUpperCase()}`, ''];
for (const check of result.checks) {
const icon = check.status === 'pass' ? '✅' : check.status === 'warn' ? '⚠️' : '❌';
lines.push(`${icon} ${check.name}: ${check.message}`);
}
return lines.join('\n');
}