mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Custom modes
This commit is contained in:
@@ -12,7 +12,7 @@ interface AnnouncementProps {
|
||||
You must update the latestAnnouncementId in ClineProvider for new announcements to show to users. This new id will be compared with whats in state for the 'last announcement shown', and if it's different then the announcement will render. As soon as an announcement is shown, the id will be updated in state. This ensures that announcements are not shown more than once, even if the user doesn't close it themselves.
|
||||
*/
|
||||
const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
|
||||
const minorVersion = version.split(".").slice(0, 2).join(".") // 2.0.0 -> 2.0
|
||||
// const minorVersion = version.split(".").slice(0, 2).join(".") // 2.0.0 -> 2.0
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -29,42 +29,36 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
|
||||
style={{ position: "absolute", top: "8px", right: "8px" }}>
|
||||
<span className="codicon codicon-close"></span>
|
||||
</VSCodeButton>
|
||||
<h2 style={{ margin: "0 0 8px" }}>
|
||||
🎉{" "}Introducing Roo Cline v{minorVersion}
|
||||
</h2>
|
||||
<h2 style={{ margin: "0 0 8px" }}>🎉{" "}Introducing Roo Code 4.0</h2>
|
||||
|
||||
<h3 style={{ margin: "0 0 8px" }}>Agent Modes Customization</h3>
|
||||
<p style={{ margin: "5px 0px" }}>
|
||||
Click the new <span className="codicon codicon-notebook" style={{ fontSize: "10px" }}></span> icon in
|
||||
the menu bar to open the Prompts Settings and customize Agent Modes for new levels of productivity.
|
||||
<ul style={{ margin: "4px 0 6px 20px", padding: 0 }}>
|
||||
<li>Tailor how Roo Cline behaves in different modes: Code, Architect, and Ask.</li>
|
||||
<li>Preview and verify your changes using the Preview System Prompt button.</li>
|
||||
</ul>
|
||||
Our biggest update yet is here - we're officially changing our name from "Roo Cline" to "Roo Code"!
|
||||
After growing beyond 50,000 installations, we're ready to chart our own course. Our heartfelt thanks to
|
||||
everyone in the Cline community who helped us reach this milestone.
|
||||
</p>
|
||||
|
||||
<h3 style={{ margin: "0 0 8px" }}>Prompt Enhancement Configuration</h3>
|
||||
<h3 style={{ margin: "12px 0 8px" }}>Custom Modes: Celebrating Our New Identity</h3>
|
||||
<p style={{ margin: "5px 0px" }}>
|
||||
Now available for all providers! Access it directly in the chat box by clicking the{" "}
|
||||
<span className="codicon codicon-sparkle" style={{ fontSize: "10px" }}></span> sparkle icon next to the
|
||||
input field. From there, you can customize the enhancement logic and provider to best suit your
|
||||
workflow.
|
||||
To mark this new chapter, we're introducing the power to shape Roo Code into any role you need! Create
|
||||
specialized personas and create an entire team of agents with deeply customized prompts:
|
||||
<ul style={{ margin: "4px 0 6px 20px", padding: 0 }}>
|
||||
<li>Customize how prompts are enhanced for better results in your workflow.</li>
|
||||
<li>
|
||||
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.
|
||||
</li>
|
||||
<li>Test your changes instantly with the Preview Prompt Enhancement tool.</li>
|
||||
<li>QA Engineers who write thorough test cases and catch edge cases</li>
|
||||
<li>Product Managers who excel at user stories and feature prioritization</li>
|
||||
<li>UI/UX Designers who craft beautiful, accessible interfaces</li>
|
||||
<li>Code Reviewers who ensure quality and maintainability</li>
|
||||
</ul>
|
||||
Just click the <span className="codicon codicon-notebook" style={{ fontSize: "10px" }}></span> icon to
|
||||
get started with Custom Modes!
|
||||
</p>
|
||||
|
||||
<h3 style={{ margin: "12px 0 8px" }}>Join Us for the Next Chapter</h3>
|
||||
<p style={{ margin: "5px 0px" }}>
|
||||
We're very excited to see what you build with this new feature! Join us at
|
||||
<VSCodeLink href="https://www.reddit.com/r/roocline" style={{ display: "inline" }}>
|
||||
reddit.com/r/roocline
|
||||
We can't wait to see how you'll push Roo Code's potential even further! Share your custom modes and join
|
||||
the discussion at{" "}
|
||||
<VSCodeLink href="https://www.reddit.com/r/RooCode" style={{ display: "inline" }}>
|
||||
reddit.com/r/RooCode
|
||||
</VSCodeLink>
|
||||
to discuss and share feedback.
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -209,7 +209,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
fontSize: "12px",
|
||||
}}>
|
||||
Auto-approve allows Cline to perform actions without asking for permission. Only enable for
|
||||
Auto-approve allows Roo Code to perform actions without asking for permission. Only enable for
|
||||
actions you fully trust.
|
||||
</div>
|
||||
{actions.map((action) => (
|
||||
|
||||
@@ -242,7 +242,7 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
|
||||
style={{ color: "var(--vscode-foreground)", marginBottom: "-1.5px" }}></span>
|
||||
)}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
<>Cline wants to use the browser:</>
|
||||
<>Roo wants to use the browser:</>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -117,7 +117,7 @@ export const ChatRowContent = ({
|
||||
<span
|
||||
className="codicon codicon-error"
|
||||
style={{ color: errorColor, marginBottom: "-1.5px" }}></span>,
|
||||
<span style={{ color: errorColor, fontWeight: "bold" }}>Cline is having trouble...</span>,
|
||||
<span style={{ color: errorColor, fontWeight: "bold" }}>Roo is having trouble...</span>,
|
||||
]
|
||||
case "command":
|
||||
return [
|
||||
@@ -128,9 +128,7 @@ export const ChatRowContent = ({
|
||||
className="codicon codicon-terminal"
|
||||
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>
|
||||
),
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>
|
||||
Cline wants to execute this command:
|
||||
</span>,
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>Roo wants to execute this command:</span>,
|
||||
]
|
||||
case "use_mcp_server":
|
||||
const mcpServerUse = JSON.parse(message.text || "{}") as ClineAskUseMcpServer
|
||||
@@ -143,8 +141,8 @@ export const ChatRowContent = ({
|
||||
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>
|
||||
),
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>
|
||||
Cline wants to {mcpServerUse.type === "use_mcp_tool" ? "use a tool" : "access a resource"} on
|
||||
the <code>{mcpServerUse.serverName}</code> MCP server:
|
||||
Roo wants to {mcpServerUse.type === "use_mcp_tool" ? "use a tool" : "access a resource"} on the{" "}
|
||||
<code>{mcpServerUse.serverName}</code> MCP server:
|
||||
</span>,
|
||||
]
|
||||
case "completion_result":
|
||||
@@ -208,7 +206,7 @@ export const ChatRowContent = ({
|
||||
<span
|
||||
className="codicon codicon-question"
|
||||
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>,
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>Cline has a question:</span>,
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>Roo has a question:</span>,
|
||||
]
|
||||
default:
|
||||
return [null, null]
|
||||
@@ -250,7 +248,7 @@ export const ChatRowContent = ({
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon(tool.tool === "appliedDiff" ? "diff" : "edit")}
|
||||
<span style={{ fontWeight: "bold" }}>Cline wants to edit this file:</span>
|
||||
<span style={{ fontWeight: "bold" }}>Roo wants to edit this file:</span>
|
||||
</div>
|
||||
<CodeAccordian
|
||||
isLoading={message.partial}
|
||||
@@ -266,7 +264,7 @@ export const ChatRowContent = ({
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("new-file")}
|
||||
<span style={{ fontWeight: "bold" }}>Cline wants to create a new file:</span>
|
||||
<span style={{ fontWeight: "bold" }}>Roo wants to create a new file:</span>
|
||||
</div>
|
||||
<CodeAccordian
|
||||
isLoading={message.partial}
|
||||
@@ -283,7 +281,7 @@ export const ChatRowContent = ({
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("file-code")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{message.type === "ask" ? "Cline wants to read this file:" : "Cline read this file:"}
|
||||
{message.type === "ask" ? "Roo wants to read this file:" : "Roo read this file:"}
|
||||
</span>
|
||||
</div>
|
||||
{/* <CodeAccordian
|
||||
@@ -341,8 +339,8 @@ export const ChatRowContent = ({
|
||||
{toolIcon("folder-opened")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{message.type === "ask"
|
||||
? "Cline wants to view the top level files in this directory:"
|
||||
: "Cline viewed the top level files in this directory:"}
|
||||
? "Roo wants to view the top level files in this directory:"
|
||||
: "Roo viewed the top level files in this directory:"}
|
||||
</span>
|
||||
</div>
|
||||
<CodeAccordian
|
||||
@@ -361,8 +359,8 @@ export const ChatRowContent = ({
|
||||
{toolIcon("folder-opened")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{message.type === "ask"
|
||||
? "Cline wants to recursively view all files in this directory:"
|
||||
: "Cline recursively viewed all files in this directory:"}
|
||||
? "Roo wants to recursively view all files in this directory:"
|
||||
: "Roo recursively viewed all files in this directory:"}
|
||||
</span>
|
||||
</div>
|
||||
<CodeAccordian
|
||||
@@ -381,8 +379,8 @@ export const ChatRowContent = ({
|
||||
{toolIcon("file-code")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{message.type === "ask"
|
||||
? "Cline wants to view source code definition names used in this directory:"
|
||||
: "Cline viewed source code definition names used in this directory:"}
|
||||
? "Roo wants to view source code definition names used in this directory:"
|
||||
: "Roo viewed source code definition names used in this directory:"}
|
||||
</span>
|
||||
</div>
|
||||
<CodeAccordian
|
||||
@@ -401,11 +399,11 @@ export const ChatRowContent = ({
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
{message.type === "ask" ? (
|
||||
<>
|
||||
Cline wants to search this directory for <code>{tool.regex}</code>:
|
||||
Roo wants to search this directory for <code>{tool.regex}</code>:
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Cline searched this directory for <code>{tool.regex}</code>:
|
||||
Roo searched this directory for <code>{tool.regex}</code>:
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
@@ -428,9 +426,9 @@ export const ChatRowContent = ({
|
||||
// {isInspecting ? <ProgressIndicator /> : toolIcon("inspect")}
|
||||
// <span style={{ fontWeight: "bold" }}>
|
||||
// {message.type === "ask" ? (
|
||||
// <>Cline wants to inspect this website:</>
|
||||
// <>Roo wants to inspect this website:</>
|
||||
// ) : (
|
||||
// <>Cline is inspecting this website:</>
|
||||
// <>Roo is inspecting this website:</>
|
||||
// )}
|
||||
// </span>
|
||||
// </div>
|
||||
@@ -663,7 +661,7 @@ export const ChatRowContent = ({
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Cline won't be able to view the command's output. Please update VSCode (
|
||||
Roo won't be able to view the command's output. Please update VSCode (
|
||||
<code>CMD/CTRL + Shift + P</code> → "Update") and make sure you're using a supported
|
||||
shell: zsh, bash, fish, or PowerShell (<code>CMD/CTRL + Shift + P</code> →
|
||||
"Terminal: Select Default Profile").{" "}
|
||||
|
||||
@@ -14,7 +14,7 @@ import ContextMenu from "./ContextMenu"
|
||||
import Thumbnails from "../common/Thumbnails"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import { WebviewMessage } from "../../../../src/shared/WebviewMessage"
|
||||
import { Mode, modes } from "../../../../src/shared/modes"
|
||||
import { Mode, getAllModes } from "../../../../src/shared/modes"
|
||||
import { CaretIcon } from "../common/CaretIcon"
|
||||
|
||||
interface ChatTextAreaProps {
|
||||
@@ -50,7 +50,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { filePaths, currentApiConfigName, listApiConfigMeta } = useExtensionState()
|
||||
const { filePaths, currentApiConfigName, listApiConfigMeta, customModes } = useExtensionState()
|
||||
const [gitCommits, setGitCommits] = useState<any[]>([])
|
||||
const [showDropdown, setShowDropdown] = useState(false)
|
||||
|
||||
@@ -730,7 +730,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
minWidth: "70px",
|
||||
flex: "0 0 auto",
|
||||
}}>
|
||||
{modes.map((mode) => (
|
||||
{getAllModes(customModes).map((mode) => (
|
||||
<option
|
||||
key={mode.slug}
|
||||
value={mode.slug}
|
||||
|
||||
@@ -24,8 +24,8 @@ const McpEnabledToggle = () => {
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
When enabled, Cline will be able to interact with MCP servers for advanced functionality. If you're not
|
||||
using MCP, you can disable this to reduce Cline's token usage.
|
||||
When enabled, Roo will be able to interact with MCP servers for advanced functionality. If you're not
|
||||
using MCP, you can disable this to reduce Roo's token usage.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -115,12 +115,12 @@ const McpView = ({ onDone }: McpViewProps) => {
|
||||
Model Context Protocol
|
||||
</VSCodeLink>{" "}
|
||||
enables communication with locally running MCP servers that provide additional tools and resources
|
||||
to extend Cline's capabilities. You can use{" "}
|
||||
to extend Roo's capabilities. You can use{" "}
|
||||
<VSCodeLink href="https://github.com/modelcontextprotocol/servers" style={{ display: "inline" }}>
|
||||
community-made servers
|
||||
</VSCodeLink>{" "}
|
||||
or ask Cline to create new tools specific to your workflow (e.g., "add a tool that gets the latest
|
||||
npm docs").
|
||||
or ask Roo to create new tools specific to your workflow (e.g., "add a tool that gets the latest npm
|
||||
docs").
|
||||
</div>
|
||||
|
||||
<McpEnabledToggle />
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
import { VSCodeButton, VSCodeTextArea, VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"
|
||||
import React, { useState, useEffect, useMemo, useCallback } from "react"
|
||||
import {
|
||||
VSCodeButton,
|
||||
VSCodeTextArea,
|
||||
VSCodeDropdown,
|
||||
VSCodeOption,
|
||||
VSCodeTextField,
|
||||
VSCodeCheckbox,
|
||||
} from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { defaultPrompts, modes, Mode, PromptComponent, getRoleDefinition } from "../../../../src/shared/modes"
|
||||
import {
|
||||
Mode,
|
||||
PromptComponent,
|
||||
getRoleDefinition,
|
||||
getAllModes,
|
||||
ModeConfig,
|
||||
enhancePrompt,
|
||||
} from "../../../../src/shared/modes"
|
||||
import { TOOL_GROUPS, GROUP_DISPLAY_NAMES, ToolGroup } from "../../../../src/shared/tool-groups"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import React, { useState, useEffect } from "react"
|
||||
|
||||
// Get all available groups from GROUP_DISPLAY_NAMES
|
||||
const availableGroups = Object.keys(TOOL_GROUPS) as ToolGroup[]
|
||||
|
||||
type PromptsViewProps = {
|
||||
onDone: () => void
|
||||
}
|
||||
|
||||
const AGENT_MODES = modes.map((mode) => ({
|
||||
id: mode.slug,
|
||||
label: mode.name,
|
||||
}))
|
||||
|
||||
const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
const {
|
||||
customPrompts,
|
||||
@@ -24,13 +37,201 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
setCustomInstructions,
|
||||
preferredLanguage,
|
||||
setPreferredLanguage,
|
||||
customModes,
|
||||
} = useExtensionState()
|
||||
|
||||
// Memoize modes to preserve array order
|
||||
const modes = useMemo(() => getAllModes(customModes), [customModes])
|
||||
|
||||
const [testPrompt, setTestPrompt] = useState("")
|
||||
const [isEnhancing, setIsEnhancing] = useState(false)
|
||||
const [activeTab, setActiveTab] = useState<Mode>(mode)
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||
const [selectedPromptContent, setSelectedPromptContent] = useState("")
|
||||
const [selectedPromptTitle, setSelectedPromptTitle] = useState("")
|
||||
const [isToolsEditMode, setIsToolsEditMode] = useState(false)
|
||||
const [isCreateModeDialogOpen, setIsCreateModeDialogOpen] = useState(false)
|
||||
|
||||
// Direct update functions
|
||||
const updateAgentPrompt = useCallback(
|
||||
(mode: Mode, promptData: PromptComponent) => {
|
||||
const existingPrompt = customPrompts?.[mode]
|
||||
const updatedPrompt = { ...existingPrompt, ...promptData }
|
||||
|
||||
// Only include properties that differ from defaults
|
||||
if (updatedPrompt.roleDefinition === getRoleDefinition(mode)) {
|
||||
delete updatedPrompt.roleDefinition
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: "updatePrompt",
|
||||
promptMode: mode,
|
||||
customPrompt: updatedPrompt,
|
||||
})
|
||||
},
|
||||
[customPrompts],
|
||||
)
|
||||
|
||||
const updateCustomMode = useCallback((slug: string, modeConfig: ModeConfig) => {
|
||||
vscode.postMessage({
|
||||
type: "updateCustomMode",
|
||||
slug,
|
||||
modeConfig,
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Helper function to find a mode by slug
|
||||
const findModeBySlug = useCallback(
|
||||
(searchSlug: string, modes: readonly ModeConfig[] | undefined): ModeConfig | undefined => {
|
||||
if (!modes) return undefined
|
||||
const isModeWithSlug = (mode: ModeConfig): mode is ModeConfig => mode.slug === searchSlug
|
||||
return modes.find(isModeWithSlug)
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const switchMode = useCallback((slug: string) => {
|
||||
vscode.postMessage({
|
||||
type: "mode",
|
||||
text: slug,
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Handle mode switching with explicit state initialization
|
||||
const handleModeSwitch = useCallback(
|
||||
(modeConfig: ModeConfig) => {
|
||||
if (modeConfig.slug === mode) return // Prevent unnecessary updates
|
||||
|
||||
// First switch the mode
|
||||
switchMode(modeConfig.slug)
|
||||
|
||||
// Exit tools edit mode when switching modes
|
||||
setIsToolsEditMode(false)
|
||||
},
|
||||
[mode, switchMode, setIsToolsEditMode],
|
||||
)
|
||||
|
||||
// Helper function to get current mode's config
|
||||
const getCurrentMode = useCallback((): ModeConfig | undefined => {
|
||||
const findMode = (m: ModeConfig): boolean => m.slug === mode
|
||||
return customModes?.find(findMode) || modes.find(findMode)
|
||||
}, [mode, customModes, modes])
|
||||
|
||||
// Helper function to safely access mode properties
|
||||
const getModeProperty = <T extends keyof ModeConfig>(
|
||||
mode: ModeConfig | undefined,
|
||||
property: T,
|
||||
): ModeConfig[T] | undefined => {
|
||||
return mode?.[property]
|
||||
}
|
||||
|
||||
// State for create mode dialog
|
||||
const [newModeName, setNewModeName] = useState("")
|
||||
const [newModeSlug, setNewModeSlug] = useState("")
|
||||
const [newModeRoleDefinition, setNewModeRoleDefinition] = useState("")
|
||||
const [newModeCustomInstructions, setNewModeCustomInstructions] = useState("")
|
||||
const [newModeGroups, setNewModeGroups] = useState<readonly ToolGroup[]>(availableGroups)
|
||||
|
||||
// Reset form fields when dialog opens
|
||||
useEffect(() => {
|
||||
if (isCreateModeDialogOpen) {
|
||||
setNewModeGroups(availableGroups)
|
||||
setNewModeRoleDefinition("")
|
||||
setNewModeCustomInstructions("")
|
||||
}
|
||||
}, [isCreateModeDialogOpen])
|
||||
|
||||
// Helper function to generate a unique slug from a name
|
||||
const generateSlug = useCallback((name: string, attempt = 0): string => {
|
||||
const baseSlug = name
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
return attempt === 0 ? baseSlug : `${baseSlug}-${attempt}`
|
||||
}, [])
|
||||
|
||||
// Handler for name changes
|
||||
const handleNameChange = useCallback(
|
||||
(name: string) => {
|
||||
setNewModeName(name)
|
||||
setNewModeSlug(generateSlug(name))
|
||||
},
|
||||
[generateSlug],
|
||||
)
|
||||
|
||||
const handleCreateMode = useCallback(() => {
|
||||
if (!newModeName.trim() || !newModeSlug.trim()) return
|
||||
|
||||
const newMode: ModeConfig = {
|
||||
slug: newModeSlug,
|
||||
name: newModeName,
|
||||
roleDefinition: newModeRoleDefinition.trim() || "",
|
||||
customInstructions: newModeCustomInstructions.trim() || undefined,
|
||||
groups: newModeGroups,
|
||||
}
|
||||
updateCustomMode(newModeSlug, newMode)
|
||||
switchMode(newModeSlug)
|
||||
setIsCreateModeDialogOpen(false)
|
||||
setNewModeName("")
|
||||
setNewModeSlug("")
|
||||
setNewModeRoleDefinition("")
|
||||
setNewModeCustomInstructions("")
|
||||
setNewModeGroups(availableGroups)
|
||||
}, [
|
||||
newModeName,
|
||||
newModeSlug,
|
||||
newModeRoleDefinition,
|
||||
newModeCustomInstructions,
|
||||
newModeGroups,
|
||||
updateCustomMode,
|
||||
switchMode,
|
||||
])
|
||||
|
||||
const isNameOrSlugTaken = useCallback(
|
||||
(name: string, slug: string) => {
|
||||
return modes.some((m) => m.slug === slug || m.name === name)
|
||||
},
|
||||
[modes],
|
||||
)
|
||||
|
||||
const openCreateModeDialog = useCallback(() => {
|
||||
const baseNamePrefix = "New Custom Mode"
|
||||
// Find unique name and slug
|
||||
let attempt = 0
|
||||
let name = baseNamePrefix
|
||||
let slug = generateSlug(name)
|
||||
while (isNameOrSlugTaken(name, slug)) {
|
||||
attempt++
|
||||
name = `${baseNamePrefix} ${attempt + 1}`
|
||||
slug = generateSlug(name)
|
||||
}
|
||||
setNewModeName(name)
|
||||
setNewModeSlug(slug)
|
||||
setIsCreateModeDialogOpen(true)
|
||||
}, [generateSlug, isNameOrSlugTaken])
|
||||
|
||||
// Handler for group checkbox changes
|
||||
const handleGroupChange = useCallback(
|
||||
(group: ToolGroup, isCustomMode: boolean, customMode: ModeConfig | undefined) =>
|
||||
(e: Event | React.FormEvent<HTMLElement>) => {
|
||||
if (!isCustomMode) return // Prevent changes to built-in modes
|
||||
const target = (e as CustomEvent)?.detail?.target || (e.target as HTMLInputElement)
|
||||
const checked = target.checked
|
||||
const oldGroups = customMode?.groups || []
|
||||
let newGroups: readonly ToolGroup[]
|
||||
if (checked) {
|
||||
newGroups = [...oldGroups, group]
|
||||
} else {
|
||||
newGroups = oldGroups.filter((g) => g !== group)
|
||||
}
|
||||
if (customMode) {
|
||||
updateCustomMode(customMode.slug, {
|
||||
...customMode,
|
||||
groups: newGroups,
|
||||
})
|
||||
}
|
||||
},
|
||||
[updateCustomMode],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (event: MessageEvent) => {
|
||||
@@ -53,24 +254,6 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
return () => window.removeEventListener("message", handler)
|
||||
}, [])
|
||||
|
||||
type AgentMode = string
|
||||
|
||||
const updateAgentPrompt = (mode: Mode, promptData: PromptComponent) => {
|
||||
const existingPrompt = customPrompts?.[mode]
|
||||
const updatedPrompt = typeof existingPrompt === "object" ? { ...existingPrompt, ...promptData } : promptData
|
||||
|
||||
// Only include properties that differ from defaults
|
||||
if (updatedPrompt.roleDefinition === getRoleDefinition(mode)) {
|
||||
delete updatedPrompt.roleDefinition
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: "updatePrompt",
|
||||
promptMode: mode,
|
||||
customPrompt: updatedPrompt,
|
||||
})
|
||||
}
|
||||
|
||||
const updateEnhancePrompt = (value: string | undefined) => {
|
||||
vscode.postMessage({
|
||||
type: "updateEnhancedPrompt",
|
||||
@@ -78,23 +261,19 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleAgentPromptChange = (mode: AgentMode, e: Event | React.FormEvent<HTMLElement>) => {
|
||||
const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value
|
||||
updateAgentPrompt(mode, { roleDefinition: value.trim() || undefined })
|
||||
}
|
||||
|
||||
const handleEnhancePromptChange = (e: Event | React.FormEvent<HTMLElement>) => {
|
||||
const handleEnhancePromptChange = (e: Event | React.FormEvent<HTMLElement>): void => {
|
||||
const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value
|
||||
const trimmedValue = value.trim()
|
||||
if (trimmedValue !== defaultPrompts.enhance) {
|
||||
updateEnhancePrompt(trimmedValue || undefined)
|
||||
if (trimmedValue !== enhancePrompt.default) {
|
||||
updateEnhancePrompt(trimmedValue || enhancePrompt.default)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAgentReset = (mode: AgentMode) => {
|
||||
const existingPrompt = customPrompts?.[mode]
|
||||
updateAgentPrompt(mode, {
|
||||
...(typeof existingPrompt === "object" ? existingPrompt : {}),
|
||||
const handleAgentReset = (modeSlug: string) => {
|
||||
// Only reset role definition for built-in modes
|
||||
const existingPrompt = customPrompts?.[modeSlug]
|
||||
updateAgentPrompt(modeSlug, {
|
||||
...existingPrompt,
|
||||
roleDefinition: undefined,
|
||||
})
|
||||
}
|
||||
@@ -103,15 +282,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
updateEnhancePrompt(undefined)
|
||||
}
|
||||
|
||||
const getAgentPromptValue = (mode: Mode): string => {
|
||||
const prompt = customPrompts?.[mode]
|
||||
return typeof prompt === "object" ? (prompt.roleDefinition ?? getRoleDefinition(mode)) : getRoleDefinition(mode)
|
||||
}
|
||||
|
||||
const getEnhancePromptValue = (): string => {
|
||||
const enhance = customPrompts?.enhance
|
||||
const defaultEnhance = typeof defaultPrompts.enhance === "string" ? defaultPrompts.enhance : ""
|
||||
return typeof enhance === "string" ? enhance : defaultEnhance
|
||||
return enhancePrompt.get(customPrompts)
|
||||
}
|
||||
|
||||
const handleTestEnhancement = () => {
|
||||
@@ -244,42 +416,185 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 20px 0" }}>Mode-Specific Prompts</h3>
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: "12px",
|
||||
}}>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>Mode-Specific Prompts</h3>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<VSCodeButton appearance="icon" onClick={openCreateModeDialog} title="Create new mode">
|
||||
<span className="codicon codicon-add"></span>
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
title="Edit modes configuration"
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "openFile",
|
||||
text: "settings/cline_custom_modes.json",
|
||||
})
|
||||
}}>
|
||||
<span className="codicon codicon-json"></span>
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "16px",
|
||||
alignItems: "center",
|
||||
marginBottom: "12px",
|
||||
}}>
|
||||
{AGENT_MODES.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
data-testid={`${tab.id}-tab`}
|
||||
data-active={activeTab === tab.id ? "true" : "false"}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
style={{
|
||||
padding: "4px 8px",
|
||||
border: "none",
|
||||
background: activeTab === tab.id ? "var(--vscode-button-background)" : "none",
|
||||
color:
|
||||
activeTab === tab.id
|
||||
? "var(--vscode-button-foreground)"
|
||||
: "var(--vscode-foreground)",
|
||||
cursor: "pointer",
|
||||
opacity: activeTab === tab.id ? 1 : 0.8,
|
||||
borderRadius: "3px",
|
||||
fontWeight: "bold",
|
||||
}}>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "12px",
|
||||
}}>
|
||||
Hit the + to create a new custom mode, or just ask Roo in chat to create one for you!
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "16px",
|
||||
alignItems: "center",
|
||||
marginBottom: "12px",
|
||||
overflowX: "auto",
|
||||
flexWrap: "nowrap",
|
||||
paddingBottom: "4px",
|
||||
paddingRight: "20px",
|
||||
}}>
|
||||
{modes.map((modeConfig) => {
|
||||
const isActive = mode === modeConfig.slug
|
||||
return (
|
||||
<button
|
||||
key={modeConfig.slug}
|
||||
data-testid={`${modeConfig.slug}-tab`}
|
||||
data-active={isActive ? "true" : "false"}
|
||||
onClick={() => handleModeSwitch(modeConfig)}
|
||||
style={{
|
||||
padding: "4px 8px",
|
||||
border: "none",
|
||||
background: isActive ? "var(--vscode-button-background)" : "none",
|
||||
color: isActive
|
||||
? "var(--vscode-button-foreground)"
|
||||
: "var(--vscode-foreground)",
|
||||
cursor: "pointer",
|
||||
opacity: isActive ? 1 : 0.8,
|
||||
borderRadius: "3px",
|
||||
fontWeight: "bold",
|
||||
}}>
|
||||
{modeConfig.name}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<div>
|
||||
{/* Only show name and delete for custom modes */}
|
||||
{mode && findModeBySlug(mode, customModes) && (
|
||||
<div style={{ display: "flex", gap: "12px", marginBottom: "16px" }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Name</div>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<VSCodeTextField
|
||||
value={getModeProperty(findModeBySlug(mode, customModes), "name") ?? ""}
|
||||
onChange={(e: Event | React.FormEvent<HTMLElement>) => {
|
||||
const target =
|
||||
(e as CustomEvent)?.detail?.target ||
|
||||
((e as any).target as HTMLInputElement)
|
||||
const customMode = findModeBySlug(mode, customModes)
|
||||
if (customMode) {
|
||||
updateCustomMode(mode, {
|
||||
...customMode,
|
||||
name: target.value,
|
||||
})
|
||||
}
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
title="Delete mode"
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "deleteCustomMode",
|
||||
slug: mode,
|
||||
})
|
||||
}}>
|
||||
<span className="codicon codicon-trash"></span>
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: "4px",
|
||||
}}>
|
||||
<div style={{ fontWeight: "bold" }}>Role Definition</div>
|
||||
{!findModeBySlug(mode, customModes) && (
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
onClick={() => {
|
||||
const currentMode = getCurrentMode()
|
||||
if (currentMode?.slug) {
|
||||
handleAgentReset(currentMode.slug)
|
||||
}
|
||||
}}
|
||||
title="Reset to default"
|
||||
data-testid="role-definition-reset">
|
||||
<span className="codicon codicon-discard"></span>
|
||||
</VSCodeButton>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
Define Roo's expertise and personality for this mode. This description shapes how Roo
|
||||
presents itself and approaches tasks.
|
||||
</div>
|
||||
<VSCodeTextArea
|
||||
value={(() => {
|
||||
const customMode = findModeBySlug(mode, customModes)
|
||||
const prompt = customPrompts?.[mode]
|
||||
return customMode?.roleDefinition ?? prompt?.roleDefinition ?? getRoleDefinition(mode)
|
||||
})()}
|
||||
onChange={(e) => {
|
||||
const value =
|
||||
(e as CustomEvent)?.detail?.target?.value ||
|
||||
((e as any).target as HTMLTextAreaElement).value
|
||||
const customMode = findModeBySlug(mode, customModes)
|
||||
if (customMode) {
|
||||
// For custom modes, update the JSON file
|
||||
updateCustomMode(mode, {
|
||||
...customMode,
|
||||
roleDefinition: value.trim() || "",
|
||||
})
|
||||
} else {
|
||||
// For built-in modes, update the prompts
|
||||
updateAgentPrompt(mode, {
|
||||
roleDefinition: value.trim() || undefined,
|
||||
})
|
||||
}
|
||||
}}
|
||||
rows={4}
|
||||
resize="vertical"
|
||||
style={{ width: "100%" }}
|
||||
data-testid={`${getCurrentMode()?.slug || "code"}-prompt-textarea`}
|
||||
/>
|
||||
</div>
|
||||
{/* Mode settings */}
|
||||
<>
|
||||
{/* Show tools for all modes */}
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
@@ -287,34 +602,72 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
alignItems: "center",
|
||||
marginBottom: "4px",
|
||||
}}>
|
||||
<div style={{ fontWeight: "bold" }}>Role Definition</div>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
onClick={() => handleAgentReset(activeTab)}
|
||||
data-testid="reset-prompt-button"
|
||||
title="Revert to default">
|
||||
<span className="codicon codicon-discard"></span>
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
Define Cline's expertise and personality for this mode. This description shapes how
|
||||
Cline presents itself and approaches tasks.
|
||||
<div style={{ fontWeight: "bold" }}>Available Tools</div>
|
||||
{findModeBySlug(mode, customModes) && (
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
onClick={() => setIsToolsEditMode(!isToolsEditMode)}
|
||||
title={isToolsEditMode ? "Done editing" : "Edit tools"}>
|
||||
<span
|
||||
className={`codicon codicon-${isToolsEditMode ? "check" : "edit"}`}></span>
|
||||
</VSCodeButton>
|
||||
)}
|
||||
</div>
|
||||
{!findModeBySlug(mode, customModes) && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
Tools for built-in modes cannot be modified
|
||||
</div>
|
||||
)}
|
||||
{isToolsEditMode && findModeBySlug(mode, customModes) ? (
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))",
|
||||
gap: "8px",
|
||||
}}>
|
||||
{availableGroups.map((group) => {
|
||||
const currentMode = getCurrentMode()
|
||||
const isCustomMode = findModeBySlug(mode, customModes)
|
||||
const customMode = isCustomMode
|
||||
const isGroupEnabled = isCustomMode
|
||||
? customMode?.groups?.includes(group)
|
||||
: currentMode?.groups?.includes(group)
|
||||
|
||||
return (
|
||||
<VSCodeCheckbox
|
||||
key={group}
|
||||
checked={isGroupEnabled}
|
||||
onChange={handleGroupChange(group, Boolean(isCustomMode), customMode)}
|
||||
disabled={!isCustomMode}>
|
||||
{GROUP_DISPLAY_NAMES[group]}
|
||||
</VSCodeCheckbox>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-foreground)",
|
||||
marginBottom: "8px",
|
||||
lineHeight: "1.4",
|
||||
}}>
|
||||
{(() => {
|
||||
const currentMode = getCurrentMode()
|
||||
const enabledGroups = currentMode?.groups || []
|
||||
return enabledGroups.map((group) => GROUP_DISPLAY_NAMES[group]).join(", ")
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<VSCodeTextArea
|
||||
value={getAgentPromptValue(activeTab)}
|
||||
onChange={(e) => handleAgentPromptChange(activeTab, e)}
|
||||
rows={4}
|
||||
resize="vertical"
|
||||
style={{ width: "100%" }}
|
||||
data-testid={`${activeTab}-prompt-textarea`}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
{/* Role definition for both built-in and custom modes */}
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Mode-specific Custom Instructions</div>
|
||||
<div
|
||||
@@ -323,28 +676,38 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
Add behavioral guidelines specific to {activeTab} mode. These instructions enhance the base
|
||||
behaviors defined above.
|
||||
Add behavioral guidelines specific to {getCurrentMode()?.name || "Code"} mode.
|
||||
</div>
|
||||
<VSCodeTextArea
|
||||
value={(() => {
|
||||
const prompt = customPrompts?.[activeTab]
|
||||
return typeof prompt === "object" ? (prompt.customInstructions ?? "") : ""
|
||||
const customMode = findModeBySlug(mode, customModes)
|
||||
const prompt = customPrompts?.[mode]
|
||||
return customMode?.customInstructions ?? prompt?.customInstructions ?? ""
|
||||
})()}
|
||||
onChange={(e) => {
|
||||
const value =
|
||||
(e as CustomEvent)?.detail?.target?.value ||
|
||||
((e as any).target as HTMLTextAreaElement).value
|
||||
const existingPrompt = customPrompts?.[activeTab]
|
||||
updateAgentPrompt(activeTab, {
|
||||
...(typeof existingPrompt === "object" ? existingPrompt : {}),
|
||||
customInstructions: value.trim() || undefined,
|
||||
})
|
||||
const customMode = findModeBySlug(mode, customModes)
|
||||
if (customMode) {
|
||||
// For custom modes, update the JSON file
|
||||
updateCustomMode(mode, {
|
||||
...customMode,
|
||||
customInstructions: value.trim() || undefined,
|
||||
})
|
||||
} else {
|
||||
// For built-in modes, update the prompts
|
||||
const existingPrompt = customPrompts?.[mode]
|
||||
updateAgentPrompt(mode, {
|
||||
...existingPrompt,
|
||||
customInstructions: value.trim() || undefined,
|
||||
})
|
||||
}
|
||||
}}
|
||||
rows={4}
|
||||
resize="vertical"
|
||||
style={{ width: "100%" }}
|
||||
data-testid={`${activeTab}-custom-instructions-textarea`}
|
||||
data-testid={`${getCurrentMode()?.slug || "code"}-custom-instructions-textarea`}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
@@ -352,7 +715,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginTop: "5px",
|
||||
}}>
|
||||
Custom instructions specific to {activeTab} mode can also be loaded from{" "}
|
||||
Custom instructions specific to {getCurrentMode()?.name || "Code"} mode can also be loaded
|
||||
from{" "}
|
||||
<span
|
||||
style={{
|
||||
color: "var(--vscode-textLink-foreground)",
|
||||
@@ -360,32 +724,20 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
textDecoration: "underline",
|
||||
}}
|
||||
onClick={() => {
|
||||
// First create/update the file with current custom instructions
|
||||
const defaultContent = `# ${activeTab} Mode Rules\n\nAdd mode-specific rules and guidelines here.`
|
||||
const existingPrompt = customPrompts?.[activeTab]
|
||||
const existingInstructions =
|
||||
typeof existingPrompt === "object"
|
||||
? existingPrompt.customInstructions
|
||||
: undefined
|
||||
vscode.postMessage({
|
||||
type: "updatePrompt",
|
||||
promptMode: activeTab,
|
||||
customPrompt: {
|
||||
...(typeof existingPrompt === "object" ? existingPrompt : {}),
|
||||
customInstructions: existingInstructions || defaultContent,
|
||||
},
|
||||
})
|
||||
// Then open the file
|
||||
const currentMode = getCurrentMode()
|
||||
if (!currentMode) return
|
||||
|
||||
// Open or create an empty file
|
||||
vscode.postMessage({
|
||||
type: "openFile",
|
||||
text: `./.clinerules-${activeTab}`,
|
||||
text: `./.clinerules-${currentMode.slug}`,
|
||||
values: {
|
||||
create: true,
|
||||
content: "",
|
||||
},
|
||||
})
|
||||
}}>
|
||||
.clinerules-{activeTab}
|
||||
.clinerules-{getCurrentMode()?.slug || "code"}
|
||||
</span>{" "}
|
||||
in your workspace.
|
||||
</div>
|
||||
@@ -395,10 +747,13 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
<VSCodeButton
|
||||
appearance="primary"
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "getSystemPrompt",
|
||||
mode: activeTab,
|
||||
})
|
||||
const currentMode = getCurrentMode()
|
||||
if (currentMode) {
|
||||
vscode.postMessage({
|
||||
type: "getSystemPrompt",
|
||||
mode: currentMode.slug,
|
||||
})
|
||||
}
|
||||
}}
|
||||
data-testid="preview-prompt-button">
|
||||
Preview System Prompt
|
||||
@@ -414,8 +769,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
marginBottom: "20px",
|
||||
marginTop: "5px",
|
||||
}}>
|
||||
Use prompt enhancement to get tailored suggestions or improvements for your inputs. This ensures
|
||||
Cline understands your intent and provides the best possible responses.
|
||||
Use prompt enhancement to get tailored suggestions or improvements for your inputs. This ensures Roo
|
||||
understands your intent and provides the best possible responses.
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
|
||||
@@ -517,6 +872,181 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
||||
<div style={{ height: "20px" }} />
|
||||
</div>
|
||||
|
||||
{isCreateModeDialogOpen && (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
zIndex: 1000,
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
width: "calc(100vw - 100px)",
|
||||
height: "100%",
|
||||
backgroundColor: "var(--vscode-editor-background)",
|
||||
boxShadow: "-2px 0 5px rgba(0, 0, 0, 0.2)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
position: "relative",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "20px",
|
||||
overflowY: "auto",
|
||||
minHeight: 0,
|
||||
}}>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
onClick={() => setIsCreateModeDialogOpen(false)}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "20px",
|
||||
right: "20px",
|
||||
}}>
|
||||
<span className="codicon codicon-close"></span>
|
||||
</VSCodeButton>
|
||||
<h2 style={{ margin: "0 0 16px" }}>Create New Mode</h2>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Name</div>
|
||||
<VSCodeTextField
|
||||
value={newModeName}
|
||||
onChange={(e: Event | React.FormEvent<HTMLElement>) => {
|
||||
const target =
|
||||
(e as CustomEvent)?.detail?.target ||
|
||||
((e as any).target as HTMLInputElement)
|
||||
handleNameChange(target.value)
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Slug</div>
|
||||
<VSCodeTextField
|
||||
value={newModeSlug}
|
||||
onChange={(e: Event | React.FormEvent<HTMLElement>) => {
|
||||
const target =
|
||||
(e as CustomEvent)?.detail?.target ||
|
||||
((e as any).target as HTMLInputElement)
|
||||
setNewModeSlug(target.value)
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginTop: "4px",
|
||||
}}>
|
||||
The slug is used in URLs and file names. It should be lowercase and contain only
|
||||
letters, numbers, and hyphens.
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Role Definition</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
Define Roo's expertise and personality for this mode.
|
||||
</div>
|
||||
<VSCodeTextArea
|
||||
value={newModeRoleDefinition}
|
||||
onChange={(e) => {
|
||||
const value =
|
||||
(e as CustomEvent)?.detail?.target?.value ||
|
||||
((e as any).target as HTMLTextAreaElement).value
|
||||
setNewModeRoleDefinition(value)
|
||||
}}
|
||||
rows={4}
|
||||
resize="vertical"
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Available Tools</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
Select which tools this mode can use.
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))",
|
||||
gap: "8px",
|
||||
}}>
|
||||
{availableGroups.map((group) => (
|
||||
<VSCodeCheckbox
|
||||
key={group}
|
||||
checked={newModeGroups.includes(group)}
|
||||
onChange={(e: Event | React.FormEvent<HTMLElement>) => {
|
||||
const target =
|
||||
(e as CustomEvent)?.detail?.target || (e.target as HTMLInputElement)
|
||||
const checked = target.checked
|
||||
if (checked) {
|
||||
setNewModeGroups([...newModeGroups, group])
|
||||
} else {
|
||||
setNewModeGroups(newModeGroups.filter((g) => g !== group))
|
||||
}
|
||||
}}>
|
||||
{GROUP_DISPLAY_NAMES[group]}
|
||||
</VSCodeCheckbox>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Custom Instructions</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
Add behavioral guidelines specific to this mode.
|
||||
</div>
|
||||
<VSCodeTextArea
|
||||
value={newModeCustomInstructions}
|
||||
onChange={(e) => {
|
||||
const value =
|
||||
(e as CustomEvent)?.detail?.target?.value ||
|
||||
((e as any).target as HTMLTextAreaElement).value
|
||||
setNewModeCustomInstructions(value)
|
||||
}}
|
||||
rows={4}
|
||||
resize="vertical"
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
padding: "12px 20px",
|
||||
gap: "8px",
|
||||
borderTop: "1px solid var(--vscode-editor-lineHighlightBorder)",
|
||||
backgroundColor: "var(--vscode-editor-background)",
|
||||
}}>
|
||||
<VSCodeButton onClick={() => setIsCreateModeDialogOpen(false)}>Cancel</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="primary"
|
||||
onClick={handleCreateMode}
|
||||
disabled={!newModeName.trim() || !newModeSlug.trim()}>
|
||||
Create Mode
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isDialogOpen && (
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -57,8 +57,12 @@ describe("PromptsView", () => {
|
||||
expect(architectTab).toHaveAttribute("data-active", "false")
|
||||
})
|
||||
|
||||
it("switches between tabs correctly", () => {
|
||||
renderPromptsView({ mode: "code" })
|
||||
it("switches between tabs correctly", async () => {
|
||||
const { rerender } = render(
|
||||
<ExtensionStateContext.Provider value={{ ...mockExtensionState, mode: "code" } as any}>
|
||||
<PromptsView onDone={jest.fn()} />
|
||||
</ExtensionStateContext.Provider>,
|
||||
)
|
||||
|
||||
const codeTab = screen.getByTestId("code-tab")
|
||||
const askTab = screen.getByTestId("ask-tab")
|
||||
@@ -68,16 +72,27 @@ describe("PromptsView", () => {
|
||||
expect(codeTab).toHaveAttribute("data-active", "true")
|
||||
expect(askTab).toHaveAttribute("data-active", "false")
|
||||
expect(architectTab).toHaveAttribute("data-active", "false")
|
||||
expect(architectTab).toHaveAttribute("data-active", "false")
|
||||
|
||||
// Click Ask tab
|
||||
// Click Ask tab and update context
|
||||
fireEvent.click(askTab)
|
||||
rerender(
|
||||
<ExtensionStateContext.Provider value={{ ...mockExtensionState, mode: "ask" } as any}>
|
||||
<PromptsView onDone={jest.fn()} />
|
||||
</ExtensionStateContext.Provider>,
|
||||
)
|
||||
|
||||
expect(askTab).toHaveAttribute("data-active", "true")
|
||||
expect(codeTab).toHaveAttribute("data-active", "false")
|
||||
expect(architectTab).toHaveAttribute("data-active", "false")
|
||||
|
||||
// Click Architect tab
|
||||
// Click Architect tab and update context
|
||||
fireEvent.click(architectTab)
|
||||
rerender(
|
||||
<ExtensionStateContext.Provider value={{ ...mockExtensionState, mode: "architect" } as any}>
|
||||
<PromptsView onDone={jest.fn()} />
|
||||
</ExtensionStateContext.Provider>,
|
||||
)
|
||||
|
||||
expect(architectTab).toHaveAttribute("data-active", "true")
|
||||
expect(askTab).toHaveAttribute("data-active", "false")
|
||||
expect(codeTab).toHaveAttribute("data-active", "false")
|
||||
@@ -105,17 +120,47 @@ describe("PromptsView", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("resets prompt to default value", () => {
|
||||
renderPromptsView()
|
||||
it("resets role definition only for built-in modes", async () => {
|
||||
const customMode = {
|
||||
slug: "custom-mode",
|
||||
name: "Custom Mode",
|
||||
roleDefinition: "Custom role",
|
||||
groups: [],
|
||||
}
|
||||
|
||||
const resetButton = screen.getByTestId("reset-prompt-button")
|
||||
fireEvent.click(resetButton)
|
||||
// Test with built-in mode (code)
|
||||
const { unmount } = render(
|
||||
<ExtensionStateContext.Provider
|
||||
value={{ ...mockExtensionState, mode: "code", customModes: [customMode] } as any}>
|
||||
<PromptsView onDone={jest.fn()} />
|
||||
</ExtensionStateContext.Provider>,
|
||||
)
|
||||
|
||||
// Find and click the role definition reset button
|
||||
const resetButton = screen.getByTestId("role-definition-reset")
|
||||
expect(resetButton).toBeInTheDocument()
|
||||
await fireEvent.click(resetButton)
|
||||
|
||||
// Verify it only resets role definition
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "updatePrompt",
|
||||
promptMode: "code",
|
||||
customPrompt: { roleDefinition: undefined },
|
||||
})
|
||||
|
||||
// Cleanup before testing custom mode
|
||||
unmount()
|
||||
|
||||
// Test with custom mode
|
||||
render(
|
||||
<ExtensionStateContext.Provider
|
||||
value={{ ...mockExtensionState, mode: "custom-mode", customModes: [customMode] } as any}>
|
||||
<PromptsView onDone={jest.fn()} />
|
||||
</ExtensionStateContext.Provider>,
|
||||
)
|
||||
|
||||
// Verify reset button is not present for custom mode
|
||||
expect(screen.queryByTestId("role-definition-reset")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("handles API configuration selection", () => {
|
||||
|
||||
@@ -557,7 +557,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Roo Code uses complex prompts and works best
|
||||
with Claude models. Less capable models may not work as expected.)
|
||||
</span>
|
||||
</p>
|
||||
@@ -626,7 +626,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
|
||||
</VSCodeLink>{" "}
|
||||
feature to use it with this extension.{" "}
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Roo Code uses complex prompts and works best
|
||||
with Claude models. Less capable models may not work as expected.)
|
||||
</span>
|
||||
</p>
|
||||
@@ -717,7 +717,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
|
||||
fontWeight: 500,
|
||||
}}>
|
||||
Note: This is a very experimental integration and may not work as expected. Please report
|
||||
any issues to the Roo-Cline GitHub repository.
|
||||
any issues to the Roo-Code GitHub repository.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -780,7 +780,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
|
||||
quickstart guide.
|
||||
</VSCodeLink>
|
||||
<span style={{ color: "var(--vscode-errorForeground)" }}>
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best
|
||||
(<span style={{ fontWeight: 500 }}>Note:</span> Roo Code uses complex prompts and works best
|
||||
with Claude models. Less capable models may not work as expected.)
|
||||
</span>
|
||||
</p>
|
||||
|
||||
@@ -240,7 +240,7 @@ const GlamaModelPicker: React.FC = () => {
|
||||
<VSCodeLink style={{ display: "inline", fontSize: "inherit" }} href="https://glama.ai/models">
|
||||
Glama.
|
||||
</VSCodeLink>
|
||||
If you're unsure which model to choose, Cline works best with{" "}
|
||||
If you're unsure which model to choose, Roo Code works best with{" "}
|
||||
<VSCodeLink
|
||||
style={{ display: "inline", fontSize: "inherit" }}
|
||||
onClick={() => handleModelChange("anthropic/claude-3.5-sonnet")}>
|
||||
|
||||
@@ -240,7 +240,7 @@ const OpenRouterModelPicker: React.FC = () => {
|
||||
<VSCodeLink style={{ display: "inline", fontSize: "inherit" }} href="https://openrouter.ai/models">
|
||||
OpenRouter.
|
||||
</VSCodeLink>
|
||||
If you're unsure which model to choose, Cline works best with{" "}
|
||||
If you're unsure which model to choose, Roo Code works best with{" "}
|
||||
<VSCodeLink
|
||||
style={{ display: "inline", fontSize: "inherit" }}
|
||||
onClick={() => handleModelChange("anthropic/claude-3.5-sonnet:beta")}>
|
||||
|
||||
@@ -193,9 +193,9 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
<div style={{ marginBottom: 40 }}>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 15px 0" }}>Auto-Approve Settings</h3>
|
||||
<p style={{ fontSize: "12px", marginBottom: 15, color: "var(--vscode-descriptionForeground)" }}>
|
||||
The following settings allow Cline to automatically perform operations without requiring
|
||||
approval. Enable these settings only if you fully trust the AI and understand the associated
|
||||
security risks.
|
||||
The following settings allow Roo to automatically perform operations without requiring approval.
|
||||
Enable these settings only if you fully trust the AI and understand the associated security
|
||||
risks.
|
||||
</p>
|
||||
|
||||
<div style={{ marginBottom: 15 }}>
|
||||
@@ -210,7 +210,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
When enabled, Cline will automatically view directory contents and read files without
|
||||
When enabled, Roo will automatically view directory contents and read files without
|
||||
requiring you to click the Approve button.
|
||||
</p>
|
||||
</div>
|
||||
@@ -485,7 +485,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
When enabled, Cline will play sound effects for notifications and events.
|
||||
When enabled, Roo will play sound effects for notifications and events.
|
||||
</p>
|
||||
</div>
|
||||
{soundEnabled && (
|
||||
@@ -560,7 +560,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
When enabled, Cline will be able to edit files more quickly and will automatically reject
|
||||
When enabled, Roo will be able to edit files more quickly and will automatically reject
|
||||
truncated full-file writes. Works best with the latest Claude 3.5 Sonnet model.
|
||||
</p>
|
||||
|
||||
@@ -635,12 +635,12 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
}}>
|
||||
<p style={{ wordWrap: "break-word", margin: 0, padding: 0 }}>
|
||||
If you have any questions or feedback, feel free to open an issue at{" "}
|
||||
<VSCodeLink href="https://github.com/RooVetGit/Roo-Cline" style={{ display: "inline" }}>
|
||||
github.com/RooVetGit/Roo-Cline
|
||||
<VSCodeLink href="https://github.com/RooVetGit/Roo-Code" style={{ display: "inline" }}>
|
||||
github.com/RooVetGit/Roo-Code
|
||||
</VSCodeLink>{" "}
|
||||
or join{" "}
|
||||
<VSCodeLink href="https://www.reddit.com/r/roocline/" style={{ display: "inline" }}>
|
||||
reddit.com/r/roocline
|
||||
<VSCodeLink href="https://www.reddit.com/r/RooCode/" style={{ display: "inline" }}>
|
||||
reddit.com/r/RooCode
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
<p style={{ fontStyle: "italic", margin: "10px 0 0 0", padding: 0, marginBottom: 100 }}>
|
||||
|
||||
@@ -22,7 +22,7 @@ const WelcomeView = () => {
|
||||
|
||||
return (
|
||||
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, padding: "0 20px" }}>
|
||||
<h2>Hi, I'm Cline</h2>
|
||||
<h2>Hi, I'm Roo!</h2>
|
||||
<p>
|
||||
I can do all kinds of tasks thanks to the latest breakthroughs in agentic coding capabilities and access
|
||||
to tools that let me create & edit files, explore complex projects, use the browser, and execute
|
||||
|
||||
Reference in New Issue
Block a user