Skip to content

Commit 979b640

Browse files
committed
feat: add 'onlychat' mode with specific role definition and instructions
1 parent a5b55da commit 979b640

File tree

5 files changed

+96
-31
lines changed

5 files changed

+96
-31
lines changed

packages/types/src/mode.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,4 +192,14 @@ export const DEFAULT_MODES: readonly ModeConfig[] = [
192192
customInstructions:
193193
"Your role is to coordinate complex workflows by delegating tasks to specialized modes. As an orchestrator, you should:\n\n1. When given a complex task, break it down into logical subtasks that can be delegated to appropriate specialized modes.\n\n2. For each subtask, use the `new_task` tool to delegate. Choose the most appropriate mode for the subtask's specific goal and provide comprehensive instructions in the `message` parameter. These instructions must include:\n * All necessary context from the parent task or previous subtasks required to complete the work.\n * A clearly defined scope, specifying exactly what the subtask should accomplish.\n * An explicit statement that the subtask should *only* perform the work outlined in these instructions and not deviate.\n * An instruction for the subtask to signal completion by using the `attempt_completion` tool, providing a concise yet thorough summary of the outcome in the `result` parameter, keeping in mind that this summary will be the source of truth used to keep track of what was completed on this project.\n * A statement that these specific instructions supersede any conflicting general instructions the subtask's mode might have.\n\n3. Track and manage the progress of all subtasks. When a subtask is completed, analyze its results and determine the next steps.\n\n4. Help the user understand how the different subtasks fit together in the overall workflow. Provide clear reasoning about why you're delegating specific tasks to specific modes.\n\n5. When all subtasks are completed, synthesize the results and provide a comprehensive overview of what was accomplished.\n\n6. Ask clarifying questions when necessary to better understand how to break down complex tasks effectively.\n\n7. Suggest improvements to the workflow based on the results of completed subtasks.\n\nUse subtasks to maintain clarity. If a request significantly shifts focus or requires a different expertise (mode), consider creating a subtask rather than overloading the current one.",
194194
},
195+
{
196+
slug: "onlychat",
197+
name: "🗨️OnlyChat",
198+
roleDefinition:
199+
"You are Roo, a knowledgeable technical assistant focused on answering questions and providing information about software development, technology, and related topics.",
200+
whenToUse:
201+
"Use this mode for pure conversational interactions without any tool execution. Ideal for discussions, explanations, and Q&A without file operations or code changes.",
202+
groups: [],
203+
description: "A chat-only mode without tool execution capabilities",
204+
},
195205
] as const

src/core/assistant-message/presentAssistantMessage.ts

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { ToolName, ClineAsk, ToolProgressStatus } from "@roo-code/types"
55
import { TelemetryService } from "@roo-code/telemetry"
66

77
import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
8-
import type { ToolParamName, ToolResponse } from "../../shared/tools"
8+
import type { ToolParamName, ToolResponse, ToolUse } from "../../shared/tools"
99

1010
import { fetchInstructionsTool } from "../tools/fetchInstructionsTool"
1111
import { listFilesTool } from "../tools/listFilesTool"
@@ -79,6 +79,33 @@ export async function presentAssistantMessage(cline: Task) {
7979

8080
const block = cloneDeep(cline.assistantMessageContent[cline.currentStreamingContentIndex]) // need to create copy bc while stream is updating the array, it could be updating the reference block properties too
8181

82+
// If block is partial, remove partial closing tag so its not
83+
// presented to user.
84+
const removeClosingTag = (tag: ToolParamName, text?: string): string => {
85+
if (!block.partial) {
86+
return text || ""
87+
}
88+
89+
if (!text) {
90+
return ""
91+
}
92+
93+
// This regex dynamically constructs a pattern to match the
94+
// closing tag:
95+
// - Optionally matches whitespace before the tag.
96+
// - Matches '<' or '</' optionally followed by any subset of
97+
// characters from the tag name.
98+
const tagRegex = new RegExp(
99+
`\\s?<\/?${tag
100+
.split("")
101+
.map((char) => `(?:${char})?`)
102+
.join("")}$`,
103+
"g",
104+
)
105+
106+
return text.replace(tagRegex, "")
107+
}
108+
82109
switch (block.type) {
83110
case "text": {
84111
if (cline.didRejectTool || cline.didAlreadyUseTool) {
@@ -145,7 +172,27 @@ export async function presentAssistantMessage(cline: Task) {
145172
}
146173
}
147174
}
148-
175+
const { mode } = (await cline.providerRef.deref()?.getState()) ?? {}
176+
if (mode === "onlychat") {
177+
await attemptCompletionTool(
178+
cline,
179+
{
180+
type: "tool_use",
181+
name: "attempt_completion",
182+
params: {
183+
result: content,
184+
},
185+
partial: block.partial,
186+
} as ToolUse,
187+
undefined as any,
188+
undefined as any,
189+
undefined as any,
190+
removeClosingTag,
191+
undefined as any,
192+
undefined as any,
193+
)
194+
break
195+
}
149196
await cline.say("text", content, undefined, block.partial)
150197
break
151198
}
@@ -314,33 +361,6 @@ export async function presentAssistantMessage(cline: Task) {
314361
pushToolResult(formatResponse.toolError(errorString))
315362
}
316363

317-
// If block is partial, remove partial closing tag so its not
318-
// presented to user.
319-
const removeClosingTag = (tag: ToolParamName, text?: string): string => {
320-
if (!block.partial) {
321-
return text || ""
322-
}
323-
324-
if (!text) {
325-
return ""
326-
}
327-
328-
// This regex dynamically constructs a pattern to match the
329-
// closing tag:
330-
// - Optionally matches whitespace before the tag.
331-
// - Matches '<' or '</' optionally followed by any subset of
332-
// characters from the tag name.
333-
const tagRegex = new RegExp(
334-
`\\s?<\/?${tag
335-
.split("")
336-
.map((char) => `(?:${char})?`)
337-
.join("")}$`,
338-
"g",
339-
)
340-
341-
return text.replace(tagRegex, "")
342-
}
343-
344364
if (block.name !== "browser_action") {
345365
await cline.browserSession.closeBrowser()
346366
}

src/core/prompts/__tests__/system-prompt.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,38 @@ describe("SYSTEM_PROMPT", () => {
669669
expect(prompt).toContain("## update_todo_list")
670670
})
671671

672+
it("should return only roleDefinition and customInstructions for onlychat mode", async () => {
673+
const onlyChatMode: ModeConfig = {
674+
slug: "onlychat",
675+
name: "Only Chat",
676+
roleDefinition: "You are a chat-only assistant.",
677+
customInstructions: "Only chat instructions.",
678+
groups: ["read"] as const,
679+
}
680+
const customModes = [onlyChatMode]
681+
const prompt = await SYSTEM_PROMPT(
682+
mockContext,
683+
"/test/path",
684+
false, // supportsComputerUse
685+
undefined, // mcpHub
686+
undefined, // diffStrategy
687+
undefined, // browserViewportSize
688+
"onlychat", // mode
689+
undefined, // customModePrompts
690+
customModes, // customModes
691+
undefined, // globalCustomInstructions
692+
true, // diffEnabled
693+
undefined, // experiments
694+
false, // enableMcpServerCreation
695+
undefined, // language
696+
undefined, // rooIgnoreInstructions
697+
undefined, // partialReadsEnabled
698+
)
699+
expect(prompt).toContain("You are a chat-only assistant.")
700+
expect(prompt).toContain("Only chat instructions.")
701+
expect(prompt).not.toContain("apply_diff")
702+
})
703+
672704
afterAll(() => {
673705
vi.restoreAllMocks()
674706
})

src/core/prompts/sections/modes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ MODES
1919
2020
- These are the currently available modes:
2121
${allModes
22+
.filter((mode: ModeConfig) => mode.slug !== "onlychat")
2223
.map((mode: ModeConfig) => {
2324
let description: string
2425
if (mode.whenToUse && mode.whenToUse.trim() !== "") {

src/core/prompts/system.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,10 @@ export const SYSTEM_PROMPT = async (
170170
// Get full mode config from custom modes or fall back to built-in modes
171171
const currentMode = getModeBySlug(mode, customModes) || modes.find((m) => m.slug === mode) || modes[0]
172172

173+
const isOnlyChatMode = currentMode.slug === "onlychat"
174+
173175
// If a file-based custom system prompt exists, use it
174-
if (fileCustomSystemPrompt) {
176+
if (fileCustomSystemPrompt || isOnlyChatMode) {
175177
const { roleDefinition, baseInstructions: baseInstructionsForFile } = getModeSelection(
176178
mode,
177179
promptComponent,
@@ -193,7 +195,7 @@ export const SYSTEM_PROMPT = async (
193195
// For file-based prompts, don't include the tool sections
194196
return `${roleDefinition}
195197
196-
${fileCustomSystemPrompt}
198+
${fileCustomSystemPrompt || ""}
197199
198200
${customInstructions}`
199201
}

0 commit comments

Comments
 (0)