- 🎉{" "}New in Cline v{minorVersion}
+ Agent Modes Customization
-
Add custom tools to Cline using MCP!
- The Model Context Protocol allows agents like Cline to plug and play custom tools,{" "}
-
- e.g. a web-search tool or GitHub tool.
-
-
-
- You can add and configure MCP servers by clicking the new{" "}
- icon in the menu bar.
-
-
- To take things a step further, Cline also has the ability to create custom tools for himself. Just say
- "add a tool that..." and watch as he builds and installs new capabilities specific to{" "}
- your workflow. For example:
+ Click the new icon in the menu bar to open the Prompts Settings and customize Agent Modes for new levels of productivity.
-
"...fetches Jira tickets": Get ticket ACs and put Cline to work
-
"...manages AWS EC2s": Check server metrics and scale up or down
-
"...pulls PagerDuty incidents": Pulls details to help Cline fix bugs
+
Tailor how Roo Cline behaves in different modes: Code, Architect, and Ask.
+
Preview and verify your changes using the Preview System Prompt button.
- Cline handles everything from creating the MCP server to installing it in the extension, ready to use in
- future tasks. The servers are saved to ~/Documents/Cline/MCP so you can easily share them
- with others too.{" "}
+
+
+ Prompt Enhancement Configuration
+
- Try it yourself by asking Cline to "add a tool that gets the latest npm docs", or
-
- see a demo of MCP in action here.
-
+ Now available for all providers! Access it directly in the chat box by clicking the sparkle icon next to the input field. From there, you can customize the enhancement logic and provider to best suit your workflow.
+
+
Customize how prompts are enhanced for better results in your workflow.
+
Use the sparkle icon in the chat box to select a API configuration and provider (e.g., GPT-4) and configure your own enhancement logic.
+
Test your changes instantly with the Preview Prompt Enhancement tool.
+
- {/*
-
- OpenRouter now supports prompt caching! They also have much higher rate limits than other providers,
- so I recommend trying them out.
-
- {!apiConfiguration?.openRouterApiKey && (
-
- Get OpenRouter API Key
-
- )}
- {apiConfiguration?.openRouterApiKey && apiConfiguration?.apiProvider !== "openrouter" && (
- {
- vscode.postMessage({
- type: "apiConfiguration",
- apiConfiguration: { ...apiConfiguration, apiProvider: "openrouter" },
- })
- }}
- style={{
- transform: "scale(0.85)",
- transformOrigin: "left center",
- margin: "4px -30px 2px 0",
- }}>
- Switch to OpenRouter
-
- )}
-
-
- Edit Cline's changes before accepting! When he creates or edits a file, you can modify his
- changes directly in the right side of the diff view (+ hover over the 'Revert Block' arrow button in
- the center to undo "{"// rest of code here"}" shenanigans)
-
-
- New search_files tool that lets Cline perform regex searches in your project, letting
- him refactor code, address TODOs and FIXMEs, remove dead code, and more!
-
-
- When Cline runs commands, you can now type directly in the terminal (+ support for Python
- environments)
-
-
*/}
-
-
- Join
-
- discord.gg/cline
+
+
+ We're very excited to see what you build with this new feature! Join us at
+
+ reddit.com/r/roocline
- for more updates!
+ to discuss and share feedback.
+ Customize Cline's prompt in each mode. The rest of the system prompt will be automatically appended. Click the button to preview the full prompt. Leave empty or click the reset button to use the default.
+
diff --git a/webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx b/webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx
index 530de12..311702f 100644
--- a/webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx
+++ b/webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx
@@ -3,7 +3,6 @@ import '@testing-library/jest-dom'
import PromptsView from '../PromptsView'
import { ExtensionStateContext } from '../../../context/ExtensionStateContext'
import { vscode } from '../../../utils/vscode'
-import { defaultPrompts } from '../../../../../src/shared/modes'
// Mock vscode API
jest.mock('../../../utils/vscode', () => ({
@@ -97,7 +96,7 @@ describe('PromptsView', () => {
expect(vscode.postMessage).toHaveBeenCalledWith({
type: 'updatePrompt',
promptMode: 'code',
- customPrompt: 'New prompt value'
+ customPrompt: { roleDefinition: 'New prompt value' }
})
})
@@ -110,7 +109,7 @@ describe('PromptsView', () => {
expect(vscode.postMessage).toHaveBeenCalledWith({
type: 'updatePrompt',
promptMode: 'code',
- customPrompt: defaultPrompts.code
+ customPrompt: { roleDefinition: undefined }
})
})
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx
index 24596ca..8ecc488 100644
--- a/webview-ui/src/context/ExtensionStateContext.tsx
+++ b/webview-ui/src/context/ExtensionStateContext.tsx
@@ -17,7 +17,7 @@ import {
checkExistKey
} from "../../../src/shared/checkExistApiConfig"
import { Mode } from "../../../src/core/prompts/types"
-import { codeMode, CustomPrompts } from "../../../src/shared/modes"
+import { codeMode, CustomPrompts, defaultPrompts } from "../../../src/shared/modes"
export interface ExtensionStateContextType extends ExtensionState {
didHydrateState: boolean
@@ -89,7 +89,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
currentApiConfigName: 'default',
listApiConfigMeta: [],
mode: codeMode,
- customPrompts: {},
+ customPrompts: defaultPrompts,
enhancementApiConfigId: '',
})
const [didHydrateState, setDidHydrateState] = useState(false)
From 365f4acf632352f38b63f9e68ddaa5f321b2fbcb Mon Sep 17 00:00:00 2001
From: Matt Rubens
Date: Tue, 14 Jan 2025 10:51:59 -0500
Subject: [PATCH 06/15] Add mode-specific custom instructions
---
src/core/Cline.ts | 10 +-
src/core/mentions/__tests__/index.test.ts | 2 +-
.../__snapshots__/system.test.ts.snap | 136 +++++++-
src/core/prompts/__tests__/system.test.ts | 161 +++++++--
src/core/prompts/ask.ts | 1 -
src/core/prompts/code.ts | 1 -
src/core/prompts/system.ts | 59 +++-
src/core/webview/ClineProvider.ts | 87 +++--
.../webview/__tests__/ClineProvider.test.ts | 182 +++++++++-
src/integrations/misc/open-file.ts | 44 ++-
src/shared/modes.ts | 1 +
.../src/components/prompts/PromptsView.tsx | 325 +++++++++++++-----
.../src/components/settings/SettingsView.tsx | 36 +-
13 files changed, 841 insertions(+), 204 deletions(-)
diff --git a/src/core/Cline.ts b/src/core/Cline.ts
index acff8bf..c0c3683 100644
--- a/src/core/Cline.ts
+++ b/src/core/Cline.ts
@@ -789,7 +789,15 @@ export class Cline {
browserViewportSize,
mode,
customPrompts
- ) + await addCustomInstructions(this.customInstructions ?? '', cwd, preferredLanguage)
+ ) + await addCustomInstructions(
+ {
+ customInstructions: this.customInstructions,
+ customPrompts,
+ preferredLanguage
+ },
+ cwd,
+ mode
+ )
// If the previous API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request
if (previousApiReqIndex >= 0) {
diff --git a/src/core/mentions/__tests__/index.test.ts b/src/core/mentions/__tests__/index.test.ts
index e816726..609f0cf 100644
--- a/src/core/mentions/__tests__/index.test.ts
+++ b/src/core/mentions/__tests__/index.test.ts
@@ -131,7 +131,7 @@ Detailed commit message with multiple lines
await openMention("/path/to/file")
expect(mockExecuteCommand).not.toHaveBeenCalled()
expect(mockOpenExternal).not.toHaveBeenCalled()
- expect(mockShowErrorMessage).toHaveBeenCalledWith("Could not open file!")
+ expect(mockShowErrorMessage).toHaveBeenCalledWith("Could not open file: File does not exist")
await openMention("problems")
expect(mockExecuteCommand).toHaveBeenCalledWith("workbench.actions.view.problems")
diff --git a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap
index c32e2e9..811c46d 100644
--- a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap
+++ b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap
@@ -2185,6 +2185,66 @@ Custom test instructions
2. Second rule"
`;
+exports[`addCustomInstructions should combine global and mode-specific instructions 1`] = `
+"
+====
+
+USER'S CUSTOM INSTRUCTIONS
+
+The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.
+
+Global instructions
+
+Mode-specific instructions
+
+# Rules from .clinerules:
+# Test Rules
+1. First rule
+2. Second rule"
+`;
+
+exports[`addCustomInstructions should fall back to generic rules when mode-specific rules not found 1`] = `
+"
+====
+
+USER'S CUSTOM INSTRUCTIONS
+
+The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.
+
+# Rules from .clinerules:
+# Test Rules
+1. First rule
+2. Second rule"
+`;
+
+exports[`addCustomInstructions should handle empty mode-specific instructions 1`] = `
+"
+====
+
+USER'S CUSTOM INSTRUCTIONS
+
+The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.
+
+# Rules from .clinerules:
+# Test Rules
+1. First rule
+2. Second rule"
+`;
+
+exports[`addCustomInstructions should handle undefined mode-specific instructions 1`] = `
+"
+====
+
+USER'S CUSTOM INSTRUCTIONS
+
+The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.
+
+# Rules from .clinerules:
+# Test Rules
+1. First rule
+2. Second rule"
+`;
+
exports[`addCustomInstructions should include custom instructions when provided 1`] = `
"
====
@@ -2217,7 +2277,7 @@ You should always speak and think in the Spanish language.
2. Second rule"
`;
-exports[`addCustomInstructions should include rules from .clinerules 1`] = `
+exports[`addCustomInstructions should prioritize mode-specific instructions after global ones 1`] = `
"
====
@@ -2225,6 +2285,80 @@ USER'S CUSTOM INSTRUCTIONS
The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.
+First instruction
+
+Second instruction
+
+# Rules from .clinerules:
+# Test Rules
+1. First rule
+2. Second rule"
+`;
+
+exports[`addCustomInstructions should prioritize mode-specific rules for architect mode 1`] = `
+"
+====
+
+USER'S CUSTOM INSTRUCTIONS
+
+The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.
+
+# Rules from .clinerules-architect:
+# Architect Mode Rules
+1. Architect specific rule
+
+# Rules from .clinerules:
+# Test Rules
+1. First rule
+2. Second rule"
+`;
+
+exports[`addCustomInstructions should prioritize mode-specific rules for ask mode 1`] = `
+"
+====
+
+USER'S CUSTOM INSTRUCTIONS
+
+The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.
+
+# Rules from .clinerules-ask:
+# Ask Mode Rules
+1. Ask specific rule
+
+# Rules from .clinerules:
+# Test Rules
+1. First rule
+2. Second rule"
+`;
+
+exports[`addCustomInstructions should prioritize mode-specific rules for code mode 1`] = `
+"
+====
+
+USER'S CUSTOM INSTRUCTIONS
+
+The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.
+
+# Rules from .clinerules-code:
+# Code Mode Rules
+1. Code specific rule
+
+# Rules from .clinerules:
+# Test Rules
+1. First rule
+2. Second rule"
+`;
+
+exports[`addCustomInstructions should trim mode-specific instructions 1`] = `
+"
+====
+
+USER'S CUSTOM INSTRUCTIONS
+
+The following additional instructions are provided by the user, and should be followed to the best of your ability without interfering with the TOOL USE guidelines.
+
+Custom mode instructions
+
# Rules from .clinerules:
# Test Rules
1. First rule
diff --git a/src/core/prompts/__tests__/system.test.ts b/src/core/prompts/__tests__/system.test.ts
index b7d0b23..178fcfd 100644
--- a/src/core/prompts/__tests__/system.test.ts
+++ b/src/core/prompts/__tests__/system.test.ts
@@ -5,6 +5,7 @@ import { ClineProvider } from '../../../core/webview/ClineProvider'
import { SearchReplaceDiffStrategy } from '../../../core/diff/strategies/search-replace'
import fs from 'fs/promises'
import os from 'os'
+import { codeMode, askMode, architectMode } from '../modes'
// Import path utils to get access to toPosix string extension
import '../../../utils/path'
@@ -18,13 +19,22 @@ jest.mock('default-shell', () => '/bin/bash')
jest.mock('os-name', () => () => 'Linux')
-// Mock fs.readFile to return empty mcpServers config and mock .clinerules
+// Mock fs.readFile to return empty mcpServers config and mock rules files
jest.mock('fs/promises', () => ({
...jest.requireActual('fs/promises'),
readFile: jest.fn().mockImplementation(async (path: string) => {
if (path.endsWith('mcpSettings.json')) {
return '{"mcpServers": {}}'
}
+ if (path.endsWith('.clinerules-code')) {
+ return '# Code Mode Rules\n1. Code specific rule'
+ }
+ if (path.endsWith('.clinerules-ask')) {
+ return '# Ask Mode Rules\n1. Ask specific rule'
+ }
+ if (path.endsWith('.clinerules-architect')) {
+ return '# Architect Mode Rules\n1. Architect specific rule'
+ }
if (path.endsWith('.clinerules')) {
return '# Test Rules\n1. First rule\n2. Second rule'
}
@@ -159,42 +169,149 @@ describe('addCustomInstructions', () => {
jest.clearAllMocks()
})
- it('should include preferred language when provided', async () => {
- const result = await addCustomInstructions(
- '',
+ it('should prioritize mode-specific rules for code mode', async () => {
+ const instructions = await addCustomInstructions(
+ {},
'/test/path',
- 'Spanish'
+ codeMode
+ )
+ expect(instructions).toMatchSnapshot()
+ })
+
+ it('should prioritize mode-specific rules for ask mode', async () => {
+ const instructions = await addCustomInstructions(
+ {},
+ '/test/path',
+ askMode
+ )
+ expect(instructions).toMatchSnapshot()
+ })
+
+ it('should prioritize mode-specific rules for architect mode', async () => {
+ const instructions = await addCustomInstructions(
+ {},
+ '/test/path',
+ architectMode
)
- expect(result).toMatchSnapshot()
+ expect(instructions).toMatchSnapshot()
+ })
+
+ it('should fall back to generic rules when mode-specific rules not found', async () => {
+ // Mock readFile to return ENOENT for mode-specific file
+ const mockReadFile = jest.fn().mockImplementation(async (path: string) => {
+ if (path.endsWith('.clinerules-code')) {
+ const error = new Error('ENOENT') as NodeJS.ErrnoException
+ error.code = 'ENOENT'
+ throw error
+ }
+ if (path.endsWith('.clinerules')) {
+ return '# Test Rules\n1. First rule\n2. Second rule'
+ }
+ return ''
+ })
+ jest.spyOn(fs, 'readFile').mockImplementation(mockReadFile)
+
+ const instructions = await addCustomInstructions(
+ {},
+ '/test/path',
+ codeMode
+ )
+
+ expect(instructions).toMatchSnapshot()
+ })
+
+ it('should include preferred language when provided', async () => {
+ const instructions = await addCustomInstructions(
+ { preferredLanguage: 'Spanish' },
+ '/test/path',
+ codeMode
+ )
+
+ expect(instructions).toMatchSnapshot()
})
it('should include custom instructions when provided', async () => {
- const result = await addCustomInstructions(
- 'Custom test instructions',
+ const instructions = await addCustomInstructions(
+ { customInstructions: 'Custom test instructions' },
'/test/path'
)
- expect(result).toMatchSnapshot()
- })
-
- it('should include rules from .clinerules', async () => {
- const result = await addCustomInstructions(
- '',
- '/test/path'
- )
-
- expect(result).toMatchSnapshot()
+ expect(instructions).toMatchSnapshot()
})
it('should combine all custom instructions', async () => {
- const result = await addCustomInstructions(
- 'Custom test instructions',
+ const instructions = await addCustomInstructions(
+ {
+ customInstructions: 'Custom test instructions',
+ preferredLanguage: 'French'
+ },
'/test/path',
- 'French'
+ codeMode
+ )
+ expect(instructions).toMatchSnapshot()
+ })
+
+ it('should handle undefined mode-specific instructions', async () => {
+ const instructions = await addCustomInstructions(
+ {},
+ '/test/path'
)
- expect(result).toMatchSnapshot()
+ expect(instructions).toMatchSnapshot()
+ })
+
+ it('should trim mode-specific instructions', async () => {
+ const instructions = await addCustomInstructions(
+ { customInstructions: ' Custom mode instructions ' },
+ '/test/path'
+ )
+
+ expect(instructions).toMatchSnapshot()
+ })
+
+ it('should handle empty mode-specific instructions', async () => {
+ const instructions = await addCustomInstructions(
+ { customInstructions: '' },
+ '/test/path'
+ )
+
+ expect(instructions).toMatchSnapshot()
+ })
+
+ it('should combine global and mode-specific instructions', async () => {
+ const instructions = await addCustomInstructions(
+ {
+ customInstructions: 'Global instructions',
+ customPrompts: {
+ code: { customInstructions: 'Mode-specific instructions' }
+ }
+ },
+ '/test/path',
+ codeMode
+ )
+
+ expect(instructions).toMatchSnapshot()
+ })
+
+ it('should prioritize mode-specific instructions after global ones', async () => {
+ const instructions = await addCustomInstructions(
+ {
+ customInstructions: 'First instruction',
+ customPrompts: {
+ code: { customInstructions: 'Second instruction' }
+ }
+ },
+ '/test/path',
+ codeMode
+ )
+
+ const instructionParts = instructions.split('\n\n')
+ const globalIndex = instructionParts.findIndex(part => part.includes('First instruction'))
+ const modeSpecificIndex = instructionParts.findIndex(part => part.includes('Second instruction'))
+
+ expect(globalIndex).toBeLessThan(modeSpecificIndex)
+ expect(instructions).toMatchSnapshot()
})
afterAll(() => {
diff --git a/src/core/prompts/ask.ts b/src/core/prompts/ask.ts
index bc86d3e..59c9b29 100644
--- a/src/core/prompts/ask.ts
+++ b/src/core/prompts/ask.ts
@@ -4,7 +4,6 @@ import {
getRulesSection,
getSystemInfoSection,
getObjectiveSection,
- addCustomInstructions,
getSharedToolUseSection,
getMcpServersSection,
getToolUseGuidelinesSection,
diff --git a/src/core/prompts/code.ts b/src/core/prompts/code.ts
index 7d2a7f3..33efa5a 100644
--- a/src/core/prompts/code.ts
+++ b/src/core/prompts/code.ts
@@ -4,7 +4,6 @@ import {
getRulesSection,
getSystemInfoSection,
getObjectiveSection,
- addCustomInstructions,
getSharedToolUseSection,
getMcpServersSection,
getToolUseGuidelinesSection,
diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts
index d4c2c8e..09063b7 100644
--- a/src/core/prompts/system.ts
+++ b/src/core/prompts/system.ts
@@ -8,11 +8,26 @@ import { CustomPrompts } from "../../shared/modes"
import fs from 'fs/promises'
import path from 'path'
-async function loadRuleFiles(cwd: string): Promise {
- const ruleFiles = ['.clinerules', '.cursorrules', '.windsurfrules']
+async function loadRuleFiles(cwd: string, mode: Mode): Promise {
let combinedRules = ''
- for (const file of ruleFiles) {
+ // First try mode-specific rules
+ const modeSpecificFile = `.clinerules-${mode}`
+ try {
+ const content = await fs.readFile(path.join(cwd, modeSpecificFile), 'utf-8')
+ if (content.trim()) {
+ combinedRules += `\n# Rules from ${modeSpecificFile}:\n${content.trim()}\n`
+ }
+ } catch (err) {
+ // Silently skip if file doesn't exist
+ if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
+ throw err
+ }
+ }
+
+ // Then try generic rules files
+ const genericRuleFiles = ['.clinerules']
+ for (const file of genericRuleFiles) {
try {
const content = await fs.readFile(path.join(cwd, file), 'utf-8')
if (content.trim()) {
@@ -29,16 +44,30 @@ async function loadRuleFiles(cwd: string): Promise {
return combinedRules
}
-export async function addCustomInstructions(customInstructions: string, cwd: string, preferredLanguage?: string): Promise {
- const ruleFileContent = await loadRuleFiles(cwd)
+interface State {
+ customInstructions?: string;
+ customPrompts?: CustomPrompts;
+ preferredLanguage?: string;
+}
+
+export async function addCustomInstructions(
+ state: State,
+ cwd: string,
+ mode: Mode = codeMode
+): Promise {
+ const ruleFileContent = await loadRuleFiles(cwd, mode)
const allInstructions = []
- if (preferredLanguage) {
- allInstructions.push(`You should always speak and think in the ${preferredLanguage} language.`)
+ if (state.preferredLanguage) {
+ allInstructions.push(`You should always speak and think in the ${state.preferredLanguage} language.`)
}
-
- if (customInstructions.trim()) {
- allInstructions.push(customInstructions.trim())
+
+ if (state.customInstructions?.trim()) {
+ allInstructions.push(state.customInstructions.trim())
+ }
+
+ if (state.customPrompts?.[mode]?.customInstructions?.trim()) {
+ allInstructions.push(state.customPrompts[mode].customInstructions.trim())
}
if (ruleFileContent && ruleFileContent.trim()) {
@@ -59,11 +88,11 @@ ${joinedInstructions}`
}
export const SYSTEM_PROMPT = async (
- cwd: string,
- supportsComputerUse: boolean,
- mcpHub?: McpHub,
- diffStrategy?: DiffStrategy,
- browserViewportSize?: string,
+ cwd: string,
+ supportsComputerUse: boolean,
+ mcpHub?: McpHub,
+ diffStrategy?: DiffStrategy,
+ browserViewportSize?: string,
mode: Mode = codeMode,
customPrompts?: CustomPrompts,
) => {
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 069301b..4d8706b 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -246,15 +246,22 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.clearTask()
const {
apiConfiguration,
- customInstructions,
+ customPrompts,
diffEnabled,
- fuzzyMatchThreshold
+ fuzzyMatchThreshold,
+ mode,
+ customInstructions: globalInstructions,
} = await this.getState()
+ const modeInstructions = customPrompts?.[mode]?.customInstructions
+ const effectiveInstructions = [globalInstructions, modeInstructions]
+ .filter(Boolean)
+ .join('\n\n')
+
this.cline = new Cline(
this,
apiConfiguration,
- customInstructions,
+ effectiveInstructions,
diffEnabled,
fuzzyMatchThreshold,
task,
@@ -266,15 +273,22 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.clearTask()
const {
apiConfiguration,
- customInstructions,
+ customPrompts,
diffEnabled,
- fuzzyMatchThreshold
+ fuzzyMatchThreshold,
+ mode,
+ customInstructions: globalInstructions,
} = await this.getState()
+ const modeInstructions = customPrompts?.[mode]?.customInstructions
+ const effectiveInstructions = [globalInstructions, modeInstructions]
+ .filter(Boolean)
+ .join('\n\n')
+
this.cline = new Cline(
this,
apiConfiguration,
- customInstructions,
+ effectiveInstructions,
diffEnabled,
fuzzyMatchThreshold,
undefined,
@@ -379,6 +393,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
async (message: WebviewMessage) => {
switch (message.type) {
case "webviewDidLaunch":
+
this.postStateToWebview()
this.workspaceTracker?.initializeFilePaths() // don't await
getTheme().then((theme) =>
@@ -572,7 +587,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
openImage(message.text!)
break
case "openFile":
- openFile(message.text!)
+ openFile(message.text!, message.values as { create?: boolean; content?: string })
break
case "openMention":
openMention(message.text)
@@ -732,30 +747,28 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.postStateToWebview()
break
case "updateEnhancedPrompt":
- if (message.text !== undefined) {
- const existingPrompts = await this.getGlobalState("customPrompts") || {}
-
- const updatedPrompts = {
- ...existingPrompts,
- enhance: message.text
- }
-
- await this.updateGlobalState("customPrompts", updatedPrompts)
-
- // Get current state and explicitly include customPrompts
- const currentState = await this.getState()
-
- const stateWithPrompts = {
- ...currentState,
- customPrompts: updatedPrompts
- }
-
- // Post state with prompts
- this.view?.webview.postMessage({
- type: "state",
- state: stateWithPrompts
- })
+ const existingPrompts = await this.getGlobalState("customPrompts") || {}
+
+ const updatedPrompts = {
+ ...existingPrompts,
+ enhance: message.text
}
+
+ await this.updateGlobalState("customPrompts", updatedPrompts)
+
+ // Get current state and explicitly include customPrompts
+ const currentState = await this.getState()
+
+ const stateWithPrompts = {
+ ...currentState,
+ customPrompts: updatedPrompts
+ }
+
+ // Post state with prompts
+ this.view?.webview.postMessage({
+ type: "state",
+ state: stateWithPrompts
+ })
break
case "updatePrompt":
if (message.promptMode && message.customPrompt !== undefined) {
@@ -893,15 +906,23 @@ export class ClineProvider implements vscode.WebviewViewProvider {
const { apiConfiguration, customPrompts, customInstructions, preferredLanguage, browserViewportSize, mcpEnabled } = await this.getState()
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) || ''
- const fullPrompt = await SYSTEM_PROMPT(
+ const mode = message.mode ?? codeMode
+ const instructions = await addCustomInstructions(
+ { customInstructions, customPrompts, preferredLanguage },
+ cwd,
+ mode
+ )
+
+ const systemPrompt = await SYSTEM_PROMPT(
cwd,
apiConfiguration.openRouterModelInfo?.supportsComputerUse ?? false,
mcpEnabled ? this.mcpHub : undefined,
undefined,
browserViewportSize ?? "900x600",
- message.mode,
+ mode,
customPrompts
- ) + await addCustomInstructions(customInstructions ?? '', cwd, preferredLanguage)
+ )
+ const fullPrompt = instructions ? `${systemPrompt}${instructions}` : systemPrompt
await this.postMessageToWebview({
type: "systemPrompt",
diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts
index 4a89eb3..ddcd7e1 100644
--- a/src/core/webview/__tests__/ClineProvider.test.ts
+++ b/src/core/webview/__tests__/ClineProvider.test.ts
@@ -130,19 +130,25 @@ jest.mock('../../../integrations/workspace/WorkspaceTracker', () => {
})
// Mock Cline
-jest.mock('../../Cline', () => {
- return {
- Cline: jest.fn().mockImplementation(() => ({
- abortTask: jest.fn(),
- handleWebviewAskResponse: jest.fn(),
- clineMessages: [],
- apiConversationHistory: [],
- overwriteClineMessages: jest.fn(),
- overwriteApiConversationHistory: jest.fn(),
- taskId: 'test-task-id'
- }))
- }
-})
+jest.mock('../../Cline', () => ({
+ Cline: jest.fn().mockImplementation((
+ provider,
+ apiConfiguration,
+ customInstructions,
+ diffEnabled,
+ fuzzyMatchThreshold,
+ task,
+ taskId
+ ) => ({
+ abortTask: jest.fn(),
+ handleWebviewAskResponse: jest.fn(),
+ clineMessages: [],
+ apiConversationHistory: [],
+ overwriteClineMessages: jest.fn(),
+ overwriteApiConversationHistory: jest.fn(),
+ taskId: taskId || 'test-task-id'
+ }))
+}))
// Mock extract-text
jest.mock('../../../integrations/misc/extract-text', () => ({
@@ -571,6 +577,82 @@ describe('ClineProvider', () => {
expect(state.customPrompts).toEqual({})
})
+ test('uses mode-specific custom instructions in Cline initialization', async () => {
+ // Setup mock state
+ const modeCustomInstructions = 'Code mode instructions';
+ const mockApiConfig = {
+ apiProvider: 'openrouter',
+ openRouterModelInfo: { supportsComputerUse: true }
+ };
+
+ jest.spyOn(provider, 'getState').mockResolvedValue({
+ apiConfiguration: mockApiConfig,
+ customPrompts: {
+ code: { customInstructions: modeCustomInstructions }
+ },
+ mode: 'code',
+ diffEnabled: true,
+ fuzzyMatchThreshold: 1.0
+ } as any);
+
+ // Reset Cline mock
+ const { Cline } = require('../../Cline');
+ (Cline as jest.Mock).mockClear();
+
+ // Initialize Cline with a task
+ await provider.initClineWithTask('Test task');
+
+ // Verify Cline was initialized with mode-specific instructions
+ expect(Cline).toHaveBeenCalledWith(
+ provider,
+ mockApiConfig,
+ modeCustomInstructions,
+ true,
+ 1.0,
+ 'Test task',
+ undefined
+ );
+ });
+ test('handles mode-specific custom instructions updates', async () => {
+ provider.resolveWebviewView(mockWebviewView)
+ const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
+
+ // Mock existing prompts
+ const existingPrompts = {
+ code: {
+ roleDefinition: 'Code role',
+ customInstructions: 'Old instructions'
+ }
+ }
+ mockContext.globalState.get = jest.fn((key: string) => {
+ if (key === 'customPrompts') {
+ return existingPrompts
+ }
+ return undefined
+ })
+
+ // Update custom instructions for code mode
+ await messageHandler({
+ type: 'updatePrompt',
+ promptMode: 'code',
+ customPrompt: {
+ roleDefinition: 'Code role',
+ customInstructions: 'New instructions'
+ }
+ })
+
+ // Verify state was updated correctly
+ expect(mockContext.globalState.update).toHaveBeenCalledWith(
+ 'customPrompts',
+ {
+ code: {
+ roleDefinition: 'Code role',
+ customInstructions: 'New instructions'
+ }
+ }
+ )
+ })
+
test('saves mode config when updating API configuration', async () => {
// Setup mock context with mode and config name
mockContext = {
@@ -848,5 +930,79 @@ describe('ClineProvider', () => {
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith('Failed to get system prompt')
})
+
+ test('uses mode-specific custom instructions in system prompt', async () => {
+ const systemPrompt = require('../../prompts/system')
+ const { addCustomInstructions } = systemPrompt
+
+ // Mock getState to return mode-specific custom instructions
+ jest.spyOn(provider, 'getState').mockResolvedValue({
+ apiConfiguration: {
+ apiProvider: 'openrouter',
+ openRouterModelInfo: { supportsComputerUse: true }
+ },
+ customPrompts: {
+ code: { customInstructions: 'Code mode specific instructions' }
+ },
+ mode: 'code',
+ mcpEnabled: false,
+ browserViewportSize: '900x600'
+ } as any)
+
+ const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
+ await messageHandler({ type: 'getSystemPrompt', mode: 'code' })
+
+ // Verify addCustomInstructions was called with mode-specific instructions
+ expect(addCustomInstructions).toHaveBeenCalledWith(
+ {
+ customInstructions: undefined,
+ customPrompts: {
+ code: { customInstructions: 'Code mode specific instructions' }
+ },
+ preferredLanguage: undefined
+ },
+ expect.any(String),
+ 'code'
+ )
+ })
+
+ test('uses correct mode-specific instructions when mode is specified', async () => {
+ const systemPrompt = require('../../prompts/system')
+ const { addCustomInstructions } = systemPrompt
+
+ // Mock getState to return instructions for multiple modes
+ jest.spyOn(provider, 'getState').mockResolvedValue({
+ apiConfiguration: {
+ apiProvider: 'openrouter',
+ openRouterModelInfo: { supportsComputerUse: true }
+ },
+ customPrompts: {
+ code: { customInstructions: 'Code mode instructions' },
+ architect: { customInstructions: 'Architect mode instructions' }
+ },
+ mode: 'code',
+ mcpEnabled: false,
+ browserViewportSize: '900x600'
+ } as any)
+
+ const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
+
+ // Request architect mode prompt
+ await messageHandler({ type: 'getSystemPrompt', mode: 'architect' })
+
+ // Verify architect mode instructions were used
+ expect(addCustomInstructions).toHaveBeenCalledWith(
+ {
+ customInstructions: undefined,
+ customPrompts: {
+ code: { customInstructions: 'Code mode instructions' },
+ architect: { customInstructions: 'Architect mode instructions' }
+ },
+ preferredLanguage: undefined
+ },
+ expect.any(String),
+ 'architect'
+ )
+ })
})
})
diff --git a/src/integrations/misc/open-file.ts b/src/integrations/misc/open-file.ts
index 8dc3029..08a3ce1 100644
--- a/src/integrations/misc/open-file.ts
+++ b/src/integrations/misc/open-file.ts
@@ -20,11 +20,41 @@ export async function openImage(dataUri: string) {
}
}
-export async function openFile(absolutePath: string) {
- try {
- const uri = vscode.Uri.file(absolutePath)
+interface OpenFileOptions {
+ create?: boolean;
+ content?: string;
+}
- // Check if the document is already open in a tab group that's not in the active editor's column. If it is, then close it (if not dirty) so that we don't duplicate tabs
+export async function openFile(filePath: string, options: OpenFileOptions = {}) {
+ try {
+ // Get workspace root
+ const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
+ if (!workspaceRoot) {
+ throw new Error('No workspace root found')
+ }
+
+ // If path starts with ./, resolve it relative to workspace root
+ const fullPath = filePath.startsWith('./') ?
+ path.join(workspaceRoot, filePath.slice(2)) :
+ filePath
+
+ const uri = vscode.Uri.file(fullPath)
+
+ // Check if file exists
+ try {
+ await vscode.workspace.fs.stat(uri)
+ } catch {
+ // File doesn't exist
+ if (!options.create) {
+ throw new Error('File does not exist')
+ }
+
+ // Create with provided content or empty string
+ const content = options.content || ''
+ await vscode.workspace.fs.writeFile(uri, Buffer.from(content, 'utf8'))
+ }
+
+ // Check if the document is already open in a tab group that's not in the active editor's column
try {
for (const group of vscode.window.tabGroups.all) {
const existingTab = group.tabs.find(
@@ -47,6 +77,10 @@ export async function openFile(absolutePath: string) {
const document = await vscode.workspace.openTextDocument(uri)
await vscode.window.showTextDocument(document, { preview: false })
} catch (error) {
- vscode.window.showErrorMessage(`Could not open file!`)
+ if (error instanceof Error) {
+ vscode.window.showErrorMessage(`Could not open file: ${error.message}`)
+ } else {
+ vscode.window.showErrorMessage(`Could not open file!`)
+ }
}
}
diff --git a/src/shared/modes.ts b/src/shared/modes.ts
index ad0d462..dc3ad6a 100644
--- a/src/shared/modes.ts
+++ b/src/shared/modes.ts
@@ -6,6 +6,7 @@ export type Mode = typeof codeMode | typeof architectMode | typeof askMode;
export type PromptComponent = {
roleDefinition?: string;
+ customInstructions?: string;
}
export type CustomPrompts = {
diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx
index 6b7bdb4..4ae7659 100644
--- a/webview-ui/src/components/prompts/PromptsView.tsx
+++ b/webview-ui/src/components/prompts/PromptsView.tsx
@@ -1,4 +1,4 @@
-import { VSCodeButton, VSCodeTextArea, VSCodeDropdown, VSCodeOption, VSCodeDivider } from "@vscode/webview-ui-toolkit/react"
+import { VSCodeButton, VSCodeTextArea, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { defaultPrompts, askMode, codeMode, architectMode, Mode, PromptComponent } from "../../../../src/shared/modes"
import { vscode } from "../../utils/vscode"
@@ -15,7 +15,14 @@ const AGENT_MODES = [
] as const
const PromptsView = ({ onDone }: PromptsViewProps) => {
- const { customPrompts, listApiConfigMeta, enhancementApiConfigId, setEnhancementApiConfigId, mode } = useExtensionState()
+ const {
+ customPrompts,
+ listApiConfigMeta,
+ enhancementApiConfigId,
+ setEnhancementApiConfigId,
+ mode,
+ customInstructions
+ } = useExtensionState()
const [testPrompt, setTestPrompt] = useState('')
const [isEnhancing, setIsEnhancing] = useState(false)
const [activeTab, setActiveTab] = useState(mode)
@@ -47,10 +54,20 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
type AgentMode = typeof codeMode | typeof architectMode | typeof askMode
const updateAgentPrompt = (mode: AgentMode, promptData: PromptComponent) => {
+ const updatedPrompt = {
+ ...customPrompts?.[mode],
+ ...promptData
+ }
+
+ // Only include properties that differ from defaults
+ if (updatedPrompt.roleDefinition === defaultPrompts[mode].roleDefinition) {
+ delete updatedPrompt.roleDefinition
+ }
+
vscode.postMessage({
type: "updatePrompt",
promptMode: mode,
- customPrompt: promptData
+ customPrompt: updatedPrompt
})
}
@@ -68,11 +85,17 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
const handleEnhancePromptChange = (e: Event | React.FormEvent) => {
const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value
- updateEnhancePrompt(value.trim() || undefined)
+ const trimmedValue = value.trim()
+ if (trimmedValue !== defaultPrompts.enhance) {
+ updateEnhancePrompt(trimmedValue || undefined)
+ }
}
const handleAgentReset = (mode: AgentMode) => {
- updateAgentPrompt(mode, { roleDefinition: undefined })
+ updateAgentPrompt(mode, {
+ ...customPrompts?.[mode],
+ roleDefinition: undefined
+ })
}
const handleEnhanceReset = () => {
@@ -120,71 +143,156 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
-
Agent Modes
-
-
- Customize Cline's prompt in each mode. The rest of the system prompt will be automatically appended. Click the button to preview the full prompt. Leave empty or click the reset button to use the default.
-
+ Add behavioral guidelines specific to {activeTab} mode. These instructions enhance the base behaviors defined above.
+
+ {
+ const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value
+ updateAgentPrompt(activeTab, {
+ ...customPrompts?.[activeTab],
+ customInstructions: value.trim() || undefined
+ })
+ }}
+ rows={4}
+ resize="vertical"
+ style={{ width: "100%" }}
+ data-testid={`${activeTab}-custom-instructions-textarea`}
+ />
+
+ Custom instructions specific to {activeTab} mode can also be loaded from {
+ // First create/update the file with current custom instructions
+ const defaultContent = `# ${activeTab} Mode Rules\n\nAdd mode-specific rules and guidelines here.`
+ vscode.postMessage({
+ type: "updatePrompt",
+ promptMode: activeTab,
+ customPrompt: {
+ ...customPrompts?.[activeTab],
+ customInstructions: customPrompts?.[activeTab]?.customInstructions || defaultContent
+ }
+ })
+ // Then open the file
+ vscode.postMessage({
+ type: "openFile",
+ text: `./.clinerules-${activeTab}`,
+ values: {
+ create: true,
+ content: "",
+ }
+ })
+ }}
+ >.clinerules-{activeTab} in your workspace.
+
+
{
Prompt Enhancement
+
+ Use prompt enhancement to get tailored suggestions or improvements for your inputs. This ensures Cline understands your intent and provides the best possible responses.
+
- These instructions are added to the end of the system prompt sent with every request. Custom instructions set in .clinerules and .cursorrules in the working directory are also included.
-
+ These instructions are added to the end of the system prompt sent with every request. Custom instructions set in .clinerules in the working directory are also included. For mode-specific instructions, use the Prompts tab in the top menu.
+
+
From 1f17f72d449221f17c09a8619186cf2b02727a51 Mon Sep 17 00:00:00 2001
From: Matt Rubens
Date: Tue, 14 Jan 2025 15:23:45 -0500
Subject: [PATCH 07/15] Release
---
.changeset/fifty-lemons-double.md | 5 +++++
README.md | 19 +++++++++++++++++--
2 files changed, 22 insertions(+), 2 deletions(-)
create mode 100644 .changeset/fifty-lemons-double.md
diff --git a/.changeset/fifty-lemons-double.md b/.changeset/fifty-lemons-double.md
new file mode 100644
index 0000000..d84b2c7
--- /dev/null
+++ b/.changeset/fifty-lemons-double.md
@@ -0,0 +1,5 @@
+---
+"roo-cline": minor
+---
+
+3.1
diff --git a/README.md b/README.md
index 67efe0b..e5f38a4 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,23 @@
-# Roo-Cline
+# Roo Cline
A fork of Cline, an autonomous coding agent, with some additional experimental features. It’s been mainly writing itself recently, with a light touch of human guidance here and there.
-## New in 3.0 - chat modes!
+## New in 3.1: Chat Mode Prompt Customization & Prompt Enhancements
+
+Hot off the heels of **v3.0** introducing Code, Architect, and Ask chat modes, one of the most requested features has arrived: **customizable prompts for each mode**! 🎉
+
+You can now tailor the **role definition** and **custom instructions** for every chat mode to perfectly fit your workflow. Want to adjust Architect mode to focus more on system scalability? Or tweak Ask mode for deeper research queries? Done. Plus, you can define these via **mode-specific `.clinerules-[mode]` files**. You’ll find all of this in the new **Prompts** tab in the top menu.
+
+The second big feature in this release is a complete revamp of **prompt enhancements**. This feature helps you craft messages to get even better results from Cline. Here’s what’s new:
+- Works with **any provider** and API configuration, not just OpenRouter.
+- Fully customizable prompts to match your unique needs.
+- Same simple workflow: just hit the ✨ **Enhance Prompt** button in the chat input to try it out.
+
+Whether you’re using GPT-4, other APIs, or switching configurations, this gives you total control over how your prompts are optimized.
+
+As always, we’d love to hear your thoughts and ideas! What features do you want to see in **v3.2**? Drop by https://www.reddit.com/r/roocline and join the discussion - we're building Roo Cline together. 🚀
+
+## New in 3.0 - Chat Modes!
You can now choose between different prompts for Roo Cline to better suit your workflow. Here’s what’s available:
From f2d25dd5a5a4106d85169401a219fe090896278c Mon Sep 17 00:00:00 2001
From: Matt Rubens
Date: Tue, 14 Jan 2025 16:01:59 -0500
Subject: [PATCH 08/15] Fix bug with clearing custom instructions
---
.changeset/kind-nails-jog.md | 5 ++++
.../src/components/prompts/PromptsView.tsx | 4 ++-
.../prompts/__tests__/PromptsView.test.tsx | 28 ++++++++++++++++++-
3 files changed, 35 insertions(+), 2 deletions(-)
create mode 100644 .changeset/kind-nails-jog.md
diff --git a/.changeset/kind-nails-jog.md b/.changeset/kind-nails-jog.md
new file mode 100644
index 0000000..252f9d3
--- /dev/null
+++ b/.changeset/kind-nails-jog.md
@@ -0,0 +1,5 @@
+---
+"roo-cline": patch
+---
+
+Fix bug with clearing custom instructions
diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx
index 4ae7659..85e9038 100644
--- a/webview-ui/src/components/prompts/PromptsView.tsx
+++ b/webview-ui/src/components/prompts/PromptsView.tsx
@@ -21,7 +21,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
enhancementApiConfigId,
setEnhancementApiConfigId,
mode,
- customInstructions
+ customInstructions,
+ setCustomInstructions
} = useExtensionState()
const [testPrompt, setTestPrompt] = useState('')
const [isEnhancing, setIsEnhancing] = useState(false)
@@ -152,6 +153,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
value={customInstructions ?? ''}
onChange={(e) => {
const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value
+ setCustomInstructions(value || undefined)
vscode.postMessage({
type: "customInstructions",
text: value.trim() || undefined
diff --git a/webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx b/webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx
index 311702f..2ccc7f1 100644
--- a/webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx
+++ b/webview-ui/src/components/prompts/__tests__/PromptsView.test.tsx
@@ -19,7 +19,9 @@ const mockExtensionState = {
],
enhancementApiConfigId: '',
setEnhancementApiConfigId: jest.fn(),
- mode: 'code'
+ mode: 'code',
+ customInstructions: 'Initial instructions',
+ setCustomInstructions: jest.fn()
}
const renderPromptsView = (props = {}) => {
@@ -131,4 +133,28 @@ describe('PromptsView', () => {
text: 'config1'
})
})
+
+ it('handles clearing custom instructions correctly', async () => {
+ const setCustomInstructions = jest.fn()
+ renderPromptsView({
+ ...mockExtensionState,
+ customInstructions: 'Initial instructions',
+ setCustomInstructions
+ })
+
+ const textarea = screen.getByTestId('global-custom-instructions-textarea')
+ const changeEvent = new CustomEvent('change', {
+ detail: { target: { value: '' } }
+ })
+ Object.defineProperty(changeEvent, 'target', {
+ value: { value: '' }
+ })
+ await fireEvent(textarea, changeEvent)
+
+ expect(setCustomInstructions).toHaveBeenCalledWith(undefined)
+ expect(vscode.postMessage).toHaveBeenCalledWith({
+ type: 'customInstructions',
+ text: undefined
+ })
+ })
})
\ No newline at end of file
From a128c3720dbfcef1b7b9247b0342569ddf0af9af Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Tue, 14 Jan 2025 21:13:33 +0000
Subject: [PATCH 09/15] changeset version bump
---
.changeset/fifty-lemons-double.md | 5 -----
.changeset/kind-nails-jog.md | 5 -----
.changeset/real-rockets-sort.md | 5 -----
CHANGELOG.md | 11 +++++++++++
package-lock.json | 4 ++--
package.json | 2 +-
6 files changed, 14 insertions(+), 18 deletions(-)
delete mode 100644 .changeset/fifty-lemons-double.md
delete mode 100644 .changeset/kind-nails-jog.md
delete mode 100644 .changeset/real-rockets-sort.md
diff --git a/.changeset/fifty-lemons-double.md b/.changeset/fifty-lemons-double.md
deleted file mode 100644
index d84b2c7..0000000
--- a/.changeset/fifty-lemons-double.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"roo-cline": minor
----
-
-3.1
diff --git a/.changeset/kind-nails-jog.md b/.changeset/kind-nails-jog.md
deleted file mode 100644
index 252f9d3..0000000
--- a/.changeset/kind-nails-jog.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"roo-cline": patch
----
-
-Fix bug with clearing custom instructions
diff --git a/.changeset/real-rockets-sort.md b/.changeset/real-rockets-sort.md
deleted file mode 100644
index 3b24461..0000000
--- a/.changeset/real-rockets-sort.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"roo-cline": patch
----
-
-Add a button to copy markdown out of the chat
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 853801c..2c5da16 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
# Roo Cline Changelog
+## 3.1.0
+
+### Minor Changes
+
+- 3.1
+
+### Patch Changes
+
+- Fix bug with clearing custom instructions
+- Add a button to copy markdown out of the chat
+
## [3.0.3]
- Update required vscode engine to ^1.84.0 to match cline
diff --git a/package-lock.json b/package-lock.json
index f9e1cb6..52339e0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "roo-cline",
- "version": "3.0.3",
+ "version": "3.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "roo-cline",
- "version": "3.0.3",
+ "version": "3.1.0",
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.10.2",
"@anthropic-ai/sdk": "^0.26.0",
diff --git a/package.json b/package.json
index ef43cda..7219253 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"displayName": "Roo Cline",
"description": "A fork of Cline, an autonomous coding agent, with some added experimental configuration and automation features.",
"publisher": "RooVeterinaryInc",
- "version": "3.0.3",
+ "version": "3.1.0",
"icon": "assets/icons/rocket.png",
"galleryBanner": {
"color": "#617A91",
From ed14c4bbc50063d43b6e6d7fc00727f3efb5432c Mon Sep 17 00:00:00 2001
From: Matt Rubens
Date: Tue, 14 Jan 2025 16:15:31 -0500
Subject: [PATCH 10/15] Update CHANGELOG.md
---
CHANGELOG.md | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c5da16..db9aee9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,14 +1,8 @@
# Roo Cline Changelog
-## 3.1.0
+## [3.1.0]
-### Minor Changes
-
-- 3.1
-
-### Patch Changes
-
-- Fix bug with clearing custom instructions
+- You can now customize the role definition and instructions for each chat mode (Code, Architect, and Ask), either through the new Prompts tab in the top menu or mode-specific .clinerules-mode files. Prompt Enhancements have also been revamped: the "Enhance Prompt" button now works with any provider and API configuration, giving you the ability to craft messages with fully customizable prompts for even better results.
- Add a button to copy markdown out of the chat
## [3.0.3]
From 14d0b69c79760fb744a3968fba2de1fdf3a74f02 Mon Sep 17 00:00:00 2001
From: Joe Manley
Date: Tue, 14 Jan 2025 14:58:24 -0800
Subject: [PATCH 11/15] Fix icon color - light theme
---
webview-ui/src/components/settings/SettingsView.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx
index 17467ea..88731c6 100644
--- a/webview-ui/src/components/settings/SettingsView.tsx
+++ b/webview-ui/src/components/settings/SettingsView.tsx
@@ -556,7 +556,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
minWidth: '20px',
display: 'flex',
alignItems: 'center',
- justifyContent: 'center'
+ justifyContent: 'center',
+ color: 'var(--vscode-button-foreground)',
}}
onClick={() => {
const newCommands = (allowedCommands ?? []).filter((_, i) => i !== index)
From 9418a8fa42809eff7c982d8e7c8f8315da62d35c Mon Sep 17 00:00:00 2001
From: Joe Manley
Date: Tue, 14 Jan 2025 15:14:02 -0800
Subject: [PATCH 12/15] Add changeset
---
.changeset/sharp-hairs-itch.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/sharp-hairs-itch.md
diff --git a/.changeset/sharp-hairs-itch.md b/.changeset/sharp-hairs-itch.md
new file mode 100644
index 0000000..20a6e16
--- /dev/null
+++ b/.changeset/sharp-hairs-itch.md
@@ -0,0 +1,5 @@
+---
+"roo-cline": patch
+---
+
+Fix color for the light+ themes
From 84a0063b99813c6c64c0402e95c80bd15a945cc8 Mon Sep 17 00:00:00 2001
From: Matt Rubens
Date: Tue, 14 Jan 2025 23:45:43 -0500
Subject: [PATCH 13/15] Fix ChatTextArea layout
---
.changeset/fresh-jobs-repair.md | 5 +
.../src/components/chat/ChatTextArea.tsx | 546 +++++++++---------
.../src/components/common/CaretIcon.tsx | 16 +
3 files changed, 306 insertions(+), 261 deletions(-)
create mode 100644 .changeset/fresh-jobs-repair.md
create mode 100644 webview-ui/src/components/common/CaretIcon.tsx
diff --git a/.changeset/fresh-jobs-repair.md b/.changeset/fresh-jobs-repair.md
new file mode 100644
index 0000000..23bc920
--- /dev/null
+++ b/.changeset/fresh-jobs-repair.md
@@ -0,0 +1,5 @@
+---
+"roo-cline": patch
+---
+
+Fix chat text input layout issues
diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx
index c7cddb5..57f1ec7 100644
--- a/webview-ui/src/components/chat/ChatTextArea.tsx
+++ b/webview-ui/src/components/chat/ChatTextArea.tsx
@@ -15,6 +15,7 @@ import Thumbnails from "../common/Thumbnails"
import { vscode } from "../../utils/vscode"
import { WebviewMessage } from "../../../../src/shared/WebviewMessage"
import { Mode } from "../../../../src/core/prompts/types"
+import { CaretIcon } from "../common/CaretIcon"
interface ChatTextAreaProps {
inputValue: string
@@ -50,7 +51,6 @@ const ChatTextArea = forwardRef(
ref,
) => {
const { filePaths, currentApiConfigName, listApiConfigMeta } = useExtensionState()
- const [isTextAreaFocused, setIsTextAreaFocused] = useState(false)
const [gitCommits, setGitCommits] = useState([])
const [showDropdown, setShowDropdown] = useState(false)
@@ -376,7 +376,6 @@ const ChatTextArea = forwardRef(
if (!isMouseDownOnMenu) {
setShowContextMenu(false)
}
- setIsTextAreaFocused(false)
}, [isMouseDownOnMenu])
const handlePaste = useCallback(
@@ -494,65 +493,97 @@ const ChatTextArea = forwardRef(
[updateCursorPosition],
)
+ const selectStyle = {
+ fontSize: "11px",
+ cursor: textAreaDisabled ? "not-allowed" : "pointer",
+ backgroundColor: "transparent",
+ border: "none",
+ color: "var(--vscode-foreground)",
+ opacity: textAreaDisabled ? 0.5 : 0.8,
+ outline: "none",
+ paddingLeft: "20px",
+ paddingRight: "6px",
+ WebkitAppearance: "none" as const,
+ MozAppearance: "none" as const,
+ appearance: "none" as const
+ }
+
+ const caretContainerStyle = {
+ position: "absolute" as const,
+ left: 6,
+ top: "50%",
+ transform: "translateY(-45%)",
+ pointerEvents: "none" as const,
+ opacity: textAreaDisabled ? 0.5 : 0.8
+ }
+
return (
-