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 }) => {
await po.page.getByRole("textbox", { name: "Value" }).fill("testValue1");
await po.page.getByRole("button", { name: "Save" }).click();
await po.navigation.goToAppsTab();
await po.chatActions.selectChatMode("agent");
await po.chatActions.selectChatMode("build");
await po.sendPrompt("[call_tool=calculator_add]", {
skipWaitForCompletion: true,
});
......@@ -115,7 +115,7 @@ testSkipIfWindows("mcp - call calculator via http", async ({ po }) => {
await po.navigation.goToSettingsTab();
await po.page.getByRole("button", { name: "Tools (MCP)" }).click();
await po.navigation.goToAppsTab();
await po.chatActions.selectChatMode("agent");
await po.chatActions.selectChatMode("build");
await po.sendPrompt("[call_tool=calculator_add]", {
skipWaitForCompletion: true,
});
......
差异被折叠。
......@@ -4,6 +4,7 @@ import { ProModeSelector } from "./ProModeSelector";
import { ChatModeSelector } from "./ChatModeSelector";
import { McpToolsPicker } from "@/components/McpToolsPicker";
import { useSettings } from "@/hooks/useSettings";
import { useMcp } from "@/hooks/useMcp";
export function ChatInputControls({
showContextFilesPicker = false,
......@@ -11,11 +12,20 @@ export function ChatInputControls({
showContextFilesPicker?: boolean;
}) {
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 (
<div className="flex">
<ChatModeSelector />
{settings?.selectedChatMode === "agent" && (
{showMcpToolsPicker && (
<>
<div className="w-1.5"></div>
<McpToolsPicker />
......
......@@ -12,6 +12,7 @@ import {
} from "@/components/ui/tooltip";
import { useSettings } from "@/hooks/useSettings";
import { useFreeAgentQuota } from "@/hooks/useFreeAgentQuota";
import { useMcp } from "@/hooks/useMcp";
import type { ChatMode } from "@/lib/schemas";
import { isDyadProEnabled } from "@/lib/schemas";
import { cn } from "@/lib/utils";
......@@ -38,9 +39,13 @@ export function ChatModeSelector() {
const chatId = routerState.location.search.id as number | undefined;
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 { messagesRemaining, isQuotaExceeded } = useFreeAgentQuota();
const { servers } = useMcp();
const enabledMcpServersCount = servers.filter((s) => s.enabled).length;
const handleModeChange = (value: string) => {
const newMode = value as ChatMode;
......@@ -77,11 +82,10 @@ export function ChatModeSelector() {
const getModeDisplayName = (mode: ChatMode) => {
switch (mode) {
case "build":
case "agent": // backwards compatibility - treat as build
return "Build";
case "ask":
return "Ask";
case "agent":
return "Build (MCP)";
case "local-agent":
// Show "Basic Agent" for non-Pro users, "Agent" for Pro users
return isProEnabled ? "Agent" : "Basic Agent";
......@@ -94,105 +98,121 @@ export function ChatModeSelector() {
const isMac = detectIsMac();
return (
<Select
value={selectedMode}
onValueChange={(v) => v && handleModeChange(v)}
>
<Tooltip>
<TooltipTrigger
render={
<MiniSelectTrigger
data-testid="chat-mode-selector"
className={cn(
"h-6 w-fit px-1.5 py-0 text-xs-sm font-medium shadow-none gap-0.5",
selectedMode === "build" ||
selectedMode === "local-agent" ||
selectedMode === "plan"
? "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"
/>
}
>
<SelectValue>{getModeDisplayName(selectedMode)}</SelectValue>
</TooltipTrigger>
<TooltipContent>
{`Open mode menu (${isMac ? "\u2318 + ." : "Ctrl + ."} to toggle)`}
</TooltipContent>
</Tooltip>
<SelectContent align="start">
{isProEnabled && (
<>
<SelectItem value="local-agent">
<div className="flex flex-col items-start">
<div className="flex items-center gap-1.5">
<span className="font-medium">Agent v2</span>
<NewBadge />
<div className="flex items-center gap-1.5">
<Select
value={selectedMode}
onValueChange={(v) => v && handleModeChange(v)}
>
<Tooltip>
<TooltipTrigger
render={
<MiniSelectTrigger
data-testid="chat-mode-selector"
className={cn(
"h-6 w-fit px-1.5 py-0 text-xs-sm font-medium shadow-none gap-0.5",
selectedMode === "build" ||
selectedMode === "local-agent" ||
selectedMode === "plan"
? "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"
/>
}
>
<SelectValue>{getModeDisplayName(selectedMode)}</SelectValue>
</TooltipTrigger>
<TooltipContent>
{`Open mode menu (${isMac ? "\u2318 + ." : "Ctrl + ."} to toggle)`}
</TooltipContent>
</Tooltip>
<SelectContent align="start">
{isProEnabled && (
<>
<SelectItem value="local-agent">
<div className="flex flex-col items-start">
<div className="flex items-center gap-1.5">
<span className="font-medium">Agent v2</span>
<NewBadge />
</div>
<span className="text-xs text-muted-foreground">
Better at bigger tasks and debugging
</span>
</div>
<span className="text-xs text-muted-foreground">
Better at bigger tasks and debugging
</span>
</div>
</SelectItem>
<SelectItem value="plan">
</SelectItem>
<SelectItem value="plan">
<div className="flex flex-col items-start">
<div className="flex items-center gap-1.5">
<span className="font-medium">Plan</span>
<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 items-center gap-1.5">
<span className="font-medium">Plan</span>
<NewBadge />
<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">
Design before you build
{isQuotaExceeded
? "Daily limit reached"
: "Try our AI agent for free"}
</span>
</div>
</SelectItem>
</>
)}
{!isProEnabled && (
<SelectItem value="local-agent" disabled={isQuotaExceeded}>
)}
<SelectItem value="build">
<div className="flex flex-col items-start">
<div className="flex items-center gap-1.5">
<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="font-medium">Build</span>
<span className="text-xs text-muted-foreground">
{isQuotaExceeded
? "Daily limit reached"
: "Try our AI agent for free"}
Generate and edit code
</span>
</div>
</SelectItem>
)}
<SelectItem value="build">
<div className="flex flex-col items-start">
<span className="font-medium">Build</span>
<span className="text-xs text-muted-foreground">
Generate and edit code
</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>
<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>
<span className="text-xs text-muted-foreground">
Like Build, but can use tools (MCP) to generate code
</span>
</div>
</SelectItem>
</SelectContent>
</Select>
</SelectItem>
</SelectContent>
</Select>
{selectedMode === "build" && <McpChip count={enabledMcpServersCount} />}
</div>
);
}
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() {
const getModeDisplayName = (mode: ChatMode) => {
switch (mode) {
case "build":
case "agent": // backwards compatibility - treat as build
return "Build";
case "agent":
return "Build (MCP)";
case "local-agent":
return isProEnabled ? "Agent" : "Basic Agent";
case "ask":
......@@ -86,14 +85,6 @@ export function DefaultChatModeSelector() {
</span>
</div>
</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>
</Select>
</div>
......
......@@ -20,13 +20,17 @@ export function useChatModeToggle() {
[isMac],
);
// Function to toggle between ask and build chat modes
// Function to toggle between chat modes (skipping deprecated "agent" mode)
const toggleChatMode = useCallback(() => {
if (!settings || !settings.selectedChatMode) return;
const currentMode = settings.selectedChatMode;
const modes = ChatModeSchema.options;
const currentIndex = modes.indexOf(settings.selectedChatMode);
// Filter out deprecated "agent" mode from toggle cycle
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];
updateSettings({ selectedChatMode: newMode });
......
......@@ -1202,46 +1202,58 @@ This conversation includes one or more image attachments. When the user uploads
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 { fullStream } = await simpleStreamText({
chatMessages: limitedHistoryChatMessages,
modelClient,
tools: {
...tools,
"generate-code": {
description:
"ALWAYS use this tool whenever generating or editing code for the codebase.",
inputSchema: z.object({}),
execute: async () => "",
const hasEnabledMcpServers = Object.keys(tools).length > 0;
// Only run MCP agent path if mode is "agent" OR if build mode has enabled MCP servers
if (settings.selectedChatMode === "agent" || hasEnabledMcpServers) {
const { fullStream } = await simpleStreamText({
chatMessages: limitedHistoryChatMessages,
modelClient,
tools: {
...tools,
"generate-code": {
description:
"ALWAYS use this tool whenever generating or editing code for the codebase.",
inputSchema: z.object({}),
execute: async () => "",
},
},
},
systemPromptOverride: constructSystemPrompt({
aiRules: await readAiRules(getDyadAppPath(updatedChat.app.path)),
chatMode: "agent",
enableTurboEditsV2: false,
}),
files: files,
dyadDisableFiles: true,
});
systemPromptOverride: constructSystemPrompt({
aiRules: await readAiRules(
getDyadAppPath(updatedChat.app.path),
),
chatMode: "agent",
enableTurboEditsV2: false,
}),
files: files,
dyadDisableFiles: true,
});
const result = await processStreamChunks({
fullStream,
fullResponse,
abortController,
chatId: req.chatId,
processResponseChunkUpdate,
});
fullResponse = result.fullResponse;
chatMessages.push({
role: "assistant",
content: fullResponse,
});
chatMessages.push({
role: "user",
content: "OK.",
});
const result = await processStreamChunks({
fullStream,
fullResponse,
abortController,
chatId: req.chatId,
processResponseChunkUpdate,
});
fullResponse = result.fullResponse;
chatMessages.push({
role: "assistant",
content: fullResponse,
});
chatMessages.push({
role: "user",
content: "OK.",
});
}
}
// When calling streamText, the messages need to be properly formatted for mixed content
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论