fix(industry): 三轮审计修复 — 3 HIGH + 4 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
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
H1: status 值不匹配 disabled→inactive + source 补 admin 映射 + valueEnum H2: experience.rs format_for_injection 添加 xml_escape H3: TriggerContext industry_keywords 接通全局缓存 M2: ID 自动生成移除中文字符保留 + 无 ASCII 时提示手动输入 M3: TS CreateIndustryRequest 添加 id? 字段 M4: ListIndustriesQuery 添加 deny_unknown_fields
This commit is contained in:
@@ -229,10 +229,10 @@ impl ExperienceExtractor {
|
||||
.unwrap_or_default();
|
||||
let line = format!(
|
||||
"- 类似「{}」做过:{},结果是{} ({})",
|
||||
truncate(&exp.pain_pattern, 30),
|
||||
step_summary,
|
||||
exp.outcome,
|
||||
industry_tag.trim_start()
|
||||
xml_escape(&truncate(&exp.pain_pattern, 30)),
|
||||
xml_escape(&step_summary),
|
||||
xml_escape(&exp.outcome),
|
||||
xml_escape(industry_tag.trim_start())
|
||||
);
|
||||
total_chars += line.chars().count();
|
||||
parts.push(line);
|
||||
@@ -257,6 +257,13 @@ fn truncate(s: &str, max_chars: usize) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Escape XML special characters for safe injection into `<butler-context>`.
|
||||
fn xml_escape(s: &str) -> String {
|
||||
s.replace('&', "&")
|
||||
.replace('<', "<")
|
||||
.replace('>', ">")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -124,10 +124,10 @@ pub async fn post_conversation_hook(
|
||||
if !_user_message.is_empty() {
|
||||
let trigger_ctx = crate::intelligence::triggers::TriggerContext {
|
||||
user_message: _user_message.to_string(),
|
||||
tool_call_count: 0, // Will be populated from trajectory recorder in future
|
||||
tool_call_count: 0,
|
||||
conversation_messages: vec![_user_message.to_string()],
|
||||
pain_confidence,
|
||||
industry_keywords: vec![], // Will be populated from industry config in Phase 3
|
||||
industry_keywords: crate::viking_commands::get_industry_keywords_flat(),
|
||||
};
|
||||
let signals = crate::intelligence::triggers::evaluate_triggers(&trigger_ctx);
|
||||
if !signals.is_empty() {
|
||||
|
||||
@@ -89,6 +89,15 @@ pub struct IndustryConfigPayload {
|
||||
/// Global storage instance
|
||||
static STORAGE: OnceCell<Arc<SqliteStorage>> = OnceCell::const_new();
|
||||
|
||||
/// Flattened industry keywords cache for trigger evaluation.
|
||||
/// Updated when `viking_load_industry_keywords` is called.
|
||||
static INDUSTRY_KEYWORDS_CACHE: std::sync::RwLock<Vec<String>> = std::sync::RwLock::new(Vec::new());
|
||||
|
||||
/// Get the flattened list of all industry keywords (for trigger evaluation).
|
||||
pub fn get_industry_keywords_flat() -> Vec<String> {
|
||||
INDUSTRY_KEYWORDS_CACHE.read().map(|g| g.clone()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get the storage directory path
|
||||
pub fn get_storage_dir() -> PathBuf {
|
||||
// Use platform-specific data directory
|
||||
@@ -727,7 +736,17 @@ pub async fn viking_load_industry_keywords(
|
||||
};
|
||||
if let Some(shared) = shared {
|
||||
let mut guard = shared.write().await;
|
||||
// Flatten all keywords for trigger evaluation cache
|
||||
let all_keywords: Vec<String> = guard.iter()
|
||||
.flat_map(|c| c.keywords.iter().cloned())
|
||||
.collect();
|
||||
*guard = industry_configs;
|
||||
drop(guard);
|
||||
|
||||
// Update the flattened keyword cache (for trigger system)
|
||||
if let Ok(mut cache) = INDUSTRY_KEYWORDS_CACHE.write() {
|
||||
*cache = all_keywords;
|
||||
}
|
||||
tracing::info!("[viking_commands] Industry keywords synced to ButlerRouter middleware");
|
||||
} else {
|
||||
tracing::warn!("[viking_commands] Kernel not initialized, industry keywords not loaded");
|
||||
|
||||
Reference in New Issue
Block a user