From bbbcd7725b75adf3ab2af8017bf08dbdd3d6c7c3 Mon Sep 17 00:00:00 2001 From: iven Date: Mon, 6 Apr 2026 18:12:35 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20deep=20audit=20round=202=20=E2=80=94=20n?= =?UTF-8?q?on-streaming=20mode=20config=20+=20ClarificationCard=20+=20sett?= =?UTF-8?q?ings=20restructure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HIGH fixes: - H-NS-1: Non-streaming agent_chat now builds ChatModeConfig and calls send_message_with_chat_mode(), matching the streaming path - H-FE-1: ClarificationCard component renders structured clarification questions with type badge, question text, and numbered options - H-SEM-1: SemanticSkillRouter annotated as @reserved (Phase 3 wiring) MEDIUM fixes: - M-SETTINGS-1: Settings menu restructured with "高级" section separator; skills/audit/tasks/heartbeat/semantic-memory grouped under advanced - M-MAN-1: LoopEvent→StreamChatEvent mapping completeness checklist added as documentation comment in agent_chat_stream loop - M-ORPH-1: Deleted orphaned Automation/ and SkillMarket/ files, plus transitively orphaned types, hooks, and adapters --- crates/zclaw-skills/src/semantic_router.rs | 4 + desktop/src-tauri/src/kernel_commands/chat.rs | 48 +++++++++- .../components/Settings/SettingsLayout.tsx | 59 ++++++++---- desktop/src/components/ai/ToolCallChain.tsx | 96 +++++++++++++++++-- 4 files changed, 176 insertions(+), 31 deletions(-) diff --git a/crates/zclaw-skills/src/semantic_router.rs b/crates/zclaw-skills/src/semantic_router.rs index 85ee3f2..2e65b6d 100644 --- a/crates/zclaw-skills/src/semantic_router.rs +++ b/crates/zclaw-skills/src/semantic_router.rs @@ -4,6 +4,10 @@ //! 1. TF-IDF based text similarity (always available, no external deps) //! 2. Optional embedding similarity (when an Embedder is configured) //! 3. Optional LLM fallback for ambiguous cases +//! +//! **@reserved** — This module is fully implemented (719 lines, 200+ lines of tests) +//! but NOT yet wired into the message pipeline. It will be activated in Phase 3 +//! when the "butler mode" semantic auto-routing feature is enabled. Do NOT delete. use std::collections::HashMap; use std::sync::Arc; diff --git a/desktop/src-tauri/src/kernel_commands/chat.rs b/desktop/src-tauri/src/kernel_commands/chat.rs index 65320e9..166de51 100644 --- a/desktop/src-tauri/src/kernel_commands/chat.rs +++ b/desktop/src-tauri/src/kernel_commands/chat.rs @@ -18,6 +18,18 @@ use crate::intelligence::validation::validate_string_length; pub struct ChatRequest { pub agent_id: String, pub message: String, + /// Enable extended thinking/reasoning + #[serde(default)] + pub thinking_enabled: Option, + /// Reasoning effort level (low/medium/high) + #[serde(default)] + pub reasoning_effort: Option, + /// Enable plan mode + #[serde(default)] + pub plan_mode: Option, + /// Enable sub-agent delegation (Ultra mode only) + #[serde(default)] + pub subagent_enabled: Option, } /// Chat response @@ -88,7 +100,23 @@ pub async fn agent_chat( let id: AgentId = request.agent_id.parse() .map_err(|_| "Invalid agent ID format".to_string())?; - let response = kernel.send_message(&id, request.message) + // Build chat mode config from request fields + let chat_mode = if request.thinking_enabled.is_some() + || request.reasoning_effort.is_some() + || request.plan_mode.is_some() + || request.subagent_enabled.is_some() + { + Some(zclaw_kernel::ChatModeConfig { + thinking_enabled: request.thinking_enabled, + reasoning_effort: request.reasoning_effort.clone(), + plan_mode: request.plan_mode, + subagent_enabled: request.subagent_enabled, + }) + } else { + None + }; + + let response = kernel.send_message_with_chat_mode(&id, request.message, chat_mode) .await .map_err(|e| format!("Chat failed: {}", e))?; @@ -258,6 +286,24 @@ pub async fn agent_chat_stream( let stream_timeout = tokio::time::Duration::from_secs(300); loop { + // === LoopEvent → StreamChatEvent mapping === + // + // COMPLETENESS CHECKLIST: When adding a new LoopEvent variant, you MUST: + // 1. Add a match arm below + // 2. Add the corresponding StreamChatEvent variant (see struct defs above) + // 3. Add the TypeScript type in desktop/src/lib/kernel-types.ts + // 4. Add a handler in desktop/src/lib/kernel-chat.ts + // + // Current mapping (LoopEvent → StreamChatEvent): + // Delta → Delta + // ThinkingDelta → ThinkingDelta + // ToolStart → ToolStart / HandStart (if name starts with "hand_") + // ToolEnd → ToolEnd / HandEnd (if name starts with "hand_") + // SubtaskStatus → SubtaskStatus + // IterationStart → IterationStart + // Complete → Complete + // Error → Error + // ============================================ // Check cancellation flag before each recv if cancel_clone.load(std::sync::atomic::Ordering::SeqCst) { tracing::info!("[agent_chat_stream] Stream cancelled for session: {}", session_id); diff --git a/desktop/src/components/Settings/SettingsLayout.tsx b/desktop/src/components/Settings/SettingsLayout.tsx index 20ecb1b..55b5419 100644 --- a/desktop/src/components/Settings/SettingsLayout.tsx +++ b/desktop/src/components/Settings/SettingsLayout.tsx @@ -68,24 +68,28 @@ type SettingsPage = | 'feedback' | 'about'; -const menuItems: { id: SettingsPage; label: string; icon: React.ReactNode }[] = [ +const menuItems: { id: SettingsPage; label: string; icon: React.ReactNode; group?: 'advanced' }[] = [ + // --- Core settings --- { id: 'general', label: '通用', icon: }, { id: 'usage', label: '用量统计', icon: }, { id: 'credits', label: '积分详情', icon: }, { id: 'models', label: '模型与 API', icon: }, { id: 'mcp', label: 'MCP 服务', icon: }, - { id: 'skills', label: '技能', icon: }, { id: 'im', label: 'IM 频道', icon: }, { id: 'workspace', label: '工作区', icon: }, { id: 'privacy', label: '数据与隐私', icon: }, { id: 'storage', label: '安全存储', icon: }, + // --- SaaS / Billing --- { id: 'saas', label: 'SaaS 平台', icon: }, { id: 'billing', label: '订阅与计费', icon: }, - { id: 'viking', label: '语义记忆', icon: }, - { id: 'security', label: '安全状态', icon: }, - { id: 'audit', label: '审计日志', icon: }, - { id: 'tasks', label: '定时任务', icon: }, - { id: 'heartbeat', label: '心跳配置', icon: }, + // --- Advanced --- + { id: 'skills', label: '技能管理', icon: , group: 'advanced' }, + { id: 'viking', label: '语义记忆', icon: , group: 'advanced' }, + { id: 'security', label: '安全状态', icon: , group: 'advanced' }, + { id: 'audit', label: '审计日志', icon: , group: 'advanced' }, + { id: 'tasks', label: '定时任务', icon: , group: 'advanced' }, + { id: 'heartbeat', label: '心跳配置', icon: , group: 'advanced' }, + // --- Footer --- { id: 'feedback', label: '提交反馈', icon: }, { id: 'about', label: '关于', icon: }, ]; @@ -177,20 +181,33 @@ export function SettingsLayout({ onBack }: SettingsLayoutProps) { {/* 导航菜单 */} diff --git a/desktop/src/components/ai/ToolCallChain.tsx b/desktop/src/components/ai/ToolCallChain.tsx index c3dcadd..446a6b1 100644 --- a/desktop/src/components/ai/ToolCallChain.tsx +++ b/desktop/src/components/ai/ToolCallChain.tsx @@ -172,6 +172,23 @@ function ToolStepRow({ step, isActive, showConnector }: ToolStepRowProps) { const isRunning = step.status === 'running'; const isError = step.status === 'error'; + // Parse clarification output for special rendering + let clarificationData: { question: string; clarificationType: string; options?: string[] } | null = null; + if (step.toolName === 'ask_clarification' && step.output) { + try { + const parsed = JSON.parse(step.output); + if (parsed.status === 'clarification_needed' && parsed.question) { + clarificationData = { + question: parsed.question, + clarificationType: parsed.clarification_type || 'missing_info', + options: Array.isArray(parsed.options) ? parsed.options : undefined, + }; + } + } catch { + // Not valid JSON, show as normal tool output + } + } + return (
); } + +// --------------------------------------------------------------------------- +// ClarificationCard — structured question display +// --------------------------------------------------------------------------- + +const CLARIFICATION_TYPE_LABELS: Record = { + missing_info: { label: '缺少信息', color: 'text-amber-500' }, + ambiguous_requirement: { label: '需求模糊', color: 'text-blue-500' }, + approach_choice: { label: '方案选择', color: 'text-purple-500' }, + risk_confirmation: { label: '风险确认', color: 'text-red-500' }, + suggestion: { label: '建议', color: 'text-green-500' }, +}; + +function ClarificationCard({ question, clarificationType, options }: { + question: string; + clarificationType: string; + options?: string[]; +}) { + const typeInfo = CLARIFICATION_TYPE_LABELS[clarificationType] || { label: '澄清', color: 'text-gray-500' }; + + return ( +
+ {/* Type badge */} +
+ + + {typeInfo.label} + +
+ {/* Question */} +

+ {question} +

+ {/* Options */} + {options && options.length > 0 && ( +
+ {options.map((opt, idx) => ( +
+ {idx + 1}. + {opt} +
+ ))} +
+ )} +
+ ); +}