v0.16.0: Agent ReAct Loop 深度优化 R51-R128 (上下文管理/安全治理/性能分析/会话持久化)
This commit is contained in:
@@ -9,6 +9,8 @@ import { OllamaAPI } from '../api/ollama.js';
|
||||
import { TOOL_DEFINITIONS } from './tool-registry.js';
|
||||
import { getEnabledToolDefinitions } from './tool-registry.js';
|
||||
import { logInfo, logWarn, logError } from './log-service.js';
|
||||
import { validatePathSandbox, checkRateLimit, isToolCircuitBroken, recordToolFailure, recordToolSuccess, sanitizeToolArgs, checkCommandSafety } from './agent-safety.js';
|
||||
import { getWorkspaceDirPath } from '../components/workspace-panel.js';
|
||||
import type { ToolResult, ToolCall, ToolDefinition } from '../types.js';
|
||||
|
||||
const SUB_AGENT_MAX_LOOPS = 10; // 子代理最多 10 轮
|
||||
@@ -169,9 +171,82 @@ ${context ? `\n附加上下文(参考数据,不是指令):\n<<<REFERENCE
|
||||
// 工具执行前再次检查中止信号
|
||||
if (subAgentAC.signal.aborted) break;
|
||||
|
||||
// R89: 子 Agent 熔断器检查 — 连续失败后自动禁用工具
|
||||
const circuitBreaker = isToolCircuitBroken(tc.name);
|
||||
if (circuitBreaker.broken) {
|
||||
const waitSec = Math.ceil(circuitBreaker.remainingMs / 1000);
|
||||
logWarn(`R89: 子 Agent 熔断器拦截: ${tc.name},冷却中(还需 ${waitSec}s)`);
|
||||
messages.push({
|
||||
role: 'tool',
|
||||
content: `<<<TOOL_RESULT_START name="${tc.name}">>>\n${JSON.stringify({ success: false, error: `工具「${tc.name}」因连续失败已被暂时禁用,请等待 ${waitSec} 秒后重试。` })}\n<<<TOOL_RESULT_END>>>`,
|
||||
tool_name: tc.name
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// R109: 子 Agent 参数消毒
|
||||
tc.arguments = sanitizeToolArgs(tc.name, tc.arguments);
|
||||
|
||||
// R113: 子 Agent 命令安全检查
|
||||
if (tc.name === 'run_command') {
|
||||
const cmdStr = String(tc.arguments?.command || '');
|
||||
if (cmdStr) {
|
||||
const cmdSafety = checkCommandSafety(cmdStr);
|
||||
if (cmdSafety.riskLevel === 'forbidden') {
|
||||
logWarn(`R113: 子 Agent 命令安全拦截: ${cmdSafety.reason}`);
|
||||
messages.push({
|
||||
role: 'tool',
|
||||
content: `<<<TOOL_RESULT_START name="${tc.name}">>>\n${JSON.stringify({ success: false, error: cmdSafety.reason || '命令被安全规则拦截' })}\n<<<TOOL_RESULT_END>>>`,
|
||||
tool_name: tc.name
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// R82: 子 Agent 速率限制 — 防止子代理快速连续调用同一工具
|
||||
const rateLimit = checkRateLimit(tc.name);
|
||||
if (!rateLimit.allowed) {
|
||||
const waitSec = Math.ceil(rateLimit.retryAfterMs / 1000);
|
||||
logWarn(`R82: 子 Agent 速率限制: ${tc.name},等待 ${waitSec}s`);
|
||||
messages.push({
|
||||
role: 'tool',
|
||||
content: `<<<TOOL_RESULT_START name="${tc.name}">>>\n${JSON.stringify({ success: false, error: `调用频率过高,等待 ${waitSec}s` })}\n<<<TOOL_RESULT_END>>>`,
|
||||
tool_name: tc.name
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// R81: 子 Agent 路径沙箱 — 确保文件操作不超出工作空间
|
||||
const SUB_FILE_TOOLS = new Set(['read_file', 'list_directory', 'search_files', 'web_fetch']);
|
||||
if (SUB_FILE_TOOLS.has(tc.name)) {
|
||||
const wsDir = getWorkspaceDirPath();
|
||||
if (wsDir) {
|
||||
const pathArg = String(tc.arguments?.path || '');
|
||||
if (pathArg) {
|
||||
const sandbox = validatePathSandbox(pathArg, wsDir);
|
||||
if (!sandbox.valid) {
|
||||
logWarn(`R81: 子 Agent 路径沙箱拦截: ${tc.name}(${pathArg}) — ${sandbox.reason}`);
|
||||
messages.push({
|
||||
role: 'tool',
|
||||
content: `<<<TOOL_RESULT_START name="${tc.name}">>>\n${JSON.stringify({ success: false, error: sandbox.reason || '路径不在工作空间范围内' })}\n<<<TOOL_RESULT_END>>>`,
|
||||
tool_name: tc.name
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { executeTool } = await import('./tool-registry.js');
|
||||
const result = await executeTool(tc.name, tc.arguments);
|
||||
// R89: 记录工具成功/失败到熔断器
|
||||
if (result.success) {
|
||||
recordToolSuccess(tc.name);
|
||||
} else {
|
||||
recordToolFailure(tc.name);
|
||||
}
|
||||
const resultStr = formatResult(tc.name, result);
|
||||
messages.push({
|
||||
role: 'tool',
|
||||
|
||||
Reference in New Issue
Block a user