diff --git a/crates/df-ai/src/context/manager_tests.rs b/crates/df-ai/src/context/manager_tests.rs index 93c5d13..96fb4c8 100644 --- a/crates/df-ai/src/context/manager_tests.rs +++ b/crates/df-ai/src/context/manager_tests.rs @@ -282,8 +282,8 @@ fn compress_old_messages_marks_compressed_and_returns_refs() { assert_eq!(compressed[1].content, "旧回复1"); // status 已改 compressed - assert_eq!(mgr.messages_mut()[0].message.status.as_deref(), Some("compressed")); - assert_eq!(mgr.messages_mut()[1].message.status.as_deref(), Some("compressed")); + assert_eq!(mgr.messages_mut()[0].message.status.as_ref(), Some(&MessageStatus::Compressed)); + assert_eq!(mgr.messages_mut()[1].message.status.as_ref(), Some(&MessageStatus::Compressed)); // 保护区外(本例 index 2)仍 active assert!(mgr.messages_mut()[2].message.is_active(), "保护区外消息不应被动"); @@ -346,8 +346,8 @@ fn compress_old_messages_skips_already_inactive() { ); // truncated 状态不被改成 compressed(保留原 truncated,语义不混淆) assert_eq!( - mgr.messages_mut()[0].message.status.as_deref(), - Some("truncated"), + mgr.messages_mut()[0].message.status.as_ref(), + Some(&MessageStatus::Truncated), "已 truncated 不应被改写为 compressed" ); } @@ -660,7 +660,7 @@ fn topic_field_survives_compress_old_messages() { assert_eq!(mgr.messages_mut()[0].topic.as_deref(), Some("code"), "compressed 消息 topic 应保留"); assert_eq!(mgr.messages_mut()[1].topic.as_deref(), Some("file"), "compressed 消息 topic 应保留"); // status 改为 compressed - assert_eq!(mgr.messages_mut()[0].message.status.as_deref(), Some("compressed")); + assert_eq!(mgr.messages_mut()[0].message.status.as_ref(), Some(&MessageStatus::Compressed)); } #[test] diff --git a/crates/df-nodes/src/human_node.rs b/crates/df-nodes/src/human_node.rs index e9e40a0..e33aea0 100644 --- a/crates/df-nodes/src/human_node.rs +++ b/crates/df-nodes/src/human_node.rs @@ -218,7 +218,7 @@ mod tests { node_id: node_id.to_string(), inputs: HashMap::new(), config, - execution_id: execution_id.to_string(), + execution_id: execution_id.into(), event_bus: event_bus.clone(), node_status: StateMachine::new(), } @@ -235,7 +235,7 @@ mod tests { ) { event_bus .send(WorkflowEvent::HumanApprovalResponse { - execution_id: execution_id.to_string(), + execution_id: execution_id.into(), node_id: node_id.to_string(), decision: decision.to_string(), decisions: vec![], @@ -254,7 +254,7 @@ mod tests { ) { event_bus .send(WorkflowEvent::HumanApprovalResponse { - execution_id: execution_id.to_string(), + execution_id: execution_id.into(), node_id: node_id.to_string(), decision: String::new(), decisions: decisions.iter().map(|s| s.to_string()).collect(), @@ -477,8 +477,8 @@ mod tests { dag.add_node("b".to_string(), Box::new(HumanNode)); dag.add_edge("a".to_string(), "b".to_string()); - let mut executor = DagExecutor::new(bus.clone(), exec_id.to_string()); - let sm = executor.state_machine(); // 共享状态机(spawn 后仍可读) + let mut executor = DagExecutor::new(bus.clone(), exec_id.into()); + let sm = executor.state_machine(); // 共享状态机(spawn 后仍可读) let run_handle = tokio::spawn(async move { executor @@ -537,7 +537,7 @@ mod tests { let mut dag = Dag::new(); dag.add_node("h".to_string(), Box::new(HumanNode)); - let mut executor = DagExecutor::new(bus.clone(), exec_id.to_string()); + let mut executor = DagExecutor::new(bus.clone(), exec_id.into()); let sm = executor.state_machine(); let run_handle = tokio::spawn(async move { diff --git a/crates/df-workflow/src/executor_helpers.rs b/crates/df-workflow/src/executor_helpers.rs index a567661..bdeb3ca 100644 --- a/crates/df-workflow/src/executor_helpers.rs +++ b/crates/df-workflow/src/executor_helpers.rs @@ -110,7 +110,7 @@ async fn node_config_overrides_global_in_node_context() { let dag = registry.build_dag(&def).expect("build_dag"); - let mut executor = DagExecutor::new(EventBus::new(), "test-nodecfg".to_string()); + let mut executor = DagExecutor::new(EventBus::new(), "test-nodecfg".into()); // 全局 config 写 foo=GLOBAL(验证被 n1 节点级覆盖,n2 回退用全局) let outputs = executor .run(&dag, serde_json::json!({ "foo": "GLOBAL" })) @@ -138,7 +138,7 @@ async fn test_same_layer_runs_in_parallel() { dag.add_node("a".to_string(), Box::new(SleepNode { sleep_ms: 100 })); dag.add_node("b".to_string(), Box::new(SleepNode { sleep_ms: 100 })); - let mut executor = DagExecutor::new(EventBus::new(), "test-exec".to_string()); + let mut executor = DagExecutor::new(EventBus::new(), "test-exec".into()); let start = std::time::Instant::now(); let outputs = executor.run(&dag, serde_json::Value::Null).await.unwrap(); let elapsed = start.elapsed(); @@ -161,7 +161,7 @@ async fn test_layer_failure_aborts_following_layers() { dag.add_node("c".to_string(), Box::new(SleepNode { sleep_ms: 10 })); dag.add_edge("a".to_string(), "c".to_string()); - let mut executor = DagExecutor::new(EventBus::new(), "test-exec".to_string()); + let mut executor = DagExecutor::new(EventBus::new(), "test-exec".into()); let err = executor .run(&dag, serde_json::Value::Null) .await @@ -206,7 +206,7 @@ async fn test_cancelled_node_skips_set_failed() { let mut dag = Dag::new(); dag.add_node("x".to_string(), Box::new(CancelSelfNode)); - let mut executor = DagExecutor::new(EventBus::new(), "test-cancel".to_string()); + let mut executor = DagExecutor::new(EventBus::new(), "test-cancel".into()); let result = executor.run(&dag, serde_json::Value::Null).await; // run 返回 Err(取消致中止后续层),但不 panic/bail transition @@ -257,7 +257,7 @@ async fn test_cancelled_node_skips_set_completed() { let mut dag = Dag::new(); dag.add_node("y".to_string(), Box::new(CancelSelfThenOkNode)); - let mut executor = DagExecutor::new(EventBus::new(), "test-cancel-ok".to_string()); + let mut executor = DagExecutor::new(EventBus::new(), "test-cancel-ok".into()); let result = executor.run(&dag, serde_json::Value::Null).await; // run 返回 Ok —— Ok 节点不应因 TOCTOU 取消被误判为失败 @@ -285,7 +285,7 @@ async fn test_cancelled_node_emits_node_cancelled_event() { let bus = EventBus::new(); let mut rx = bus.subscribe(); - let mut executor = DagExecutor::new(bus, "test-cancel-event".to_string()); + let mut executor = DagExecutor::new(bus, "test-cancel-event".into()); let _ = executor.run(&dag, serde_json::Value::Null).await; // 收集所有事件(run 已结束,事件总线无新事件) @@ -408,7 +408,7 @@ async fn conditions_eval_routes_by_edge_condition() { "$.ok == false".to_string(), ); - let mut executor = DagExecutor::new(EventBus::new(), "test-cond-route".to_string()); + let mut executor = DagExecutor::new(EventBus::new(), "test-cond-route".into()); let outputs = executor .run(&dag, serde_json::Value::Null) .await @@ -462,7 +462,7 @@ async fn conditions_eval_skip_keeps_pending() { "$.ok == false".to_string(), ); - let mut executor = DagExecutor::new(EventBus::new(), "test-cond-skip".to_string()); + let mut executor = DagExecutor::new(EventBus::new(), "test-cond-skip".into()); let outputs = executor .run(&dag, serde_json::Value::Null) .await @@ -504,7 +504,7 @@ async fn conditions_eval_unconditional_edge_always_passes() { "$.ok == false".to_string(), ); - let mut executor = DagExecutor::new(EventBus::new(), "test-cond-mix".to_string()); + let mut executor = DagExecutor::new(EventBus::new(), "test-cond-mix".into()); let outputs = executor .run(&dag, serde_json::Value::Null) .await diff --git a/src-tauri/src/commands/ai/audit/mod.rs b/src-tauri/src/commands/ai/audit/mod.rs index c8278ef..dcdb895 100644 --- a/src-tauri/src/commands/ai/audit/mod.rs +++ b/src-tauri/src/commands/ai/audit/mod.rs @@ -485,6 +485,7 @@ pub(crate) async fn process_tool_calls( #[cfg(test)] mod tests { use super::*; + use super::path_auth::extract_file_tool_paths; /// grep 走单路径授权申请路径(NeedsAuth),非 search_files 盲拒(Denied)。 /// diff --git a/src-tauri/src/commands/ai/augmentation/inject.rs b/src-tauri/src/commands/ai/augmentation/inject.rs index 64ee54d..fda0bef 100644 --- a/src-tauri/src/commands/ai/augmentation/inject.rs +++ b/src-tauri/src/commands/ai/augmentation/inject.rs @@ -189,6 +189,7 @@ mod tests { status: ProjectStatus::InProgress, description: "AI-native dev tool".into(), path: Some(SanitizedPath::new("E:/wk-lab/devflow")), + extra: vec![], }; let seg = build_augmentation_segment(&[aug], "zh"); assert!(seg.starts_with("--- 以下是用户选择的上下文参考"), "头部应隔离标注, got: {}", seg); @@ -206,6 +207,7 @@ mod tests { status: TaskStatus::Todo, description: "do X".into(), project_name: Some("devflow".into()), + extra: vec![], }; let seg = build_augmentation_segment(&[aug], "zh"); assert!(seg.contains("【任务】Implement X")); @@ -234,6 +236,7 @@ mod tests { title: "Idea1".into(), status: IdeaStatus::Approved, description: String::new(), + extra: vec![], }, Augmentation::Project { id: "p1".into(), @@ -241,6 +244,7 @@ mod tests { status: ProjectStatus::Planning, description: String::new(), path: None, + extra: vec![], }, ]; let seg = build_augmentation_segment(&augs, "zh"); @@ -258,6 +262,7 @@ mod tests { title: "Idea1".into(), status: IdeaStatus::Approved, description: String::new(), + extra: vec![], }; let seg = build_augmentation_segment(&[aug], "en"); assert!(seg.starts_with("--- The following is the context"), "en 头部: {}", seg); @@ -271,6 +276,7 @@ mod tests { title: "Idea1".into(), status: IdeaStatus::Approved, description: String::new(), + extra: vec![], }; let seg = build_augmentation_segment(&[aug], "fr"); assert!(seg.starts_with("--- 以下是用户选择的上下文参考"), "未知 lang 应按 zh 兜底"); @@ -284,6 +290,7 @@ mod tests { status: ProjectStatus::Planning, description: String::new(), path: None, + extra: vec![], }; let seg = build_augmentation_segment(&[aug], "zh"); assert!(!seg.contains("目录:"), "无 path 不应渲染目录行: {}", seg); diff --git a/src-tauri/src/commands/ai/commands/conversation.rs b/src-tauri/src/commands/ai/commands/conversation.rs index 1db60d4..d4d5173 100644 --- a/src-tauri/src/commands/ai/commands/conversation.rs +++ b/src-tauri/src/commands/ai/commands/conversation.rs @@ -637,7 +637,7 @@ pub async fn ai_conversation_export( #[cfg(test)] mod tests { use super::*; - use df_ai::provider::{ChatMessage, ContentPart, MessageRole, MessageStatus, ToolCall, ToolCallFunction, ToolType}; + use df_ai::provider::{ChatMessage, ContentPart, MessageRole, MessageStatus, ToolCall, ToolCallFunction}; fn base_msg() -> ChatMessage { ChatMessage { @@ -821,7 +821,7 @@ mod tests { serde_json::to_string(&msg.tool_calls).unwrap() ); assert_eq!(back.model.as_deref(), Some("m")); - assert_eq!(back.status.as_deref(), Some("compressed")); + assert_eq!(back.status.as_ref(), Some(&MessageStatus::Compressed)); assert_eq!(back.reasoning_content.as_deref(), Some("r")); assert_eq!(back.timestamp, Some(123)); } @@ -875,7 +875,7 @@ mod tests { }, }]), status: if i % 6 == 0 { - Some("compressed".into()) + Some(MessageStatus::Compressed) } else { None }, @@ -980,7 +980,7 @@ mod tests { id: Some("m_summary".into()), role: MessageRole::Assistant, content: "压缩摘要".into(), - status: Some("compressed".into()), + status: Some(MessageStatus::Compressed), ..base_msg() }, ChatMessage { diff --git a/src-tauri/src/commands/ai/tool_registry.rs b/src-tauri/src/commands/ai/tool_registry.rs index b2557e2..1b6c8ee 100644 --- a/src-tauri/src/commands/ai/tool_registry.rs +++ b/src-tauri/src/commands/ai/tool_registry.rs @@ -4081,7 +4081,7 @@ mod tests { id: "proj-svc-1".to_string(), name: "proj-svc-1".to_string(), description: String::new(), - status: "planning".to_string(), + status: ProjectStatus::Planning, idea_id: None, path: None, stack: None, diff --git a/src/components/AiChat.vue b/src/components/AiChat.vue index d6f33ef..0faad00 100644 --- a/src/components/AiChat.vue +++ b/src/components/AiChat.vue @@ -128,7 +128,7 @@