diff --git a/src/core/prompts/tools/new-task.ts b/src/core/prompts/tools/new-task.ts
index 7301b7b422..85324dad3b 100644
--- a/src/core/prompts/tools/new-task.ts
+++ b/src/core/prompts/tools/new-task.ts
@@ -2,22 +2,33 @@ import { ToolArgs } from "./types"
export function getNewTaskDescription(_args: ToolArgs): string {
return `## new_task
-Description: This will let you create a new task instance in the chosen mode using your provided message.
+Description: This will let you create a new task instance in the chosen mode using your provided message and optionally specify an API configuration profile to use.
Parameters:
- mode: (required) The slug of the mode to start the new task in (e.g., "code", "debug", "architect").
- message: (required) The initial user message or instructions for this new task.
+- config: (optional) The slug/name of the API configuration profile to use for this task (e.g., "claude-3-5-sonnet", "gpt-4-debug", "fast-model"). If not specified, uses the default configuration for the mode.
Usage:
your-mode-slug-here
Your initial instructions here
+optional-config-slug-here
-Example:
+Examples:
+
+1. Basic usage (without config):
code
Implement a new feature for the application.
+
+2. With specific configuration:
+
+architect
+Design the database schema for the new feature
+accurate-model
+
`
}
diff --git a/src/core/tools/__tests__/newTaskTool.spec.ts b/src/core/tools/__tests__/newTaskTool.spec.ts
index 1dd79d6e98..ced02780dd 100644
--- a/src/core/tools/__tests__/newTaskTool.spec.ts
+++ b/src/core/tools/__tests__/newTaskTool.spec.ts
@@ -26,6 +26,7 @@ const mockInitClineWithTask = vi.fn<() => Promise>().mockReso
const mockEmit = vi.fn()
const mockRecordToolError = vi.fn()
const mockSayAndCreateMissingParamError = vi.fn()
+const mockHasConfig = vi.fn()
// Mock the Cline instance and its methods/properties
const mockCline = {
@@ -41,6 +42,9 @@ const mockCline = {
getState: vi.fn(() => ({ customModes: [], mode: "ask" })),
handleModeSwitch: vi.fn(),
initClineWithTask: mockInitClineWithTask,
+ providerSettingsManager: {
+ hasConfig: mockHasConfig,
+ },
})),
},
}
@@ -63,6 +67,7 @@ describe("newTaskTool", () => {
}) // Default valid mode
mockCline.consecutiveMistakeCount = 0
mockCline.isPaused = false
+ mockHasConfig.mockResolvedValue(true) // Default to config exists
})
it("should correctly un-escape \\\\@ to \\@ in the message passed to the new task", async () => {
@@ -93,6 +98,7 @@ describe("newTaskTool", () => {
"Review this: \\@file1.txt and also \\\\\\@file2.txt", // Unit Test Expectation: \\@ -> \@, \\\\@ -> \\\\@
undefined,
mockCline,
+ undefined, // No config parameter for this test
)
// Verify side effects
@@ -126,6 +132,7 @@ describe("newTaskTool", () => {
"This is already unescaped: \\@file1.txt", // Expected: \@ remains \@
undefined,
mockCline,
+ undefined, // No config parameter for this test
)
})
@@ -153,6 +160,7 @@ describe("newTaskTool", () => {
"A normal mention @file1.txt", // Expected: @ remains @
undefined,
mockCline,
+ undefined, // No config parameter for this test
)
})
@@ -180,8 +188,188 @@ describe("newTaskTool", () => {
"Mix: @file0.txt, \\@file1.txt, \\@file2.txt, \\\\\\@file3.txt", // Unit Test Expectation: @->@, \@->\@, \\@->\@, \\\\@->\\\\@
undefined,
mockCline,
+ undefined, // No config parameter for this test
)
})
+ // Tests for the new config parameter functionality
+ describe("config parameter", () => {
+ it("should pass config parameter to initClineWithTask when valid config is provided", async () => {
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ config: "fast-model",
+ },
+ partial: false,
+ }
+
+ mockHasConfig.mockResolvedValue(true)
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Verify hasConfig was called to validate the config
+ expect(mockHasConfig).toHaveBeenCalledWith("fast-model")
+
+ // Verify initClineWithTask was called with the config parameter
+ expect(mockInitClineWithTask).toHaveBeenCalledWith(
+ "Test message",
+ undefined,
+ mockCline,
+ "fast-model", // The config parameter should be passed
+ )
+
+ // Verify success message includes config name
+ expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("configuration 'fast-model'"))
+ })
+
+ it("should continue without config when invalid config is provided", async () => {
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ config: "non-existent-config",
+ },
+ partial: false,
+ }
+
+ mockHasConfig.mockResolvedValue(false)
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Verify hasConfig was called
+ expect(mockHasConfig).toHaveBeenCalledWith("non-existent-config")
+
+ // Verify error message was pushed
+ expect(mockPushToolResult).toHaveBeenCalledWith(
+ expect.stringContaining("Configuration profile 'non-existent-config' not found"),
+ )
+
+ // Verify initClineWithTask was called without the config parameter
+ expect(mockInitClineWithTask).toHaveBeenCalledWith(
+ "Test message",
+ undefined,
+ mockCline,
+ undefined, // No config should be passed
+ )
+
+ // Verify success message doesn't include config
+ expect(mockPushToolResult).toHaveBeenCalledWith(
+ expect.stringContaining("Successfully created new task in Code Mode mode with message: Test message"),
+ )
+ })
+
+ it("should work without config parameter (backward compatibility)", async () => {
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ // No config parameter
+ },
+ partial: false,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Verify hasConfig was NOT called
+ expect(mockHasConfig).not.toHaveBeenCalled()
+
+ // Verify initClineWithTask was called without config
+ expect(mockInitClineWithTask).toHaveBeenCalledWith(
+ "Test message",
+ undefined,
+ mockCline,
+ undefined, // No config parameter
+ )
+
+ // Verify success message doesn't include config
+ expect(mockPushToolResult).toHaveBeenCalledWith(
+ expect.stringContaining("Successfully created new task in Code Mode mode with message: Test message"),
+ )
+ })
+
+ it("should include config in approval message when config is provided", async () => {
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ config: "accurate-model",
+ },
+ partial: false,
+ }
+
+ mockHasConfig.mockResolvedValue(true)
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Verify askApproval was called with a message containing the config
+ expect(mockAskApproval).toHaveBeenCalledWith("tool", expect.stringContaining('"config":"accurate-model"'))
+ })
+
+ it("should handle partial messages with config parameter", async () => {
+ const block: ToolUse = {
+ type: "tool_use",
+ name: "new_task",
+ params: {
+ mode: "code",
+ message: "Test message",
+ config: "fast-model",
+ },
+ partial: true,
+ }
+
+ await newTaskTool(
+ mockCline as any,
+ block,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Verify ask was called with partial message including config
+ expect(mockCline.ask).toHaveBeenCalledWith("tool", expect.stringContaining('"config":"fast-model"'), true)
+
+ // Verify initClineWithTask was NOT called for partial message
+ expect(mockInitClineWithTask).not.toHaveBeenCalled()
+ })
+ })
+
// Add more tests for error handling (missing params, invalid mode, approval denied) if needed
})
diff --git a/src/core/tools/newTaskTool.ts b/src/core/tools/newTaskTool.ts
index 46a1fe5d9b..a6d49632b2 100644
--- a/src/core/tools/newTaskTool.ts
+++ b/src/core/tools/newTaskTool.ts
@@ -18,6 +18,7 @@ export async function newTaskTool(
) {
const mode: string | undefined = block.params.mode
const message: string | undefined = block.params.message
+ const config: string | undefined = block.params.config
try {
if (block.partial) {
@@ -25,6 +26,7 @@ export async function newTaskTool(
tool: "newTask",
mode: removeClosingTag("mode", mode),
content: removeClosingTag("message", message),
+ config: config ? removeClosingTag("config", config) : undefined,
})
await cline.ask("tool", partialMessage, block.partial).catch(() => {})
@@ -57,10 +59,33 @@ export async function newTaskTool(
return
}
+ // If a config was specified, verify it exists
+ let configName: string | undefined
+ if (config) {
+ const provider = cline.providerRef.deref()
+ if (!provider) {
+ return
+ }
+
+ // Check if the specified config exists
+ const hasConfig = await provider.providerSettingsManager.hasConfig(config)
+ if (!hasConfig) {
+ pushToolResult(
+ formatResponse.toolError(
+ `Configuration profile '${config}' not found. Using default configuration.`,
+ ),
+ )
+ // Continue without the config rather than failing completely
+ } else {
+ configName = config
+ }
+ }
+
const toolMessage = JSON.stringify({
tool: "newTask",
mode: targetMode.name,
content: message,
+ ...(configName && { config: configName }),
})
const didApprove = await askApproval("tool", toolMessage)
@@ -83,7 +108,7 @@ export async function newTaskTool(
cline.pausedModeSlug = (await provider.getState()).mode ?? defaultModeSlug
// Create new task instance first (this preserves parent's current mode in its history)
- const newCline = await provider.initClineWithTask(unescapedMessage, undefined, cline)
+ const newCline = await provider.initClineWithTask(unescapedMessage, undefined, cline, configName)
if (!newCline) {
pushToolResult(t("tools:newTask.errors.policy_restriction"))
return
@@ -97,7 +122,10 @@ export async function newTaskTool(
cline.emit(RooCodeEventName.TaskSpawned, newCline.taskId)
- pushToolResult(`Successfully created new task in ${targetMode.name} mode with message: ${unescapedMessage}`)
+ const successMessage = configName
+ ? `Successfully created new task in ${targetMode.name} mode with configuration '${configName}' and message: ${unescapedMessage}`
+ : `Successfully created new task in ${targetMode.name} mode with message: ${unescapedMessage}`
+ pushToolResult(successMessage)
// Set the isPaused flag to true so the parent
// task can wait for the sub-task to finish.
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 274060a19b..1895a47478 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -638,14 +638,35 @@ export class ClineProvider
text?: string,
images?: string[],
parentTask?: Task,
- options: Partial<
+ configNameOrOptions?:
+ | string
+ | Partial<
+ Pick<
+ TaskOptions,
+ | "enableDiff"
+ | "enableCheckpoints"
+ | "fuzzyMatchThreshold"
+ | "consecutiveMistakeLimit"
+ | "experiments"
+ >
+ >,
+ ) {
+ // Handle both string (config name) and options object
+ let configName: string | undefined
+ let options: Partial<
Pick<
TaskOptions,
"enableDiff" | "enableCheckpoints" | "fuzzyMatchThreshold" | "consecutiveMistakeLimit" | "experiments"
>
- > = {},
- ) {
- const {
+ > = {}
+
+ if (typeof configNameOrOptions === "string") {
+ configName = configNameOrOptions
+ } else if (configNameOrOptions) {
+ options = configNameOrOptions
+ }
+
+ let {
apiConfiguration,
organizationAllowList,
diffEnabled: enableDiff,
@@ -654,6 +675,26 @@ export class ClineProvider
experiments,
} = await this.getState()
+ // If a specific config was requested, load and apply it
+ if (configName) {
+ try {
+ const configProfile = await this.providerSettingsManager.getProfile({ name: configName })
+ // Use the configuration from the specified profile
+ apiConfiguration = configProfile
+ // Also use the diff and fuzzy match settings from the profile if available
+ if (configProfile.diffEnabled !== undefined) {
+ enableDiff = configProfile.diffEnabled
+ }
+ if (configProfile.fuzzyMatchThreshold !== undefined) {
+ fuzzyMatchThreshold = configProfile.fuzzyMatchThreshold
+ }
+ } catch (error) {
+ // Log error but continue with default config
+ this.log(`Failed to load config '${configName}' for new task: ${error}`)
+ // Continue with the current/default configuration
+ }
+ }
+
if (!ProfileValidator.isProfileAllowed(apiConfiguration, organizationAllowList)) {
throw new OrganizationAllowListViolationError(t("common:errors.violated_organization_allowlist"))
}
diff --git a/src/shared/tools.ts b/src/shared/tools.ts
index 67972243fe..e3b11e1dd1 100644
--- a/src/shared/tools.ts
+++ b/src/shared/tools.ts
@@ -65,6 +65,7 @@ export const toolParamNames = [
"query",
"args",
"todos",
+ "config",
] as const
export type ToolParamName = (typeof toolParamNames)[number]
@@ -155,7 +156,7 @@ export interface SwitchModeToolUse extends ToolUse {
export interface NewTaskToolUse extends ToolUse {
name: "new_task"
- params: Partial, "mode" | "message">>
+ params: Partial, "mode" | "message" | "config">>
}
export interface SearchAndReplaceToolUse extends ToolUse {