feat(growth,skills,saas,desktop): C线差异化全量实现 — C1日报+C2飞轮+C3引导
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

C3 零配置引导 (P0):
- use-cold-start.ts: 4阶段→6阶段对话驱动状态机 (idle→greeting→industry→identity→task→completed)
- cold-start-mapper.ts: 关键词行业检测 + 肯定/否定/名字提取
- cold_start_prompt.rs: Rust侧6阶段system prompt生成 + 7个测试
- FirstConversationPrompt.tsx: 动态行业卡片 + 行业任务引导 + 通用快捷操作

C1 管家日报 (P0):
- kernel注册DailyReportHand (第8个Hand)
- DailyReportPanel.tsx已存在,事件监听+持久化完整

C2 行业知识飞轮 (P1):
- heartbeat.rs: 经验缓存(EXPERIENCE_CACHE) + check_unresolved_pains增强经验感知
- heartbeat_update_experiences Tauri命令 + VikingStorage持久化
- semantic_router.rs: 经验权重boost(0.05*ln(count+1), 上限0.15) + update_experience_boosts方法
- service.rs: auto_optimize_config() 基于使用频率自动优化行业skill_priorities

验证: tsc 0 errors, cargo check 0 warnings, 7 cold_start + 5 daily_report + 1 experience_boost tests PASS
This commit is contained in:
iven
2026-04-21 18:28:45 +08:00
parent ae56aba366
commit 13507682f7
9 changed files with 741 additions and 74 deletions

View File

@@ -299,3 +299,68 @@ pub async fn seed_builtin_industries(pool: &PgPool) -> SaasResult<()> {
tracing::info!("Seeded {} builtin industries", builtin_industries().len());
Ok(())
}
/// Auto-optimize industry config based on actual usage data.
///
/// Analyzes experience data for all agents under an account and updates
/// `skill_priorities` and `pain_seed_categories` to reflect actual usage
/// patterns rather than static configuration.
pub async fn auto_optimize_config(
pool: &sqlx::PgPool,
account_id: i64,
usage_signals: &std::collections::HashMap<String, u32>,
) -> crate::Result<()> {
// Find active industries for this account
let industries: Vec<(String, serde_json::Value)> = sqlx::query_as(
"SELECT i.id, i.skill_priorities FROM industries i
JOIN account_industries ai ON ai.industry_id = i.id
WHERE ai.account_id = $1 AND i.status = 'active'",
)
.bind(account_id)
.fetch_all(pool)
.await
.map_err(crate::SaasError::from)?;
if industries.is_empty() {
return Ok(());
}
// Build updated skill_priorities based on actual usage
let mut new_priorities: Vec<(String, i32)> = Vec::new();
for (skill, count) in usage_signals {
let priority = (*count as i32).min(10);
if priority > 0 {
new_priorities.push((skill.clone(), priority));
}
}
// Sort by priority descending
new_priorities.sort_by(|a, b| b.1.cmp(&a.1));
if new_priorities.is_empty() {
return Ok(());
}
// Update each linked industry's skill_priorities
let priorities_json = serde_json::to_string(&new_priorities)
.unwrap_or_else(|_| "[]".to_string());
for (industry_id, _old_priorities) in &industries {
sqlx::query(
"UPDATE industries SET skill_priorities = $1, updated_at = NOW() WHERE id = $2",
)
.bind(&priorities_json)
.bind(industry_id)
.execute(pool)
.await
.map_err(crate::SaasError::from)?;
}
tracing::info!(
"[auto_optimize] Updated skill_priorities for {} industries under account {}",
industries.len(),
account_id,
);
Ok(())
}