refactor: 移除多余依赖,统一 MUI 为唯一 UI 库
- 移除 radix-ui、clsx、tailwind-merge、class-variance-authority、react-router-dom - 前后端全面适配 MUI 组件,清理残留 Tailwind 工具类引用 - 优化 Agent Loop 引擎、IPC 处理器、Provider Adapter 等后端模块 - 新增 Agent 网络工具通用设计文档 v2
This commit is contained in:
+93
-13
@@ -20,6 +20,8 @@ import type { AuditService } from '../services/audit.service';
|
||||
import type { SessionRecorder } from '../services/session-recorder.service';
|
||||
import type { MemoryManager } from '../harness/memory/manager';
|
||||
import type { MCPManager } from '../services/mcp-manager.service';
|
||||
import type { PromptInjectionDefender } from '../harness/security/prompt-injection-defense';
|
||||
import type { OutputValidator } from '../harness/verification/output-validator';
|
||||
import type { MetonaMessage, MetonaStreamEvent } from '../harness/types';
|
||||
import { MetonaErrorCode, MetonaStreamEventType } from '../harness/types';
|
||||
import type { MetonaError } from '../harness/types';
|
||||
@@ -35,12 +37,14 @@ export function registerAllIPCHandlers(
|
||||
workspaceService: WorkspaceService,
|
||||
contextBuilder: ContextBuilder,
|
||||
agentLoop: AgentLoopEngine,
|
||||
_toolRegistry: ToolRegistry,
|
||||
toolRegistry: ToolRegistry,
|
||||
auditService: AuditService,
|
||||
sessionRecorder: SessionRecorder,
|
||||
memoryManager: MemoryManager,
|
||||
mcpManager: MCPManager,
|
||||
reloadAdapter: () => void,
|
||||
promptInjectionDefender: PromptInjectionDefender,
|
||||
outputValidator: OutputValidator,
|
||||
): void {
|
||||
|
||||
// ===== Agent 交互 =====
|
||||
@@ -55,11 +59,12 @@ export function registerAllIPCHandlers(
|
||||
auditService.logSessionStart(sessionId);
|
||||
|
||||
// 保存用户消息到数据库
|
||||
// IPC boundary: frontend sends attachments not defined in the MetonaMessage type
|
||||
sessionService.saveMessage({
|
||||
sessionId,
|
||||
role: 'user',
|
||||
content: userMessage.content,
|
||||
attachments: (userMessage as any).attachments,
|
||||
attachments: (userMessage as MetonaMessage & { attachments?: unknown[] }).attachments,
|
||||
});
|
||||
|
||||
// 加载历史消息
|
||||
@@ -71,6 +76,8 @@ export function registerAllIPCHandlers(
|
||||
role: m.role as MetonaMessage['role'],
|
||||
content: m.content,
|
||||
reasoningContent: m.reasoningContent,
|
||||
toolCalls: m.toolCalls as MetonaMessage['toolCalls'],
|
||||
toolResult: m.toolResult as MetonaMessage['toolResult'],
|
||||
timestamp: m.timestamp,
|
||||
}));
|
||||
|
||||
@@ -98,6 +105,40 @@ export function registerAllIPCHandlers(
|
||||
agentLoop.on('stateChange', onStateChange);
|
||||
|
||||
try {
|
||||
// 提示注入检测(安全模块)
|
||||
const injectionResult = promptInjectionDefender.detect(userMessage.content);
|
||||
if (injectionResult.riskScore >= 7) {
|
||||
log.warn('[PromptInjectionDefender] Blocked message:', injectionResult.findings);
|
||||
if (!mainWindow.isDestroyed()) {
|
||||
const metonaError: MetonaError = {
|
||||
code: MetonaErrorCode.UNKNOWN,
|
||||
message: `Message blocked by prompt injection defense: ${injectionResult.recommendation}`,
|
||||
retryable: false,
|
||||
};
|
||||
const errorEvent: MetonaStreamEvent = {
|
||||
type: MetonaStreamEventType.ERROR,
|
||||
requestId: '',
|
||||
sessionId,
|
||||
iteration: 0,
|
||||
seq: 0,
|
||||
timestamp: Date.now(),
|
||||
error: metonaError,
|
||||
};
|
||||
mainWindow.webContents.send('agent:streamEvent', errorEvent);
|
||||
}
|
||||
// TRACE 层:停止录制
|
||||
sessionRecorder.stopRecording({
|
||||
totalIterations: 0,
|
||||
totalTokens: 0,
|
||||
durationMs: 0,
|
||||
terminationReason: 'error',
|
||||
});
|
||||
return { success: false, error: 'Message blocked by prompt injection defense' };
|
||||
}
|
||||
if (injectionResult.riskScore >= 4) {
|
||||
log.warn('[PromptInjectionDefender] Suspicious patterns detected:', injectionResult.findings);
|
||||
}
|
||||
|
||||
// TRACE 层:记录上下文构建
|
||||
sessionRecorder.recordContextBuilt({
|
||||
tokenCount: history.reduce((sum, m) => sum + Math.ceil(m.content.length / 2), 0),
|
||||
@@ -107,12 +148,47 @@ export function registerAllIPCHandlers(
|
||||
// 启动 Agent Loop
|
||||
const output = await agentLoop.runStream(userMessage, sessionId, history, systemPrompt);
|
||||
|
||||
// 保存 assistant 回复到数据库
|
||||
sessionService.saveMessage({
|
||||
sessionId,
|
||||
role: 'assistant',
|
||||
content: output.finalAnswer,
|
||||
});
|
||||
// 输出验证(不阻塞响应,仅记录警告)
|
||||
try {
|
||||
const validation = await outputValidator.validate(output.finalAnswer);
|
||||
if (!validation.valid || validation.issues.length > 0) {
|
||||
log.warn('[OutputValidator] Validation issues:', validation.issues);
|
||||
}
|
||||
log.debug(`[OutputValidator] Score: ${validation.score}, Valid: ${validation.valid}`);
|
||||
} catch (err) {
|
||||
log.error('[OutputValidator] Validation failed:', err);
|
||||
}
|
||||
|
||||
// 保存每轮迭代的 assistant 消息到数据库(含思考内容和工具调用)
|
||||
for (const step of output.iterations) {
|
||||
if (!step.thought) continue;
|
||||
|
||||
// 将 MetonaToolCall + MetonaToolResult 转换为前端 ToolCallInfo 格式
|
||||
const toolCallsWithResults = step.toolCalls?.map((tc) => {
|
||||
const result = step.toolResults?.find((r) => r.toolCallId === tc.id);
|
||||
return {
|
||||
id: tc.id,
|
||||
name: tc.name,
|
||||
args: tc.args,
|
||||
status: result?.success ? 'success' as const : 'error' as const,
|
||||
result: result?.result,
|
||||
durationMs: result?.durationMs,
|
||||
error: result?.error,
|
||||
};
|
||||
});
|
||||
|
||||
// 只有当有内容、思考内容或工具调用时才保存
|
||||
if (step.thought.content || step.thought.reasoningContent || toolCallsWithResults?.length) {
|
||||
sessionService.saveMessage({
|
||||
sessionId,
|
||||
role: 'assistant',
|
||||
content: step.thought.content,
|
||||
reasoningContent: step.thought.reasoningContent || undefined,
|
||||
toolCalls: toolCallsWithResults,
|
||||
iteration: step.iteration,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 Token 统计
|
||||
if (output.totalTokenUsage.totalTokens > 0) {
|
||||
@@ -284,8 +360,8 @@ export function registerAllIPCHandlers(
|
||||
try {
|
||||
await mcpManager.removeServer(name);
|
||||
return { success: true };
|
||||
} catch {
|
||||
return { success: false };
|
||||
} catch (error) {
|
||||
return { success: false, error: (error instanceof Error ? error.message : String(error)) };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -293,8 +369,8 @@ export function registerAllIPCHandlers(
|
||||
try {
|
||||
await mcpManager.toggleServer(name, enabled);
|
||||
return { success: true };
|
||||
} catch {
|
||||
return { success: false };
|
||||
} catch (error) {
|
||||
return { success: false, error: (error instanceof Error ? error.message : String(error)) };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -363,7 +439,7 @@ export function registerAllIPCHandlers(
|
||||
// ===== 工具管理 =====
|
||||
|
||||
ipcMain.handle('tools:list', async () => {
|
||||
return _toolRegistry.listTools().map((t) => ({
|
||||
return toolRegistry.listTools().map((t) => ({
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
category: t.category,
|
||||
@@ -407,8 +483,10 @@ export function registerAllIPCHandlers(
|
||||
ipcMain.handle('data:clearSessions', async () => {
|
||||
try {
|
||||
const db = sessionService.getDB();
|
||||
db.exec('BEGIN');
|
||||
db.exec('DELETE FROM messages');
|
||||
db.exec('DELETE FROM sessions');
|
||||
db.exec('COMMIT');
|
||||
log.info('[DATA] All sessions cleared');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
@@ -433,6 +511,7 @@ export function registerAllIPCHandlers(
|
||||
try {
|
||||
// 审计日志是 INSERT-ONLY,需要先禁用触发器
|
||||
const db = sessionService.getDB();
|
||||
db.exec('BEGIN');
|
||||
db.exec('DROP TRIGGER IF EXISTS audit_no_delete');
|
||||
db.exec('DELETE FROM audit_logs');
|
||||
db.exec(`
|
||||
@@ -441,6 +520,7 @@ export function registerAllIPCHandlers(
|
||||
SELECT RAISE(ABORT, 'Audit logs are INSERT-ONLY. Deletion is not allowed.');
|
||||
END
|
||||
`);
|
||||
db.exec('COMMIT');
|
||||
log.info('[DATA] Audit logs cleared');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user