mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Slash commands for switching modes
This commit is contained in:
parent
55e8170369
commit
97128bc144
3 changed files with 141 additions and 2 deletions
|
|
@ -52,7 +52,7 @@ import { parseMentions } from "./mentions"
|
|||
import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseName } from "./assistant-message"
|
||||
import { formatResponse } from "./prompts/responses"
|
||||
import { SYSTEM_PROMPT } from "./prompts/system"
|
||||
import { modes, defaultModeSlug, getModeBySlug } from "../shared/modes"
|
||||
import { modes, defaultModeSlug, getModeBySlug, parseSlashCommand } from "../shared/modes"
|
||||
import { truncateHalfConversation } from "./sliding-window"
|
||||
import { ClineProvider, GlobalFileNames } from "./webview/ClineProvider"
|
||||
import { detectCodeOmission } from "../integrations/editor/detect-omission"
|
||||
|
|
@ -77,6 +77,29 @@ export class Cline {
|
|||
private terminalManager: TerminalManager
|
||||
private urlContentFetcher: UrlContentFetcher
|
||||
private browserSession: BrowserSession
|
||||
|
||||
/**
|
||||
* Processes a message for slash commands and handles mode switching if needed.
|
||||
* @param message The message to process
|
||||
* @returns The processed message with slash command removed if one was present
|
||||
*/
|
||||
private async handleSlashCommand(message: string): Promise<string> {
|
||||
if (!message) return message
|
||||
|
||||
const { customModes } = (await this.providerRef.deref()?.getState()) ?? {}
|
||||
const slashCommand = parseSlashCommand(message, customModes)
|
||||
|
||||
if (slashCommand) {
|
||||
// Switch mode before processing the remaining message
|
||||
const provider = this.providerRef.deref()
|
||||
if (provider) {
|
||||
await provider.handleModeSwitch(slashCommand.modeSlug)
|
||||
return slashCommand.remainingMessage
|
||||
}
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
private didEditFile: boolean = false
|
||||
customInstructions?: string
|
||||
diffStrategy?: DiffStrategy
|
||||
|
|
@ -355,6 +378,11 @@ export class Cline {
|
|||
}
|
||||
|
||||
async handleWebviewAskResponse(askResponse: ClineAskResponse, text?: string, images?: string[]) {
|
||||
// Process slash command if present
|
||||
if (text) {
|
||||
text = await this.handleSlashCommand(text)
|
||||
}
|
||||
|
||||
this.askResponse = askResponse
|
||||
this.askResponseText = text
|
||||
this.askResponseImages = images
|
||||
|
|
@ -437,6 +465,22 @@ export class Cline {
|
|||
this.apiConversationHistory = []
|
||||
await this.providerRef.deref()?.postStateToWebview()
|
||||
|
||||
// Check for slash command if task is provided
|
||||
if (task) {
|
||||
const { customModes } = (await this.providerRef.deref()?.getState()) ?? {}
|
||||
const slashCommand = parseSlashCommand(task, customModes)
|
||||
|
||||
if (slashCommand) {
|
||||
// Switch mode before processing the remaining message
|
||||
const provider = this.providerRef.deref()
|
||||
if (provider) {
|
||||
await provider.handleModeSwitch(slashCommand.modeSlug)
|
||||
// Update task to be just the remaining message
|
||||
task = slashCommand.remainingMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.say("text", task, images)
|
||||
|
||||
let imageBlocks: Anthropic.ImageBlockParam[] = formatResponse.imageBlocks(images)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { isToolAllowedForMode, FileRestrictionError, ModeConfig } from "../modes"
|
||||
import { isToolAllowedForMode, FileRestrictionError, ModeConfig, parseSlashCommand } from "../modes"
|
||||
|
||||
describe("isToolAllowedForMode", () => {
|
||||
const customModes: ModeConfig[] = [
|
||||
|
|
@ -332,3 +332,65 @@ describe("FileRestrictionError", () => {
|
|||
expect(error.name).toBe("FileRestrictionError")
|
||||
})
|
||||
})
|
||||
|
||||
describe("parseSlashCommand", () => {
|
||||
const customModes: ModeConfig[] = [
|
||||
{
|
||||
slug: "custom-mode",
|
||||
name: "Custom Mode",
|
||||
roleDefinition: "Custom role",
|
||||
groups: ["read"],
|
||||
},
|
||||
]
|
||||
|
||||
it("returns null for non-slash messages", () => {
|
||||
expect(parseSlashCommand("hello world")).toBeNull()
|
||||
expect(parseSlashCommand("code help me")).toBeNull()
|
||||
})
|
||||
|
||||
it("returns null for incomplete commands", () => {
|
||||
expect(parseSlashCommand("/")).toBeNull()
|
||||
expect(parseSlashCommand("/code")).toBeNull()
|
||||
expect(parseSlashCommand("/code ")).toBeNull()
|
||||
})
|
||||
|
||||
it("returns null for invalid mode slugs", () => {
|
||||
expect(parseSlashCommand("/invalid help me")).toBeNull()
|
||||
expect(parseSlashCommand("/nonexistent do something")).toBeNull()
|
||||
})
|
||||
|
||||
it("successfully parses valid commands", () => {
|
||||
expect(parseSlashCommand("/code help me write tests")).toEqual({
|
||||
modeSlug: "code",
|
||||
remainingMessage: "help me write tests",
|
||||
})
|
||||
|
||||
expect(parseSlashCommand("/ask what is typescript?")).toEqual({
|
||||
modeSlug: "ask",
|
||||
remainingMessage: "what is typescript?",
|
||||
})
|
||||
|
||||
expect(parseSlashCommand("/architect plan this feature")).toEqual({
|
||||
modeSlug: "architect",
|
||||
remainingMessage: "plan this feature",
|
||||
})
|
||||
})
|
||||
|
||||
it("preserves whitespace in remaining message", () => {
|
||||
expect(parseSlashCommand("/code help me write tests ")).toEqual({
|
||||
modeSlug: "code",
|
||||
remainingMessage: "help me write tests",
|
||||
})
|
||||
})
|
||||
|
||||
it("handles custom modes", () => {
|
||||
expect(parseSlashCommand("/custom-mode do something", customModes)).toEqual({
|
||||
modeSlug: "custom-mode",
|
||||
remainingMessage: "do something",
|
||||
})
|
||||
})
|
||||
|
||||
it("returns null for invalid custom mode slugs", () => {
|
||||
expect(parseSlashCommand("/invalid-custom do something", customModes)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -257,3 +257,36 @@ export function getCustomInstructions(modeSlug: string, customModes?: ModeConfig
|
|||
}
|
||||
return mode.customInstructions ?? ""
|
||||
}
|
||||
|
||||
// Slash command parsing types and functions
|
||||
export type SlashCommandResult = {
|
||||
modeSlug: string
|
||||
remainingMessage: string
|
||||
} | null
|
||||
|
||||
export function parseSlashCommand(message: string, customModes?: ModeConfig[]): SlashCommandResult {
|
||||
// Check if message starts with a slash
|
||||
if (!message.startsWith("/")) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Extract command (everything between / and first space)
|
||||
const parts = message.trim().split(/\s+/)
|
||||
if (parts.length < 2) {
|
||||
return null // Need both command and message
|
||||
}
|
||||
|
||||
const command = parts[0].substring(1) // Remove leading slash
|
||||
const remainingMessage = parts.slice(1).join(" ")
|
||||
|
||||
// Validate command is a valid mode slug
|
||||
const mode = getModeBySlug(command, customModes)
|
||||
if (!mode) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
modeSlug: command,
|
||||
remainingMessage,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue