新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理
后端: - 工作流推进链(D-03):advance_task/状态机/闸门走 df-nodes Node trait,conditions 条件引擎扩展 - 想法评估闭环:启发式评分+对抗评估,df-ideas/scoring + df-storage/idea_eval_repo + idea 前端打通 - 全局事件数据总线:df-ai/context+context_helpers+augmentation 跨模块解耦 - AI planner/plan_hint/intent:aichat B 路线并行多轮基础 - patch_file 加固(TD-03/04):读改写整体锁防 lost update,expected_hash 合约闭环 - 压缩超时兜底(F-15 卡死根治) - F-09 多会话并发:LlmConcurrency per-conv + streamingGuard 前端守护 + verify 脚本 - 知识注入 DRY/skills/audit 扩展 清理: - aichat 技术债(误报 allow/死导入/过时注释 30 项) - URGENT.md 删除(11 项加急全解决/迁 todo) - 文档整理(todo/待决策/待审查/ARCHITECTURE/INDEX + 总线/技术债审查新文档)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
//! AI 聊天命令模块 — 流式对话、工具调用、审批门控、提供商管理
|
||||
//!
|
||||
//! 由原单文件 ai.rs(2663 行)按职责拆分为 11 个子模块。
|
||||
//! 由原单文件 ai.rs(2663 行)按职责拆分为 15 个子模块。
|
||||
//!
|
||||
//! 模块布局:
|
||||
//! - [`commands`] — 所有 `#[tauri::command]` IPC 函数
|
||||
@@ -15,6 +15,9 @@
|
||||
//! - [`compress`] — F-15 上下文压缩 LLM 摘要(compress_via_llm 公共函数)
|
||||
//! - [`tool_registry`] — AI 工具注册表构建 + 文件路径校验
|
||||
//! - [`knowledge_inject`] — 知识库注入 + 提炼
|
||||
//! - [`augmentation`] — 上下文增强
|
||||
//! - [`http`] — HTTP 调用封装
|
||||
//! - [`secret`] — 密钥/凭证管理
|
||||
//!
|
||||
//! 事件协议:通过 app.emit("ai-chat-event", payload) 流式推送到前端
|
||||
//! - AiTextDelta: 流式文本片段
|
||||
@@ -23,6 +26,7 @@
|
||||
//! - AiCompleted/AiError: 完成/错误
|
||||
|
||||
pub mod agentic;
|
||||
pub mod augmentation;
|
||||
pub mod audit;
|
||||
pub mod commands;
|
||||
pub mod compress;
|
||||
@@ -62,9 +66,12 @@ pub use self::commands::*;
|
||||
#[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};
|
||||
pub use self::knowledge_inject::{retry_failed_embeddings, spawn_embedding_for_knowledge, trigger_extraction_now};
|
||||
#[allow(unused_imports)]
|
||||
pub use self::tool_registry::build_ai_tool_registry;
|
||||
// Input Augmentation 层(核心设计2):ResolverRegistry 构建 helper(state.rs init 调用)
|
||||
#[allow(unused_imports)]
|
||||
pub use self::augmentation::build_resolver_registry;
|
||||
|
||||
// ============================================================
|
||||
// 事件载荷类型(放 mod.rs,各子文件经 use super::* 拿到)
|
||||
@@ -151,7 +158,7 @@ pub enum AiChatEvent {
|
||||
/// F-260619-03 Phase B: 路径授权弹窗(LLM 想访问白名单外目录,挂起 loop 等用户决定)。
|
||||
///
|
||||
/// 触发:resolve_workspace_path 预校验路径未命中 persistent/session 白名单(且非黑名单)。
|
||||
/// 复用审批挂起架构(PendingApproval.path_auth 标记路径授权挂起),
|
||||
/// 复用审批挂起架构(PendingApproval.kind=Path 标记路径授权挂起,阶段3a 单真相源合并),
|
||||
/// 用户经 ai_authorize_dir IPC 选择"仅本次/未来都允许/拒绝",恢复 loop。
|
||||
/// `dir`:规范化后的待授权目录(父目录,目录粒度对齐 session_trust)。
|
||||
AiDirAuthRequired {
|
||||
@@ -183,7 +190,9 @@ pub enum AiChatEvent {
|
||||
///
|
||||
/// 优先级:`pending_approvals` 非空(有挂起审批优先报 AwaitingApproval,
|
||||
/// 即使同时在流式)> `generating`(Streaming)> 都不满足(Idle)。
|
||||
#[allow(dead_code)] // SW-260618-21 b:预留读视图(SW-02 类终态化复用·当前 0 消费者·保留扩展点)
|
||||
// SW-260618-21 b:原预留读视图(0 消费者)。path_auth 审批链阶段1 接入后有消费者
|
||||
// (try_continue_agent_loop / ai_chat_stop 经 session_state 替代手写 has_pending),不再 dead_code。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SessionState {
|
||||
/// 空闲:既未生成、也无挂起审批
|
||||
Idle,
|
||||
@@ -305,21 +314,20 @@ pub struct AiSession {
|
||||
pub active_conversation_id: Option<String>,
|
||||
/// 活跃对话创建时间(懒创建:首条消息落库前仅存内存,upsert 时用作 created_at)
|
||||
pub active_conv_created_at: Option<String>,
|
||||
/// 挂起的审批。
|
||||
/// 挂起的审批(阶段3a 单真相源合并:原 path_auth_pending + risk_pending 双 HashMap)。
|
||||
///
|
||||
/// **结构**:单层 `HashMap<tool_call_id, PendingApproval>`,按 `tool_call_id`
|
||||
/// 路由审批结果(`tool_call_id` 是路由键)。`PendingApproval.conversation_id`
|
||||
/// 是业务语义(哪个对话产生的审批)而非路由键——IPC 端按 `tool_call_id` 精确命中。
|
||||
/// 决策1(状态层合 + 决策层分):状态层把两类挂起合并进单 HashMap,
|
||||
/// `PendingApproval.kind` 字段区分语义(`ApprovalKind::Path` vs `ApprovalKind::Risk`);
|
||||
/// 决策层仍双轨 —— `ai_authorize_dir` 只消费 `kind==Path`(once/always 写持久白名单),
|
||||
/// `ai_approve` 只消费 `kind==Risk`(一次性 approve/reject),入口 kind 校验防误调。
|
||||
/// 键为 `tool_call_id`(路由键不变),`conversation_id` 是业务语义(哪个对话产生)。
|
||||
///
|
||||
/// **现状**:`ai_pending_tool_calls` 等读取路径按 `conversation_id` 做 O(n) 线性
|
||||
/// 过滤。在典型场景(单对话、少量并发审批)下 n 极小,O(n) 可接受,无需二级索引。
|
||||
///
|
||||
/// **未来**:若多会话并发、审批量显著增大,可考虑加二级索引
|
||||
/// (`conversation_id → Vec<tool_call_id>`)把按会话过滤降到 O(1)。
|
||||
/// 兜底/回退:本合并是结构性的,回退走 git revert(plan 风险表「分 3a/3b 各 revert」)。
|
||||
/// 兼容靠 kind 字段默认 Risk(老构造点不传 kind 即默认 Risk,语义不变)。
|
||||
pub pending_approvals: HashMap<String, PendingApproval>,
|
||||
/// F-260616-09 B 阶段 多会话并发架构 — 会话级状态按 conversation_id 切分。
|
||||
///
|
||||
/// **批4 状态(per_conv 唯一真相源)**:批2-批4 已迁移所有调用方(agentic.rs loop +
|
||||
/// **批4 状态(per_conv 唯一真相源)**:批2-批4 已迁移所有调用方(agentic/mod.rs loop +
|
||||
/// GeneratingGuard + try_continue + commands.rs IPC 写路径 + audit.rs process_tool_calls +
|
||||
/// conversation.rs save + title.rs + knowledge_inject.rs + lib.rs L0)读写
|
||||
/// `session.conv(conv_id).*` / `session.conv_read(conv_id)`。顶层单例会话级字段已全部删除。
|
||||
@@ -348,15 +356,15 @@ impl AiSession {
|
||||
/// F-260616-09 B 批4:per_conv 化后接 conv_id 参数(顶层 generating 已删除);
|
||||
/// pending 判断改为按 conv_id 过滤(决策 e 真并发下各 conv 独立)。
|
||||
///
|
||||
/// # 待替换调用点(本次仅新增视图,不替换调用点)
|
||||
/// path_auth 审批链阶段1(本批)接入:`try_continue_agent_loop`(agentic/mod.rs) +
|
||||
/// `ai_chat_stop`(commands/chat.rs) 改调本方法替代各自手写的 has_pending,
|
||||
/// 收敛两处状态机判定到单一入口。
|
||||
///
|
||||
/// - `agentic.rs` — agent loop 入口/恢复处对 generating + 审批的组合判断
|
||||
/// - `commands.rs` — `ai_pending_tool_calls` 等读取前的状态门控
|
||||
/// - `commands.rs` — 审批提交/会话复位路径的状态判断
|
||||
///
|
||||
/// 上述三处替换由 P0 任务承接,本方法先就位供其切换。
|
||||
#[allow(dead_code)] // SW-260618-21 b:预留(P0 终态化任务承接·当前 0 调用·保留扩展点)
|
||||
/// 兜底/回退:若本方法判定异常需快速回退手写 has_pending,调用方注释已保留原
|
||||
/// 三路组合(generating + path_auth + risk)字面量,改一行即可切回。
|
||||
pub fn session_state(&self, conv_id: &str) -> SessionState {
|
||||
// 阶段3a 单真相源合并后:两类挂起(path_auth + risk)合一进 pending_approvals,
|
||||
// kind 字段区分语义但状态机只看「有无挂起」,单表 any 一次即可(消除原双表 OR 合并判断)。
|
||||
let has_pending = self.pending_approvals.values()
|
||||
.any(|a| a.conversation_id.as_deref() == Some(conv_id));
|
||||
if has_pending {
|
||||
@@ -371,9 +379,9 @@ impl AiSession {
|
||||
// ============================================================
|
||||
// F-260616-09 B 阶段批1 PerConvState 访问器(设计 §2.1.2)
|
||||
//
|
||||
// 批1 仅定义访问器,**不迁移调用方**(批2 承接)。当前 0 调用方,行为完全不变。
|
||||
// 批2 迁移:所有 `session.messages` / `session.generating` 等读写改走
|
||||
// `session.conv(conv_id).*`(写)/ `session.conv_read(conv_id)`(读)。
|
||||
// 批4 已迁移完成:所有 `session.messages` / `session.generating` 等读写已全部改走
|
||||
// `session.conv(conv_id).*`(写)/ `session.conv_read(conv_id)`(读),
|
||||
// per_conv 现为唯一真相源,顶层单例字段已删除。
|
||||
// ============================================================
|
||||
|
||||
/// 取或惰性创建指定会话的可变状态(设计 §2.1.2)。
|
||||
@@ -410,6 +418,11 @@ mod tests_f09_per_conv {
|
||||
assert!(s.model_override.is_none(), "model_override 初值应为 None");
|
||||
assert!(s.session_trust.is_empty(), "session_trust 初值应为空 HashSet");
|
||||
assert!(s.created_at.is_none(), "created_at 初值应为 None");
|
||||
// P1 修复(提炼重复触发):knowledge_extracted 初值应为 false(新会话未提炼)
|
||||
assert!(
|
||||
!s.knowledge_extracted,
|
||||
"knowledge_extracted 初值应为 false(新会话未提炼)"
|
||||
);
|
||||
// stop_flag 初值 false(与 AiSession::new 对齐)
|
||||
assert!(
|
||||
!s.stop_flag.load(std::sync::atomic::Ordering::SeqCst),
|
||||
@@ -471,6 +484,95 @@ mod tests_f09_per_conv {
|
||||
"已创建的 conv_id conv_read() 应返 Some"
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// P1 修复(提炼重复触发): knowledge_extracted 去重标志生命周期测试
|
||||
//
|
||||
// 锁定标志三态语义:
|
||||
// 1) 新会话初值 false → 自动提炼可触发
|
||||
// 2) 成功提炼后置 true → maybe_spawn_extraction 入口判重跳过
|
||||
// 3) trigger_extraction_now 清位 → 允许手动重提炼
|
||||
// ============================================================
|
||||
|
||||
/// 新会话 knowledge_extracted 默认 false(自动提炼可触发)
|
||||
#[test]
|
||||
fn test_knowledge_extracted_default_false() {
|
||||
let session = AiSession::new();
|
||||
// 未创建的 conv_id → conv_read None → maybe_spawn_extraction 视为 false(可触发)
|
||||
assert!(
|
||||
session
|
||||
.conv_read("new-conv")
|
||||
.map(|c| c.knowledge_extracted)
|
||||
.unwrap_or(false)
|
||||
== false,
|
||||
"新会话 knowledge_extracted 应为 false"
|
||||
);
|
||||
}
|
||||
|
||||
/// 成功提炼后置位 true(自动路径去重标志)
|
||||
#[test]
|
||||
fn test_knowledge_extracted_set_after_extraction() {
|
||||
let mut session = AiSession::new();
|
||||
// 模拟提炼成功后置位(extract_knowledge_from_conversation Ok(>0) → maybe_spawn_extraction 置位)
|
||||
session.conv("conv-done").knowledge_extracted = true;
|
||||
assert!(
|
||||
session.conv_read("conv-done").unwrap().knowledge_extracted,
|
||||
"提炼成功后 knowledge_extracted 应为 true"
|
||||
);
|
||||
}
|
||||
|
||||
/// maybe_spawn_extraction 入口判重语义:已置位 → 视为已提炼(跳过)
|
||||
#[test]
|
||||
fn test_knowledge_extracted_dedup_guard() {
|
||||
let mut session = AiSession::new();
|
||||
session.conv("conv-dedup").knowledge_extracted = true;
|
||||
// 模拟 maybe_spawn_extraction 入口判重读(already_extracted)
|
||||
let already_extracted = session
|
||||
.conv_read("conv-dedup")
|
||||
.map(|c| c.knowledge_extracted)
|
||||
.unwrap_or(false);
|
||||
assert!(
|
||||
already_extracted,
|
||||
"已置位会话入口判重应为 true(跳过自动提炼)"
|
||||
);
|
||||
}
|
||||
|
||||
/// trigger_extraction_now 清位语义:手动路径强制清位,允许重提炼
|
||||
#[test]
|
||||
fn test_knowledge_extracted_manual_clear() {
|
||||
let mut session = AiSession::new();
|
||||
session.active_conversation_id = Some("conv-manual".to_string());
|
||||
session.conv("conv-manual").knowledge_extracted = true;
|
||||
// 模拟 trigger_extraction_now 入口清位逻辑:
|
||||
// active_conversation_id clone(脱离 immutable borrow)后再 mutable borrow conv()
|
||||
let cid = session.active_conversation_id.clone();
|
||||
if let Some(cid) = cid.as_deref() {
|
||||
session.conv(cid).knowledge_extracted = false;
|
||||
}
|
||||
assert!(
|
||||
!session
|
||||
.conv_read("conv-manual")
|
||||
.unwrap()
|
||||
.knowledge_extracted,
|
||||
"手动触发后 knowledge_extracted 应被清位(允许重提炼)"
|
||||
);
|
||||
}
|
||||
|
||||
/// 不同会话标志互相独立(P1 去重按 conv_id 切分)
|
||||
#[test]
|
||||
fn test_knowledge_extracted_per_conv_independent() {
|
||||
let mut session = AiSession::new();
|
||||
session.conv("conv-a").knowledge_extracted = true;
|
||||
// conv-b 未提炼,标志应独立为 false
|
||||
assert!(
|
||||
session.conv("conv-a").knowledge_extracted,
|
||||
"conv-a 已提炼标志 true"
|
||||
);
|
||||
assert!(
|
||||
!session.conv("conv-b").knowledge_extracted,
|
||||
"conv-b 未提炼标志 false(各 conv 独立)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -484,18 +586,19 @@ mod tests_f09_per_conv {
|
||||
// agent_language/model_override/session_trust 保留原样),不迁移现有调用方(批2 承接)
|
||||
// - per_conv HashMap 初始空,旧调用路径继续读写 AiSession 顶层单例字段,完全无感
|
||||
//
|
||||
// 共存期:批1 后顶层单例字段(唯一真相源)与 per_conv(全空,无写入)并存,行为不变。
|
||||
// 批2 迁移:把所有调用方从 session.messages / session.generating 等改为 session.conv(conv_id).*,
|
||||
// 迁移完成后顶层单例字段废弃删除(批3)。
|
||||
// 共存期(已结束):批1 后顶层单例字段(原唯一真相源)与 per_conv(全空,无写入)曾并存,行为不变。
|
||||
// 批2-批4 迁移:把所有调用方从 session.messages / session.generating 等改为 session.conv(conv_id).*,
|
||||
// 迁移完成后顶层单例字段已废弃删除(批3)。现 per_conv 为唯一真相源。
|
||||
// ============================================================
|
||||
|
||||
/// 单会话级状态(F-260616-09 B 阶段批1 纯新增,设计 §2.1.2)。
|
||||
///
|
||||
/// 持有当前会话(按 conversation_id 切分)私有的可变状态。批1 仅定义 + 惰性创建访问器,
|
||||
/// **不迁移** AiSession 顶层单例字段(共存期顶层字段仍是唯一真相源)。
|
||||
/// 持有当前会话(按 conversation_id 切分)私有的可变状态。批4 已迁移完成,本结构现为
|
||||
/// **唯一真相源**:所有调用方(agentic loop / IPC / audit / conversation / title /
|
||||
/// knowledge_inject 等约 60+ 处)均经 `session.conv(conv_id).*` / `session.conv_read(conv_id)`
|
||||
/// 读写,AiSession 顶层单例字段已全部删除。
|
||||
///
|
||||
/// 字段初值逐字对齐 [`AiSession::new`](#method.new),保证批2 迁移调用方时行为不变。
|
||||
#[allow(dead_code)] // F-09 B 批1 共存期:0 调用方(批2 迁移承接),字段待迁移后消费
|
||||
/// 字段初值逐字对齐 [`AiSession::new`](#method.new)。
|
||||
pub struct PerConvState {
|
||||
/// 对话历史(ContextManager:会话级消息真相源,裁剪仅影响发送视图)
|
||||
pub messages: ContextManager,
|
||||
@@ -513,8 +616,24 @@ pub struct PerConvState {
|
||||
pub model_override: Option<String>,
|
||||
/// 会话级信任(Session Trust):随会话销毁,不落库
|
||||
pub session_trust: HashSet<TrustKey>,
|
||||
/// 会话创建时间(懒创建:首条消息落库前仅存内存,upsert 时用作 created_at)
|
||||
/// 会话创建时间(懒创建:首条消息落库前仅存内存,预留 upsert 时用作 created_at)。
|
||||
///
|
||||
/// 预留字段:构造期写入但当前 upsert 路径未读取(cargo 报 never read),标 allow 保留;
|
||||
/// 待 upsert 接入会话创建时间回填时消费。符合零调用方≠垃圾(预留)。
|
||||
#[allow(dead_code)]
|
||||
pub created_at: Option<String>,
|
||||
/// 知识提炼去重标志(P1 修复-提炼重复触发):本会话已成功提炼(至少 insert 1 条 candidate)后置位。
|
||||
///
|
||||
/// 背景:审批续跑 try_continue → 重 spawn run_agentic_loop → 正常完成块 →
|
||||
/// maybe_spawn_extraction 二次触发,无去重致同知识点重复 candidate 刷屏。
|
||||
///
|
||||
/// 守卫语义:
|
||||
/// - maybe_spawn_extraction 入口判重:已置位则跳过 + warn(防重复触发刷 candidate)
|
||||
/// - extract_knowledge_from_conversation 成功(inserted ≥ 1)后由调用方置位
|
||||
/// - trigger_extraction_now(手动按钮)强制清位:允许用户手动重提炼(语义=手动覆盖自动去重)
|
||||
///
|
||||
/// 随会话销毁(per_conv 内存,不落库):会话切换/删除后该标志消失,新会话从 false 开始。
|
||||
pub knowledge_extracted: bool,
|
||||
}
|
||||
|
||||
impl PerConvState {
|
||||
@@ -530,6 +649,7 @@ impl PerConvState {
|
||||
/// - model_override: None
|
||||
/// - session_trust: HashSet::new()
|
||||
/// - created_at: None(批1 新增字段,AiSession 现有 active_conv_created_at 同语义)
|
||||
/// - knowledge_extracted: false(新会话未提炼,P1 去重标志)
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
messages: ContextManager::new(ContextConfig::default()),
|
||||
@@ -541,6 +661,7 @@ impl PerConvState {
|
||||
model_override: None,
|
||||
session_trust: HashSet::new(),
|
||||
created_at: None,
|
||||
knowledge_extracted: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -552,6 +673,11 @@ impl Default for PerConvState {
|
||||
}
|
||||
|
||||
/// 待审批的工具调用
|
||||
///
|
||||
/// 阶段3a(单真相源合并):新增 `kind: ApprovalKind` 字段区分两类挂起(Path/Risk),
|
||||
/// 原顶层 `diff` / `path_auth` 字段下沉进 `ApprovalKind` enum 变体(决策1:状态层合 +
|
||||
/// 决策层分 —— kind 在 PendingApproval 内携带语义,但 ai_approve/ai_authorize_dir
|
||||
/// 各只消费自己 kind,入口校验防误调)。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingApproval {
|
||||
pub tool_call_id: String,
|
||||
@@ -560,21 +686,48 @@ pub struct PendingApproval {
|
||||
pub conversation_id: Option<String>,
|
||||
/// 重启恢复的积压审批:无 live loop 持有 session.messages,审批后不 save(防空 messages 污染老对话)、不续跑
|
||||
pub recovered: bool,
|
||||
/// AE-2025-03(路径 B):write_file 的行级 unified diff(旧文件 vs 新内容)。
|
||||
/// 仅挂起审批前预读注入,供前端审批卡预览;旧文件不存在(新建)或非 write_file 为 None。
|
||||
/// recovered 审批(启动重建)无 diff(文件可能已变,重读无意义)。
|
||||
#[allow(dead_code)] // IPC 活跃(useAiEvents:252+ToolCard·UX-260618-06)·cargo Rust never-read 误报
|
||||
pub diff: Option<String>,
|
||||
/// F-260619-03 Phase B: 路径授权挂起标记(Some = 路径授权挂起,None = 普通 RiskLevel 审批)。
|
||||
/// 携带待授权目录(规范化父目录),ai_authorize_dir IPC 据此决定写入 session/persistent。
|
||||
pub path_auth: Option<PathAuthRequest>,
|
||||
/// 阶段3a:审批类型(Path 路径授权挂起 / Risk 普通 RiskLevel 审批)。
|
||||
/// 老构造点不传 kind 时默认 Risk(兼容:原 risk_pending 路径行为不变)。
|
||||
pub kind: ApprovalKind,
|
||||
/// 阶段4(容错/恢复,开关 `df-ai-approval-retry`):同 tc_id 重试计数。
|
||||
///
|
||||
/// 触发:同 tool_call_id 在审计表已有一条 executed/failed/rejected 落定记录(即该
|
||||
/// tc_id 已被执行/审批过一次),LLM 又用同 id 重试(process_tool_calls 再次 insert pending)。
|
||||
/// 插入时查审计表落定记录推算 retry_count;≥1 时跳过执行,emit AiToolCallCompleted 带
|
||||
/// "已跳过重试"提示,防 LLM 死循环重试同卡死工具(超时/权限错等)。
|
||||
///
|
||||
/// 兜底/回退:flag 关 → 不查审计表、retry_count 恒 0,等价原行为(单次审批执行)。
|
||||
//
|
||||
// dead_code 说明:当前断路决策在 insert 点(detect_retry_count 查审计表),不读存储的
|
||||
// retry_count;字段保留作审计/未来"允许多次重试阈值"扩展(u32 暂仅 0/1),对齐 Risk{diff}
|
||||
// 变体同款 allow 处理。
|
||||
#[allow(dead_code)]
|
||||
pub retry_count: u32,
|
||||
}
|
||||
|
||||
/// F-260619-03 Phase B: 路径授权挂起请求(PendingApproval.path_auth 标记)。
|
||||
/// 阶段3a:审批类型枚举(决策层分离,kind 区分 ai_approve vs ai_authorize_dir 消费)。
|
||||
///
|
||||
/// - `Path(req)`:F-260619-03 Phase B 路径授权挂起,由 `ai_authorize_dir` 消费(once/always/deny)。
|
||||
/// - `Risk { diff }`:普通 Med/High RiskLevel 审批,由 `ai_approve` 消费(一次性 approve/reject)。
|
||||
/// `diff`:AE-2025-03(路径 B)write_file 行级 unified diff,供前端审批卡预览;
|
||||
/// 旧文件不存在(新建)或非 write_file / recovered 审批为 None。
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ApprovalKind {
|
||||
/// F-260619-03 Phase B: 路径授权挂起(携带待授权目录列表)。
|
||||
Path(PathAuthRequest),
|
||||
/// 普通 RiskLevel 审批(Med/High 风险工具)。
|
||||
/// diff 仅 write_file 挂起审批前预读注入;其余工具 / recovered 审批为 None。
|
||||
#[allow(dead_code)] // IPC 活跃(useAiEvents:252+ToolCard·UX-260618-06)·cargo Rust never-read 误报
|
||||
Risk { diff: Option<String> },
|
||||
}
|
||||
|
||||
/// F-260619-03 Phase B: 路径授权挂起请求(ApprovalKind::Path 标记)。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PathAuthRequest {
|
||||
/// 待授权目录(规范化父目录,对齐 session_trust 目录粒度)。
|
||||
pub dir: std::path::PathBuf,
|
||||
/// 待授权目录列表(规范化父目录,对齐 session_trust 目录粒度)。
|
||||
/// L1 补丁(rename_file 双路径漏校):收集所有未授权父目录,rename_file 的 from+to
|
||||
/// 若分别属于不同未授权目录,都需挂起授权(原仅收首个致 to 路径漏校)。单路径工具通常 1 项。
|
||||
pub dirs: Vec<std::path::PathBuf>,
|
||||
/// 原始路径参数(read_file.path / write_file.path / rename_file.from+to / ...)。
|
||||
pub raw_paths: Vec<String>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user