fix(growth): 二次审计修复 — 6项 CRITICAL/HIGH/MEDIUM 全部修复
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

CRITICAL-1/2: json_utils 花括号匹配改为括号平衡算法
  - 处理字符串字面量中的花括号和转义引号
  - 新增 5 个测试(平衡匹配、字符串内花括号、转义引号、extract_string_array)

HIGH-4: EvolutionMiddleware 只取第一个事件(remove(0)),不丢弃后续
HIGH-5: EvolutionMiddleware 先 read() 判空再 write(),减少锁竞争
HIGH-7: from_experience_store 使用传入 store 的 viking 实例(不再忽略参数)
  - ExperienceStore 新增 viking() getter

MEDIUM-9: skill_generator + workflow_composer JSON 数组解析去重
  - 新增 json_utils::extract_string_array() 共享函数
MEDIUM-14: EvolutionMiddleware 注入文本去除多余缩进空格

测试: zclaw-growth 133 tests, zclaw-runtime 87 tests, workspace 0 failures
This commit is contained in:
iven
2026-04-18 22:30:10 +08:00
parent a9ea9d8691
commit cb727fdcc7
7 changed files with 150 additions and 59 deletions

View File

@@ -68,35 +68,37 @@ impl AgentMiddleware for EvolutionMiddleware {
&self,
ctx: &mut MiddlewareContext,
) -> Result<MiddlewareDecision> {
// 先用 read lock 快速判空,避免每次对话都获取写锁
if self.pending.read().await.is_empty() {
return Ok(MiddlewareDecision::Continue);
}
// 只移除第一个事件,保留后续事件留待下次注入
let to_inject = {
let mut pending = self.pending.write().await;
if pending.is_empty() {
return Ok(MiddlewareDecision::Continue);
}
// 只取第一条(最近的)事件注入,避免信息过载
// drain 已注入的事件,防止重复注入
std::mem::take(&mut *pending)
pending.remove(0)
};
if let Some(evolution) = to_inject.into_iter().next() {
let injection = format!(
"\n\n<evolution-suggestion>\n\
我注意到你经常做「{pattern}相关的事情\n\
我可以帮你整理成一个技能,以后直接说「{trigger}」就能用了。\n\
技能描述:{desc}\n\
如果你同意,请回复 '确认保存技能'。如果你想调整,可以告诉我怎么改。\n\
</evolution-suggestion>",
pattern = evolution.pattern_name,
trigger = evolution.trigger_suggestion,
desc = evolution.description,
);
ctx.system_prompt.push_str(&injection);
let injection = format!(
"\n\n<evolution-suggestion>\n\
我注意到你经常做「{pattern}」相关的事情。\n\
我可以帮你整理成一个技能,以后直接说「{trigger}就能用了\n\
技能描述:{desc}\n\
如果你同意,请回复 '确认保存技能'。如果你想调整,可以告诉我怎么改。\n\
</evolution-suggestion>",
pattern = to_inject.pattern_name,
trigger = to_inject.trigger_suggestion,
desc = to_inject.description,
);
ctx.system_prompt.push_str(&injection);
tracing::info!(
"[EvolutionMiddleware] Injected evolution suggestion for: {}",
evolution.pattern_name
);
}
tracing::info!(
"[EvolutionMiddleware] Injected evolution suggestion for: {}",
to_inject.pattern_name
);
Ok(MiddlewareDecision::Continue)
}
@@ -135,4 +137,29 @@ mod tests {
assert_eq!(mw.name(), "evolution");
assert_eq!(mw.priority(), 78);
}
#[tokio::test]
async fn test_only_first_event_injected() {
let mw = EvolutionMiddleware::new();
mw.add_pending(PendingEvolution {
pattern_name: "事件A".to_string(),
trigger_suggestion: "触发A".to_string(),
description: "描述A".to_string(),
})
.await;
mw.add_pending(PendingEvolution {
pattern_name: "事件B".to_string(),
trigger_suggestion: "触发B".to_string(),
description: "描述B".to_string(),
})
.await;
// 模拟注入:用 read 判空 + write 取第一个
let first = {
let mut pending = mw.pending.write().await;
pending.remove(0)
};
assert_eq!(first.pattern_name, "事件A");
assert_eq!(mw.pending_count().await, 1); // 事件B 仍保留
}
}