refactor: Agent 配置硬编码化、压缩系统不可变重构、前端组件化与安全硬化
- AgentConfig: 移除 10+ 个环境变量读取,仅保留 TOKEN_SOFT/HARD_LIMIT 两个
可调参数,context_char_limit 替换为统一的 token_soft_limit 阈值
- compact: find_safe_cut_point 重写为 HashSet O(n) 算法,
micro_compact 改为不可变风格,compress_context 签名升级为
token_soft_limit + max_messages 双参数,新增 COMPACTION_OUTPUT_RESERVE
- modes: ModeConfig.max_steps/tool_timeout_secs 去 Optional 化,
Deep Research 步数 16→100,Literature Reader 步数 6→25
- dashboard: 提取 AgentMarkdown/ThoughtCard/ToolCallCard/AnswerCard/
SubAgentContainer 等共享组件,ResearchAgentPanel 大幅瘦身,
交互卡片重构为 console-panel 紧凑风格
- security: 移除 HERMES_YOLO_MODE、AGENT_BLOCK_NETWORK 开关、
AGENT_CHECKPOINT_ENABLED 开关,关键安全机制强制启用
This commit is contained in:
@@ -151,15 +151,8 @@ impl CheckpointManager {
|
||||
CheckpointManager {
|
||||
enabled: enabled && inner.repo.is_some(),
|
||||
repo_path: store_path,
|
||||
max_snapshots: std::env::var("AGENT_CHECKPOINT_MAX_SNAPSHOTS")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(10),
|
||||
max_file_size: std::env::var("AGENT_CHECKPOINT_MAX_FILE_SIZE_MB")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<usize>().ok())
|
||||
.map(|mb| mb * 1024 * 1024)
|
||||
.unwrap_or(10 * 1024 * 1024), // 10 MB
|
||||
max_snapshots: 10,
|
||||
max_file_size: 10 * 1024 * 1024, // 10 MB
|
||||
inner: Mutex::new(inner),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,24 +285,9 @@ impl HardlineResult {
|
||||
///
|
||||
/// 执行反规避标准化后再匹配,返回第一个命中的模式。
|
||||
///
|
||||
/// 注意:此函数在模块导入时冻结 `HERMES_YOLO_MODE`,
|
||||
/// 确保运行时无法通过设置环境变量绕过 hardline 检查。
|
||||
pub fn check_command(raw_command: &str) -> HardlineResult {
|
||||
let normalized = normalize_command(raw_command);
|
||||
|
||||
// ── YOLO 模式冻结 ──
|
||||
// YOLO 模式在首次调用时从环境变量读取并缓存,
|
||||
// 后续设置环境变量不会生效(防止注入攻击)。
|
||||
static YOLO_MODE_FROZEN: LazyLock<bool> = LazyLock::new(|| {
|
||||
let val = std::env::var("HERMES_YOLO_MODE")
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
val == "1" || val == "true" || val == "yes" || val == "on"
|
||||
});
|
||||
|
||||
// Hardline 即使在 YOLO 模式下也不可绕过
|
||||
let _yolo = *YOLO_MODE_FROZEN;
|
||||
|
||||
let matches: Vec<usize> = HARDLINE_REGEX_SET
|
||||
.matches(&normalized)
|
||||
.into_iter()
|
||||
|
||||
+21
-79
@@ -66,9 +66,7 @@ pub struct AgentConfig {
|
||||
pub tool_timeout_secs: u64,
|
||||
/// 工具输出最大字符数
|
||||
pub max_tool_output_chars: usize,
|
||||
/// 上下文 Token 估算上限(触发自动摘要压缩)
|
||||
pub context_char_limit: usize,
|
||||
/// Token 预算软限制(触发 nudging 提醒)
|
||||
/// Token 预算软限制 — 各压缩层统一触发阈值
|
||||
pub token_soft_limit: usize,
|
||||
/// Token 预算硬限制(触发强制动作)
|
||||
pub token_hard_limit: usize,
|
||||
@@ -99,80 +97,32 @@ pub struct AgentConfig {
|
||||
impl AgentConfig {
|
||||
/// 从环境变量加载配置,缺失时使用默认值。
|
||||
pub fn from_env_optional() -> Self {
|
||||
let mut config = AgentConfig {
|
||||
max_steps: std::env::var("AGENT_MAX_STEPS")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(8),
|
||||
AgentConfig {
|
||||
max_steps: 8,
|
||||
duplicate_call_threshold: 3,
|
||||
tool_timeout_secs: std::env::var("AGENT_TOOL_TIMEOUT_SECS")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(120),
|
||||
max_tool_output_chars: std::env::var("AGENT_MAX_TOOL_OUTPUT_CHARS")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(4000),
|
||||
context_char_limit: std::env::var("AGENT_CONTEXT_CHAR_LIMIT")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(16000),
|
||||
tool_timeout_secs: 120,
|
||||
max_tool_output_chars: 4000,
|
||||
token_soft_limit: std::env::var("AGENT_TOKEN_SOFT_LIMIT")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(32000),
|
||||
.unwrap_or(80000),
|
||||
token_hard_limit: std::env::var("AGENT_TOKEN_HARD_LIMIT")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(40000),
|
||||
max_messages: std::env::var("AGENT_MAX_MESSAGES")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(50),
|
||||
.unwrap_or(100000),
|
||||
max_messages: 50,
|
||||
enable_thinking: false,
|
||||
permission_deny_rules: parse_comma_list("AGENT_PERMISSIONS_DENY"),
|
||||
permission_allow_rules: parse_comma_list("AGENT_PERMISSIONS_ALLOW"),
|
||||
permission_ask_rules: parse_comma_list("AGENT_PERMISSIONS_ASK"),
|
||||
permission_mode: std::env::var("AGENT_PERMISSION_MODE")
|
||||
.unwrap_or_else(|_| "default".to_string()),
|
||||
denial_max_consecutive: std::env::var("AGENT_DENIAL_MAX_CONSECUTIVE")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(3),
|
||||
denial_max_total: std::env::var("AGENT_DENIAL_MAX_TOTAL")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(20),
|
||||
denial_max_consecutive: 3,
|
||||
denial_max_total: 20,
|
||||
additional_allowed_dirs: parse_comma_list("AGENT_ADDITIONAL_DIRS"),
|
||||
subagent_allowed_tools: parse_comma_list("AGENT_SUBAGENT_ALLOWED_TOOLS"),
|
||||
mode: std::env::var("AGENT_MODE").unwrap_or_else(|_| "default".to_string()),
|
||||
};
|
||||
|
||||
// 加载权限档案(AGENT_PERMISSION_PROFILE),追加到现有规则
|
||||
let profile_name = std::env::var("AGENT_PERMISSION_PROFILE").unwrap_or_default();
|
||||
if !profile_name.is_empty() {
|
||||
if let Some(profile) = permission_profile::load_profile(&profile_name) {
|
||||
info!(
|
||||
"[AgentConfig] 加载权限档案: {} — {}",
|
||||
profile.name, profile.description
|
||||
);
|
||||
permission_profile::apply_profile_to_config(
|
||||
&profile,
|
||||
&mut config.permission_deny_rules,
|
||||
&mut config.permission_allow_rules,
|
||||
&mut config.permission_ask_rules,
|
||||
&mut config.permission_mode,
|
||||
);
|
||||
} else {
|
||||
warn!(
|
||||
"[AgentConfig] 未知的权限档案: {}(可用: {:?})",
|
||||
profile_name,
|
||||
permission_profile::list_available_profiles()
|
||||
);
|
||||
}
|
||||
mode: "default".to_string(),
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,10 +330,7 @@ impl AgentRuntime {
|
||||
apply_mode_tool_filter(&mut tool_registry, mode);
|
||||
|
||||
// 初始化 checkpoint 管理器
|
||||
let checkpoint_enabled = std::env::var("AGENT_CHECKPOINT_ENABLED")
|
||||
.unwrap_or_else(|_| "true".to_string())
|
||||
.to_lowercase()
|
||||
!= "false";
|
||||
let checkpoint_enabled = true;
|
||||
let checkpoint_store = app_state.config.library_dir.join("..").join(".checkpoints");
|
||||
let checkpoint_manager = Arc::new(checkpoint::CheckpointManager::new(
|
||||
std::fs::canonicalize(&checkpoint_store).unwrap_or(checkpoint_store),
|
||||
@@ -455,10 +402,7 @@ impl AgentRuntime {
|
||||
apply_mode_tool_filter(&mut tool_registry, mode);
|
||||
|
||||
// 初始化 checkpoint 管理器
|
||||
let checkpoint_enabled = std::env::var("AGENT_CHECKPOINT_ENABLED")
|
||||
.unwrap_or_else(|_| "true".to_string())
|
||||
.to_lowercase()
|
||||
!= "false";
|
||||
let checkpoint_enabled = true;
|
||||
let checkpoint_store = app_state.config.library_dir.join("..").join(".checkpoints");
|
||||
let checkpoint_manager = Arc::new(checkpoint::CheckpointManager::new(
|
||||
std::fs::canonicalize(&checkpoint_store).unwrap_or(checkpoint_store),
|
||||
@@ -561,7 +505,8 @@ impl AgentRuntime {
|
||||
compact::compress_context_with_hooks_and_log(
|
||||
messages,
|
||||
llm,
|
||||
self.config.context_char_limit,
|
||||
self.config.token_soft_limit,
|
||||
self.config.max_messages,
|
||||
session_id,
|
||||
Some(hook_registry),
|
||||
Some(&self.collapse_log),
|
||||
@@ -799,7 +744,7 @@ impl AgentRuntime {
|
||||
None => compact::rough_estimate_tokens(messages),
|
||||
};
|
||||
|
||||
// 使用 token 预算的软限制作为压缩触发点(而非粗糙的 context_char_limit * 1.5)
|
||||
// 使用 TokenBudget 软限制作为压缩触发点
|
||||
let token_limit = token_budget.soft_limit;
|
||||
|
||||
let mut did_compress = false;
|
||||
@@ -1418,14 +1363,15 @@ impl AgentRuntime {
|
||||
error_recovery::RecoveryStep::AggressiveCompact => {
|
||||
info!("[AgentRuntime] 错误恢复: 激进压缩 (snip + micro with keep_recent=2)");
|
||||
compact::snip_compact(messages, self.config.max_messages);
|
||||
compact::micro_compact(messages, 2);
|
||||
*messages = compact::micro_compact(messages, 2);
|
||||
}
|
||||
error_recovery::RecoveryStep::ReactiveCompact => {
|
||||
info!("[AgentRuntime] 错误恢复: LLM 摘要压缩");
|
||||
compact::compress_context(
|
||||
messages,
|
||||
llm,
|
||||
self.config.context_char_limit,
|
||||
self.config.token_soft_limit,
|
||||
self.config.max_messages,
|
||||
session_id,
|
||||
)
|
||||
.await;
|
||||
@@ -1846,15 +1792,11 @@ impl AgentRuntime {
|
||||
///
|
||||
/// 仅覆盖 mode_config 中 Some 的字段,None 保持原值不变。
|
||||
fn apply_mode_config(config: &mut AgentConfig, mode: &AgentMode) {
|
||||
if let Some(max_steps) = mode.mode_config.max_steps {
|
||||
config.max_steps = max_steps;
|
||||
}
|
||||
config.max_steps = mode.mode_config.max_steps;
|
||||
if let Some(enable_thinking) = mode.mode_config.enable_thinking {
|
||||
config.enable_thinking = enable_thinking;
|
||||
}
|
||||
if let Some(tool_timeout_secs) = mode.mode_config.tool_timeout_secs {
|
||||
config.tool_timeout_secs = tool_timeout_secs;
|
||||
}
|
||||
config.tool_timeout_secs = mode.mode_config.tool_timeout_secs;
|
||||
|
||||
// 加载权限档案(模式绑定的 permission_profile)
|
||||
if let Some(profile_name) = mode.mode_config.permission_profile {
|
||||
|
||||
Reference in New Issue
Block a user