Fallback to balanced smart context for mentioned apps because not sup… (#1886)

…ported in deep context

Fixes https://github.com/dyad-sh/dyad/issues/1715

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Deep context now only applies with no app mentions; otherwise we
fallback to balanced and pass the effective mode to the engine, with
updated history limits and new e2e coverage.
> 
> - **Smart Context behavior**:
> - Deep mode activates only when no `@app:` mentions are present;
otherwise uses `balanced`.
> - Max chat turns set to 201 only for deep-without-mentions; otherwise
use configured limit.
> - **Engine integration**:
> - Send effective smart context mode via `dyadSmartContextMode` and map
to `dyad_options.smart_context_mode` in `llm_engine_provider`.
> - Add `SmartContextMode` schema (`balanced|conservative|deep`) and use
it in settings/types.
> - Remove static `smartContextMode` from engine options; use
per-request mode instead.
> - **Tests**:
> - Add e2e test `smart context deep - mention app should fallback to
balanced` with snapshot asserting `smart_context_mode: "balanced"` and
mentioned app files.
> - **Misc**:
>   - Tighten app path validation regex in `rename-app` handler.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
5aada2bd246e297d7b35e36738f75c8531b897ae. 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
Fallback to balanced smart context when an app is mentioned in deep
mode, since deep doesn’t support mentioned apps. This keeps context
limits correct and updates engine config, with new e2e coverage.

- **Bug Fixes**
- Deep mode only applies when no apps are mentioned; otherwise we use
balanced.
- Max chat turns: 201 only for deep without mentions; otherwise use
configured limit.
- Plumb smart context mode via dyadSmartContextMode to the engine; add
SmartContextMode schema.
- Add e2e test to verify fallback in deep mode when an app is mentioned.

<sup>Written for commit 5aada2bd246e297d7b35e36738f75c8531b897ae.
Summary will update automatically on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Will Chen
2025-12-04 15:19:37 -08:00
committed by GitHub
parent 196f02c979
commit 3701886505
7 changed files with 537 additions and 13 deletions

View File

@@ -16,3 +16,25 @@ testSkipIfWindows("smart context deep - read write read", async ({ po }) => {
await po.snapshotServerDump("request");
await po.snapshotMessages({ replaceDumpPath: true });
});
testSkipIfWindows(
"smart context deep - mention app should fallback to balanced",
async ({ po }) => {
await po.setUpDyadPro();
// First, create an imported app.
await po.importApp("minimal-with-ai-rules");
await po.goToAppsTab();
const proModesDialog = await po.openProModesDialog({
location: "home-chat-input-container",
});
await proModesDialog.setSmartContextMode("deep");
await proModesDialog.close();
// Mentioned the imported app
await po.sendPrompt("[dump] @app:minimal-with-ai-rules hi");
await po.snapshotServerDump("request");
},
);

View File

@@ -1205,7 +1205,7 @@ export function registerAppHandlers() {
const pathChanged = appPath !== app.path;
if (pathChanged) {
const invalidChars = /[<>:"|?*\/\\]/;
const invalidChars = /[<>:"|?*/\\]/;
const hasInvalidChars =
invalidChars.test(appPath) || /[\x00-\x1f]/.test(appPath);

View File

@@ -14,6 +14,7 @@ import {
import { db } from "../../db";
import { chats, messages } from "../../db/schema";
import { and, eq, isNull } from "drizzle-orm";
import type { SmartContextMode } from "../../lib/schemas";
import {
constructSystemPrompt,
readAiRules,
@@ -516,6 +517,12 @@ ${componentSnippet}
updatedChat.app.id, // Exclude current app
);
const isDeepContextEnabled =
isEngineEnabled &&
settings.proSmartContextOption === "deep" &&
mentionedAppsCodebases.length === 0;
logger.log(`isDeepContextEnabled: ${isDeepContextEnabled}`);
// Combine current app codebase with mentioned apps' codebases
let otherAppsCodebaseInfo = "";
if (mentionedAppsCodebases.length > 0) {
@@ -555,8 +562,7 @@ ${componentSnippet}
//
// Limit chat history based on maxChatTurnsInContext setting
// We add 1 because the current prompt counts as a turn.
const maxChatTurns =
isEngineEnabled && settings.proSmartContextOption === "deep"
const maxChatTurns = isDeepContextEnabled
? 201
: (settings.maxChatTurnsInContext || MAX_CHAT_TURNS_IN_CONTEXT) + 1;
@@ -812,19 +818,24 @@ This conversation includes one or more image attachments. When the user uploads
logger.log("sending AI request");
}
let versionedFiles: VersionedFiles | undefined;
if (isEngineEnabled && settings.proSmartContextOption === "deep") {
if (isDeepContextEnabled) {
versionedFiles = await getVersionedFiles({
files,
chatMessages,
appPath,
});
}
const smartContextMode: SmartContextMode = isDeepContextEnabled
? "deep"
: // Keep in sync with getCurrentValue in ProModeSelector.tsx
"balanced";
// Build provider options with correct Google/Vertex thinking config gating
const providerOptions: Record<string, any> = {
"dyad-engine": {
dyadAppId: updatedChat.app.id,
dyadRequestId,
dyadDisableFiles,
dyadSmartContextMode: smartContextMode,
dyadFiles: versionedFiles ? undefined : files,
dyadVersionedFiles: versionedFiles,
dyadMentionedApps: mentionedAppsCodebases.map(

View File

@@ -92,8 +92,6 @@ export async function getModelClient(
: settings.enableProLazyEditsMode &&
settings.proLazyEditsMode !== "v2",
enableSmartFilesContext,
// Keep in sync with getCurrentValue in ProModeSelector.tsx
smartContextMode: settings.proSmartContextOption ?? "balanced",
enableWebSearch: settings.enableProWebSearch,
},
settings,

View File

@@ -42,7 +42,6 @@ or to provide a custom fetch implementation for e.g. testing.
enableLazyEdits?: boolean;
enableSmartFilesContext?: boolean;
enableWebSearch?: boolean;
smartContextMode?: "balanced" | "conservative" | "deep";
};
settings: UserSettings;
}
@@ -149,6 +148,10 @@ export function createDyadEngine(
if ("dyadMentionedApps" in parsedBody) {
delete parsedBody.dyadMentionedApps;
}
const dyadSmartContextMode = parsedBody.dyadSmartContextMode;
if ("dyadSmartContextMode" in parsedBody) {
delete parsedBody.dyadSmartContextMode;
}
// Track and modify requestId with attempt number
let modifiedRequestId = requestId;
@@ -166,7 +169,7 @@ export function createDyadEngine(
enable_lazy_edits: options.dyadOptions.enableLazyEdits,
enable_smart_files_context:
options.dyadOptions.enableSmartFilesContext,
smart_context_mode: options.dyadOptions.smartContextMode,
smart_context_mode: dyadSmartContextMode,
enable_web_search: options.dyadOptions.enableWebSearch,
app_id: dyadAppId,
};

View File

@@ -214,6 +214,12 @@ export type ReleaseChannel = z.infer<typeof ReleaseChannelSchema>;
export const ZoomLevelSchema = z.enum(["90", "100", "110", "125", "150"]);
export type ZoomLevel = z.infer<typeof ZoomLevelSchema>;
export const SmartContextModeSchema = z.enum([
"balanced",
"conservative",
"deep",
]);
export type SmartContextMode = z.infer<typeof SmartContextModeSchema>;
/**
* Zod schema for user settings
*/
@@ -238,9 +244,7 @@ export const UserSettingsSchema = z.object({
proLazyEditsMode: z.enum(["off", "v1", "v2"]).optional(),
enableProSmartFilesContextMode: z.boolean().optional(),
enableProWebSearch: z.boolean().optional(),
proSmartContextOption: z
.enum(["balanced", "conservative", "deep"])
.optional(),
proSmartContextOption: SmartContextModeSchema.optional(),
selectedTemplateId: z.string(),
enableSupabaseWriteSqlMigration: z.boolean().optional(),
selectedChatMode: ChatModeSchema.optional(),