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

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:
iven
2026-04-06 12:46:43 +08:00
parent 9c346ed6fb
commit cb140b5151
10 changed files with 72 additions and 105 deletions

View File

@@ -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(

View File

@@ -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>
{/* 聊天区域 */}
<ChatArea />
</div>
{/* 详情抽屉 - 按需显示 */}

View File

@@ -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 */}

View File

@@ -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));
};

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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()}`;

View File

@@ -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,
}
);