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

feat: make ask mode always use local agent handler (#2434)

## Summary - Ask mode now always uses the local agent handler with read-only tools, regardless of Dyad Pro status - This gives all users access to code reading tools while in ask mode - Ask mode does not consume free agent quota ## Test plan - Test ask mode in a non-Pro account and verify it can use local agent tools (file reading, searching) - Verify ask mode still operates in read-only mode (no editing capabilities) - Confirm ask mode doesn't consume free agent quota #skip-bugbot 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2434"> <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 --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Ask mode now always uses the local agent handler in read-only mode. It’s available to all users and doesn’t consume free agent quota. - **New Features** - Routes all Ask mode chats to the local agent. - Removes the Pro check; available to all users. - Read-only tools only; no edits. - Engine-dependent tools (code search, web search, web crawl) require Dyad Pro. - **Bug Fixes** - Skips file upload instructions in Ask mode since write tools aren’t available. <sup>Written for commit 9732679491a53e7be7fa40dae6f18db4b2d43701. 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>
上级 22d35641
...@@ -6,20 +6,23 @@ testSkipIfWindows("toggle chat panel visibility", async ({ po }) => { ...@@ -6,20 +6,23 @@ testSkipIfWindows("toggle chat panel visibility", async ({ po }) => {
// We are in the chat view after setUp // We are in the chat view after setUp
await po.sendPrompt("basic"); await po.sendPrompt("basic");
// Chat panel should be visible initially. // Chat panel content should be visible initially.
const chatPanel = po.page.locator("#chat-panel"); // We check the ChatPanel content rather than the panel container itself,
await expect(chatPanel).toBeVisible(); // since the container is always present but resized to 1% when collapsed.
const chatPanelContent = po.page.getByTestId("messages-list");
await expect(chatPanelContent).toBeVisible();
// Toggle button // Toggle button
const toggleButton = po.page.getByTestId("preview-toggle-chat-panel-button"); const toggleButton = po.page.getByTestId("preview-toggle-chat-panel-button");
// Collapse // Collapse
await toggleButton.click(); await toggleButton.click();
await expect(chatPanel).toBeHidden(); // When collapsed, the ChatPanel component is not rendered (isChatPanelHidden = true)
await expect(chatPanelContent).toBeHidden();
// Expand // Expand
await toggleButton.click(); await toggleButton.click();
// Expect chat panel to be visible // Expect chat panel content to be visible again
await expect(chatPanel).toBeVisible(); await expect(chatPanelContent).toBeVisible();
}); });
差异被折叠。
...@@ -86,7 +86,6 @@ import { replacePromptReference } from "../utils/replacePromptReference"; ...@@ -86,7 +86,6 @@ import { replacePromptReference } from "../utils/replacePromptReference";
import { mcpManager } from "../utils/mcp_manager"; import { mcpManager } from "../utils/mcp_manager";
import z from "zod"; import z from "zod";
import { import {
isDyadProEnabled,
isBasicAgentMode, isBasicAgentMode,
isSupabaseConnected, isSupabaseConnected,
isTurboEditsV2Enabled, isTurboEditsV2Enabled,
...@@ -536,8 +535,7 @@ ${componentSnippet} ...@@ -536,8 +535,7 @@ ${componentSnippet}
); );
const willUseLocalAgentStream = const willUseLocalAgentStream =
(settings.selectedChatMode === "local-agent" || (settings.selectedChatMode === "local-agent" ||
(settings.selectedChatMode === "ask" && settings.selectedChatMode === "ask") &&
isDyadProEnabled(settings))) &&
!mentionedAppsCodebases.length; !mentionedAppsCodebases.length;
const isDeepContextEnabled = const isDeepContextEnabled =
...@@ -729,8 +727,9 @@ ${componentSnippet} ...@@ -729,8 +727,9 @@ ${componentSnippet}
// print out the dyad-write tags. // print out the dyad-write tags.
// Usually, AI models will want to use the image as reference to generate code (e.g. UI mockups) anyways, so // Usually, AI models will want to use the image as reference to generate code (e.g. UI mockups) anyways, so
// it's not that critical to include the image analysis instructions. // it's not that critical to include the image analysis instructions.
const isAskMode = settings.selectedChatMode === "ask";
if (hasUploadedAttachments) { if (hasUploadedAttachments) {
if (willUseLocalAgentStream) { if (willUseLocalAgentStream && !isAskMode) {
systemPrompt += ` systemPrompt += `
When files are attached to this conversation, upload them to the codebase using the \`write_file\` tool. When files are attached to this conversation, upload them to the codebase using the \`write_file\` tool.
...@@ -742,7 +741,7 @@ write_file(path="src/components/Button.jsx", content="DYAD_ATTACHMENT_0", descri ...@@ -742,7 +741,7 @@ write_file(path="src/components/Button.jsx", content="DYAD_ATTACHMENT_0", descri
\`\`\` \`\`\`
`; `;
} else { } else if (!isAskMode) {
systemPrompt += ` systemPrompt += `
When files are attached to this conversation, upload them to the codebase using this exact format: When files are attached to this conversation, upload them to the codebase using this exact format:
...@@ -1029,11 +1028,11 @@ This conversation includes one or more image attachments. When the user uploads ...@@ -1029,11 +1028,11 @@ This conversation includes one or more image attachments. When the user uploads
return fullResponse; return fullResponse;
}; };
// Handle pro ask mode: use local-agent in read-only mode // Handle ask mode: use local-agent in read-only mode
// This gives pro users access to code reading tools while in ask mode // This gives users access to code reading tools while in ask mode
// Ask mode does not consume free agent quota
if ( if (
settings.selectedChatMode === "ask" && settings.selectedChatMode === "ask" &&
isDyadProEnabled(settings) &&
!mentionedAppsCodebases.length !mentionedAppsCodebases.length
) { ) {
// Reconstruct system prompt for local-agent read-only mode // Reconstruct system prompt for local-agent read-only mode
...@@ -1045,7 +1044,14 @@ This conversation includes one or more image attachments. When the user uploads ...@@ -1045,7 +1044,14 @@ This conversation includes one or more image attachments. When the user uploads
readOnly: true, readOnly: true,
}); });
await handleLocalAgentStream(event, req, abortController, { // Return value indicates success/failure for quota tracking.
// Ask mode doesn't consume quota, but we still capture it for
// consistent error handling.
const streamSuccess = await handleLocalAgentStream(
event,
req,
abortController,
{
placeholderMessageId: placeholderAssistantMessage.id, placeholderMessageId: placeholderAssistantMessage.id,
// Note: this is using the read-only system prompt rather than the // Note: this is using the read-only system prompt rather than the
// regular system prompt which gets overrides for special intents // regular system prompt which gets overrides for special intents
...@@ -1057,7 +1063,13 @@ This conversation includes one or more image attachments. When the user uploads ...@@ -1057,7 +1063,13 @@ This conversation includes one or more image attachments. When the user uploads
dyadRequestId: dyadRequestId ?? "[no-request-id]", dyadRequestId: dyadRequestId ?? "[no-request-id]",
readOnly: true, readOnly: true,
messageOverride: isSummarizeIntent ? chatMessages : undefined, messageOverride: isSummarizeIntent ? chatMessages : undefined,
}); },
);
if (!streamSuccess) {
logger.warn(
"Ask mode local agent stream did not complete successfully",
);
}
return; return;
} }
......
...@@ -131,7 +131,8 @@ export async function handleLocalAgentStream( ...@@ -131,7 +131,8 @@ export async function handleLocalAgentStream(
// Check Pro status or Basic Agent mode // Check Pro status or Basic Agent mode
// Basic Agent mode allows non-Pro users with quota (quota check is done in chat_stream_handlers) // Basic Agent mode allows non-Pro users with quota (quota check is done in chat_stream_handlers)
if (!isDyadProEnabled(settings) && !isBasicAgentMode(settings)) { // Read-only mode (ask mode) is allowed for all users without Pro
if (!readOnly && !isDyadProEnabled(settings) && !isBasicAgentMode(settings)) {
safeSend(event.sender, "chat:response:error", { safeSend(event.sender, "chat:response:error", {
chatId: req.chatId, chatId: req.chatId,
error: error:
...@@ -192,7 +193,7 @@ export async function handleLocalAgentStream( ...@@ -192,7 +193,7 @@ export async function handleLocalAgentStream(
todos: [], todos: [],
dyadRequestId, dyadRequestId,
fileEditTracker, fileEditTracker,
isBasicAgentMode: isBasicAgentMode(settings), isDyadPro: isDyadProEnabled(settings),
onXmlStream: (accumulatedXml: string) => { onXmlStream: (accumulatedXml: string) => {
// Stream accumulated XML to UI without persisting // Stream accumulated XML to UI without persisting
streamingPreview = accumulatedXml; streamingPreview = accumulatedXml;
......
...@@ -76,8 +76,8 @@ export const codeSearchTool: ToolDefinition<z.infer<typeof codeSearchSchema>> = ...@@ -76,8 +76,8 @@ export const codeSearchTool: ToolDefinition<z.infer<typeof codeSearchSchema>> =
inputSchema: codeSearchSchema, inputSchema: codeSearchSchema,
defaultConsent: "always", defaultConsent: "always",
// Disable in Basic Agent mode (free tier) - requires engine // Requires Dyad Pro engine API
isEnabled: (ctx) => !ctx.isBasicAgentMode, isEnabled: (ctx) => ctx.isDyadPro,
getConsentPreview: (args) => `Search for "${args.query}"`, getConsentPreview: (args) => `Search for "${args.query}"`,
......
...@@ -142,8 +142,8 @@ export const editFileTool: ToolDefinition<z.infer<typeof editFileSchema>> = { ...@@ -142,8 +142,8 @@ export const editFileTool: ToolDefinition<z.infer<typeof editFileSchema>> = {
defaultConsent: "always", defaultConsent: "always",
modifiesState: true, modifiesState: true,
// Disable in Basic Agent mode (free tier) - requires engine // Requires Dyad Pro engine API
isEnabled: (ctx) => !ctx.isBasicAgentMode, isEnabled: (ctx) => ctx.isDyadPro,
getConsentPreview: (args) => `Edit ${args.path}`, getConsentPreview: (args) => `Edit ${args.path}`,
......
...@@ -45,7 +45,7 @@ describe("searchReplaceTool", () => { ...@@ -45,7 +45,7 @@ describe("searchReplaceTool", () => {
supabaseOrganizationSlug: null, supabaseOrganizationSlug: null,
messageId: 1, messageId: 1,
isSharedModulesChanged: false, isSharedModulesChanged: false,
isBasicAgentMode: false, isDyadPro: false,
todos: [], todos: [],
dyadRequestId: "test-request", dyadRequestId: "test-request",
fileEditTracker: {}, fileEditTracker: {},
......
...@@ -58,10 +58,10 @@ export interface AgentContext { ...@@ -58,10 +58,10 @@ export interface AgentContext {
/** Tracks file edit tool usage per file for telemetry */ /** Tracks file edit tool usage per file for telemetry */
fileEditTracker: FileEditTracker; fileEditTracker: FileEditTracker;
/** /**
* If true, this is Basic Agent mode (free tier with quota). * If true, the user has Dyad Pro enabled.
* Engine-dependent tools are disabled in this mode. * Engine-dependent tools require this to access the Dyad Pro API.
*/ */
isBasicAgentMode: boolean; isDyadPro: boolean;
/** /**
* Streams accumulated XML to UI without persisting to DB (for live preview). * Streams accumulated XML to UI without persisting to DB (for live preview).
* Call this repeatedly with the full accumulated XML so far. * Call this repeatedly with the full accumulated XML so far.
......
...@@ -80,8 +80,8 @@ export const webCrawlTool: ToolDefinition<z.infer<typeof webCrawlSchema>> = { ...@@ -80,8 +80,8 @@ export const webCrawlTool: ToolDefinition<z.infer<typeof webCrawlSchema>> = {
inputSchema: webCrawlSchema, inputSchema: webCrawlSchema,
defaultConsent: "ask", defaultConsent: "ask",
// Disable in Basic Agent mode (free tier) - requires engine // Requires Dyad Pro engine API
isEnabled: (ctx) => !ctx.isBasicAgentMode, isEnabled: (ctx) => ctx.isDyadPro,
getConsentPreview: (args) => `Crawl URL: "${args.url}"`, getConsentPreview: (args) => `Crawl URL: "${args.url}"`,
......
...@@ -161,8 +161,8 @@ export const webSearchTool: ToolDefinition<z.infer<typeof webSearchSchema>> = { ...@@ -161,8 +161,8 @@ export const webSearchTool: ToolDefinition<z.infer<typeof webSearchSchema>> = {
inputSchema: webSearchSchema, inputSchema: webSearchSchema,
defaultConsent: "ask", defaultConsent: "ask",
// Disable in Basic Agent mode (free tier) - requires engine // Requires Dyad Pro engine API
isEnabled: (ctx) => !ctx.isBasicAgentMode, isEnabled: (ctx) => ctx.isDyadPro,
getConsentPreview: (args) => `Search the web: "${args.query}"`, getConsentPreview: (args) => `Search the web: "${args.query}"`,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论