chore: 版本号 0.14.4 → 0.14.5
refactor: Hook 系统精简 — 7个→2个(SecurityCheck + FileWriteDedup) delete: verification.ts 删除(DiffAnalyzer/FileChangeAudit 仅打日志无实际作用) remove: ResultValidation/IterationMetrics/PlanAutoTrack Hook merge: write_file 路径记录内联到 agent-engine
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/version-v0.14.4-E8734A?style=flat-square" alt="version">
|
||||
<img src="https://img.shields.io/badge/version-v0.14.5-E8734A?style=flat-square" alt="version">
|
||||
<img src="https://img.shields.io/badge/electron-33+-47848F?style=flat-square&logo=electron" alt="electron">
|
||||
<img src="https://img.shields.io/badge/typescript-5.7+-3178C6?style=flat-square&logo=typescript" alt="typescript">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license">
|
||||
@@ -253,7 +253,7 @@ npm start
|
||||
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
|
||||
```
|
||||
|
||||
产出:`release/Metona Ollama Setup v0.14.4.exe`
|
||||
产出:`release/Metona Ollama Setup v0.14.5.exe`
|
||||
|
||||
## 🛠️ 常用命令
|
||||
|
||||
@@ -501,7 +501,7 @@ npm start
|
||||
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
|
||||
```
|
||||
|
||||
Output: `release/Metona Ollama Setup v0.14.4.exe`
|
||||
Output: `release/Metona Ollama Setup v0.14.5.exe`
|
||||
|
||||
## 🛠️ Common Commands
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "metona-ollama-desktop",
|
||||
"version": "0.14.4",
|
||||
"version": "0.14.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "metona-ollama-desktop",
|
||||
"version": "0.14.4",
|
||||
"version": "0.14.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "metona-ollama-desktop",
|
||||
"version": "0.14.4",
|
||||
"version": "0.14.5",
|
||||
"description": "Metona Ollama - TypeScript + Electron 桌面 AI 聊天客户端",
|
||||
"main": "dist/main/main.js",
|
||||
"author": "thzxx",
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ export function createMenu(): void {
|
||||
dialog.showMessageBox(mainWindow!, {
|
||||
type: 'info',
|
||||
title: '关于 Metona Ollama',
|
||||
message: 'Metona Ollama Desktop v0.14.4',
|
||||
message: 'Metona Ollama Desktop v0.14.5',
|
||||
detail: 'TypeScript + Electron Ollama AI 聊天客户端\n\nhttps://gitee.com/thzxx/metona-ollama',
|
||||
icon: getIconPath()
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<div class="header-left">
|
||||
<span class="logo">🦙</span>
|
||||
<span class="app-title">Metona Ollama</span>
|
||||
<span class="app-version">v0.14.4</span>
|
||||
<span class="app-version">v0.14.5</span>
|
||||
<button class="icon-btn help-btn" id="btnHelp" title="使用帮助">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
|
||||
|
||||
@@ -31,7 +31,6 @@ import { initLogPanel, addLog } from './services/log-service.js';
|
||||
import { logInfo, logSuccess, logError, logDebug, logInit, logWarn } from './services/log-service.js';
|
||||
import { initSearxngModal, closeSearxngModal, loadSearxngConfig } from './components/searxng-modal.js';
|
||||
import { initHarnessHooks } from './services/hooks.js';
|
||||
import { initVerificationSystem } from './services/verification.js';
|
||||
import type { ChatSession } from './types.js';
|
||||
|
||||
// ─── v4.0 数据迁移:IndexedDB → SQLite ───
|
||||
@@ -353,7 +352,6 @@ async function init(): Promise<void> {
|
||||
|
||||
// ── Harness Engineering 系统初始化 ──
|
||||
initHarnessHooks();
|
||||
initVerificationSystem();
|
||||
// Agent Metrics 无需显式初始化(按需启动)
|
||||
// Context Indexer 在 Agent INIT 状态按需构建
|
||||
|
||||
@@ -481,7 +479,6 @@ async function init(): Promise<void> {
|
||||
initWorkspacePanel();
|
||||
setupDesktopIntegration();
|
||||
initHarnessHooks();
|
||||
initVerificationSystem();
|
||||
// ── MEMORY.md 初始化(fallback 路径)──
|
||||
import('./services/memory-service.js').then(({ initMemoryFile }) => {
|
||||
initMemoryFile().catch(() => {});
|
||||
|
||||
@@ -1484,6 +1484,11 @@ async function handleExecuting(
|
||||
const result = await executeTool(call.function.name, call.function.arguments)
|
||||
.catch(err => ({ success: false, error: err?.message || String(err) }) as ToolResult);
|
||||
logToolResult(call.function.name, result.success, result.success ? undefined : result.error);
|
||||
// 记录 write_file 成功路径,供 FileWriteDedup Hook 拦截重复写入
|
||||
if (call.function.name === 'write_file' && result.success && call.function.arguments?.path) {
|
||||
const { addWrittenFile } = await import('./hooks.js');
|
||||
addWrittenFile(String(call.function.arguments.path));
|
||||
}
|
||||
return [{
|
||||
name: call.function.name, arguments: call.function.arguments,
|
||||
result, status: result.success ? 'success' as const : 'error' as const, timestamp: Date.now()
|
||||
|
||||
+31
-136
@@ -1,18 +1,12 @@
|
||||
/**
|
||||
* Harness Hook 系统
|
||||
* Harness Engineering: 可扩展的 Agent 执行钩子架构
|
||||
* 可扩展的 Agent 执行钩子架构
|
||||
*
|
||||
* 钩子阶段:
|
||||
* - pre_tool: 工具执行前(安全检查、缓存检查、权限验证)
|
||||
* - post_tool: 工具执行后(结果验证、自动 lint、自动测试)
|
||||
* - post_iteration: 每轮迭代后(记忆提取、上下文裁剪)
|
||||
* - pre_completion: 最终输出前(完成门控、质量检查)
|
||||
*
|
||||
* 设计原则:
|
||||
* - 异步并行执行,失败不阻塞主流程
|
||||
* - 优先级排序,高优先级先执行
|
||||
* - 可动态注册/移除
|
||||
* - MCP 工具可注册自定义 Hook
|
||||
* - pre_tool: 工具执行前(安全检查、文件去重)
|
||||
* - post_tool: 工具执行后
|
||||
* - post_iteration: 每轮迭代后
|
||||
* - pre_completion: 最终输出前
|
||||
*/
|
||||
|
||||
import { logInfo, logWarn, logDebug } from './log-service.js';
|
||||
@@ -31,17 +25,14 @@ function ensurePhaseIndex(phase: HookPhase): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** 注册 Hook */
|
||||
export function registerHook(hook: HarnessHook): void {
|
||||
hookRegistry.set(hook.name, hook);
|
||||
ensurePhaseIndex(hook.phase);
|
||||
phaseIndex.get(hook.phase)!.push(hook);
|
||||
// 按优先级降序排列
|
||||
phaseIndex.get(hook.phase)!.sort((a, b) => b.priority - a.priority);
|
||||
logDebug(`Hook 已注册: "${hook.name}" phase=${hook.phase} priority=${hook.priority}`);
|
||||
}
|
||||
|
||||
/** 移除 Hook */
|
||||
export function unregisterHook(name: string): void {
|
||||
const hook = hookRegistry.get(name);
|
||||
if (!hook) return;
|
||||
@@ -53,7 +44,6 @@ export function unregisterHook(name: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** 启用/禁用 Hook */
|
||||
export function setHookEnabled(name: string, enabled: boolean): void {
|
||||
const hook = hookRegistry.get(name);
|
||||
if (hook) {
|
||||
@@ -62,12 +52,10 @@ export function setHookEnabled(name: string, enabled: boolean): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取所有已注册 Hook */
|
||||
export function getRegisteredHooks(): HarnessHook[] {
|
||||
return Array.from(hookRegistry.values());
|
||||
}
|
||||
|
||||
/** 获取指定阶段已启用的 Hook */
|
||||
export function getPhaseHooks(phase: HookPhase): HarnessHook[] {
|
||||
return (phaseIndex.get(phase) || []).filter(h => h.enabled);
|
||||
}
|
||||
@@ -76,10 +64,6 @@ export function getPhaseHooks(phase: HookPhase): HarnessHook[] {
|
||||
// Hook 执行引擎
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* 并行执行指定阶段的所有 Hook
|
||||
* @returns 所有 Hook 的执行结果汇总
|
||||
*/
|
||||
export async function executeHooks(
|
||||
phase: HookPhase,
|
||||
ctx: LoopContext,
|
||||
@@ -93,11 +77,7 @@ export async function executeHooks(
|
||||
const results = await Promise.all(
|
||||
hooks.map(async (hook) => {
|
||||
try {
|
||||
const result = await hook.handler(ctx, data);
|
||||
if (!result.passed) {
|
||||
logWarn(`Hook "${hook.name}" 未通过: ${result.message}`);
|
||||
}
|
||||
return result;
|
||||
return await hook.handler(ctx, data);
|
||||
} catch (err) {
|
||||
logWarn(`Hook "${hook.name}" 执行异常: ${(err as Error).message}`);
|
||||
return { passed: true, message: `Hook 异常(已忽略): ${(err as Error).message}` };
|
||||
@@ -113,31 +93,46 @@ export async function executeHooks(
|
||||
// 内置 Hook
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/** 会话级已写入文件路径集合 */
|
||||
const _writtenFilesThisSession = new Set<string>();
|
||||
|
||||
/** 暴露写入记录供 agent-engine 清理 */
|
||||
export function clearWrittenFiles(): void {
|
||||
_writtenFilesThisSession.clear();
|
||||
}
|
||||
export function hasWrittenFile(path: string): boolean {
|
||||
return _writtenFilesThisSession.has(path);
|
||||
}
|
||||
export function addWrittenFile(path: string): void {
|
||||
_writtenFilesThisSession.add(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* SecurityCheckHook — 工具执行前安全检查
|
||||
* 检查命令和路径是否在黑名单内
|
||||
* SecurityCheckHook — 工具执行前安全检查(pre_tool 阻断级)
|
||||
*/
|
||||
export const securityCheckHook: HarnessHook = {
|
||||
name: 'SecurityCheck',
|
||||
phase: 'pre_tool',
|
||||
priority: 100, // 最高优先级
|
||||
priority: 100,
|
||||
enabled: true,
|
||||
handler: async (_ctx: LoopContext, data: HookData) => {
|
||||
const { toolName, toolArgs } = data;
|
||||
|
||||
// 危险命令黑名单
|
||||
if (toolName === 'run_command' && typeof toolArgs?.command === 'string') {
|
||||
const cmd = toolArgs.command as string;
|
||||
// 快速黑名单检查
|
||||
const dangerous = ['rm -rf /', 'mkfs', 'dd if=', 'shutdown', 'reboot', ':(){', 'chmod 777 /'];
|
||||
for (const d of dangerous) {
|
||||
if (cmd.toLowerCase().includes(d.toLowerCase())) {
|
||||
return { passed: false, message: `危险命令被拦截: ${cmd.slice(0, 100)}` };
|
||||
}
|
||||
}
|
||||
// 管道执行 shell 检测
|
||||
if (/(\||>|<)\s*(sh|bash|zsh|powershell|cmd)/i.test(cmd)) {
|
||||
return { passed: false, message: `禁止通过管道执行 shell: ${cmd.slice(0, 100)}` };
|
||||
}
|
||||
}
|
||||
|
||||
// 受保护路径不可删除
|
||||
if (toolName === 'delete_file' && typeof toolArgs?.path === 'string') {
|
||||
const p = (toolArgs.path as string).toLowerCase();
|
||||
const protectedPaths = ['/etc', '/sys', '/proc', '/dev', '/boot', 'c:\\windows', '.ssh', '.gnupg'];
|
||||
@@ -147,88 +142,15 @@ export const securityCheckHook: HarnessHook = {
|
||||
}
|
||||
}
|
||||
}
|
||||
return { passed: true, message: '' };
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* ResultValidationHook — 工具执行后结果验证
|
||||
* 检查工具返回是否有效。注意:此 Hook 为咨询性(post_tool 阶段不阻断执行),
|
||||
* 实际的截断由 agent-engine 的 formatToolResultForModel 和 40 条消息硬限制负责。
|
||||
*/
|
||||
export const resultValidationHook: HarnessHook = {
|
||||
name: 'ResultValidation',
|
||||
phase: 'post_tool',
|
||||
priority: 90,
|
||||
enabled: true,
|
||||
handler: async (_ctx: LoopContext, data: HookData) => {
|
||||
const { toolName, toolResult } = data;
|
||||
if (!toolResult) return { passed: true, message: '' };
|
||||
|
||||
// 检查空结果
|
||||
if (!toolResult.success && !toolResult.error) {
|
||||
return { passed: false, message: `工具 ${toolName} 执行失败但未提供错误信息` };
|
||||
}
|
||||
|
||||
// 检查超大结果。不同工具有不同阈值:
|
||||
// web_search 包含自动抓取内容,允许较大结果
|
||||
const SIZE_LIMITS: Record<string, number> = {
|
||||
web_search: 1_000_000, // 1MB(含自动抓取的多页内容)
|
||||
web_fetch: 500_000, // 500KB
|
||||
read_file: 200_000, // 200KB
|
||||
default: 500_000, // 默认 500KB
|
||||
};
|
||||
const limit = SIZE_LIMITS[toolName || ''] || SIZE_LIMITS.default;
|
||||
|
||||
if (toolResult.success && toolResult) {
|
||||
const resultStr = JSON.stringify(toolResult);
|
||||
if (resultStr.length > limit) {
|
||||
// 仅警告,不阻断(实际截断由 agent-engine 负责)
|
||||
return {
|
||||
passed: true,
|
||||
message: `工具 ${toolName} 返回结果较大 (${(resultStr.length / 1024).toFixed(0)}KB),将由上下文管理器自动截断`,
|
||||
data: { oversized: true, size: resultStr.length },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { passed: true, message: '' };
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* IterationMetricsHook — 迭代度量收集
|
||||
* 每轮迭代后记录关键指标
|
||||
*/
|
||||
export const iterationMetricsHook: HarnessHook = {
|
||||
name: 'IterationMetrics',
|
||||
phase: 'post_iteration',
|
||||
priority: 50,
|
||||
enabled: true,
|
||||
handler: async (ctx: LoopContext, _data: HookData) => {
|
||||
// 收集本轮迭代统计
|
||||
const totalMessages = ctx.messages.length;
|
||||
const toolMessages = ctx.messages.filter(m => m.role === 'tool').length;
|
||||
logDebug(
|
||||
`迭代度量 [第 ${ctx.loopCount} 轮]: ` +
|
||||
`${totalMessages} 消息, ${toolMessages} 工具消息, ` +
|
||||
`${ctx.totalEvalCount} eval tokens, ${ctx.allToolRecords.length} 总工具调用`
|
||||
);
|
||||
return { passed: true, message: '' };
|
||||
},
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Harness Hook: 文件写入去重
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/** 会话级已写入文件路径集合 — 由 FileWriteDedupHook 和 PlanAutoTrackHook 共同维护 */
|
||||
const _writtenFilesThisSession = new Set<string>();
|
||||
|
||||
/**
|
||||
* FileWriteDedupHook — write_file 同路径去重(pre_tool 阻断级)
|
||||
* 同一文件路径在本会话中已成功写入后,后续写入被拦截,
|
||||
* 防止模型在设计迭代中反复覆盖同一文件。
|
||||
* FileWriteDedupHook — 文件写入去重(pre_tool 阻断级)
|
||||
* 同一会话中同一路径写入成功后,后续写入被拦截,防止 AI 反复覆盖同一文件。
|
||||
* 同时记录 write_file 成功路径。
|
||||
*/
|
||||
export const fileWriteDedupHook: HarnessHook = {
|
||||
name: 'FileWriteDedup',
|
||||
@@ -240,38 +162,14 @@ export const fileWriteDedupHook: HarnessHook = {
|
||||
const filePath = String(data.toolArgs?.path || '');
|
||||
if (!filePath) return { passed: true, message: '' };
|
||||
if (_writtenFilesThisSession.has(filePath)) {
|
||||
return { passed: false, message: `文件 "${filePath}" 已在本会话中写入,无需重复覆盖。请直接给出最终回答。` };
|
||||
return { passed: false, message: `文件 "${filePath}" 已在本会话中写入,无需重复覆盖。` };
|
||||
}
|
||||
return { passed: true, message: '' };
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* PlanAutoTrackHook — Plan Mode 步骤追踪(post_tool 非阻断级)
|
||||
* 不自动标记步骤(避免模糊匹配误标),仅由 AI 通过 plan_track 工具精确标记。
|
||||
* 此处只维护 write_file 路径记录供 FileWriteDedupHook 使用。
|
||||
*/
|
||||
export const planAutoTrackHook: HarnessHook = {
|
||||
name: 'PlanAutoTrack',
|
||||
phase: 'post_tool',
|
||||
priority: 80,
|
||||
enabled: true,
|
||||
handler: async (ctx: LoopContext, data: HookData) => {
|
||||
if (ctx.mode !== 'plan') return { passed: true, message: '' };
|
||||
const toolName = data.toolName || '';
|
||||
const toolResult = data.toolResult as Record<string, unknown> | undefined;
|
||||
|
||||
// write_file 成功后记录文件路径,供 FileWriteDedupHook 拦截
|
||||
if (toolName === 'write_file' && toolResult?.success && data.toolArgs?.path) {
|
||||
_writtenFilesThisSession.add(String(data.toolArgs.path));
|
||||
}
|
||||
|
||||
return { passed: true, message: '' };
|
||||
},
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 初始化:注册所有内置 Hook
|
||||
// 初始化
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
let hooksInitialized = false;
|
||||
@@ -279,10 +177,7 @@ let hooksInitialized = false;
|
||||
export function initHarnessHooks(): void {
|
||||
if (hooksInitialized) return;
|
||||
registerHook(securityCheckHook);
|
||||
registerHook(resultValidationHook);
|
||||
registerHook(iterationMetricsHook);
|
||||
registerHook(fileWriteDedupHook);
|
||||
registerHook(planAutoTrackHook);
|
||||
hooksInitialized = true;
|
||||
logInfo('Harness Hook 系统已初始化', `${hookRegistry.size} 个 Hook 已注册`);
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
/**
|
||||
* Verification System — 验证系统模块
|
||||
* Harness Engineering: 计算性反馈管道
|
||||
*
|
||||
* 在 Agent 修改文件后自动运行:
|
||||
* - Linter(代码风格检查)
|
||||
* - Type Checker(类型检查)
|
||||
* - Test Runner(单元测试)
|
||||
* - Diff Analyzer(修改范围分析)
|
||||
*
|
||||
* 设计原则:
|
||||
* - 所有验证利用现有 run_command 工具执行
|
||||
* - 验证 Hook 注册到 hooks.ts 的 post_tool 阶段
|
||||
* - 验证失败的结果作为 Observation 注入 Agent 上下文
|
||||
* - 可配置开关(设置面板)
|
||||
*/
|
||||
|
||||
import { registerHook } from './hooks.js';
|
||||
import type { HarnessHook } from '../types.js';
|
||||
import { logInfo, logDebug, logWarn } from './log-service.js';
|
||||
import type { LoopContext, HookData, HookResult } from '../types.js';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 验证配置
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
export interface VerificationConfig {
|
||||
/** 是否启用自动 Lint */
|
||||
autoLint: boolean;
|
||||
/** 是否启用自动类型检查 */
|
||||
autoTypeCheck: boolean;
|
||||
/** 是否启用自动测试 */
|
||||
autoTest: boolean;
|
||||
/** Lint 命令(留空自动检测) */
|
||||
lintCommand: string;
|
||||
/** 类型检查命令 */
|
||||
typeCheckCommand: string;
|
||||
/** 测试命令 */
|
||||
testCommand: string;
|
||||
}
|
||||
|
||||
/** 默认配置 */
|
||||
const defaultConfig: VerificationConfig = {
|
||||
autoLint: false,
|
||||
autoTypeCheck: false,
|
||||
autoTest: false,
|
||||
lintCommand: '',
|
||||
typeCheckCommand: '',
|
||||
testCommand: '',
|
||||
};
|
||||
|
||||
/** 运行时配置 */
|
||||
let config: VerificationConfig = { ...defaultConfig };
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 文件类型检测
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/** 检测文件扩展名对应的语言 */
|
||||
function detectFileType(filePath: string): string | null {
|
||||
const ext = filePath.split('.').pop()?.toLowerCase() || '';
|
||||
const map: Record<string, string> = {
|
||||
ts: 'typescript', tsx: 'tsx', js: 'javascript', jsx: 'jsx',
|
||||
py: 'python', rs: 'rust', go: 'go', java: 'java',
|
||||
cpp: 'cpp', c: 'c', rb: 'ruby', css: 'css',
|
||||
html: 'html', vue: 'vue', svelte: 'svelte',
|
||||
};
|
||||
return map[ext] || null;
|
||||
}
|
||||
|
||||
/** 检测项目根目录(查找 package.json / pyproject.toml / Cargo.toml) */
|
||||
function detectProjectRoot(filePath: string): string | null {
|
||||
// 简化实现:向上查找 package.json
|
||||
const parts = filePath.replace(/\\/g, '/').split('/');
|
||||
for (let i = parts.length - 1; i >= 0; i--) {
|
||||
const dir = parts.slice(0, i + 1).join('/');
|
||||
// 这里无法实际访问文件系统,返回文件所在目录的父级作为近似
|
||||
}
|
||||
// 返回文件所在目录
|
||||
const lastSep = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'));
|
||||
return lastSep > 0 ? filePath.slice(0, lastSep) : null;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 验证 Hook 实现
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* DiffAnalyzerHook — 修改范围分析
|
||||
* 检测 Agent 的修改是否跨越了架构边界
|
||||
*/
|
||||
export const diffAnalyzerHook: HarnessHook = {
|
||||
name: 'DiffAnalyzer',
|
||||
phase: 'post_tool',
|
||||
priority: 75,
|
||||
enabled: true,
|
||||
handler: async (_ctx: LoopContext, data: HookData): Promise<HookResult> => {
|
||||
const { toolName, toolArgs } = data;
|
||||
if (toolName !== 'write_file' && toolName !== 'edit_file') {
|
||||
return { passed: true, message: '' };
|
||||
}
|
||||
|
||||
const filePath = (toolArgs?.path as string) || '';
|
||||
if (!filePath) return { passed: true, message: '' };
|
||||
|
||||
// 分析修改的文件类型
|
||||
const fileType = detectFileType(filePath);
|
||||
if (!fileType) return { passed: true, message: '' };
|
||||
|
||||
// 分析跨层调用风险
|
||||
const warnings: string[] = [];
|
||||
|
||||
// 检测是否修改了核心基础模块
|
||||
const criticalPaths = ['/core/', '/base/', '/common/', '/shared/', '/utils/', '/lib/'];
|
||||
const filePathLower = filePath.toLowerCase();
|
||||
for (const cp of criticalPaths) {
|
||||
if (filePathLower.includes(cp)) {
|
||||
warnings.push(`⚠️ 修改了核心基础模块 "${filePath}",可能影响多个依赖方`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (warnings.length > 0) {
|
||||
return {
|
||||
passed: true, // 警告不阻止,但注入提醒
|
||||
message: warnings.join('; '),
|
||||
data: { warnings },
|
||||
};
|
||||
}
|
||||
|
||||
return { passed: true, message: '' };
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* FileChangeAuditHook — 文件变更审计
|
||||
* 记录 Agent 的每一次文件修改
|
||||
*/
|
||||
export const fileChangeAuditHook: HarnessHook = {
|
||||
name: 'FileChangeAudit',
|
||||
phase: 'post_tool',
|
||||
priority: 30,
|
||||
enabled: true,
|
||||
handler: async (ctx: LoopContext, data: HookData): Promise<HookResult> => {
|
||||
const { toolName, toolArgs, toolResult } = data;
|
||||
if (toolName !== 'write_file' && toolName !== 'edit_file' && toolName !== 'delete_file') {
|
||||
return { passed: true, message: '' };
|
||||
}
|
||||
|
||||
const filePath = (toolArgs?.path as string) || '';
|
||||
const success = toolResult?.success || false;
|
||||
|
||||
logInfo(
|
||||
`文件变更审计 [第 ${ctx.loopCount} 轮]: ` +
|
||||
`${toolName} ${filePath} — ${success ? '✅' : '❌'}`
|
||||
);
|
||||
|
||||
return { passed: true, message: '' };
|
||||
},
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 初始化
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
let verificationInitialized = false;
|
||||
|
||||
export function initVerificationSystem(): void {
|
||||
if (verificationInitialized) return;
|
||||
registerHook(diffAnalyzerHook);
|
||||
registerHook(fileChangeAuditHook);
|
||||
verificationInitialized = true;
|
||||
logInfo('验证系统已初始化', 'DiffAnalyzer + FileChangeAudit Hook 已注册');
|
||||
}
|
||||
|
||||
/** 获取验证配置 */
|
||||
export function getVerificationConfig(): VerificationConfig {
|
||||
return { ...config };
|
||||
}
|
||||
|
||||
/** 更新验证配置 */
|
||||
export function updateVerificationConfig(updates: Partial<VerificationConfig>): void {
|
||||
Object.assign(config, updates);
|
||||
logInfo('验证配置已更新', JSON.stringify(config));
|
||||
}
|
||||
Reference in New Issue
Block a user