feat: 升级至 v0.2.1 — 流式渲染修复、安全增强、工具自动执行

流式渲染修复:
- runId 机制防止 abort 后旧流事件污染新 run
- run lock 防止并发 run 污染引擎状态
- abort race 提前退出工具执行等待
- TERMINATED 状态通过 stateChange 发射
- tool_call_delta 流式参数拼接 + pending 占位替换
- 首轮卡片创建路径统一,traceStep 按 ID 精确匹配
- compressed 事件转发为 toast 通知

安全增强:
- ConfirmationHook 支持持久化自动执行(跨会话)
- 设置面板新增自动执行工具管理 UI
- SandboxManager 双重安全校验 fail-closed
- 审计日志链式哈希防篡改
- PromptInjectionDefender 中文注入标记清理
- scanCode 28 模式 + base64/$() 检测
- validatePath realpathSync 防符号链接逃逸
- code-search 使用 execFile 防命令注入

新增工具:
- file_editor、code_search、task_manager、diff_viewer

其他:
- Agent Loop 加 PARSING/REFLECTING 状态 + 指数退避重试
- MemoryManager TF-IDF 语义检索
- run_command Windows 中文编码修复(chcp 65001)
- 版本号 0.2.0 → 0.2.1
This commit is contained in:
thzxx
2026-07-12 12:54:52 +08:00
parent 469f53b623
commit 3c5aea8fb7
41 changed files with 4972 additions and 423 deletions
+70 -11
View File
@@ -30,6 +30,7 @@ import { WindowManager } from './services/window-manager.service';
import { MCPManager } from './services/mcp-manager.service';
import { ContextBuilder } from './harness/prompts/context-builder';
import { MemoryManager } from './harness/memory/manager';
import { MemoryConsolidator } from './harness/memory/consolidator';
import { registerAllIPCHandlers } from './ipc/handlers';
import { AgentLoopEngine } from './harness/agent-loop';
import { ToolRegistry } from './harness/tools/registry';
@@ -43,8 +44,14 @@ import {
RunCommandTool,
WebBrowserTool, cleanupBrowser,
DelegateTaskTool,
// v0.2.0 新增工具
FileEditorTool,
CodeSearchTool,
TaskManagerTool,
DiffViewerTool,
} from './harness/tools/built-in';
import { AuditLogHook, MemoryTriggerHook, PermissionCheckHook, RateLimitHook } from './harness/hooks';
import { ConfirmationHook } from './harness/hooks/confirmation-hook';
import { PolicyEngine } from './harness/sandbox/permissions';
import { SandboxManager } from './harness/sandbox/sandbox';
import { PromptInjectionDefender } from './harness/security/prompt-injection-defense';
@@ -150,6 +157,19 @@ async function initialize(): Promise<void> {
// ===== 步骤 5: 工作空间文件 + System Prompt =====
const contextBuilder = new ContextBuilder();
// ===== v0.2.0: 安全模块(必须在工具注册之前,便于 RunCommandTool 注入 SandboxManager=====
const policyEngine = new PolicyEngine();
const sandboxManager = new SandboxManager({
allowedPaths: [workspaceInfo.path],
networkPolicy: 'allowlist',
});
const promptDefender = new PromptInjectionDefender();
const outputValidator = new OutputValidator();
// v0.2.0: ConfirmationHook(提前创建,mainWindow 创建后再注入)
// 注入 ConfigService 以支持持久化自动执行设置
const confirmationHook = new ConfirmationHook(null, configService);
// ===== 步骤 6: 注册内置工具 =====
const toolRegistry = new ToolRegistry();
toolRegistry.registerBuiltin(new ReadFileTool());
@@ -157,6 +177,11 @@ async function initialize(): Promise<void> {
toolRegistry.registerBuiltin(new ListDirectoryTool());
toolRegistry.registerBuiltin(new SearchFilesTool());
// v0.2.0: 新增文件工具
toolRegistry.registerBuiltin(new FileEditorTool());
toolRegistry.registerBuiltin(new CodeSearchTool());
toolRegistry.registerBuiltin(new DiffViewerTool());
// WebFetchTool 先于 WebSearchTool 构造,注入为依赖
const webFetchTool = new WebFetchTool();
toolRegistry.registerBuiltin(webFetchTool);
@@ -164,7 +189,14 @@ async function initialize(): Promise<void> {
toolRegistry.registerBuiltin(new MemoryStoreTool(memoryManager));
toolRegistry.registerBuiltin(new MemorySearchTool(memoryManager));
toolRegistry.registerBuiltin(new RunCommandTool());
// v0.2.0: RunCommandTool 注入 SandboxManager
const runCommandTool = new RunCommandTool();
runCommandTool.setSandboxManager(sandboxManager);
toolRegistry.registerBuiltin(runCommandTool);
// v0.2.0: 任务管理工具
toolRegistry.registerBuiltin(new TaskManagerTool(() => db));
// 注册 Web Browser 统一浏览器工具
toolRegistry.registerBuiltin(new WebBrowserTool());
@@ -176,19 +208,13 @@ async function initialize(): Promise<void> {
log.warn('MCP Manager initialization error:', err);
});
// ===== 安全模块 =====
const policyEngine = new PolicyEngine();
const sandboxManager = new SandboxManager({
allowedPaths: [workspaceInfo.path],
networkPolicy: 'allowlist',
});
const promptDefender = new PromptInjectionDefender();
const outputValidator = new OutputValidator();
// ===== Hooks =====
// v0.2.0: ConfirmationHook 注入到 preToolHooks 管道
// 注意:setToolDefs 延迟到 DelegateTaskTool 注册后调用,确保包含所有工具的风险等级
const preToolHooks = [
new PermissionCheckHook(policyEngine),
new RateLimitHook(20),
confirmationHook,
];
const postToolHooks = [
new AuditLogHook(auditService),
@@ -215,6 +241,9 @@ async function initialize(): Promise<void> {
agentLoop.setTools(toolRegistry.listTools());
agentLoop.setWorkspacePath(workspaceInfo.path);
// ===== Memory Consolidator(会话结束 AI 提取重要记忆到 MEMORY.md=====
const memoryConsolidator = new MemoryConsolidator(adapter, workspaceService);
// ===== Task Orchestrator(子任务委派)=====
const orchestrator = new TaskOrchestrator(
agentLoop, toolRegistry, preToolHooks, postToolHooks,
@@ -227,12 +256,16 @@ async function initialize(): Promise<void> {
toolRegistry.registerBuiltin(new DelegateTaskTool(orchestrator));
// 重新设置工具列表,包含新注册的 delegate_task
agentLoop.setTools(toolRegistry.listTools());
// v0.2.0: 在所有工具(包括 DelegateTaskTool)注册完成后,刷新 ConfirmationHook 的工具定义缓存
confirmationHook.setToolDefs(toolRegistry.listAllTools());
// ===== 热重载 Adapter 回调(设置变更时触发)=====
let lastProvider = configService.get<string>('llm.provider') ?? '';
const reloadAdapter = () => {
try {
const newAdapter = createAdapter();
agentLoop.setAdapter(newAdapter);
memoryConsolidator.setAdapter(newAdapter);
// Provider 切换时同步 contextLength:仅 Ollama 使用 numCtx 作为有效上下文窗口
const provider = configService.get<string>('llm.provider') ?? '';
if (provider === 'ollama') {
@@ -242,8 +275,30 @@ async function initialize(): Promise<void> {
agentLoop.updateConfig({ contextLength: undefined });
}
log.info(`[CONFIG] Adapter reloaded: provider=${provider}`);
// 通知渲染进程 Provider 已切换(UI 显示 Toast + 系统消息)
if (mainWindow && !mainWindow.isDestroyed()) {
if (lastProvider && lastProvider !== provider) {
mainWindow.webContents.send('agent:providerSwitched', {
from: lastProvider,
to: provider,
reason: 'config_changed',
});
}
mainWindow.webContents.send('toast:show', {
type: 'success',
message: `Provider 已切换: ${lastProvider || '未知'}${provider}`,
});
}
lastProvider = provider;
} catch (err) {
log.error(`[CONFIG] Failed to reload adapter: ${(err as Error).message}`);
// 通知渲染进程 Provider 切换失败(UI 显示错误 Toast)
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('toast:show', {
type: 'error',
message: `Provider 切换失败: ${(err as Error).message}`,
});
}
}
};
@@ -255,6 +310,9 @@ async function initialize(): Promise<void> {
title: 'MetonaAI Desktop',
});
// v0.2.0: 为 ConfirmationHook 注入主窗口(用于向渲染进程发送确认请求)
confirmationHook.setMainWindow(mainWindow);
// ===== 系统托盘 =====
const resourcesPath = join(__dirname, '../../assets');
trayManager = new TrayManager(resourcesPath);
@@ -267,7 +325,7 @@ async function initialize(): Promise<void> {
agentLoop.on('stateChange', (data: { previous: string; current: string; state?: string }) => {
const statusMap: Record<string, 'idle' | 'thinking' | 'executing' | 'error'> = {
INIT: 'idle', THINKING: 'thinking', PARSING: 'thinking',
EXECUTING: 'executing', OBSERVING: 'executing', REFLECTING: 'thinking',
EXECUTING: 'executing', OBSERVING: 'thinking', REFLECTING: 'thinking',
COMPRESSING: 'thinking', TERMINATED: 'idle',
};
const stateValue = (data.current || data.state) ?? '';
@@ -289,6 +347,7 @@ async function initialize(): Promise<void> {
contextBuilder, agentLoop, toolRegistry, auditService,
sessionRecorder, memoryManager, mcpManager, createAdapter,
promptDefender, outputValidator,
confirmationHook, memoryConsolidator,
);
// TODO: Initialize UpdateService for auto-update functionality