Skip to content

Commit 4c245e7

Browse files
committed
Merge branch 'main' into refactor-system-prompt
2 parents 83effee + 751afaa commit 4c245e7

File tree

25 files changed

+295
-23
lines changed

25 files changed

+295
-23
lines changed

.changeset/dark-swans-marry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Add a 'when to use' section to mode definitions

src/core/prompts/instructions/create-mode.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,31 @@ Workspace-specific modes override global modes with the same slug.
3737
- roleDefinition: Detailed description of the mode's capabilities
3838
- groups: Array of tool access groups
3939
40+
## Optional Fields (Highly Recommended)
41+
42+
- whenToUse: A clear description of when this mode should be selected and what types of tasks it's best suited for. This helps the Orchestrator mode make better decisions.
43+
- customInstructions: Additional instructions for how the mode should operate
44+
4045
## Example Structure
4146
4247
\`\`\`json
4348
{
44-
"customModes": [
45-
{
46-
"slug": "designer",
47-
"name": "Designer",
48-
"roleDefinition": "You are Roo, a UI/UX expert specializing in design systems and frontend development. Your expertise includes:\\n- Creating and maintaining design systems\\n- Implementing responsive and accessible web interfaces\\n- Working with CSS, HTML, and modern frontend frameworks",
49-
"groups": [
50-
"read", // read_file, fetch_instructions, search_files, list_files, list_code_definition_names
51-
"edit", // apply_diff, write_to_file (all files)
52-
// Restricted editing example:
53-
// ["edit", { "fileRegex": "\\.md$", "description": "Markdown files only" }],
54-
"browser", // browser_action
55-
"command" // execute_command
56-
],
57-
"customInstructions": "Optional additional instructions for the mode"
49+
"customModes": [
50+
{
51+
"slug": "designer", // Required: unique slug with lowercase letters, numbers, and hyphens
52+
"name": "Designer", // Required: mode display name
53+
"roleDefinition": "You are Roo, a UI/UX expert specializing in design systems and frontend development. Your expertise includes:\\n- Creating and maintaining design systems\\n- Implementing responsive and accessible web interfaces\\n- Working with CSS, HTML, and modern frontend frameworks\\n- Ensuring consistent user experiences across platforms", // Required: non-empty
54+
"whenToUse": "Use this mode when creating or modifying UI components, implementing design systems, or ensuring responsive web interfaces. This mode is especially effective with CSS, HTML, and modern frontend frameworks.", // Optional but recommended
55+
"groups": [ // Required: array of tool groups (can be empty)
56+
"read", // Read files group (read_file, fetch_instructions, search_files, list_files, list_code_definition_names)
57+
"edit", // Edit files group (apply_diff, write_to_file) - allows editing any file
58+
// Or with file restrictions:
59+
// ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], // Edit group that only allows editing markdown files
60+
"browser", // Browser group (browser_action)
61+
"command", // Command group (execute_command)
62+
"mcp" // MCP group (use_mcp_tool, access_mcp_resource)
63+
],
64+
"customInstructions": "Additional instructions for the Designer mode" // Optional
5865
}
5966
]
6067
}

src/core/prompts/sections/modes.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,24 @@ export async function getModesSection(context: vscode.ExtensionContext): Promise
1717
// Get all modes with their overrides from extension state
1818
const allModes = await getAllModesWithPrompts(context)
1919

20-
// Format each mode as a list item with name, slug, and brief description
21-
const modesList = allModes
22-
.map((mode: ModeConfig) => ` * "${mode.name}" mode (${mode.slug}) - ${mode.roleDefinition.split(".")[0]}`)
23-
.join("\n")
24-
2520
return `====
2621
2722
MODES
2823
2924
- These are the currently available modes:
30-
${modesList}
25+
${allModes
26+
.map((mode: ModeConfig) => {
27+
let description: string
28+
if (mode.whenToUse && mode.whenToUse.trim() !== "") {
29+
// Use whenToUse as the primary description, indenting subsequent lines for readability
30+
description = mode.whenToUse.replace(/\n/g, "\n ")
31+
} else {
32+
// Fallback to the first sentence of roleDefinition if whenToUse is not available
33+
description = mode.roleDefinition.split(".")[0]
34+
}
35+
return ` * "${mode.name}" mode (${mode.slug}) - ${description}`
36+
})
37+
.join("\n")}
3138
3239
If the user asks you to create or edit a new mode for this project, you should read the instructions by using the fetch_instructions tool, like this:
3340
<fetch_instructions>

src/exports/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ type GlobalSettings = {
138138
slug: string
139139
name: string
140140
roleDefinition: string
141+
whenToUse?: string | undefined
141142
customInstructions?: string | undefined
142143
groups: (
143144
| ("read" | "edit" | "browser" | "command" | "mcp" | "modes")
@@ -157,6 +158,7 @@ type GlobalSettings = {
157158
[x: string]:
158159
| {
159160
roleDefinition?: string | undefined
161+
whenToUse?: string | undefined
160162
customInstructions?: string | undefined
161163
}
162164
| undefined
@@ -834,6 +836,7 @@ type IpcMessage =
834836
slug: string
835837
name: string
836838
roleDefinition: string
839+
whenToUse?: string | undefined
837840
customInstructions?: string | undefined
838841
groups: (
839842
| ("read" | "edit" | "browser" | "command" | "mcp" | "modes")
@@ -853,6 +856,7 @@ type IpcMessage =
853856
[x: string]:
854857
| {
855858
roleDefinition?: string | undefined
859+
whenToUse?: string | undefined
856860
customInstructions?: string | undefined
857861
}
858862
| undefined
@@ -1296,6 +1300,7 @@ type TaskCommand =
12961300
slug: string
12971301
name: string
12981302
roleDefinition: string
1303+
whenToUse?: string | undefined
12991304
customInstructions?: string | undefined
13001305
groups: (
13011306
| ("read" | "edit" | "browser" | "command" | "mcp" | "modes")
@@ -1315,6 +1320,7 @@ type TaskCommand =
13151320
[x: string]:
13161321
| {
13171322
roleDefinition?: string | undefined
1323+
whenToUse?: string | undefined
13181324
customInstructions?: string | undefined
13191325
}
13201326
| undefined

src/schemas/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ export const modeConfigSchema = z.object({
217217
slug: z.string().regex(/^[a-zA-Z0-9-]+$/, "Slug must contain only letters numbers and dashes"),
218218
name: z.string().min(1, "Name is required"),
219219
roleDefinition: z.string().min(1, "Role definition is required"),
220+
whenToUse: z.string().optional(),
220221
customInstructions: z.string().optional(),
221222
groups: groupEntryArraySchema,
222223
source: z.enum(["global", "project"]).optional(),
@@ -256,6 +257,7 @@ export type CustomModesSettings = z.infer<typeof customModesSettingsSchema>
256257

257258
export const promptComponentSchema = z.object({
258259
roleDefinition: z.string().optional(),
260+
whenToUse: z.string().optional(),
259261
customInstructions: z.string().optional(),
260262
})
261263

src/shared/modes.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ export const defaultPrompts: Readonly<CustomModePrompts> = Object.freeze(
266266
mode.slug,
267267
{
268268
roleDefinition: mode.roleDefinition,
269+
whenToUse: mode.whenToUse,
269270
customInstructions: mode.customInstructions,
270271
},
271272
]),
@@ -281,6 +282,7 @@ export async function getAllModesWithPrompts(context: vscode.ExtensionContext):
281282
return allModes.map((mode) => ({
282283
...mode,
283284
roleDefinition: customModePrompts[mode.slug]?.roleDefinition ?? mode.roleDefinition,
285+
whenToUse: customModePrompts[mode.slug]?.whenToUse ?? mode.whenToUse,
284286
customInstructions: customModePrompts[mode.slug]?.customInstructions ?? mode.customInstructions,
285287
}))
286288
}
@@ -304,6 +306,7 @@ export async function getFullModeDetails(
304306

305307
// Get the base custom instructions
306308
const baseCustomInstructions = promptComponent?.customInstructions || baseMode.customInstructions || ""
309+
const baseWhenToUse = promptComponent?.whenToUse || baseMode.whenToUse || ""
307310

308311
// If we have cwd, load and combine all custom instructions
309312
let fullCustomInstructions = baseCustomInstructions
@@ -321,6 +324,7 @@ export async function getFullModeDetails(
321324
return {
322325
...baseMode,
323326
roleDefinition: promptComponent?.roleDefinition || baseMode.roleDefinition,
327+
whenToUse: baseWhenToUse,
324328
customInstructions: fullCustomInstructions,
325329
}
326330
}
@@ -335,6 +339,16 @@ export function getRoleDefinition(modeSlug: string, customModes?: ModeConfig[]):
335339
return mode.roleDefinition
336340
}
337341

342+
// Helper function to safely get whenToUse
343+
export function getWhenToUse(modeSlug: string, customModes?: ModeConfig[]): string {
344+
const mode = getModeBySlug(modeSlug, customModes)
345+
if (!mode) {
346+
console.warn(`No mode found for slug: ${modeSlug}`)
347+
return ""
348+
}
349+
return mode.whenToUse ?? ""
350+
}
351+
338352
// Helper function to safely get custom instructions
339353
export function getCustomInstructions(modeSlug: string, customModes?: ModeConfig[]): string {
340354
const mode = getModeBySlug(modeSlug, customModes)

webview-ui/src/components/prompts/PromptsView.tsx

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Mode,
88
PromptComponent,
99
getRoleDefinition,
10+
getWhenToUse,
1011
getCustomInstructions,
1112
getAllModes,
1213
ModeConfig,
@@ -106,6 +107,9 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
106107
if (updatedPrompt.roleDefinition === getRoleDefinition(mode)) {
107108
delete updatedPrompt.roleDefinition
108109
}
110+
if (updatedPrompt.whenToUse === getWhenToUse(mode)) {
111+
delete updatedPrompt.whenToUse
112+
}
109113

110114
vscode.postMessage({
111115
type: "updatePrompt",
@@ -195,6 +199,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
195199
const [newModeName, setNewModeName] = useState("")
196200
const [newModeSlug, setNewModeSlug] = useState("")
197201
const [newModeRoleDefinition, setNewModeRoleDefinition] = useState("")
202+
const [newModeWhenToUse, setNewModeWhenToUse] = useState("")
198203
const [newModeCustomInstructions, setNewModeCustomInstructions] = useState("")
199204
const [newModeGroups, setNewModeGroups] = useState<GroupEntry[]>(availableGroups)
200205
const [newModeSource, setNewModeSource] = useState<ModeSource>("global")
@@ -212,6 +217,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
212217
setNewModeSlug("")
213218
setNewModeGroups(availableGroups)
214219
setNewModeRoleDefinition("")
220+
setNewModeWhenToUse("")
215221
setNewModeCustomInstructions("")
216222
setNewModeSource("global")
217223
// Reset error states
@@ -258,6 +264,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
258264
slug: newModeSlug,
259265
name: newModeName,
260266
roleDefinition: newModeRoleDefinition.trim(),
267+
whenToUse: newModeWhenToUse.trim() || undefined,
261268
customInstructions: newModeCustomInstructions.trim() || undefined,
262269
groups: newModeGroups,
263270
source,
@@ -299,6 +306,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
299306
newModeName,
300307
newModeSlug,
301308
newModeRoleDefinition,
309+
newModeWhenToUse, // Add whenToUse dependency
302310
newModeCustomInstructions,
303311
newModeGroups,
304312
newModeSource,
@@ -396,7 +404,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
396404
})
397405
}
398406

399-
const handleAgentReset = (modeSlug: string, type: "roleDefinition" | "customInstructions") => {
407+
const handleAgentReset = (modeSlug: string, type: "roleDefinition" | "whenToUse" | "customInstructions") => {
400408
// Only reset for built-in modes
401409
const existingPrompt = customModePrompts?.[modeSlug] as PromptComponent
402410
const updatedPrompt = { ...existingPrompt }
@@ -699,6 +707,61 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
699707
data-testid={`${getCurrentMode()?.slug || "code"}-prompt-textarea`}
700708
/>
701709
</div>
710+
711+
{/* When to Use section */}
712+
<div className="mb-4">
713+
<div className="flex justify-between items-center mb-1">
714+
<div className="font-bold">{t("prompts:whenToUse.title")}</div>
715+
{!findModeBySlug(visualMode, customModes) && (
716+
<Button
717+
variant="ghost"
718+
size="icon"
719+
onClick={() => {
720+
const currentMode = getCurrentMode()
721+
if (currentMode?.slug) {
722+
handleAgentReset(currentMode.slug, "whenToUse")
723+
}
724+
}}
725+
title={t("prompts:whenToUse.resetToDefault")}
726+
data-testid="when-to-use-reset">
727+
<span className="codicon codicon-discard"></span>
728+
</Button>
729+
)}
730+
</div>
731+
<div className="text-sm text-vscode-descriptionForeground mb-2">
732+
{t("prompts:whenToUse.description")}
733+
</div>
734+
<VSCodeTextArea
735+
value={(() => {
736+
const customMode = findModeBySlug(visualMode, customModes)
737+
const prompt = customModePrompts?.[visualMode] as PromptComponent
738+
return customMode?.whenToUse ?? prompt?.whenToUse ?? getWhenToUse(visualMode)
739+
})()}
740+
onChange={(e) => {
741+
const value =
742+
(e as unknown as CustomEvent)?.detail?.target?.value ||
743+
((e as any).target as HTMLTextAreaElement).value
744+
const customMode = findModeBySlug(visualMode, customModes)
745+
if (customMode) {
746+
// For custom modes, update the JSON file
747+
updateCustomMode(visualMode, {
748+
...customMode,
749+
whenToUse: value.trim() || undefined,
750+
source: customMode.source || "global",
751+
})
752+
} else {
753+
// For built-in modes, update the prompts
754+
updateAgentPrompt(visualMode, {
755+
whenToUse: value.trim() || undefined,
756+
})
757+
}
758+
}}
759+
className="resize-y w-full"
760+
rows={3}
761+
data-testid={`${getCurrentMode()?.slug || "code"}-when-to-use-textarea`}
762+
/>
763+
</div>
764+
702765
{/* Mode settings */}
703766
<>
704767
<div className="mb-3">
@@ -1258,6 +1321,21 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
12581321
</div>
12591322
)}
12601323
</div>
1324+
1325+
<div className="mb-4">
1326+
<div className="font-bold mb-1">{t("prompts:createModeDialog.whenToUse.label")}</div>
1327+
<div className="text-[13px] text-vscode-descriptionForeground mb-2">
1328+
{t("prompts:createModeDialog.whenToUse.description")}
1329+
</div>
1330+
<VSCodeTextArea
1331+
value={newModeWhenToUse}
1332+
onChange={(e) => {
1333+
setNewModeWhenToUse((e.target as HTMLTextAreaElement).value)
1334+
}}
1335+
rows={3}
1336+
className="w-full resize-y"
1337+
/>
1338+
</div>
12611339
<div className="mb-4">
12621340
<div className="font-bold mb-1">{t("prompts:createModeDialog.tools.label")}</div>
12631341
<div className="text-[13px] text-vscode-descriptionForeground mb-2">

webview-ui/src/i18n/locales/ca/prompts.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
"resetToDefault": "Restablir a valors predeterminats",
3535
"description": "Definiu l'experiència i personalitat de Roo per a aquest mode. Aquesta descripció determina com Roo es presenta i aborda les tasques."
3636
},
37+
"whenToUse": {
38+
"title": "Quan utilitzar (opcional)",
39+
"description": "Descriviu quan s'hauria d'utilitzar aquest mode. Això ajuda l'Orchestrator a escollir el mode correcte per a una tasca.",
40+
"resetToDefault": "Restablir la descripció 'Quan utilitzar' a valors predeterminats"
41+
},
3742
"customInstructions": {
3843
"title": "Instruccions personalitzades específiques del mode (opcional)",
3944
"resetToDefault": "Restablir a valors predeterminats",
@@ -131,6 +136,10 @@
131136
"label": "Definició de rol",
132137
"description": "Definiu l'experiència i personalitat de Roo per a aquest mode."
133138
},
139+
"whenToUse": {
140+
"label": "Quan utilitzar (opcional)",
141+
"description": "Proporcioneu una descripció clara de quan aquest mode és més efectiu i per a quins tipus de tasques excel·leix."
142+
},
134143
"tools": {
135144
"label": "Eines disponibles",
136145
"description": "Seleccioneu quines eines pot utilitzar aquest mode."

webview-ui/src/i18n/locales/de/prompts.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
"resetToDefault": "Auf Standardwerte zurücksetzen",
3535
"description": "Definiere Roos Expertise und Persönlichkeit für diesen Modus. Diese Beschreibung prägt, wie Roo sich präsentiert und an Aufgaben herangeht."
3636
},
37+
"whenToUse": {
38+
"title": "Wann zu verwenden (optional)",
39+
"description": "Beschreibe, wann dieser Modus verwendet werden sollte. Dies hilft dem Orchestrator, den richtigen Modus für eine Aufgabe auszuwählen.",
40+
"resetToDefault": "Beschreibung 'Wann zu verwenden' auf Standardwerte zurücksetzen"
41+
},
3742
"customInstructions": {
3843
"title": "Modusspezifische benutzerdefinierte Anweisungen (optional)",
3944
"resetToDefault": "Auf Standardwerte zurücksetzen",
@@ -131,6 +136,10 @@
131136
"label": "Rollendefinition",
132137
"description": "Definiere Roos Expertise und Persönlichkeit für diesen Modus."
133138
},
139+
"whenToUse": {
140+
"label": "Wann zu verwenden (optional)",
141+
"description": "Gib eine klare Beschreibung, wann dieser Modus am effektivsten ist und für welche Arten von Aufgaben er sich besonders eignet."
142+
},
134143
"tools": {
135144
"label": "Verfügbare Werkzeuge",
136145
"description": "Wähle, welche Werkzeuge dieser Modus verwenden kann."

0 commit comments

Comments
 (0)