Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export type ProviderSettingsEntry = z.infer<typeof providerSettingsEntrySchema>
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
34 changes: 34 additions & 0 deletions src/core/config/ProviderSettingsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const providerProfilesSchema = z.object({
.object({
rateLimitSecondsMigrated: z.boolean().optional(),
diffSettingsMigrated: z.boolean().optional(),
todoListSettingsMigrated: z.boolean().optional(),
openAiHeadersMigrated: z.boolean().optional(),
})
.optional(),
Expand All @@ -47,6 +48,7 @@ export class ProviderSettingsManager {
migrations: {
rateLimitSecondsMigrated: true, // Mark as migrated on fresh installs
diffSettingsMigrated: true, // Mark as migrated on fresh installs
todoListSettingsMigrated: true, // Mark as migrated on fresh installs
openAiHeadersMigrated: true, // Mark as migrated on fresh installs
},
}
Expand Down Expand Up @@ -112,6 +114,7 @@ export class ProviderSettingsManager {
providerProfiles.migrations = {
rateLimitSecondsMigrated: false,
diffSettingsMigrated: false,
todoListSettingsMigrated: false,
openAiHeadersMigrated: false,
} // Initialize with default values
isDirty = true
Expand All @@ -129,6 +132,12 @@ export class ProviderSettingsManager {
isDirty = true
}

if (!providerProfiles.migrations.todoListSettingsMigrated) {
await this.migrateTodoListSettings(providerProfiles)
providerProfiles.migrations.todoListSettingsMigrated = true
isDirty = true
}

if (!providerProfiles.migrations.openAiHeadersMigrated) {
await this.migrateOpenAiHeaders(providerProfiles)
providerProfiles.migrations.openAiHeadersMigrated = true
Expand Down Expand Up @@ -204,6 +213,31 @@ export class ProviderSettingsManager {
}
}

private async migrateTodoListSettings(providerProfiles: ProviderProfiles) {
try {
let todoListEnabled: boolean | undefined

try {
todoListEnabled = await this.context.globalState.get<boolean>("alwaysAllowUpdateTodoList")
} catch (error) {
console.error("[MigrateTodoListSettings] Error getting global todo list settings:", error)
}

if (todoListEnabled === undefined) {
// Failed to get the existing value, use the default.
todoListEnabled = true
}

for (const [_name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) {
if (apiConfig.todoListEnabled === undefined) {
apiConfig.todoListEnabled = todoListEnabled
}
}
} catch (error) {
console.error(`[MigrateTodoListSettings] Failed to migrate todo list settings:`, error)
}
}

private async migrateOpenAiHeaders(providerProfiles: ProviderProfiles) {
try {
for (const [_name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ describe("ProviderSettingsManager", () => {
rateLimitSecondsMigrated: true,
diffSettingsMigrated: true,
openAiHeadersMigrated: true,
todoListSettingsMigrated: true,
},
}),
)
Expand Down
4 changes: 4 additions & 0 deletions src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ async function generatePrompt(
partialReadsEnabled?: boolean,
settings?: Record<string, any>,
todoList?: TodoItem[],
todoListEnabled?: boolean,
): Promise<string> {
if (!context) {
throw new Error("Extension context is required for generating system prompt")
Expand Down Expand Up @@ -98,6 +99,7 @@ ${getToolDescriptionsForMode(
experiments,
partialReadsEnabled,
settings,
todoListEnabled,
)}

${getToolUseGuidelinesSection(codeIndexManager)}
Expand Down Expand Up @@ -138,6 +140,7 @@ export const SYSTEM_PROMPT = async (
partialReadsEnabled?: boolean,
settings?: Record<string, any>,
todoList?: TodoItem[],
todoListEnabled?: boolean,
): Promise<string> => {
if (!context) {
throw new Error("Extension context is required for generating system prompt")
Expand Down Expand Up @@ -205,5 +208,6 @@ ${customInstructions}`
partialReadsEnabled,
settings,
todoList,
todoListEnabled,
)
}
119 changes: 119 additions & 0 deletions src/core/prompts/tools/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// npx vitest src/core/prompts/tools/__tests__/index.spec.ts

import { getToolDescriptionsForMode } from "../index"

describe("getToolDescriptionsForMode", () => {
const mockArgs = {
mode: "code" as const,
cwd: "/test",
supportsComputerUse: false,
codeIndexManager: undefined,
diffStrategy: undefined,
browserViewportSize: undefined,
mcpHub: undefined,
customModes: undefined,
experiments: undefined,
partialReadsEnabled: undefined,
settings: undefined,
}

it("should include update_todo_list tool when todoListEnabled is true", () => {
const tools = getToolDescriptionsForMode(
mockArgs.mode,
mockArgs.cwd,
mockArgs.supportsComputerUse,
mockArgs.codeIndexManager,
mockArgs.diffStrategy,
mockArgs.browserViewportSize,
mockArgs.mcpHub,
mockArgs.customModes,
mockArgs.experiments,
mockArgs.partialReadsEnabled,
mockArgs.settings,
true, // todoListEnabled
)

expect(tools).toContain("update_todo_list")
})

it("should exclude update_todo_list tool when todoListEnabled is false", () => {
const tools = getToolDescriptionsForMode(
mockArgs.mode,
mockArgs.cwd,
mockArgs.supportsComputerUse,
mockArgs.codeIndexManager,
mockArgs.diffStrategy,
mockArgs.browserViewportSize,
mockArgs.mcpHub,
mockArgs.customModes,
mockArgs.experiments,
mockArgs.partialReadsEnabled,
mockArgs.settings,
false, // todoListEnabled
)

expect(tools).not.toContain("update_todo_list")
})

it("should include update_todo_list tool when todoListEnabled is undefined (default true)", () => {
const tools = getToolDescriptionsForMode(
mockArgs.mode,
mockArgs.cwd,
mockArgs.supportsComputerUse,
mockArgs.codeIndexManager,
mockArgs.diffStrategy,
mockArgs.browserViewportSize,
mockArgs.mcpHub,
mockArgs.customModes,
mockArgs.experiments,
mockArgs.partialReadsEnabled,
mockArgs.settings,
undefined, // todoListEnabled
)

expect(tools).toContain("update_todo_list")
})

it("should include other tools regardless of todoListEnabled setting", () => {
const toolsWithTodo = getToolDescriptionsForMode(
mockArgs.mode,
mockArgs.cwd,
mockArgs.supportsComputerUse,
mockArgs.codeIndexManager,
mockArgs.diffStrategy,
mockArgs.browserViewportSize,
mockArgs.mcpHub,
mockArgs.customModes,
mockArgs.experiments,
mockArgs.partialReadsEnabled,
mockArgs.settings,
true, // todoListEnabled
)

const toolsWithoutTodo = getToolDescriptionsForMode(
mockArgs.mode,
mockArgs.cwd,
mockArgs.supportsComputerUse,
mockArgs.codeIndexManager,
mockArgs.diffStrategy,
mockArgs.browserViewportSize,
mockArgs.mcpHub,
mockArgs.customModes,
mockArgs.experiments,
mockArgs.partialReadsEnabled,
mockArgs.settings,
false, // todoListEnabled
)

// Both should have other tools like read_file, write_to_file, etc.
expect(toolsWithTodo.length).toBeGreaterThan(100) // Should have substantial content
expect(toolsWithoutTodo.length).toBeGreaterThan(100) // Should have substantial content

// Tools with todo should be longer than tools without todo
expect(toolsWithTodo.length).toBeGreaterThan(toolsWithoutTodo.length)

// Both should contain common tools
expect(toolsWithTodo).toContain("read_file")
expect(toolsWithoutTodo).toContain("read_file")
})
})
6 changes: 6 additions & 0 deletions src/core/prompts/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export function getToolDescriptionsForMode(
experiments?: Record<string, boolean>,
partialReadsEnabled?: boolean,
settings?: Record<string, any>,
todoListEnabled?: boolean,
): string {
const config = getModeConfig(mode, customModes)
const args: ToolArgs = {
Expand Down Expand Up @@ -109,6 +110,11 @@ export function getToolDescriptionsForMode(
tools.delete("codebase_search")
}

// Conditionally exclude update_todo_list if feature is disabled
if (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
6 changes: 6 additions & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export type TaskOptions = {
provider: ClineProvider
apiConfiguration: ProviderSettings
enableDiff?: boolean
enableTodoList?: boolean
enableCheckpoints?: boolean
fuzzyMatchThreshold?: number
consecutiveMistakeLimit?: number
Expand Down Expand Up @@ -172,6 +173,7 @@ export class Task extends EventEmitter<ClineEvents> {
diffViewProvider: DiffViewProvider
diffStrategy?: DiffStrategy
diffEnabled: boolean = false
todoListEnabled: boolean = true
fuzzyMatchThreshold: number
didEditFile: boolean = false

Expand Down Expand Up @@ -213,6 +215,7 @@ export class Task extends EventEmitter<ClineEvents> {
provider,
apiConfiguration,
enableDiff = false,
enableTodoList = true,
enableCheckpoints = true,
fuzzyMatchThreshold = 1.0,
consecutiveMistakeLimit = 3,
Expand Down Expand Up @@ -253,6 +256,7 @@ export class Task extends EventEmitter<ClineEvents> {
this.urlContentFetcher = new UrlContentFetcher(provider.context)
this.browserSession = new BrowserSession(provider.context)
this.diffEnabled = enableDiff
this.todoListEnabled = enableTodoList
this.fuzzyMatchThreshold = fuzzyMatchThreshold
this.consecutiveMistakeLimit = consecutiveMistakeLimit
this.providerRef = new WeakRef(provider)
Expand Down Expand Up @@ -1649,6 +1653,8 @@ export class Task extends EventEmitter<ClineEvents> {
{
maxConcurrentFileReads,
},
this.todoList,
this.todoListEnabled,
)
})()
}
Expand Down
4 changes: 4 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,9 @@ export class ClineProvider
experiments,
} = await this.getState()

// Extract todoListEnabled from provider settings
const todoListEnabled = apiConfiguration.todoListEnabled ?? true

if (!ProfileValidator.isProfileAllowed(apiConfiguration, organizationAllowList)) {
throw new OrganizationAllowListViolationError(t("common:errors.violated_organization_allowlist"))
}
Expand All @@ -551,6 +554,7 @@ export class ClineProvider
provider: this,
apiConfiguration,
enableDiff,
enableTodoList: todoListEnabled,
enableCheckpoints,
fuzzyMatchThreshold,
task,
Expand Down
5 changes: 5 additions & 0 deletions src/core/webview/generateSystemPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
maxConcurrentFileReads,
} = await provider.getState()

// Get todoListEnabled from provider settings with fallback to global setting
const todoListEnabled = apiConfiguration.todoListEnabled ?? true

// Check experiment to determine which diff strategy to use
const isMultiFileApplyDiffEnabled = experimentsModule.isEnabled(
experiments ?? {},
Expand Down Expand Up @@ -83,6 +86,8 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
{
maxConcurrentFileReads,
},
undefined, // todoList parameter
todoListEnabled,
)

return systemPrompt
Expand Down
49 changes: 49 additions & 0 deletions webview-ui/src/components/settings/AdvancedSettingsSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useState } from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { DiffSettingsControl } from "./DiffSettingsControl"
import { TodoListSettingsControl } from "./TodoListSettingsControl"

interface AdvancedSettingsSectionProps {
diffEnabled?: boolean
fuzzyMatchThreshold?: number
todoListEnabled?: boolean
onChange: (field: "diffEnabled" | "fuzzyMatchThreshold" | "todoListEnabled", value: any) => void
}

export const AdvancedSettingsSection: React.FC<AdvancedSettingsSectionProps> = ({
diffEnabled,
fuzzyMatchThreshold,
todoListEnabled,
onChange,
}) => {
const { t } = useAppTranslation()
const [isExpanded, setIsExpanded] = useState(false)

const toggleExpanded = () => {
setIsExpanded(!isExpanded)
}

return (
<div className="flex flex-col gap-3">
<VSCodeButton
appearance="secondary"
onClick={toggleExpanded}
className="flex items-center justify-between w-full text-left">
<span className="font-medium">{t("settings:advanced.section.label")}</span>
<span className="ml-2">{isExpanded ? "▼" : "▶"}</span>
</VSCodeButton>

{isExpanded && (
<div className="flex flex-col gap-4 pl-4 border-l-2 border-vscode-button-background">
<DiffSettingsControl
diffEnabled={diffEnabled}
fuzzyMatchThreshold={fuzzyMatchThreshold}
onChange={onChange}
/>
<TodoListSettingsControl todoListEnabled={todoListEnabled} onChange={onChange} />
</div>
)}
</div>
)
}
Loading
Loading