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 }) => {
// We are in the chat view after setUp
await po.sendPrompt("basic");
// Chat panel should be visible initially.
const chatPanel = po.page.locator("#chat-panel");
await expect(chatPanel).toBeVisible();
// Chat panel content should be visible initially.
// We check the ChatPanel content rather than the panel container itself,
// 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
const toggleButton = po.page.getByTestId("preview-toggle-chat-panel-button");
// Collapse
await toggleButton.click();
await expect(chatPanel).toBeHidden();
// When collapsed, the ChatPanel component is not rendered (isChatPanelHidden = true)
await expect(chatPanelContent).toBeHidden();
// Expand
await toggleButton.click();
// Expect chat panel to be visible
await expect(chatPanel).toBeVisible();
// Expect chat panel content to be visible again
await expect(chatPanelContent).toBeVisible();
});
差异被折叠。
......@@ -86,7 +86,6 @@ import { replacePromptReference } from "../utils/replacePromptReference";
import { mcpManager } from "../utils/mcp_manager";
import z from "zod";
import {
isDyadProEnabled,
isBasicAgentMode,
isSupabaseConnected,
isTurboEditsV2Enabled,
......@@ -536,8 +535,7 @@ ${componentSnippet}
);
const willUseLocalAgentStream =
(settings.selectedChatMode === "local-agent" ||
(settings.selectedChatMode === "ask" &&
isDyadProEnabled(settings))) &&
settings.selectedChatMode === "ask") &&
!mentionedAppsCodebases.length;
const isDeepContextEnabled =
......@@ -729,8 +727,9 @@ ${componentSnippet}
// 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
// it's not that critical to include the image analysis instructions.
const isAskMode = settings.selectedChatMode === "ask";
if (hasUploadedAttachments) {
if (willUseLocalAgentStream) {
if (willUseLocalAgentStream && !isAskMode) {
systemPrompt += `
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
\`\`\`
`;
} else {
} else if (!isAskMode) {
systemPrompt += `
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
return fullResponse;
};
// Handle pro ask mode: use local-agent in read-only mode
// This gives pro users access to code reading tools while in ask mode
// Handle ask mode: use local-agent in read-only mode
// This gives users access to code reading tools while in ask mode
// Ask mode does not consume free agent quota
if (
settings.selectedChatMode === "ask" &&
isDyadProEnabled(settings) &&
!mentionedAppsCodebases.length
) {
// 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
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,
// Note: this is using the read-only system prompt rather than the
// 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
dyadRequestId: dyadRequestId ?? "[no-request-id]",
readOnly: true,
messageOverride: isSummarizeIntent ? chatMessages : undefined,
});
},
);
if (!streamSuccess) {
logger.warn(
"Ask mode local agent stream did not complete successfully",
);
}
return;
}
......
......@@ -131,7 +131,8 @@ export async function handleLocalAgentStream(
// Check Pro status or Basic Agent mode
// 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", {
chatId: req.chatId,
error:
......@@ -192,7 +193,7 @@ export async function handleLocalAgentStream(
todos: [],
dyadRequestId,
fileEditTracker,
isBasicAgentMode: isBasicAgentMode(settings),
isDyadPro: isDyadProEnabled(settings),
onXmlStream: (accumulatedXml: string) => {
// Stream accumulated XML to UI without persisting
streamingPreview = accumulatedXml;
......
......@@ -76,8 +76,8 @@ export const codeSearchTool: ToolDefinition<z.infer<typeof codeSearchSchema>> =
inputSchema: codeSearchSchema,
defaultConsent: "always",
// Disable in Basic Agent mode (free tier) - requires engine
isEnabled: (ctx) => !ctx.isBasicAgentMode,
// Requires Dyad Pro engine API
isEnabled: (ctx) => ctx.isDyadPro,
getConsentPreview: (args) => `Search for "${args.query}"`,
......
......@@ -142,8 +142,8 @@ export const editFileTool: ToolDefinition<z.infer<typeof editFileSchema>> = {
defaultConsent: "always",
modifiesState: true,
// Disable in Basic Agent mode (free tier) - requires engine
isEnabled: (ctx) => !ctx.isBasicAgentMode,
// Requires Dyad Pro engine API
isEnabled: (ctx) => ctx.isDyadPro,
getConsentPreview: (args) => `Edit ${args.path}`,
......
......@@ -45,7 +45,7 @@ describe("searchReplaceTool", () => {
supabaseOrganizationSlug: null,
messageId: 1,
isSharedModulesChanged: false,
isBasicAgentMode: false,
isDyadPro: false,
todos: [],
dyadRequestId: "test-request",
fileEditTracker: {},
......
......@@ -58,10 +58,10 @@ export interface AgentContext {
/** Tracks file edit tool usage per file for telemetry */
fileEditTracker: FileEditTracker;
/**
* If true, this is Basic Agent mode (free tier with quota).
* Engine-dependent tools are disabled in this mode.
* If true, the user has Dyad Pro enabled.
* 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).
* Call this repeatedly with the full accumulated XML so far.
......
......@@ -80,8 +80,8 @@ export const webCrawlTool: ToolDefinition<z.infer<typeof webCrawlSchema>> = {
inputSchema: webCrawlSchema,
defaultConsent: "ask",
// Disable in Basic Agent mode (free tier) - requires engine
isEnabled: (ctx) => !ctx.isBasicAgentMode,
// Requires Dyad Pro engine API
isEnabled: (ctx) => ctx.isDyadPro,
getConsentPreview: (args) => `Crawl URL: "${args.url}"`,
......
......@@ -161,8 +161,8 @@ export const webSearchTool: ToolDefinition<z.infer<typeof webSearchSchema>> = {
inputSchema: webSearchSchema,
defaultConsent: "ask",
// Disable in Basic Agent mode (free tier) - requires engine
isEnabled: (ctx) => !ctx.isBasicAgentMode,
// Requires Dyad Pro engine API
isEnabled: (ctx) => ctx.isDyadPro,
getConsentPreview: (args) => `Search the web: "${args.query}"`,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论