feat: DeerFlow 2.0 core capabilities — Phase 1.0 + 1.1
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
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
Phase 1.0 — Butler Mode UI: - Hide "自动化" and "技能市场" entries from sidebar navigation - Remove AutomationPanel and SkillMarket view rendering from App.tsx - Simplify MainViewType to only 'chat' - Main interface is now: chat + conversation list + detail panel only Phase 1.1 — Mode Differentiation: - Add subagent_enabled field to ChatModeConfig (Rust), StreamChatRequest (Tauri), gateway-client, kernel-client, saas-relay-client, and streamStore - TaskTool is now only registered when subagent_enabled=true (Ultra mode) - System prompt includes sub-agent delegation instructions only in Ultra mode - Frontend transmits subagent_enabled from ChatMode config through the full stack This connects the 4-tier mode selector (Flash/Thinking/Pro/Ultra) to actual backend behavioral differences — Ultra mode now truly enables sub-agent delegation.
This commit is contained in:
@@ -4,12 +4,13 @@ use tokio::sync::mpsc;
|
||||
use zclaw_types::{AgentId, Result};
|
||||
|
||||
/// Chat mode configuration passed from the frontend.
|
||||
/// Controls thinking, reasoning, and plan mode behavior.
|
||||
/// Controls thinking, reasoning, plan mode, and sub-agent behavior.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChatModeConfig {
|
||||
pub thinking_enabled: Option<bool>,
|
||||
pub reasoning_effort: Option<String>,
|
||||
pub plan_mode: Option<bool>,
|
||||
pub subagent_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
use zclaw_runtime::{AgentLoop, tool::builtin::PathValidator};
|
||||
@@ -45,7 +46,8 @@ impl Kernel {
|
||||
let model = self.config.model().to_string();
|
||||
|
||||
// Create agent loop with model configuration
|
||||
let tools = self.create_tool_registry();
|
||||
let subagent_enabled = chat_mode.as_ref().and_then(|m| m.subagent_enabled).unwrap_or(false);
|
||||
let tools = self.create_tool_registry(subagent_enabled);
|
||||
let mut loop_runner = AgentLoop::new(
|
||||
*agent_id,
|
||||
self.driver.clone(),
|
||||
@@ -92,7 +94,10 @@ impl Kernel {
|
||||
}
|
||||
|
||||
// Build system prompt with skill information injected
|
||||
let system_prompt = self.build_system_prompt_with_skills(agent_config.system_prompt.as_ref()).await;
|
||||
let system_prompt = self.build_system_prompt_with_skills(
|
||||
agent_config.system_prompt.as_ref(),
|
||||
subagent_enabled,
|
||||
).await;
|
||||
let loop_runner = loop_runner.with_system_prompt(&system_prompt);
|
||||
|
||||
// Run the loop
|
||||
@@ -147,7 +152,8 @@ impl Kernel {
|
||||
let model = self.config.model().to_string();
|
||||
|
||||
// Create agent loop with model configuration
|
||||
let tools = self.create_tool_registry();
|
||||
let subagent_enabled = chat_mode.as_ref().and_then(|m| m.subagent_enabled).unwrap_or(false);
|
||||
let tools = self.create_tool_registry(subagent_enabled);
|
||||
let mut loop_runner = AgentLoop::new(
|
||||
*agent_id,
|
||||
self.driver.clone(),
|
||||
@@ -197,7 +203,10 @@ impl Kernel {
|
||||
// Use external prompt if provided, otherwise build default
|
||||
let system_prompt = match system_prompt_override {
|
||||
Some(prompt) => prompt,
|
||||
None => self.build_system_prompt_with_skills(agent_config.system_prompt.as_ref()).await,
|
||||
None => self.build_system_prompt_with_skills(
|
||||
agent_config.system_prompt.as_ref(),
|
||||
subagent_enabled,
|
||||
).await,
|
||||
};
|
||||
let loop_runner = loop_runner.with_system_prompt(&system_prompt);
|
||||
|
||||
@@ -206,8 +215,13 @@ impl Kernel {
|
||||
loop_runner.run_streaming(session_id, message).await
|
||||
}
|
||||
|
||||
/// Build a system prompt with skill information injected
|
||||
pub(super) async fn build_system_prompt_with_skills(&self, base_prompt: Option<&String>) -> String {
|
||||
/// Build a system prompt with skill information injected.
|
||||
/// When `subagent_enabled` is true, adds sub-agent delegation instructions.
|
||||
pub(super) async fn build_system_prompt_with_skills(
|
||||
&self,
|
||||
base_prompt: Option<&String>,
|
||||
subagent_enabled: bool,
|
||||
) -> String {
|
||||
// Get skill list asynchronously
|
||||
let skills = self.skills.list().await;
|
||||
|
||||
@@ -215,7 +229,8 @@ impl Kernel {
|
||||
.map(|p| p.clone())
|
||||
.unwrap_or_else(|| "You are a helpful AI assistant.".to_string());
|
||||
|
||||
// Inject skill information with categories
|
||||
// Inject skill metadata only (progressive loading pattern from DeerFlow).
|
||||
// Full skill content is loaded on-demand via `load_skill_content` tool.
|
||||
if !skills.is_empty() {
|
||||
prompt.push_str("\n\n## Available Skills\n\n");
|
||||
prompt.push_str("You have access to specialized skills. Analyze user intent and autonomously call `execute_skill` with the appropriate skill_id.\n\n");
|
||||
@@ -245,6 +260,21 @@ impl Kernel {
|
||||
prompt.push_str("User: \"分析腾讯财报\" → Intent: Financial analysis → Call: execute_skill(\"finance-tracker\", {...})\n");
|
||||
}
|
||||
|
||||
// Sub-agent delegation instructions (Ultra mode only)
|
||||
if subagent_enabled {
|
||||
prompt.push_str("\n\n## Sub-Agent Delegation\n\n");
|
||||
prompt.push_str("You can delegate complex sub-tasks to sub-agents using the `task` tool. This enables parallel execution of independent work.\n\n");
|
||||
prompt.push_str("### When to use sub-agents:\n");
|
||||
prompt.push_str("- Complex tasks that can be decomposed into independent parallel sub-tasks\n");
|
||||
prompt.push_str("- Research tasks requiring multiple independent searches\n");
|
||||
prompt.push_str("- Tasks requiring different expertise areas simultaneously\n\n");
|
||||
prompt.push_str("### Guidelines:\n");
|
||||
prompt.push_str("- Break complex work into clear, self-contained sub-tasks\n");
|
||||
prompt.push_str("- Each sub-task should have a clear objective and expected output\n");
|
||||
prompt.push_str("- Synthesize sub-agent results into a coherent final response\n");
|
||||
prompt.push_str("- Maximum 3 concurrent sub-agents — batch if more are needed\n");
|
||||
}
|
||||
|
||||
prompt
|
||||
}
|
||||
|
||||
|
||||
@@ -162,18 +162,22 @@ impl Kernel {
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a tool registry with built-in tools
|
||||
pub(crate) fn create_tool_registry(&self) -> ToolRegistry {
|
||||
/// Create a tool registry with built-in tools.
|
||||
/// When `subagent_enabled` is false, TaskTool is excluded to prevent
|
||||
/// the LLM from attempting sub-agent delegation in non-Ultra modes.
|
||||
pub(crate) fn create_tool_registry(&self, subagent_enabled: bool) -> ToolRegistry {
|
||||
let mut tools = ToolRegistry::new();
|
||||
zclaw_runtime::tool::builtin::register_builtin_tools(&mut tools);
|
||||
|
||||
// Register TaskTool with driver and memory for sub-agent delegation
|
||||
// Register TaskTool only when sub-agent mode is enabled (Ultra mode)
|
||||
if subagent_enabled {
|
||||
let task_tool = zclaw_runtime::tool::builtin::TaskTool::new(
|
||||
self.driver.clone(),
|
||||
self.memory.clone(),
|
||||
self.config.model(),
|
||||
);
|
||||
tools.register(Box::new(task_tool));
|
||||
}
|
||||
|
||||
tools
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@ pub struct StreamChatRequest {
|
||||
/// Enable plan mode
|
||||
#[serde(default)]
|
||||
pub plan_mode: Option<bool>,
|
||||
/// Enable sub-agent delegation (Ultra mode only)
|
||||
#[serde(default)]
|
||||
pub subagent_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -216,6 +219,7 @@ pub async fn agent_chat_stream(
|
||||
thinking_enabled: request.thinking_enabled,
|
||||
reasoning_effort: request.reasoning_effort.clone(),
|
||||
plan_mode: request.plan_mode,
|
||||
subagent_enabled: request.subagent_enabled,
|
||||
};
|
||||
|
||||
let rx = kernel.send_message_stream_with_prompt(
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import './index.css';
|
||||
import { Sidebar, MainViewType } from './components/Sidebar';
|
||||
import { Sidebar } from './components/Sidebar';
|
||||
import { ChatArea } from './components/ChatArea';
|
||||
import { RightPanel } from './components/RightPanel';
|
||||
import { SettingsLayout } from './components/Settings/SettingsLayout';
|
||||
import { AutomationPanel } from './components/Automation';
|
||||
import { SkillMarket } from './components/SkillMarket';
|
||||
import { AgentOnboardingWizard } from './components/AgentOnboardingWizard';
|
||||
import { HandApprovalModal } from './components/HandApprovalModal';
|
||||
import { TopBar } from './components/TopBar';
|
||||
@@ -17,7 +14,7 @@ import { useHandStore, type HandRun } from './store/handStore';
|
||||
import { useChatStore } from './store/chatStore';
|
||||
import { initializeStores } from './store';
|
||||
import { getStoredGatewayToken } from './lib/gateway-client';
|
||||
import { pageVariants, defaultTransition, fadeInVariants } from './lib/animations';
|
||||
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { isTauriRuntime, getLocalGatewayStatus, startLocalGateway } from './lib/tauri-gateway';
|
||||
import { LoginPage } from './components/LoginPage';
|
||||
@@ -49,7 +46,6 @@ function BootstrapScreen({ status }: { status: string }) {
|
||||
|
||||
function App() {
|
||||
const [view, setView] = useState<View>('main');
|
||||
const [mainContentView, setMainContentView] = useState<MainViewType>('chat');
|
||||
const [bootstrapping, setBootstrapping] = useState(true);
|
||||
const [bootstrapStatus, setBootstrapStatus] = useState('Initializing...');
|
||||
const [showOnboarding, setShowOnboarding] = useState(false);
|
||||
@@ -379,11 +375,6 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理主视图切换
|
||||
const handleMainViewChange = (view: MainViewType) => {
|
||||
setMainContentView(view);
|
||||
};
|
||||
|
||||
// 登录门禁 — 必须登录才能使用
|
||||
if (isRestoring) {
|
||||
return <BootstrapScreen status="Restoring session..." />;
|
||||
@@ -457,7 +448,6 @@ function App() {
|
||||
{/* 左侧边栏 */}
|
||||
<Sidebar
|
||||
onOpenSettings={() => setView('settings')}
|
||||
onMainViewChange={handleMainViewChange}
|
||||
/>
|
||||
|
||||
{/* 主内容区 */}
|
||||
@@ -468,62 +458,8 @@ function App() {
|
||||
onOpenDetail={() => setShowDetailDrawer(true)}
|
||||
/>
|
||||
|
||||
{/* 内容区域 */}
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.main
|
||||
key={mainContentView}
|
||||
variants={pageVariants}
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
transition={defaultTransition}
|
||||
className="flex-1 overflow-hidden relative flex flex-col"
|
||||
>
|
||||
{mainContentView === 'automation' ? (
|
||||
<motion.div
|
||||
variants={fadeInVariants}
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
className="h-full overflow-y-auto"
|
||||
>
|
||||
<div className="sticky top-0 z-10 flex items-center gap-2 px-4 py-2 bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm border-b border-gray-200 dark:border-gray-800">
|
||||
<button
|
||||
onClick={() => handleMainViewChange('chat')}
|
||||
className="p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-md transition-colors text-gray-600 dark:text-gray-400"
|
||||
title="返回聊天"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m15 18-6-6 6-6"/></svg>
|
||||
</button>
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">自动化</span>
|
||||
</div>
|
||||
<AutomationPanel />
|
||||
</motion.div>
|
||||
) : mainContentView === 'skills' ? (
|
||||
<motion.div
|
||||
variants={fadeInVariants}
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
className="h-full overflow-hidden"
|
||||
>
|
||||
<div className="flex items-center gap-2 px-4 py-2 border-b border-gray-200 dark:border-gray-800">
|
||||
<button
|
||||
onClick={() => handleMainViewChange('chat')}
|
||||
className="p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-md transition-colors text-gray-600 dark:text-gray-400"
|
||||
title="返回聊天"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m15 18-6-6 6-6"/></svg>
|
||||
</button>
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">技能市场</span>
|
||||
</div>
|
||||
<div className="h-[calc(100%-40px)] overflow-auto">
|
||||
<SkillMarket />
|
||||
</div>
|
||||
</motion.div>
|
||||
) : (
|
||||
{/* 聊天区域 */}
|
||||
<ChatArea />
|
||||
)}
|
||||
</motion.main>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* 详情抽屉 - 按需显示 */}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import {
|
||||
SquarePen, MessageSquare, Bot, Search, X, Settings, Zap, Sparkles
|
||||
SquarePen, MessageSquare, Bot, Search, X, Settings
|
||||
} from 'lucide-react';
|
||||
import { ConversationList } from './ConversationList';
|
||||
import { CloneManager } from './CloneManager';
|
||||
@@ -15,7 +15,7 @@ const sidebarTabVariants = {
|
||||
exit: { opacity: 0, transition: { duration: 0.1 } },
|
||||
};
|
||||
|
||||
export type MainViewType = 'chat' | 'automation' | 'skills';
|
||||
export type MainViewType = 'chat';
|
||||
|
||||
interface SidebarProps {
|
||||
onOpenSettings?: () => void;
|
||||
@@ -89,23 +89,6 @@ export function Sidebar({
|
||||
智能体
|
||||
</button>
|
||||
|
||||
{/* Divider between primary nav and secondary tools */}
|
||||
<div className="mx-1 my-1 border-t border-[#e8e6e1]/40 dark:border-gray-800" />
|
||||
|
||||
<button
|
||||
onClick={() => onMainViewChange?.('automation')}
|
||||
className="w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-gray-600 dark:text-gray-400 hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<Zap className="w-4 h-4" />
|
||||
自动化
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onMainViewChange?.('skills')}
|
||||
className="w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-gray-600 dark:text-gray-400 hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<Sparkles className="w-4 h-4" />
|
||||
技能市场
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
|
||||
@@ -476,6 +476,7 @@ export class GatewayClient {
|
||||
thinking_enabled?: boolean;
|
||||
reasoning_effort?: string;
|
||||
plan_mode?: boolean;
|
||||
subagent_enabled?: boolean;
|
||||
}
|
||||
): Promise<{ runId: string }> {
|
||||
const agentId = opts?.agentId || this.defaultAgentId;
|
||||
@@ -489,6 +490,7 @@ export class GatewayClient {
|
||||
thinking_enabled: opts?.thinking_enabled,
|
||||
reasoning_effort: opts?.reasoning_effort,
|
||||
plan_mode: opts?.plan_mode,
|
||||
subagent_enabled: opts?.subagent_enabled,
|
||||
};
|
||||
this.fetchDefaultAgentId().then(() => {
|
||||
const resolvedAgentId = this.defaultAgentId;
|
||||
@@ -529,6 +531,7 @@ export class GatewayClient {
|
||||
thinking_enabled?: boolean;
|
||||
reasoning_effort?: string;
|
||||
plan_mode?: boolean;
|
||||
subagent_enabled?: boolean;
|
||||
}
|
||||
): void {
|
||||
// Close existing connection if any
|
||||
@@ -570,6 +573,9 @@ export class GatewayClient {
|
||||
if (chatModeOpts?.plan_mode !== undefined) {
|
||||
chatRequest.plan_mode = chatModeOpts.plan_mode;
|
||||
}
|
||||
if (chatModeOpts?.subagent_enabled !== undefined) {
|
||||
chatRequest.subagent_enabled = chatModeOpts.subagent_enabled;
|
||||
}
|
||||
this.zclawWs?.send(JSON.stringify(chatRequest));
|
||||
};
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ export function installChatMethods(ClientClass: { prototype: KernelClient }): vo
|
||||
thinking_enabled?: boolean;
|
||||
reasoning_effort?: string;
|
||||
plan_mode?: boolean;
|
||||
subagent_enabled?: boolean;
|
||||
}
|
||||
): Promise<{ runId: string }> {
|
||||
const runId = crypto.randomUUID();
|
||||
@@ -185,6 +186,7 @@ export function installChatMethods(ClientClass: { prototype: KernelClient }): vo
|
||||
thinkingEnabled: opts?.thinking_enabled,
|
||||
reasoningEffort: opts?.reasoning_effort,
|
||||
planMode: opts?.plan_mode,
|
||||
subagentEnabled: opts?.subagent_enabled,
|
||||
},
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
|
||||
@@ -403,7 +403,7 @@ export interface KernelClient {
|
||||
|
||||
// Chat (kernel-chat.ts)
|
||||
chat(message: string, opts?: { sessionKey?: string; agentId?: string }): Promise<{ runId: string; sessionId?: string; response?: string }>;
|
||||
chatStream(message: string, callbacks: import('./kernel-types').StreamCallbacks, opts?: { sessionKey?: string; agentId?: string; thinking_enabled?: boolean; reasoning_effort?: string; plan_mode?: boolean }): Promise<{ runId: string }>;
|
||||
chatStream(message: string, callbacks: import('./kernel-types').StreamCallbacks, opts?: { sessionKey?: string; agentId?: string; thinking_enabled?: boolean; reasoning_effort?: string; plan_mode?: boolean; subagent_enabled?: boolean }): Promise<{ runId: string }>;
|
||||
cancelStream(sessionId: string): Promise<void>;
|
||||
fetchDefaultAgentId(): Promise<string | null>;
|
||||
setDefaultAgentId(agentId: string): void;
|
||||
|
||||
@@ -108,6 +108,7 @@ export function createSaaSRelayGatewayClient(
|
||||
thinking_enabled?: boolean;
|
||||
reasoning_effort?: string;
|
||||
plan_mode?: boolean;
|
||||
subagent_enabled?: boolean;
|
||||
},
|
||||
): Promise<{ runId: string }> {
|
||||
const runId = `run_${Date.now()}`;
|
||||
|
||||
@@ -425,6 +425,7 @@ export const useStreamStore = create<StreamState>()(
|
||||
thinking_enabled: get().getChatModeConfig().thinking_enabled,
|
||||
reasoning_effort: get().getChatModeConfig().reasoning_effort,
|
||||
plan_mode: get().getChatModeConfig().plan_mode,
|
||||
subagent_enabled: get().getChatModeConfig().subagent_enabled,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user