新增: Phase2 阶段收尾(Sprint 1-20)

重构:删 5 零引用 crate(df-evolve/plugin/stages/task/traceability)+ 清死模块、ai.rs 拆 11 子 module、ai.ts 拆 6 composable、i18n 拆目录
功能:知识库全栈(df-project/scan + CRUD + 时间线 + 前端)、Settings 拆分、appSettings KV 迁移、模型池、LLM 并发 Semaphore
修复:审批持久化根治、ConditionEngine 默认拒绝、NodeRegistry unimplemented 清除、promote 补偿删除、工具结果截断 50KB、路径校验防 symlink 逃逸
文档:B-03 人工审批设计、决策记录三分档、规格契约自检、经验记录、todo 看板、PROGRESS 更新

详见 PROGRESS.md。src-tauri/儿童每日打卡应用/ 与本项目无关,已排除。
This commit is contained in:
2026-06-14 14:08:20 +08:00
parent 98393b4908
commit cf017f81e2
167 changed files with 19549 additions and 6886 deletions

View File

@@ -0,0 +1,146 @@
//! AI 聊天命令模块 — 流式对话、工具调用、审批门控、提供商管理
//!
//! 由原单文件 ai.rs(2663 行)按职责拆分为 11 个子模块。
//!
//! 模块布局:
//! - [`commands`] — 所有 `#[tauri::command]` IPC 函数
//! - [`agentic`] — run_agentic_loop / try_continue_agent_loop / MAX_AGENT_ITERATIONS
//! - [`stream_recv`] — stream_llm 流式接收
//! - [`conversation`] — save_conversation / TokenAccumulator / accumulate_tokens
//! - [`title`] — 对话标题生成
//! - [`audit`] — 工具调用审计 + pending 审批恢复 + 工具调用处理
//! - [`skills`] — 本机 Claude 技能扫描
//! - [`prompt`] — 系统提示词构建
//! - [`tool_registry`] — AI 工具注册表构建 + 文件路径校验
//! - [`knowledge_inject`] — 知识库注入 + 提炼
//!
//! 事件协议:通过 app.emit("ai-chat-event", payload) 流式推送到前端
//! - AiTextDelta: 流式文本片段
//! - AiToolCallStarted/Completed: 工具调用生命周期
//! - AiApprovalRequired: 需要人工审批
//! - AiCompleted/AiError: 完成/错误
pub mod agentic;
pub mod audit;
pub mod commands;
pub mod conversation;
pub mod knowledge_inject;
pub mod prompt;
pub mod skills;
pub mod stream_recv;
pub mod title;
pub mod tool_registry;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use serde::Serialize;
use df_ai::ai_tools::RiskLevel;
use df_ai::context::ContextManager;
use df_ai::context::ContextConfig;
// ============================================================
// 重导出 — 保路径不变(state.rs / lib.rs / knowledge.rs 在用)
// ============================================================
// commands 子模块(glob 重导出) — `#[tauri::command]` 宏生成的命令函数 +
// 内部符号(__cmd__xxx / __tauri_command_name_xxx)同源同模块定义,
// glob 把它们全部拉到 commands::ai 路径,使 generate_handler! 能解析到。
// 静默 unused_imports:glob 重导出用于跨模块路径解析,本文件不引用这些符号。
#[allow(unused_imports)]
pub use self::commands::*;
// 子模块的非命令 pub 项,逐个保路径
#[allow(unused_imports)]
pub use self::audit::restore_pending_approvals;
#[allow(unused_imports)]
pub use self::knowledge_inject::{spawn_embedding_for_knowledge, trigger_extraction_now};
#[allow(unused_imports)]
pub use self::tool_registry::build_ai_tool_registry;
// ============================================================
// 事件载荷类型(放 mod.rs,各子文件经 use super::* 拿到)
// ============================================================
/// AI 聊天事件(推送到前端)
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
pub enum AiChatEvent {
/// 流式文本片段
AiTextDelta { delta: String, conversation_id: Option<String> },
/// 工具调用开始
AiToolCallStarted { id: String, name: String, args: serde_json::Value, conversation_id: Option<String> },
/// 工具调用完成
AiToolCallCompleted { id: String, result: serde_json::Value, conversation_id: Option<String> },
/// 需要人工审批
AiApprovalRequired { id: String, name: String, args: serde_json::Value, reason: String, conversation_id: Option<String> },
/// 审批结果
AiApprovalResult { id: String, approved: bool, conversation_id: Option<String> },
/// AI 响应完成
AiCompleted { total_tokens: u32, prompt_tokens: u32, completion_tokens: u32, conversation_id: Option<String> },
/// 错误
AiError { error: String, conversation_id: Option<String> },
/// Agent 循环新一轮(前端需新建 assistant 消息)
AiAgentRound { round: u32, conversation_id: Option<String> },
}
// ============================================================
// 会话状态(放 mod.rs,各子文件经 use super::* 拿到)
// ============================================================
/// AI 会话内状态Mutex 保护)
pub struct AiSession {
/// 对话历史ContextManager唯一消息真相源裁剪仅影响发送视图不影响持久化
pub messages: ContextManager,
/// 当前提供商 ID
pub active_provider_id: Option<String>,
/// 当前活跃对话 ID
pub active_conversation_id: Option<String>,
/// 活跃对话创建时间(懒创建:首条消息落库前仅存内存,upsert 时用作 created_at)
pub active_conv_created_at: Option<String>,
/// 挂起的审批tool_call_id → 审批信息)
pub pending_approvals: HashMap<String, PendingApproval>,
/// 是否正在生成
pub generating: bool,
/// 当前 agent 循环的语言设置(用于审批后恢复循环)
pub agent_language: Option<String>,
/// 停止信号ai_chat_stop 置位agentic loop / stream_llm 检测后尽快退出
pub stop_flag: Arc<AtomicBool>,
}
impl AiSession {
pub fn new() -> Self {
Self {
messages: ContextManager::new(ContextConfig::default()),
active_provider_id: None,
active_conversation_id: None,
active_conv_created_at: None,
pending_approvals: HashMap::new(),
generating: false,
agent_language: None,
stop_flag: Arc::new(AtomicBool::new(false)),
}
}
}
/// 待审批的工具调用
#[derive(Debug, Clone)]
pub struct PendingApproval {
pub tool_call_id: String,
pub tool_name: String,
pub arguments: serde_json::Value,
pub risk_level: RiskLevel,
pub conversation_id: Option<String>,
/// 重启恢复的积压审批:无 live loop 持有 session.messages,审批后不 save(防空 messages 污染老对话)、不续跑
pub recovered: bool,
}
/// 工具调用草稿(流式收集时的临时结构)
#[derive(Debug, Clone, Default)]
pub(crate) struct ToolCallDraft {
pub(crate) id: String,
pub(crate) name: String,
pub(crate) args: String,
}