Skip to content
Merged
1 change: 1 addition & 0 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3
const baseProviderSettingsSchema = z.object({
includeMaxTokens: z.boolean().optional(),
diffEnabled: z.boolean().optional(),
todoListEnabled: z.boolean().optional(),
fuzzyMatchThreshold: z.number().optional(),
modelTemperature: z.number().nullish(),
rateLimitSeconds: z.number().optional(),
Expand Down
21 changes: 21 additions & 0 deletions src/core/config/ProviderSettingsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const providerProfilesSchema = z.object({
diffSettingsMigrated: z.boolean().optional(),
openAiHeadersMigrated: z.boolean().optional(),
consecutiveMistakeLimitMigrated: z.boolean().optional(),
todoListEnabledMigrated: z.boolean().optional(),
})
.optional(),
})
Expand All @@ -51,6 +52,7 @@ export class ProviderSettingsManager {
diffSettingsMigrated: true, // Mark as migrated on fresh installs
openAiHeadersMigrated: true, // Mark as migrated on fresh installs
consecutiveMistakeLimitMigrated: true, // Mark as migrated on fresh installs
todoListEnabledMigrated: true, // Mark as migrated on fresh installs
},
}

Expand Down Expand Up @@ -117,6 +119,7 @@ export class ProviderSettingsManager {
diffSettingsMigrated: false,
openAiHeadersMigrated: false,
consecutiveMistakeLimitMigrated: false,
todoListEnabledMigrated: false,
} // Initialize with default values
isDirty = true
}
Expand Down Expand Up @@ -145,6 +148,12 @@ export class ProviderSettingsManager {
isDirty = true
}

if (!providerProfiles.migrations.todoListEnabledMigrated) {
await this.migrateTodoListEnabled(providerProfiles)
providerProfiles.migrations.todoListEnabledMigrated = true
isDirty = true
}

if (isDirty) {
await this.store(providerProfiles)
}
Expand Down Expand Up @@ -250,6 +259,18 @@ export class ProviderSettingsManager {
}
}

private async migrateTodoListEnabled(providerProfiles: ProviderProfiles) {
try {
for (const [_name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) {
if (apiConfig.todoListEnabled === undefined) {
apiConfig.todoListEnabled = true
}
}
} catch (error) {
console.error(`[MigrateTodoListEnabled] Failed to migrate todo list enabled setting:`, error)
}
}

/**
* List all available configs with metadata.
*/
Expand Down
43 changes: 43 additions & 0 deletions src/core/config/__tests__/ProviderSettingsManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ describe("ProviderSettingsManager", () => {
diffSettingsMigrated: true,
openAiHeadersMigrated: true,
consecutiveMistakeLimitMigrated: true,
todoListEnabledMigrated: true,
},
}),
)
Expand Down Expand Up @@ -186,6 +187,48 @@ describe("ProviderSettingsManager", () => {
expect(storedConfig.migrations.consecutiveMistakeLimitMigrated).toEqual(true)
})

it("should call migrateTodoListEnabled if it has not done so already", async () => {
mockSecrets.get.mockResolvedValue(
JSON.stringify({
currentApiConfigName: "default",
apiConfigs: {
default: {
config: {},
id: "default",
todoListEnabled: undefined,
},
test: {
apiProvider: "anthropic",
todoListEnabled: undefined,
},
existing: {
apiProvider: "anthropic",
// this should not really be possible, unless someone has loaded a hand edited config,
// but we don't overwrite so we'll check that
todoListEnabled: false,
},
},
migrations: {
rateLimitSecondsMigrated: true,
diffSettingsMigrated: true,
openAiHeadersMigrated: true,
consecutiveMistakeLimitMigrated: true,
todoListEnabledMigrated: false,
},
}),
)

await providerSettingsManager.initialize()

// Get the last call to store, which should contain the migrated config
const calls = mockSecrets.store.mock.calls
const storedConfig = JSON.parse(calls[calls.length - 1][1])
expect(storedConfig.apiConfigs.default.todoListEnabled).toEqual(true)
expect(storedConfig.apiConfigs.test.todoListEnabled).toEqual(true)
expect(storedConfig.apiConfigs.existing.todoListEnabled).toEqual(false)
expect(storedConfig.migrations.todoListEnabledMigrated).toEqual(true)
})

it("should throw error if secrets storage fails", async () => {
mockSecrets.get.mockRejectedValue(new Error("Storage failed"))

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 89 additions & 1 deletion src/core/prompts/__tests__/system-prompt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ __setMockImplementation(
globalCustomInstructions: string,
cwd: string,
mode: string,
options?: { language?: string },
options?: { language?: string; rooIgnoreInstructions?: string; settings?: Record<string, any> },
) => {
const sections = []

Expand Down Expand Up @@ -575,6 +575,94 @@ describe("SYSTEM_PROMPT", () => {
expect(prompt.indexOf(modes[0].roleDefinition)).toBeLessThan(prompt.indexOf("TOOL USE"))
})

it("should exclude update_todo_list tool when todoListEnabled is false", async () => {
const settings = {
todoListEnabled: false,
}

const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
settings, // settings
)

// Should not contain the tool description
expect(prompt).not.toContain("## update_todo_list")
// Mode instructions will still reference the tool with a fallback to markdown
})

it("should include update_todo_list tool when todoListEnabled is true", async () => {
const settings = {
todoListEnabled: true,
}

const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
settings, // settings
)

expect(prompt).toContain("update_todo_list")
expect(prompt).toContain("## update_todo_list")
})

it("should include update_todo_list tool when todoListEnabled is undefined", async () => {
const settings = {
// todoListEnabled not set
}

const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
settings, // settings
)

expect(prompt).toContain("update_todo_list")
expect(prompt).toContain("## update_todo_list")
})

afterAll(() => {
vi.restoreAllMocks()
})
Expand Down
2 changes: 1 addition & 1 deletion src/core/prompts/sections/custom-instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export async function addCustomInstructions(
globalCustomInstructions: string,
cwd: string,
mode: string,
options: { language?: string; rooIgnoreInstructions?: string } = {},
options: { language?: string; rooIgnoreInstructions?: string; settings?: Record<string, any> } = {},
): Promise<string> {
const sections = []

Expand Down
4 changes: 2 additions & 2 deletions src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ ${getSystemInfoSection(cwd)}

${getObjectiveSection(codeIndexManager, experiments)}

${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions })}`
${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, { language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions, settings })}`

return basePrompt
}
Expand Down Expand Up @@ -177,7 +177,7 @@ export const SYSTEM_PROMPT = async (
globalCustomInstructions || "",
cwd,
mode,
{ language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions },
{ language: language ?? formatLanguage(vscode.env.language), rooIgnoreInstructions, settings },
)

// For file-based prompts, don't include the tool sections
Expand Down
5 changes: 5 additions & 0 deletions src/core/prompts/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ export function getToolDescriptionsForMode(
tools.delete("codebase_search")
}

// Conditionally exclude update_todo_list if disabled in settings
if (settings?.todoListEnabled === false) {
tools.delete("update_todo_list")
}

// Map tool descriptions for allowed tools
const descriptions = Array.from(tools).map((toolName) => {
const descriptionFn = toolDescriptionMap[toolName]
Expand Down
2 changes: 1 addition & 1 deletion src/shared/modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const modes: readonly ModeConfig[] = [
description: "Plan and design before implementation",
groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"],
customInstructions:
"1. Do some information gathering (using provided tools) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.\n\n6. Include Mermaid diagrams if they help clarify complex workflows or system architecture. Please avoid using double quotes (\"\") and parentheses () inside square brackets ([]) in Mermaid diagrams, as this can cause parsing errors.\n\n7. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**",
"1. Do some information gathering (using provided tools) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.\n\n6. Include Mermaid diagrams if they help clarify complex workflows or system architecture. Please avoid using double quotes (\"\") and parentheses () inside square brackets ([]) in Mermaid diagrams, as this can cause parsing errors.\n\n7. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**",
},
{
slug: "code",
Expand Down
Loading