Unverified 提交 a6baaef1 authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Remove deprecated agent mode and consolidate with build mode (#2435)

## Summary - Remove "Build with MCP" option from chat mode selector dropdowns - Route build mode to MCP agent code path when MCP servers are enabled - Add MCP chip showing "N MCP" next to Build button when servers enabled - Show MCP tools picker in build mode when MCP servers are enabled - Skip deprecated agent mode in keyboard shortcut toggle cycle - Update e2e tests to use build mode instead of agent mode ## Test plan - [x] MCP e2e tests pass (verified locally) - [ ] Verify Build mode works normally without MCP servers configured - [ ] Configure an MCP server and verify the "N MCP" chip appears next to Build button - [ ] Verify clicking MCP tools picker shows enabled MCP servers - [ ] Verify sending a message in Build mode with MCP servers uses the agent code path 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2435"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the chat-mode UI and the main chat streaming branching so Build may enter the MCP/tooling code path when MCP servers are enabled; this can alter assistant behavior and tool-consent flows. Lockfile churn may also affect dependency installs across environments. > > **Overview** > **Removes the user-facing “Build with MCP”/`agent` mode selection** and treats stored `agent` settings as **Build** for backwards compatibility. > > **Build now conditionally enables MCP tooling**: the MCP tools picker appears in Build only when at least one MCP server is enabled, and the main streaming handler runs the MCP agent pre-pass only when Build has enabled MCP servers (or when legacy `agent` is set). > > Adds a small **“N MCP” status chip** next to Build when MCP servers are enabled, updates the keyboard mode-toggle cycle to skip deprecated `agent`, updates MCP e2e tests to select Build, and includes a large `package-lock.json` metadata update (peer flag changes). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 6338761b9e45313d399dd2f4873e5ef9a53ff36a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Removed the deprecated Agent mode by folding it into Build. Build now uses MCP tooling only when MCP servers are enabled; the UI shows an MCP count chip and tools picker, and old “agent” settings still work. - **Refactors** - Removed Agent from mode selectors; legacy “agent” appears as Build and the keyboard toggle skips it. - Build routes through the MCP agent path only when enabled MCP servers exist; otherwise behaves like normal Build. - Preserved compatibility for users with “agent” in settings and in the streaming path. - Updated E2E tests to use Build and aligned test_helper with the modular PageObject structure. - **New Features** - Added an “N MCP” chip next to Build when servers are enabled. - Show the MCP tools picker in Build when MCP servers are enabled. <sup>Written for commit 6338761b9e45313d399dd2f4873e5ef9a53ff36a. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: 's avatarClaude Opus 4.5 <noreply@anthropic.com> Co-authored-by: 's avatarclaude[bot] <41898282+claude[bot]@users.noreply.github.com>
上级 743672c3
...@@ -30,7 +30,7 @@ testSkipIfWindows("mcp - call calculator", async ({ po }) => { ...@@ -30,7 +30,7 @@ testSkipIfWindows("mcp - call calculator", async ({ po }) => {
await po.page.getByRole("textbox", { name: "Value" }).fill("testValue1"); await po.page.getByRole("textbox", { name: "Value" }).fill("testValue1");
await po.page.getByRole("button", { name: "Save" }).click(); await po.page.getByRole("button", { name: "Save" }).click();
await po.navigation.goToAppsTab(); await po.navigation.goToAppsTab();
await po.chatActions.selectChatMode("agent"); await po.chatActions.selectChatMode("build");
await po.sendPrompt("[call_tool=calculator_add]", { await po.sendPrompt("[call_tool=calculator_add]", {
skipWaitForCompletion: true, skipWaitForCompletion: true,
}); });
...@@ -115,7 +115,7 @@ testSkipIfWindows("mcp - call calculator via http", async ({ po }) => { ...@@ -115,7 +115,7 @@ testSkipIfWindows("mcp - call calculator via http", async ({ po }) => {
await po.navigation.goToSettingsTab(); await po.navigation.goToSettingsTab();
await po.page.getByRole("button", { name: "Tools (MCP)" }).click(); await po.page.getByRole("button", { name: "Tools (MCP)" }).click();
await po.navigation.goToAppsTab(); await po.navigation.goToAppsTab();
await po.chatActions.selectChatMode("agent"); await po.chatActions.selectChatMode("build");
await po.sendPrompt("[call_tool=calculator_add]", { await po.sendPrompt("[call_tool=calculator_add]", {
skipWaitForCompletion: true, skipWaitForCompletion: true,
}); });
......
差异被折叠。
...@@ -4,6 +4,7 @@ import { ProModeSelector } from "./ProModeSelector"; ...@@ -4,6 +4,7 @@ import { ProModeSelector } from "./ProModeSelector";
import { ChatModeSelector } from "./ChatModeSelector"; import { ChatModeSelector } from "./ChatModeSelector";
import { McpToolsPicker } from "@/components/McpToolsPicker"; import { McpToolsPicker } from "@/components/McpToolsPicker";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { useMcp } from "@/hooks/useMcp";
export function ChatInputControls({ export function ChatInputControls({
showContextFilesPicker = false, showContextFilesPicker = false,
...@@ -11,11 +12,20 @@ export function ChatInputControls({ ...@@ -11,11 +12,20 @@ export function ChatInputControls({
showContextFilesPicker?: boolean; showContextFilesPicker?: boolean;
}) { }) {
const { settings } = useSettings(); const { settings } = useSettings();
const { servers } = useMcp();
const enabledMcpServersCount = servers.filter((s) => s.enabled).length;
// Show MCP tools picker when:
// 1. Mode is "agent" (backwards compatibility) OR
// 2. Mode is "build" AND there are enabled MCP servers
const showMcpToolsPicker =
settings?.selectedChatMode === "agent" ||
(settings?.selectedChatMode === "build" && enabledMcpServersCount > 0);
return ( return (
<div className="flex"> <div className="flex">
<ChatModeSelector /> <ChatModeSelector />
{settings?.selectedChatMode === "agent" && ( {showMcpToolsPicker && (
<> <>
<div className="w-1.5"></div> <div className="w-1.5"></div>
<McpToolsPicker /> <McpToolsPicker />
......
...@@ -12,6 +12,7 @@ import { ...@@ -12,6 +12,7 @@ import {
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { useFreeAgentQuota } from "@/hooks/useFreeAgentQuota"; import { useFreeAgentQuota } from "@/hooks/useFreeAgentQuota";
import { useMcp } from "@/hooks/useMcp";
import type { ChatMode } from "@/lib/schemas"; import type { ChatMode } from "@/lib/schemas";
import { isDyadProEnabled } from "@/lib/schemas"; import { isDyadProEnabled } from "@/lib/schemas";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
...@@ -38,9 +39,13 @@ export function ChatModeSelector() { ...@@ -38,9 +39,13 @@ export function ChatModeSelector() {
const chatId = routerState.location.search.id as number | undefined; const chatId = routerState.location.search.id as number | undefined;
const currentChatMessages = chatId ? (messagesById.get(chatId) ?? []) : []; const currentChatMessages = chatId ? (messagesById.get(chatId) ?? []) : [];
const selectedMode = settings?.selectedChatMode || "build"; // Treat "agent" mode as "build" for UI purposes (backwards compatibility)
const rawSelectedMode = settings?.selectedChatMode || "build";
const selectedMode = rawSelectedMode === "agent" ? "build" : rawSelectedMode;
const isProEnabled = settings ? isDyadProEnabled(settings) : false; const isProEnabled = settings ? isDyadProEnabled(settings) : false;
const { messagesRemaining, isQuotaExceeded } = useFreeAgentQuota(); const { messagesRemaining, isQuotaExceeded } = useFreeAgentQuota();
const { servers } = useMcp();
const enabledMcpServersCount = servers.filter((s) => s.enabled).length;
const handleModeChange = (value: string) => { const handleModeChange = (value: string) => {
const newMode = value as ChatMode; const newMode = value as ChatMode;
...@@ -77,11 +82,10 @@ export function ChatModeSelector() { ...@@ -77,11 +82,10 @@ export function ChatModeSelector() {
const getModeDisplayName = (mode: ChatMode) => { const getModeDisplayName = (mode: ChatMode) => {
switch (mode) { switch (mode) {
case "build": case "build":
case "agent": // backwards compatibility - treat as build
return "Build"; return "Build";
case "ask": case "ask":
return "Ask"; return "Ask";
case "agent":
return "Build (MCP)";
case "local-agent": case "local-agent":
// Show "Basic Agent" for non-Pro users, "Agent" for Pro users // Show "Basic Agent" for non-Pro users, "Agent" for Pro users
return isProEnabled ? "Agent" : "Basic Agent"; return isProEnabled ? "Agent" : "Basic Agent";
...@@ -94,105 +98,121 @@ export function ChatModeSelector() { ...@@ -94,105 +98,121 @@ export function ChatModeSelector() {
const isMac = detectIsMac(); const isMac = detectIsMac();
return ( return (
<Select <div className="flex items-center gap-1.5">
value={selectedMode} <Select
onValueChange={(v) => v && handleModeChange(v)} value={selectedMode}
> onValueChange={(v) => v && handleModeChange(v)}
<Tooltip> >
<TooltipTrigger <Tooltip>
render={ <TooltipTrigger
<MiniSelectTrigger render={
data-testid="chat-mode-selector" <MiniSelectTrigger
className={cn( data-testid="chat-mode-selector"
"h-6 w-fit px-1.5 py-0 text-xs-sm font-medium shadow-none gap-0.5", className={cn(
selectedMode === "build" || "h-6 w-fit px-1.5 py-0 text-xs-sm font-medium shadow-none gap-0.5",
selectedMode === "local-agent" || selectedMode === "build" ||
selectedMode === "plan" selectedMode === "local-agent" ||
? "bg-background hover:bg-muted/50 focus:bg-muted/50" selectedMode === "plan"
: "bg-primary/10 hover:bg-primary/20 focus:bg-primary/20 text-primary border-primary/20 dark:bg-primary/20 dark:hover:bg-primary/30 dark:focus:bg-primary/30", ? "bg-background hover:bg-muted/50 focus:bg-muted/50"
)} : "bg-primary/10 hover:bg-primary/20 focus:bg-primary/20 text-primary border-primary/20 dark:bg-primary/20 dark:hover:bg-primary/30 dark:focus:bg-primary/30",
size="sm" )}
/> size="sm"
} />
> }
<SelectValue>{getModeDisplayName(selectedMode)}</SelectValue> >
</TooltipTrigger> <SelectValue>{getModeDisplayName(selectedMode)}</SelectValue>
<TooltipContent> </TooltipTrigger>
{`Open mode menu (${isMac ? "\u2318 + ." : "Ctrl + ."} to toggle)`} <TooltipContent>
</TooltipContent> {`Open mode menu (${isMac ? "\u2318 + ." : "Ctrl + ."} to toggle)`}
</Tooltip> </TooltipContent>
<SelectContent align="start"> </Tooltip>
{isProEnabled && ( <SelectContent align="start">
<> {isProEnabled && (
<SelectItem value="local-agent"> <>
<div className="flex flex-col items-start"> <SelectItem value="local-agent">
<div className="flex items-center gap-1.5"> <div className="flex flex-col items-start">
<span className="font-medium">Agent v2</span> <div className="flex items-center gap-1.5">
<NewBadge /> <span className="font-medium">Agent v2</span>
<NewBadge />
</div>
<span className="text-xs text-muted-foreground">
Better at bigger tasks and debugging
</span>
</div> </div>
<span className="text-xs text-muted-foreground"> </SelectItem>
Better at bigger tasks and debugging <SelectItem value="plan">
</span> <div className="flex flex-col items-start">
</div> <div className="flex items-center gap-1.5">
</SelectItem> <span className="font-medium">Plan</span>
<SelectItem value="plan"> <NewBadge />
</div>
<span className="text-xs text-muted-foreground">
Design before you build
</span>
</div>
</SelectItem>
</>
)}
{!isProEnabled && (
<SelectItem value="local-agent" disabled={isQuotaExceeded}>
<div className="flex flex-col items-start"> <div className="flex flex-col items-start">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="font-medium">Plan</span> <span className="font-medium">Basic Agent</span>
<NewBadge /> <span className="text-xs text-muted-foreground">
({isQuotaExceeded ? "0" : messagesRemaining}/5 remaining for
today)
</span>
</div> </div>
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
Design before you build {isQuotaExceeded
? "Daily limit reached"
: "Try our AI agent for free"}
</span> </span>
</div> </div>
</SelectItem> </SelectItem>
</> )}
)} <SelectItem value="build">
{!isProEnabled && (
<SelectItem value="local-agent" disabled={isQuotaExceeded}>
<div className="flex flex-col items-start"> <div className="flex flex-col items-start">
<div className="flex items-center gap-1.5"> <span className="font-medium">Build</span>
<span className="font-medium">Basic Agent</span>
<span className="text-xs text-muted-foreground">
({isQuotaExceeded ? "0" : messagesRemaining}/5 remaining for
today)
</span>
</div>
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
{isQuotaExceeded Generate and edit code
? "Daily limit reached"
: "Try our AI agent for free"}
</span> </span>
</div> </div>
</SelectItem> </SelectItem>
)} <SelectItem value="ask">
<SelectItem value="build"> <div className="flex flex-col items-start">
<div className="flex flex-col items-start"> <span className="font-medium">Ask</span>
<span className="font-medium">Build</span> <span className="text-xs text-muted-foreground">
<span className="text-xs text-muted-foreground"> Ask questions about the app
Generate and edit code </span>
</span>
</div>
</SelectItem>
<SelectItem value="ask">
<div className="flex flex-col items-start">
<span className="font-medium">Ask</span>
<span className="text-xs text-muted-foreground">
Ask questions about the app
</span>
</div>
</SelectItem>
<SelectItem value="agent">
<div className="flex flex-col items-start">
<div className="flex items-center gap-1.5">
<span className="font-medium">Build with MCP</span>
</div> </div>
<span className="text-xs text-muted-foreground"> </SelectItem>
Like Build, but can use tools (MCP) to generate code </SelectContent>
</span> </Select>
</div> {selectedMode === "build" && <McpChip count={enabledMcpServersCount} />}
</SelectItem> </div>
</SelectContent> );
</Select> }
function McpChip({ count }: { count: number }) {
if (count === 0) return null;
return (
<Tooltip>
<TooltipTrigger
render={
<span
data-testid="mcp-servers-chip"
className="inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-400 border border-purple-200 dark:border-purple-800 cursor-default"
/>
}
>
{count} MCP
</TooltipTrigger>
<TooltipContent>
<span>
{count} MCP server{count !== 1 ? "s" : ""} enabled
</span>
</TooltipContent>
</Tooltip>
); );
} }
...@@ -36,9 +36,8 @@ export function DefaultChatModeSelector() { ...@@ -36,9 +36,8 @@ export function DefaultChatModeSelector() {
const getModeDisplayName = (mode: ChatMode) => { const getModeDisplayName = (mode: ChatMode) => {
switch (mode) { switch (mode) {
case "build": case "build":
case "agent": // backwards compatibility - treat as build
return "Build"; return "Build";
case "agent":
return "Build (MCP)";
case "local-agent": case "local-agent":
return isProEnabled ? "Agent" : "Basic Agent"; return isProEnabled ? "Agent" : "Basic Agent";
case "ask": case "ask":
...@@ -86,14 +85,6 @@ export function DefaultChatModeSelector() { ...@@ -86,14 +85,6 @@ export function DefaultChatModeSelector() {
</span> </span>
</div> </div>
</SelectItem> </SelectItem>
<SelectItem value="agent">
<div className="flex flex-col items-start">
<span className="font-medium">Build with MCP</span>
<span className="text-xs text-muted-foreground">
Build with tools (MCP)
</span>
</div>
</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
......
...@@ -20,13 +20,17 @@ export function useChatModeToggle() { ...@@ -20,13 +20,17 @@ export function useChatModeToggle() {
[isMac], [isMac],
); );
// Function to toggle between ask and build chat modes // Function to toggle between chat modes (skipping deprecated "agent" mode)
const toggleChatMode = useCallback(() => { const toggleChatMode = useCallback(() => {
if (!settings || !settings.selectedChatMode) return; if (!settings || !settings.selectedChatMode) return;
const currentMode = settings.selectedChatMode; const currentMode = settings.selectedChatMode;
const modes = ChatModeSchema.options; // Filter out deprecated "agent" mode from toggle cycle
const currentIndex = modes.indexOf(settings.selectedChatMode); const modes = ChatModeSchema.options.filter((m) => m !== "agent");
// If current mode is "agent", treat it as "build" for indexing
const effectiveCurrentMode =
currentMode === "agent" ? "build" : currentMode;
const currentIndex = modes.indexOf(effectiveCurrentMode);
const newMode = modes[(currentIndex + 1) % modes.length]; const newMode = modes[(currentIndex + 1) % modes.length];
updateSettings({ selectedChatMode: newMode }); updateSettings({ selectedChatMode: newMode });
......
...@@ -1202,46 +1202,58 @@ This conversation includes one or more image attachments. When the user uploads ...@@ -1202,46 +1202,58 @@ This conversation includes one or more image attachments. When the user uploads
return; return;
} }
if (settings.selectedChatMode === "agent") { // Use MCP agent code path if:
// 1. Mode is explicitly "agent" (backwards compatibility for existing settings)
// 2. Mode is "build" AND there are enabled MCP servers
if (
settings.selectedChatMode === "agent" ||
settings.selectedChatMode === "build"
) {
const tools = await getMcpTools(event); const tools = await getMcpTools(event);
const hasEnabledMcpServers = Object.keys(tools).length > 0;
const { fullStream } = await simpleStreamText({
chatMessages: limitedHistoryChatMessages, // Only run MCP agent path if mode is "agent" OR if build mode has enabled MCP servers
modelClient, if (settings.selectedChatMode === "agent" || hasEnabledMcpServers) {
tools: { const { fullStream } = await simpleStreamText({
...tools, chatMessages: limitedHistoryChatMessages,
"generate-code": { modelClient,
description: tools: {
"ALWAYS use this tool whenever generating or editing code for the codebase.", ...tools,
inputSchema: z.object({}), "generate-code": {
execute: async () => "", description:
"ALWAYS use this tool whenever generating or editing code for the codebase.",
inputSchema: z.object({}),
execute: async () => "",
},
}, },
}, systemPromptOverride: constructSystemPrompt({
systemPromptOverride: constructSystemPrompt({ aiRules: await readAiRules(
aiRules: await readAiRules(getDyadAppPath(updatedChat.app.path)), getDyadAppPath(updatedChat.app.path),
chatMode: "agent", ),
enableTurboEditsV2: false, chatMode: "agent",
}), enableTurboEditsV2: false,
files: files, }),
dyadDisableFiles: true, files: files,
}); dyadDisableFiles: true,
});
const result = await processStreamChunks({ const result = await processStreamChunks({
fullStream, fullStream,
fullResponse, fullResponse,
abortController, abortController,
chatId: req.chatId, chatId: req.chatId,
processResponseChunkUpdate, processResponseChunkUpdate,
}); });
fullResponse = result.fullResponse; fullResponse = result.fullResponse;
chatMessages.push({ chatMessages.push({
role: "assistant", role: "assistant",
content: fullResponse, content: fullResponse,
}); });
chatMessages.push({ chatMessages.push({
role: "user", role: "user",
content: "OK.", content: "OK.",
}); });
}
} }
// When calling streamText, the messages need to be properly formatted for mixed content // When calling streamText, the messages need to be properly formatted for mixed content
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论