From e07e447a20dd8d120f7b662dae303159e1677ea5 Mon Sep 17 00:00:00 2001 From: Ruslan Andreev Date: Tue, 24 Feb 2026 16:53:08 +0300 Subject: [PATCH] feat: introduce subagent functionality with tool integration - Added new tool parameters for subagent including "description" and "subagent_type". - Implemented filtering logic to include/exclude the subagent tool based on experiment settings. - Enhanced localization for subagent tool in multiple languages. - Updated UI components to display subagent status and progress. - Introduced subagent-specific tool restrictions in the backend logic. This commit lays the groundwork for running subagents in the background for research or sub-tasks, enhancing the overall functionality of the toolset. --- packages/types/src/experiment.ts | 9 +- packages/types/src/message.ts | 1 + packages/types/src/tool.ts | 1 + packages/types/src/vscode-extension-host.ts | 9 + .../assistant-message/NativeToolCallParser.ts | 29 +++ .../__tests__/NativeToolCallParser.spec.ts | 50 +++++ .../presentAssistantMessage.ts | 14 ++ .../__tests__/filter-tools-for-mode.spec.ts | 52 +++++ .../prompts/tools/filter-tools-for-mode.ts | 7 + src/core/prompts/tools/native-tools/index.ts | 2 + .../prompts/tools/native-tools/subagent.ts | 36 ++++ src/core/task/Task.ts | 80 ++++++- .../flushPendingToolResultsToHistory.spec.ts | 127 ++++++++++++ src/core/task/build-tools.ts | 48 ++++- src/core/tools/AttemptCompletionTool.ts | 8 + src/core/tools/SubagentTool.ts | 128 ++++++++++++ src/core/tools/__tests__/SubagentTool.spec.ts | 196 ++++++++++++++++++ src/core/webview/ClineProvider.ts | 100 +++++++++ src/shared/__tests__/experiments.spec.ts | 3 + src/shared/experiments.ts | 2 + src/shared/subagent.ts | 74 +++++++ src/shared/tools.ts | 5 + webview-ui/src/components/chat/ChatRow.tsx | 129 ++++++++++++ .../src/components/common/CodeAccordion.tsx | 10 +- .../__tests__/ExtensionStateContext.spec.tsx | 2 + webview-ui/src/i18n/locales/ca/chat.json | 30 +++ webview-ui/src/i18n/locales/ca/settings.json | 4 + webview-ui/src/i18n/locales/de/chat.json | 30 +++ webview-ui/src/i18n/locales/de/settings.json | 4 + webview-ui/src/i18n/locales/en/chat.json | 30 +++ webview-ui/src/i18n/locales/en/settings.json | 4 + webview-ui/src/i18n/locales/es/chat.json | 30 +++ webview-ui/src/i18n/locales/es/settings.json | 4 + webview-ui/src/i18n/locales/fr/chat.json | 30 +++ webview-ui/src/i18n/locales/fr/settings.json | 4 + webview-ui/src/i18n/locales/hi/chat.json | 30 +++ webview-ui/src/i18n/locales/hi/settings.json | 4 + webview-ui/src/i18n/locales/id/chat.json | 30 +++ webview-ui/src/i18n/locales/id/settings.json | 4 + webview-ui/src/i18n/locales/it/chat.json | 30 +++ webview-ui/src/i18n/locales/it/settings.json | 4 + webview-ui/src/i18n/locales/ja/chat.json | 30 +++ webview-ui/src/i18n/locales/ja/settings.json | 4 + webview-ui/src/i18n/locales/ko/chat.json | 30 +++ webview-ui/src/i18n/locales/ko/settings.json | 4 + webview-ui/src/i18n/locales/nl/chat.json | 30 +++ webview-ui/src/i18n/locales/nl/settings.json | 4 + webview-ui/src/i18n/locales/pl/chat.json | 30 +++ webview-ui/src/i18n/locales/pl/settings.json | 4 + webview-ui/src/i18n/locales/pt-BR/chat.json | 30 +++ .../src/i18n/locales/pt-BR/settings.json | 4 + webview-ui/src/i18n/locales/ru/chat.json | 30 +++ webview-ui/src/i18n/locales/ru/settings.json | 4 + webview-ui/src/i18n/locales/tr/chat.json | 30 +++ webview-ui/src/i18n/locales/tr/settings.json | 4 + webview-ui/src/i18n/locales/vi/chat.json | 30 +++ webview-ui/src/i18n/locales/vi/settings.json | 4 + webview-ui/src/i18n/locales/zh-CN/chat.json | 30 +++ .../src/i18n/locales/zh-CN/settings.json | 4 + webview-ui/src/i18n/locales/zh-TW/chat.json | 30 +++ .../src/i18n/locales/zh-TW/settings.json | 4 + 61 files changed, 1727 insertions(+), 7 deletions(-) create mode 100644 src/core/prompts/tools/native-tools/subagent.ts create mode 100644 src/core/tools/SubagentTool.ts create mode 100644 src/core/tools/__tests__/SubagentTool.spec.ts create mode 100644 src/shared/subagent.ts diff --git a/packages/types/src/experiment.ts b/packages/types/src/experiment.ts index d7eb0b03d6c..f299da519d3 100644 --- a/packages/types/src/experiment.ts +++ b/packages/types/src/experiment.ts @@ -6,7 +6,13 @@ import type { Keys, Equals, AssertEqual } from "./type-fu.js" * ExperimentId */ -export const experimentIds = ["preventFocusDisruption", "imageGeneration", "runSlashCommand", "customTools"] as const +export const experimentIds = [ + "preventFocusDisruption", + "imageGeneration", + "runSlashCommand", + "customTools", + "subagent", +] as const export const experimentIdsSchema = z.enum(experimentIds) @@ -21,6 +27,7 @@ export const experimentsSchema = z.object({ imageGeneration: z.boolean().optional(), runSlashCommand: z.boolean().optional(), customTools: z.boolean().optional(), + subagent: z.boolean().optional(), }) export type Experiments = z.infer diff --git a/packages/types/src/message.ts b/packages/types/src/message.ts index e518972a1c2..7c8c17cfb51 100644 --- a/packages/types/src/message.ts +++ b/packages/types/src/message.ts @@ -183,6 +183,7 @@ export type ClineSay = z.infer export const toolProgressStatusSchema = z.object({ icon: z.string().optional(), text: z.string().optional(), + spin: z.boolean().optional(), }) export type ToolProgressStatus = z.infer diff --git a/packages/types/src/tool.ts b/packages/types/src/tool.ts index 4f90b63e9fc..638c8cd818f 100644 --- a/packages/types/src/tool.ts +++ b/packages/types/src/tool.ts @@ -46,6 +46,7 @@ export const toolNames = [ "skill", "generate_image", "custom_tool", + "subagent", ] as const export const toolNamesSchema = z.enum(toolNames) diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index 253ce3e55d5..82bd2b0f783 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -780,6 +780,8 @@ export interface ClineSayTool { | "runSlashCommand" | "updateTodoList" | "skill" + | "subagentRunning" + | "subagentCompleted" path?: string // For readCommandOutput readStart?: number @@ -837,6 +839,13 @@ export interface ClineSayTool { description?: string // Properties for skill tool skill?: string + // Properties for subagent tool (subagentRunning / subagentCompleted) + currentTask?: string + result?: string + error?: string + /** When set (e.g. CANCELLED), webview shows t(messageKey) instead of result/error. */ + resultCode?: string + messageKey?: string } export interface ClineAskUseMcpServer { diff --git a/src/core/assistant-message/NativeToolCallParser.ts b/src/core/assistant-message/NativeToolCallParser.ts index bda7c71eb8d..c9b5adae83a 100644 --- a/src/core/assistant-message/NativeToolCallParser.ts +++ b/src/core/assistant-message/NativeToolCallParser.ts @@ -637,6 +637,20 @@ export class NativeToolCallParser { } break + case "subagent": + if ( + partialArgs.description !== undefined || + partialArgs.prompt !== undefined || + partialArgs.subagent_type !== undefined + ) { + nativeArgs = { + description: partialArgs.description, + prompt: partialArgs.prompt, + subagent_type: partialArgs.subagent_type, + } + } + break + default: break } @@ -911,6 +925,21 @@ export class NativeToolCallParser { } break + case "subagent": + if ( + args.description !== undefined && + args.prompt !== undefined && + args.subagent_type !== undefined && + (args.subagent_type === "general" || args.subagent_type === "explore") + ) { + nativeArgs = { + description: args.description, + prompt: args.prompt, + subagent_type: args.subagent_type, + } as NativeArgsFor + } + break + case "use_mcp_tool": if (args.server_name !== undefined && args.tool_name !== undefined) { nativeArgs = { diff --git a/src/core/assistant-message/__tests__/NativeToolCallParser.spec.ts b/src/core/assistant-message/__tests__/NativeToolCallParser.spec.ts index 2c15e12069c..534742dfd21 100644 --- a/src/core/assistant-message/__tests__/NativeToolCallParser.spec.ts +++ b/src/core/assistant-message/__tests__/NativeToolCallParser.spec.ts @@ -291,6 +291,56 @@ describe("NativeToolCallParser", () => { }) }) }) + + describe("subagent tool", () => { + it("should parse description, prompt, and subagent_type into nativeArgs", () => { + const toolCall = { + id: "toolu_subagent_1", + name: "subagent" as const, + arguments: JSON.stringify({ + description: "Explore codebase", + prompt: "List all exports from src/index.ts", + subagent_type: "explore", + }), + } + + const result = NativeToolCallParser.parseToolCall(toolCall) + + expect(result).not.toBeNull() + expect(result?.type).toBe("tool_use") + if (result?.type === "tool_use") { + expect(result.nativeArgs).toBeDefined() + const nativeArgs = result.nativeArgs as { + description: string + prompt: string + subagent_type: "general" | "explore" + } + expect(nativeArgs.description).toBe("Explore codebase") + expect(nativeArgs.prompt).toBe("List all exports from src/index.ts") + expect(nativeArgs.subagent_type).toBe("explore") + } + }) + + it("should parse general subagent_type", () => { + const toolCall = { + id: "toolu_subagent_2", + name: "subagent" as const, + arguments: JSON.stringify({ + description: "Fix bug", + prompt: "Fix the null check in utils.ts", + subagent_type: "general", + }), + } + + const result = NativeToolCallParser.parseToolCall(toolCall) + + expect(result).not.toBeNull() + if (result?.type === "tool_use" && result.nativeArgs) { + const nativeArgs = result.nativeArgs as { subagent_type: string } + expect(nativeArgs.subagent_type).toBe("general") + } + }) + }) }) describe("processStreamingChunk", () => { diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 7f5862be154..7d8203d2edc 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -8,6 +8,7 @@ import { customToolRegistry } from "@roo-code/core" import { t } from "../../i18n" +import { SUBAGENT_STATUS_THINKING } from "../../shared/subagent" import { defaultModeSlug, getModeBySlug } from "../../shared/modes" import type { ToolParamName, ToolResponse, ToolUse, McpToolUse } from "../../shared/tools" @@ -30,6 +31,7 @@ import { askFollowupQuestionTool } from "../tools/AskFollowupQuestionTool" import { switchModeTool } from "../tools/SwitchModeTool" import { attemptCompletionTool, AttemptCompletionCallbacks } from "../tools/AttemptCompletionTool" import { newTaskTool } from "../tools/NewTaskTool" +import { subagentTool } from "../tools/SubagentTool" import { updateTodoListTool } from "../tools/UpdateTodoListTool" import { runSlashCommandTool } from "../tools/RunSlashCommandTool" import { skillTool } from "../tools/SkillTool" @@ -292,6 +294,7 @@ export async function presentAssistantMessage(cline: Task) { content = content.replace(/\s?<\/thinking>/g, "") } + cline.subagentProgressCallback?.(SUBAGENT_STATUS_THINKING) await cline.say("text", content, undefined, block.partial) break } @@ -383,6 +386,8 @@ export async function presentAssistantMessage(cline: Task) { return `[${block.name} for '${block.params.skill}'${block.params.args ? ` with args: ${block.params.args}` : ""}]` case "generate_image": return `[${block.name} for '${block.params.path}']` + case "subagent": + return `[${block.name}: ${block.params.description ?? "(no description)"}]` default: return `[${block.name}]` } @@ -675,6 +680,7 @@ export async function presentAssistantMessage(cline: Task) { } } + cline.subagentProgressCallback?.(toolDescription()) switch (block.name) { case "write_to_file": await checkpointSaveAndMark(cline) @@ -812,6 +818,14 @@ export async function presentAssistantMessage(cline: Task) { toolCallId: block.id, }) break + case "subagent": + await subagentTool.handle(cline, block as ToolUse<"subagent">, { + askApproval, + handleError, + pushToolResult, + toolCallId: block.id, + }) + break case "attempt_completion": { const completionCallbacks: AttemptCompletionCallbacks = { askApproval, diff --git a/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts b/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts index 0b776a2bad9..78d51b9957f 100644 --- a/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts +++ b/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts @@ -89,3 +89,55 @@ describe("filterNativeToolsForMode - disabledTools", () => { expect(resultNames).not.toContain("edit") }) }) + +describe("filterNativeToolsForMode - subagent experiment", () => { + const codeMode = { + slug: "code", + name: "Code", + roleDefinition: "Test", + groups: ["read", "edit", "command", "mcp"] as ("read" | "edit" | "command" | "mcp")[], + } + + const mockSubagentTool: OpenAI.Chat.ChatCompletionTool = { + type: "function", + function: { + name: "subagent", + description: "Run subagent", + parameters: {}, + }, + } + + const mockNativeTools: OpenAI.Chat.ChatCompletionTool[] = [ + makeTool("read_file"), + makeTool("write_to_file"), + mockSubagentTool, + ] + + it("should exclude subagent when experiment is not enabled", () => { + const filtered = filterNativeToolsForMode( + mockNativeTools, + "code", + [codeMode], + { subagent: false }, + undefined, + {}, + undefined, + ) + const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) + expect(toolNames).not.toContain("subagent") + }) + + it("should include subagent when experiment is enabled", () => { + const filtered = filterNativeToolsForMode( + mockNativeTools, + "code", + [codeMode], + { subagent: true }, + undefined, + {}, + undefined, + ) + const toolNames = filtered.map((t) => ("function" in t ? t.function.name : "")) + expect(toolNames).toContain("subagent") + }) +}) diff --git a/src/core/prompts/tools/filter-tools-for-mode.ts b/src/core/prompts/tools/filter-tools-for-mode.ts index fdd41e7e330..c90a9022a63 100644 --- a/src/core/prompts/tools/filter-tools-for-mode.ts +++ b/src/core/prompts/tools/filter-tools-for-mode.ts @@ -291,6 +291,10 @@ export function filterNativeToolsForMode( allowedToolNames.delete("run_slash_command") } + if (!experiments?.subagent) { + allowedToolNames.delete("subagent") + } + // Remove tools that are explicitly disabled via the disabledTools setting if (settings?.disabledTools?.length) { for (const toolName of settings.disabledTools) { @@ -379,6 +383,9 @@ export function isToolAllowedInMode( if (toolName === "run_slash_command") { return experiments?.runSlashCommand === true } + if (toolName === "subagent") { + return experiments?.subagent === true + } return true } diff --git a/src/core/prompts/tools/native-tools/index.ts b/src/core/prompts/tools/native-tools/index.ts index 758914d2d65..3488631dd5b 100644 --- a/src/core/prompts/tools/native-tools/index.ts +++ b/src/core/prompts/tools/native-tools/index.ts @@ -13,6 +13,7 @@ import newTask from "./new_task" import readCommandOutput from "./read_command_output" import { createReadFileTool, type ReadFileToolOptions } from "./read_file" import runSlashCommand from "./run_slash_command" +import subagent from "./subagent" import skill from "./skill" import searchReplace from "./search_replace" import edit_file from "./edit_file" @@ -60,6 +61,7 @@ export function getNativeTools(options: NativeToolsOptions = {}): OpenAI.Chat.Ch readCommandOutput, createReadFileTool(readFileOptions), runSlashCommand, + subagent, skill, searchReplace, edit_file, diff --git a/src/core/prompts/tools/native-tools/subagent.ts b/src/core/prompts/tools/native-tools/subagent.ts new file mode 100644 index 00000000000..36fa48e77aa --- /dev/null +++ b/src/core/prompts/tools/native-tools/subagent.ts @@ -0,0 +1,36 @@ +import type OpenAI from "openai" + +const SUBAGENT_DESCRIPTION = `Run a subagent in the background to do a focused sub-task. When the subagent finishes, its result or summary is returned as this tool's result and you can use it in your next step. Use when: (1) you need to offload research, exploration, or a multi-step sub-task without switching the user's view, or (2) you want read-only exploration (subagent_type "explore") with no file edits or commands. Do not use for creating a new user-visible task—use new_task instead.` + +const DESCRIPTION_PARAM = `Short label for this subagent, shown in the chat (e.g. "List exports in src", "Check README")` +const PROMPT_PARAM = `Full instructions for the subagent. Its result or summary will be returned as this tool's result.` +const SUBAGENT_TYPE_PARAM = `"explore": read-only—subagent can only use read/search/list tools (no file edits or commands). Use for research or gathering information. "general": full tools—subagent can read, edit, and run commands. Use when the sub-task may need to change files or run commands.` + +export default { + type: "function", + function: { + name: "subagent", + description: SUBAGENT_DESCRIPTION, + strict: true, + parameters: { + type: "object", + properties: { + description: { + type: "string", + description: DESCRIPTION_PARAM, + }, + prompt: { + type: "string", + description: PROMPT_PARAM, + }, + subagent_type: { + type: "string", + description: SUBAGENT_TYPE_PARAM, + enum: ["general", "explore"], + }, + }, + required: ["description", "prompt", "subagent_type"], + additionalProperties: false, + }, + }, +} satisfies OpenAI.Chat.ChatCompletionTool diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 1d4320493a0..6fd0fc6752b 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -70,6 +70,7 @@ import { t } from "../../i18n" import { getApiMetrics, hasTokenUsageChanged, hasToolUsageChanged } from "../../shared/getApiMetrics" import { ClineAskResponse } from "../../shared/WebviewMessage" import { defaultModeSlug, getModeBySlug } from "../../shared/modes" +import { SUBAGENT_TOOL_NAMES, type SubagentRunningPayload, type SubagentStructuredResult } from "../../shared/subagent" import { DiffStrategy, type ToolUse, type ToolParamName, toolParamNames } from "../../shared/tools" import { getModelMaxOutputTokens } from "../../shared/api" @@ -157,6 +158,10 @@ export interface TaskOptions extends CreateTaskOptions { workspacePath?: string /** Initial status for the task's history item (e.g., "active" for child tasks) */ initialStatus?: "active" | "delegated" | "completed" + /** When set, this task runs as a background subagent; "explore" = read-only tools */ + subagentType?: "general" | "explore" + /** When false, task is not persisted to task history (e.g. subagents). Defaults to false when subagentType is set, true otherwise. */ + needUpdateHistory?: boolean } export class Task extends EventEmitter implements TaskLike { @@ -417,6 +422,17 @@ export class Task extends EventEmitter implements TaskLike { // MessageManager for high-level message operations (lazy initialized) private _messageManager?: MessageManager + /** When set, attempt_completion will resolve this and abort instead of normal flow */ + public backgroundCompletionResolve?: (result: string | SubagentStructuredResult) => void + /** When set, tool building is restricted to read-only for "explore" */ + public subagentType?: "general" | "explore" + /** When set, child reports current step (e.g. tool description) for parent to show in subagentRunning row */ + public subagentProgressCallback?: (currentTask: string) => void + /** When false, saveClineMessages does not call updateTaskHistory (e.g. subagents). */ + private readonly needUpdateHistory: boolean + /** When this task is the parent of a running subagent, holds the child task until it completes or is cancelled. */ + public activeSubagentChild?: Task + constructor({ provider, apiConfiguration, @@ -436,9 +452,14 @@ export class Task extends EventEmitter implements TaskLike { initialTodos, workspacePath, initialStatus, + subagentType, + needUpdateHistory, }: TaskOptions) { super() + this.subagentType = subagentType + this.needUpdateHistory = needUpdateHistory ?? subagentType === undefined + if (startTask && !task && !images && !historyItem) { throw new Error("Either historyItem or task/images must be provided") } @@ -1240,7 +1261,9 @@ export class Task extends EventEmitter implements TaskLike { // - Final state is emitted when updates stop (trailing: true) this.debouncedEmitTokenUsage(tokenUsage, this.toolUsage) - await this.providerRef.deref()?.updateTaskHistory(historyItem) + if (this.needUpdateHistory) { + await this.providerRef.deref()?.updateTaskHistory(historyItem) + } return true } catch (error) { console.error("Failed to save Roo messages:", error) @@ -1258,6 +1281,32 @@ export class Task extends EventEmitter implements TaskLike { return undefined } + /** + * Updates the last "subagentRunning" say message with currentTask so the UI can show it in real time. + * No-op if no such message exists. + */ + public reportSubagentProgress(currentTask: string): void { + const idx = findLastIndex(this.clineMessages, (m) => { + if (m.type !== "say" || m.say !== "tool" || !m.text) return false + try { + const parsed = JSON.parse(m.text) as { tool?: string } + return parsed.tool === SUBAGENT_TOOL_NAMES.running + } catch { + return false + } + }) + if (idx === -1) return + const msg = this.clineMessages[idx] + try { + const payload = JSON.parse(msg.text!) as SubagentRunningPayload + payload.currentTask = currentTask + msg.text = JSON.stringify(payload) + void this.updateClineMessage(msg) + } catch { + // ignore malformed message + } + } + // Note that `partial` has three valid states true (partial message), // false (completion of partial message), undefined (individual complete // message). @@ -1671,6 +1720,7 @@ export class Task extends EventEmitter implements TaskLike { disabledTools: state?.disabledTools, modelInfo, includeAllToolsWithRestrictions: false, + subagentType: this.subagentType, }) allTools = toolsResult.tools } @@ -1795,6 +1845,7 @@ export class Task extends EventEmitter implements TaskLike { text, images, partial, + progressStatus, contextCondense, contextTruncation, }) @@ -1833,6 +1884,7 @@ export class Task extends EventEmitter implements TaskLike { say: type, text, images, + progressStatus, contextCondense, contextTruncation, }) @@ -1857,6 +1909,7 @@ export class Task extends EventEmitter implements TaskLike { text, images, checkpoint, + progressStatus, contextCondense, contextTruncation, }) @@ -1873,6 +1926,28 @@ export class Task extends EventEmitter implements TaskLike { return formatResponse.toolError(formatResponse.missingToolParameterError(paramName)) } + /** + * Starts the task loop with the given prompt. Used for background subagents. + * Call only when task was created with startTask: false and subagentType set. + */ + public async runBackgroundSubagentLoop(initialPrompt: string): Promise { + this.clineMessages = [] + this.apiConversationHistory = [] + const typeInstructions = + this.subagentType === "explore" + ? "You are running as an **explore** subagent (read-only). Use only read, search, and list tools; do not edit files or run commands. Gather the requested information and put your findings and summary in your completion; that will be returned to the parent task.\n\n" + : "You are running as a **general** subagent with full tool access. You may read, edit files, and run commands as needed. Put your findings and final result in your completion; that summary will be returned to the parent task.\n\n" + const taskContent = `${typeInstructions}${initialPrompt}` + await this.say("text", taskContent) + this.isInitialized = true + await this.initiateTaskLoop([{ type: "text", text: `\n${taskContent}\n` }]).catch((error) => { + if (this.abandoned === true || this.backgroundCompletionResolve === undefined) { + return + } + throw error + }) + } + // Lifecycle // Start / Resume / Abort / Dispose @@ -3859,6 +3934,7 @@ export class Task extends EventEmitter implements TaskLike { disabledTools: state?.disabledTools, modelInfo, includeAllToolsWithRestrictions: false, + subagentType: this.subagentType, }) allTools = toolsResult.tools } @@ -4073,6 +4149,7 @@ export class Task extends EventEmitter implements TaskLike { disabledTools: state?.disabledTools, modelInfo, includeAllToolsWithRestrictions: false, + subagentType: this.subagentType, }) contextMgmtTools = toolsResult.tools } @@ -4237,6 +4314,7 @@ export class Task extends EventEmitter implements TaskLike { disabledTools: state?.disabledTools, modelInfo, includeAllToolsWithRestrictions: supportsAllowedFunctionNames, + subagentType: this.subagentType, }) allTools = toolsResult.tools allowedFunctionNames = toolsResult.allowedFunctionNames diff --git a/src/core/task/__tests__/flushPendingToolResultsToHistory.spec.ts b/src/core/task/__tests__/flushPendingToolResultsToHistory.spec.ts index f19645d9697..cc87b46b1fb 100644 --- a/src/core/task/__tests__/flushPendingToolResultsToHistory.spec.ts +++ b/src/core/task/__tests__/flushPendingToolResultsToHistory.spec.ts @@ -448,3 +448,130 @@ describe("flushPendingToolResultsToHistory", () => { expect(task.apiConversationHistory.length).toBe(0) }) }) + +describe("reportSubagentProgress", () => { + let mockProvider: any + let mockApiConfig: ProviderSettings + let mockExtensionContext: vscode.ExtensionContext + + beforeEach(() => { + if (!TelemetryService.hasInstance()) { + TelemetryService.createInstance([]) + } + const storageUri = { fsPath: path.join(os.tmpdir(), "test-storage-reportSubagent") } + mockExtensionContext = { + globalState: { + get: vi.fn().mockImplementation((_key: keyof GlobalState) => undefined), + update: vi.fn().mockImplementation((_k, _v) => Promise.resolve()), + keys: vi.fn().mockReturnValue([]), + }, + globalStorageUri: storageUri, + workspaceState: { + get: vi.fn().mockImplementation((_k) => undefined), + update: vi.fn().mockImplementation((_k, _v) => Promise.resolve()), + keys: vi.fn().mockReturnValue([]), + }, + secrets: { + get: vi.fn().mockResolvedValue(undefined), + store: vi.fn().mockResolvedValue(undefined), + delete: vi.fn().mockResolvedValue(undefined), + }, + extensionUri: { fsPath: "/mock/extension" }, + extension: { packageJSON: { version: "1.0.0" } }, + } as unknown as vscode.ExtensionContext + const mockOutputChannel = { + name: "test", + appendLine: vi.fn(), + append: vi.fn(), + clear: vi.fn(), + show: vi.fn(), + hide: vi.fn(), + dispose: vi.fn(), + replace: vi.fn(), + } + mockProvider = new ClineProvider( + mockExtensionContext, + mockOutputChannel, + "sidebar", + new ContextProxy(mockExtensionContext), + ) as any + mockApiConfig = { + apiProvider: "anthropic", + apiModelId: "claude-3-5-sonnet-20241022", + apiKey: "test-key", + } + mockProvider.postMessageToWebview = vi.fn().mockResolvedValue(undefined) + mockProvider.postStateToWebview = vi.fn().mockResolvedValue(undefined) + mockProvider.updateTaskHistory = vi.fn().mockResolvedValue(undefined) + }) + + it("updates last subagentRunning message with currentTask and calls postMessageToWebview", () => { + const task = new Task({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "parent task", + startTask: false, + }) + const ts = 12345 + task.clineMessages = [ + { + type: "say", + say: "tool", + text: JSON.stringify({ tool: "subagentRunning", description: "Explore codebase" }), + ts, + }, + ] + + task.reportSubagentProgress("Read file for src/index.ts") + + const updated = JSON.parse(task.clineMessages[0].text!) + expect(updated.currentTask).toBe("Read file for src/index.ts") + expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({ + type: "messageUpdated", + clineMessage: task.clineMessages[0], + }) + }) + + it("does nothing when no subagentRunning message exists", () => { + const task = new Task({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "parent task", + startTask: false, + }) + task.clineMessages = [{ type: "say", say: "tool", text: JSON.stringify({ tool: "runSlashCommand" }), ts: 1 }] + + task.reportSubagentProgress("Step one") + + expect(mockProvider.postMessageToWebview).not.toHaveBeenCalled() + expect(task.clineMessages[0].text).not.toContain("currentTask") + }) + + it("updates the last subagentRunning when multiple tool messages exist", () => { + const task = new Task({ + provider: mockProvider, + apiConfiguration: mockApiConfig, + task: "parent task", + startTask: false, + }) + task.clineMessages = [ + { type: "say", say: "tool", text: JSON.stringify({ tool: "readFile" }), ts: 1 }, + { + type: "say", + say: "tool", + text: JSON.stringify({ tool: "subagentRunning", description: "Sub" }), + ts: 2, + }, + ] + + task.reportSubagentProgress("Attempt completion...") + + const lastToolMsg = task.clineMessages[1] + const parsed = JSON.parse(lastToolMsg.text!) + expect(parsed.currentTask).toBe("Attempt completion...") + expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({ + type: "messageUpdated", + clineMessage: lastToolMsg, + }) + }) +}) diff --git a/src/core/task/build-tools.ts b/src/core/task/build-tools.ts index c32d8f6f9b2..5be3862b34c 100644 --- a/src/core/task/build-tools.ts +++ b/src/core/task/build-tools.ts @@ -2,7 +2,7 @@ import path from "path" import type OpenAI from "openai" -import type { ProviderSettings, ModeConfig, ModelInfo } from "@roo-code/types" +import type { ProviderSettings, ModeConfig, ModelInfo, ToolName } from "@roo-code/types" import { customToolRegistry, formatNative } from "@roo-code/core" import type { ClineProvider } from "../webview/ClineProvider" @@ -14,6 +14,42 @@ import { filterMcpToolsForMode, resolveToolAlias, } from "../prompts/tools/filter-tools-for-mode" +import type { SubagentType } from "../../shared/subagent" +import { TOOL_GROUPS } from "../../shared/tools" + +/** Tools that require user interaction or affect parent-level UI. Disallowed for all subagents (general and explore). */ +const SUBAGENT_EXCLUDE_TOOLS: Set = new Set([ + "ask_followup_question", + "new_task", + "switch_mode", + "update_todo_list", +] as ToolName[]) + +/** + * Applies subagent-specific tool restrictions: for "explore" only read-only tools + attempt_completion; + * for any subagent, excludes nested subagent and tools that require user interaction. + */ +function applySubagentToolRestrictions( + tools: T[], + subagentType: SubagentType | undefined, + getName: (t: T) => string, + resolveName: (name: string) => string, +): T[] { + let result = tools + if (subagentType === "explore") { + const readOnlyNames = new Set([...TOOL_GROUPS.read.tools, "attempt_completion"]) + result = result.filter((t) => readOnlyNames.has(resolveName(getName(t)))) + } + if (subagentType !== undefined) { + result = result.filter((t) => { + const name = resolveName(getName(t)) + if (name === "subagent") return false + if (SUBAGENT_EXCLUDE_TOOLS.has(name)) return false + return true + }) + } + return result +} interface BuildToolsOptions { provider: ClineProvider @@ -31,6 +67,8 @@ interface BuildToolsOptions { * to pass all tool definitions while restricting callable tools. */ includeAllToolsWithRestrictions?: boolean + /** When "explore", only read-only tools are allowed (no edit/command) */ + subagentType?: "general" | "explore" } interface BuildToolsResult { @@ -90,6 +128,7 @@ export async function buildNativeToolsArrayWithRestrictions(options: BuildToolsO disabledTools, modelInfo, includeAllToolsWithRestrictions, + subagentType, } = options const mcpHub = provider.getMcpHub() @@ -141,8 +180,9 @@ export async function buildNativeToolsArrayWithRestrictions(options: BuildToolsO } } - // Combine filtered tools (for backward compatibility and for allowedFunctionNames) - const filteredTools = [...filteredNativeTools, ...filteredMcpTools, ...nativeCustomTools] + // Combine filtered tools, then apply subagent restrictions when this task is a subagent + const combined = [...filteredNativeTools, ...filteredMcpTools, ...nativeCustomTools] + const filteredTools = applySubagentToolRestrictions(combined, subagentType, getToolName, resolveToolAlias) // If includeAllToolsWithRestrictions is true, return ALL tools but provide // allowed names based on mode filtering @@ -150,7 +190,7 @@ export async function buildNativeToolsArrayWithRestrictions(options: BuildToolsO // Combine ALL tools (unfiltered native + all MCP + custom) const allTools = [...nativeTools, ...mcpTools, ...nativeCustomTools] - // Extract names of tools that are allowed based on mode filtering. + // Extract names of tools that are allowed based on mode filtering and subagent restrictions. // Resolve any alias names to canonical names to ensure consistency with allTools // (which uses canonical names). This prevents Gemini errors when tools are renamed // to aliases in filteredTools but allTools contains the original canonical names. diff --git a/src/core/tools/AttemptCompletionTool.ts b/src/core/tools/AttemptCompletionTool.ts index a406a15c8b4..14921fe533a 100644 --- a/src/core/tools/AttemptCompletionTool.ts +++ b/src/core/tools/AttemptCompletionTool.ts @@ -88,6 +88,14 @@ export class AttemptCompletionTool extends BaseTool<"attempt_completion"> { TelemetryService.instance.captureTaskCompleted(task.taskId) task.emit(RooCodeEventName.TaskCompleted, task.taskId, task.getTokenUsage(), task.toolUsage) + if (task.backgroundCompletionResolve) { + task.subagentProgressCallback = undefined + task.backgroundCompletionResolve(result) + task.backgroundCompletionResolve = undefined + task.abortTask() + return + } + // Check for subtask using parentTaskId (metadata-driven delegation) if (task.parentTaskId) { // Check if this subtask has already completed and returned to parent diff --git a/src/core/tools/SubagentTool.ts b/src/core/tools/SubagentTool.ts new file mode 100644 index 00000000000..13092b6094a --- /dev/null +++ b/src/core/tools/SubagentTool.ts @@ -0,0 +1,128 @@ +import { + SUBAGENT_CANCELLED_MODEL_MESSAGE, + SUBAGENT_FAILED_MODEL_MESSAGE, + SUBAGENT_STATUS_STARTING, + SUBAGENT_TOOL_NAMES, + type RunSubagentInBackgroundParams, + type SubagentCompletedPayload, + type SubagentRunningPayload, + type SubagentStructuredResult, + isSubagentRunner, +} from "../../shared/subagent" +import type { ToolUse } from "../../shared/tools" +import { Task } from "../task/Task" +import { formatResponse } from "../prompts/responses" +import { BaseTool, ToolCallbacks } from "./BaseTool" + +interface SubagentParams { + description: string + prompt: string + subagent_type: "general" | "explore" +} + +export class SubagentTool extends BaseTool<"subagent"> { + readonly name = "subagent" as const + + parseLegacy(params: Partial>): SubagentParams { + return { + description: params.description ?? "", + prompt: params.prompt ?? "", + subagent_type: (params.subagent_type === "general" || params.subagent_type === "explore" + ? params.subagent_type + : "general") as "general" | "explore", + } + } + + async execute(params: SubagentParams, task: Task, callbacks: ToolCallbacks): Promise { + const { description, prompt, subagent_type } = params + const { pushToolResult } = callbacks + + const provider = task.providerRef.deref() + if (!provider || !isSubagentRunner(provider)) { + pushToolResult(formatResponse.toolError("Provider reference lost")) + return + } + + if (!description?.trim()) { + task.consecutiveMistakeCount++ + task.recordToolError("subagent") + task.didToolFailInCurrentTurn = true + pushToolResult(await task.sayAndCreateMissingParamError("subagent", "description")) + return + } + if (!prompt?.trim()) { + task.consecutiveMistakeCount++ + task.recordToolError("subagent") + task.didToolFailInCurrentTurn = true + pushToolResult(await task.sayAndCreateMissingParamError("subagent", "prompt")) + return + } + + task.consecutiveMistakeCount = 0 + + const runningPayload: SubagentRunningPayload = { + tool: SUBAGENT_TOOL_NAMES.running, + description, + currentTask: SUBAGENT_STATUS_STARTING, + } + const runningText = JSON.stringify(runningPayload) + const progressStatus = { icon: "sync", spin: true } + + await task.say("tool", runningText, undefined, true, undefined, progressStatus, { + isNonInteractive: true, + }) + + try { + const runParams: RunSubagentInBackgroundParams = { + parentTaskId: task.taskId, + prompt, + subagentType: subagent_type, + onProgress: (currentTask) => task.reportSubagentProgress(currentTask), + } + const result = await provider.runSubagentInBackground(runParams) + + const isStructured = (r: string | SubagentStructuredResult): r is SubagentStructuredResult => + typeof r === "object" && r !== null && "code" in r && "messageKey" in r + + let completedPayload: SubagentCompletedPayload + let toolResult: string + if (isStructured(result)) { + completedPayload = { + tool: SUBAGENT_TOOL_NAMES.completed, + description, + result: SUBAGENT_CANCELLED_MODEL_MESSAGE, + resultCode: result.code, + messageKey: result.messageKey, + } + toolResult = SUBAGENT_CANCELLED_MODEL_MESSAGE + } else { + completedPayload = { + tool: SUBAGENT_TOOL_NAMES.completed, + description, + result, + } + toolResult = result + } + const completedText = JSON.stringify(completedPayload) + await task.say("tool", completedText, undefined, false, undefined, undefined, { + isNonInteractive: true, + }) + pushToolResult(formatResponse.toolResult(toolResult)) + } catch (error) { + console.error("Subagent failed:", error) + task.recordToolError("subagent") + const errorPayload: SubagentCompletedPayload = { + tool: SUBAGENT_TOOL_NAMES.completed, + description, + error: SUBAGENT_FAILED_MODEL_MESSAGE, + } + const errorPayloadStr = JSON.stringify(errorPayload) + await task.say("tool", errorPayloadStr, undefined, false, undefined, undefined, { + isNonInteractive: true, + }) + pushToolResult(formatResponse.toolError(SUBAGENT_FAILED_MODEL_MESSAGE)) + } + } +} + +export const subagentTool = new SubagentTool() diff --git a/src/core/tools/__tests__/SubagentTool.spec.ts b/src/core/tools/__tests__/SubagentTool.spec.ts new file mode 100644 index 00000000000..dcd6dd4d830 --- /dev/null +++ b/src/core/tools/__tests__/SubagentTool.spec.ts @@ -0,0 +1,196 @@ +import { + SUBAGENT_CANCELLED_MODEL_MESSAGE, + SUBAGENT_CANCELLED_STRUCTURED_RESULT, + SUBAGENT_FAILED_MODEL_MESSAGE, + type SubagentStructuredResult, +} from "../../../shared/subagent" +import { subagentTool } from "../SubagentTool" +import type { ToolUse } from "../../../shared/tools" + +vi.mock("../../prompts/responses", () => ({ + formatResponse: { + toolError: (msg: string) => `Tool Error: ${msg}`, + toolResult: (content: string) => content, + }, +})) + +const mockRunSubagentInBackground = + vi.fn< + (p: { + parentTaskId: string + prompt: string + subagentType: "general" | "explore" + }) => Promise + >() +const mockSay = vi.fn() +const mockPushToolResult = vi.fn() +const mockHandleError = vi.fn() +const mockSayAndCreateMissingParamError = vi.fn().mockResolvedValue("Missing param error") +const mockRecordToolError = vi.fn() +const mockReportSubagentProgress = vi.fn() + +const mockTask = { + taskId: "parent-1", + consecutiveMistakeCount: 0, + didToolFailInCurrentTurn: false, + recordToolError: mockRecordToolError, + sayAndCreateMissingParamError: mockSayAndCreateMissingParamError, + say: mockSay, + reportSubagentProgress: mockReportSubagentProgress, + providerRef: { + deref: () => ({ + runSubagentInBackground: mockRunSubagentInBackground, + }), + }, +} + +describe("SubagentTool", () => { + beforeEach(() => { + vi.clearAllMocks() + mockRunSubagentInBackground.mockResolvedValue("Subagent completed successfully.") + }) + + describe("parseLegacy", () => { + it("parses description, prompt, and subagent_type", () => { + const params = subagentTool.parseLegacy({ + description: "Do something", + prompt: "Detailed instructions", + subagent_type: "explore", + }) + expect(params).toEqual({ + description: "Do something", + prompt: "Detailed instructions", + subagent_type: "explore", + }) + }) + + it("defaults subagent_type to general when invalid", () => { + const params = subagentTool.parseLegacy({ + description: "X", + prompt: "Y", + subagent_type: "invalid", + }) + expect(params.subagent_type).toBe("general") + }) + }) + + describe("execute", () => { + it("calls runSubagentInBackground and pushes result on success", async () => { + const block: ToolUse<"subagent"> = { + type: "tool_use", + name: "subagent", + params: {}, + partial: false, + nativeArgs: { + description: "Explore files", + prompt: "List files in src/", + subagent_type: "explore", + }, + } + + await subagentTool.execute(block.nativeArgs!, mockTask as any, { + askApproval: vi.fn(), + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockRunSubagentInBackground).toHaveBeenCalledWith( + expect.objectContaining({ + parentTaskId: "parent-1", + prompt: "List files in src/", + subagentType: "explore", + }), + ) + expect(mockRunSubagentInBackground).toHaveBeenCalledWith( + expect.objectContaining({ + onProgress: expect.any(Function), + }), + ) + expect(mockSay).toHaveBeenCalled() + expect(mockPushToolResult).toHaveBeenCalledWith("Subagent completed successfully.") + }) + + it("sends completed payload with resultCode/messageKey and pushes model message when result is structured (cancelled)", async () => { + mockRunSubagentInBackground.mockResolvedValue(SUBAGENT_CANCELLED_STRUCTURED_RESULT) + + await subagentTool.execute( + { description: "Do X", prompt: "Do it", subagent_type: "general" }, + mockTask as any, + { + askApproval: vi.fn(), + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }, + ) + + const sayCalls = mockSay.mock.calls + const completedCall = sayCalls.find((c) => { + try { + const payload = JSON.parse(c[1]) + return payload.tool === "subagentCompleted" + } catch { + return false + } + }) + expect(completedCall).toBeDefined() + const payload = JSON.parse(completedCall![1]) + expect(payload.resultCode).toBe("CANCELLED") + expect(payload.messageKey).toBe("chat:subagents.cancelledByUser") + expect(payload.result).toBe(SUBAGENT_CANCELLED_MODEL_MESSAGE) + expect(mockPushToolResult).toHaveBeenCalledWith(SUBAGENT_CANCELLED_MODEL_MESSAGE) + }) + + it("pushes error when description is missing", async () => { + await subagentTool.execute( + { description: "", prompt: "Do it", subagent_type: "general" }, + mockTask as any, + { + askApproval: vi.fn(), + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }, + ) + + expect(mockRunSubagentInBackground).not.toHaveBeenCalled() + expect(mockSayAndCreateMissingParamError).toHaveBeenCalledWith("subagent", "description") + expect(mockPushToolResult).toHaveBeenCalledWith("Missing param error") + }) + + it("pushes error when prompt is missing", async () => { + await subagentTool.execute({ description: "X", prompt: "", subagent_type: "general" }, mockTask as any, { + askApproval: vi.fn(), + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockRunSubagentInBackground).not.toHaveBeenCalled() + expect(mockSayAndCreateMissingParamError).toHaveBeenCalledWith("subagent", "prompt") + }) + + it("logs error and pushes generic tool error when runSubagentInBackground rejects (no raw message leak)", async () => { + mockRunSubagentInBackground.mockRejectedValue(new Error("API failed")) + + await subagentTool.execute({ description: "X", prompt: "Y", subagent_type: "general" }, mockTask as any, { + askApproval: vi.fn(), + handleError: mockHandleError, + pushToolResult: mockPushToolResult, + }) + + expect(mockHandleError).not.toHaveBeenCalled() + expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining(SUBAGENT_FAILED_MODEL_MESSAGE)) + expect(mockRecordToolError).toHaveBeenCalledWith("subagent") + const sayCalls = mockSay.mock.calls + const completedCall = sayCalls.find((c) => { + try { + const payload = JSON.parse(c[1]) + return payload.tool === "subagentCompleted" && payload.error !== undefined + } catch { + return false + } + }) + expect(completedCall).toBeDefined() + const payload = JSON.parse(completedCall![1]) + expect(payload.error).toBe(SUBAGENT_FAILED_MODEL_MESSAGE) + }) + }) +}) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 231e2794c52..8639f667756 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -60,6 +60,11 @@ import { experimentDefault } from "../../shared/experiments" import { formatLanguage } from "../../shared/language" import { WebviewMessage } from "../../shared/WebviewMessage" import { EMBEDDING_MODEL_PROFILES } from "../../shared/embeddingModels" +import { + SUBAGENT_CANCELLED_STRUCTURED_RESULT, + type RunSubagentInBackgroundParams, + type SubagentStructuredResult, +} from "../../shared/subagent" import { ProfileValidator } from "../../shared/ProfileValidator" import { Terminal } from "../../integrations/terminal/Terminal" @@ -2977,6 +2982,22 @@ export class ClineProvider return } + // If the current task has a running subagent, cancel only the subagent and return the cancellation result to the parent. + const subagentChild = task.activeSubagentChild + if (subagentChild) { + task.activeSubagentChild = undefined + subagentChild.subagentProgressCallback = undefined + if (subagentChild.backgroundCompletionResolve) { + subagentChild.backgroundCompletionResolve(SUBAGENT_CANCELLED_STRUCTURED_RESULT) + subagentChild.backgroundCompletionResolve = undefined + } + subagentChild.abandoned = true + subagentChild.cancelCurrentRequest() + subagentChild.abortTask() + this.log(`[cancelTask] cancelled subagent ${subagentChild.taskId}.${subagentChild.instanceId}`) + return + } + console.log(`[cancelTask] cancelling task ${task.taskId}.${task.instanceId}`) const { historyItem, uiMessagesFilePath } = await this.getTaskWithId(task.taskId) @@ -3333,6 +3354,85 @@ export class ClineProvider return child } + /** + * Runs a subagent in the background. Parent stays current; child runs until attempt_completion. + * Resolves with the completion result string, or rejects on error. + */ + public async runSubagentInBackground( + params: RunSubagentInBackgroundParams, + ): Promise { + const { parentTaskId, prompt, subagentType, onProgress } = params + const parent = this.getCurrentTask() + if (!parent) { + throw new Error("[runSubagentInBackground] No current task") + } + if (parent.taskId !== parentTaskId) { + throw new Error( + `[runSubagentInBackground] Parent mismatch: expected ${parentTaskId}, current ${parent.taskId}`, + ) + } + + const { apiConfiguration, organizationAllowList, enableCheckpoints, checkpointTimeout, experiments } = + await this.getState() + + if (!ProfileValidator.isProfileAllowed(apiConfiguration, organizationAllowList)) { + throw new OrganizationAllowListViolationError(t("common:errors.violated_organization_allowlist")) + } + + const child = new Task({ + provider: this, + apiConfiguration, + enableCheckpoints, + checkpointTimeout, + consecutiveMistakeLimit: apiConfiguration.consecutiveMistakeLimit, + task: prompt, + images: undefined, + experiments, + rootTask: this.clineStack.length > 0 ? this.clineStack[0] : undefined, + parentTask: parent, + taskNumber: this.clineStack.length + 1, + onCreated: this.taskCreationCallback, + startTask: false, + subagentType, + initialStatus: "active", + }) + if (onProgress) { + child.subagentProgressCallback = onProgress + } + + parent.activeSubagentChild = child + + return new Promise((resolve, reject) => { + let settled = false + child.backgroundCompletionResolve = (result: string | SubagentStructuredResult) => { + if (!settled) { + settled = true + parent.activeSubagentChild = undefined + resolve(result) + } + } + child + .runBackgroundSubagentLoop(prompt) + .then(() => { + if (!settled) { + settled = true + parent.activeSubagentChild = undefined + reject(new Error("Subagent ended without attempt_completion")) + } + }) + .catch((err) => { + if (!settled) { + settled = true + parent.activeSubagentChild = undefined + reject(err) + } + }) + .finally(() => { + parent.activeSubagentChild = undefined + }) + }) + } + /** * Reopen parent task from delegation with write-back and events. */ diff --git a/src/shared/__tests__/experiments.spec.ts b/src/shared/__tests__/experiments.spec.ts index 92a7d7604ff..3e8d149017e 100644 --- a/src/shared/__tests__/experiments.spec.ts +++ b/src/shared/__tests__/experiments.spec.ts @@ -21,6 +21,7 @@ describe("experiments", () => { imageGeneration: false, runSlashCommand: false, customTools: false, + subagent: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)).toBe(false) }) @@ -31,6 +32,7 @@ describe("experiments", () => { imageGeneration: false, runSlashCommand: false, customTools: false, + subagent: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)).toBe(true) }) @@ -41,6 +43,7 @@ describe("experiments", () => { imageGeneration: false, runSlashCommand: false, customTools: false, + subagent: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)).toBe(false) }) diff --git a/src/shared/experiments.ts b/src/shared/experiments.ts index e189f99e23d..f3b1fbc2dec 100644 --- a/src/shared/experiments.ts +++ b/src/shared/experiments.ts @@ -5,6 +5,7 @@ export const EXPERIMENT_IDS = { IMAGE_GENERATION: "imageGeneration", RUN_SLASH_COMMAND: "runSlashCommand", CUSTOM_TOOLS: "customTools", + SUBAGENT: "subagent", } as const satisfies Record type _AssertExperimentIds = AssertEqual>> @@ -20,6 +21,7 @@ export const experimentConfigsMap: Record = { IMAGE_GENERATION: { enabled: false }, RUN_SLASH_COMMAND: { enabled: false }, CUSTOM_TOOLS: { enabled: false }, + SUBAGENT: { enabled: false }, } export const experimentDefault = Object.fromEntries( diff --git a/src/shared/subagent.ts b/src/shared/subagent.ts new file mode 100644 index 00000000000..674ead5086b --- /dev/null +++ b/src/shared/subagent.ts @@ -0,0 +1,74 @@ +/** + * Shared constants and types for the subagent feature. + * Used by SubagentTool, Task, build-tools, and UI to stay in sync with tool message shapes. + */ + +/** Tool names used in say("tool", ...) payloads for subagent progress and completion. */ +export const SUBAGENT_TOOL_NAMES = { + running: "subagentRunning", + completed: "subagentCompleted", +} as const + +/** i18n keys sent as currentTask; webview uses t(key) to show locale. */ +export const SUBAGENT_STATUS_STARTING = "chat:subagents.starting" +export const SUBAGENT_STATUS_THINKING = "chat:subagents.thinking" + +/** Structured result codes for subagent completion; webview uses messageKey with t() for display. */ +export const SUBAGENT_RESULT_CODE_CANCELLED = "CANCELLED" as const +export const SUBAGENT_CANCELLED_MESSAGE_KEY = "chat:subagents.cancelledByUser" as const + +/** Sent when the user cancels the subagent. Extension passes this to backgroundCompletionResolve. */ +export const SUBAGENT_CANCELLED_STRUCTURED_RESULT = { + code: SUBAGENT_RESULT_CODE_CANCELLED, + messageKey: SUBAGENT_CANCELLED_MESSAGE_KEY, +} as const + +/** English message shown to the model when subagent is cancelled (tool result). */ +export const SUBAGENT_CANCELLED_MODEL_MESSAGE = "Subagent was cancelled by the user." + +/** Generic message shown to the model/UI when subagent fails (avoids leaking internal error details). */ +export const SUBAGENT_FAILED_MODEL_MESSAGE = "The subagent failed." + +export type SubagentStructuredResult = typeof SUBAGENT_CANCELLED_STRUCTURED_RESULT + +/** Payload for the "subagentRunning" tool message (progress updates). */ +export interface SubagentRunningPayload { + tool: typeof SUBAGENT_TOOL_NAMES.running + description?: string + currentTask?: string +} + +/** Payload for the "subagentCompleted" tool message (result or error). */ +export interface SubagentCompletedPayload { + tool: typeof SUBAGENT_TOOL_NAMES.completed + description?: string + result?: string + error?: string + /** When set, webview shows t(messageKey) instead of result/error. */ + resultCode?: string + messageKey?: string +} + +export type SubagentType = "general" | "explore" + +/** Parameters for running a subagent in the background. */ +export interface RunSubagentInBackgroundParams { + parentTaskId: string + prompt: string + subagentType: SubagentType + onProgress?: (currentTask: string) => void +} + +/** Provider interface for running a subagent. Allows SubagentTool to call without type assertion. */ +export interface SubagentRunner { + runSubagentInBackground(params: RunSubagentInBackgroundParams): Promise +} + +export function isSubagentRunner(provider: unknown): provider is SubagentRunner { + return ( + typeof provider === "object" && + provider !== null && + "runSubagentInBackground" in provider && + typeof (provider as SubagentRunner).runSubagentInBackground === "function" + ) +} diff --git a/src/shared/tools.ts b/src/shared/tools.ts index d2dd9907b17..96d0abf1c74 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -57,6 +57,8 @@ export const toolParamNames = [ "end_line", "todos", "prompt", + "description", // subagent short label + "subagent_type", "image", // read_file parameters (native protocol) "operations", // search_and_replace parameter for multiple operations @@ -116,6 +118,7 @@ export type NativeToolArgs = { update_todo_list: { todos: string } use_mcp_tool: { server_name: string; tool_name: string; arguments?: Record } write_to_file: { path: string; content: string } + subagent: { description: string; prompt: string; subagent_type: "general" | "explore" } // Add more tools as they are migrated to native protocol } @@ -290,6 +293,7 @@ export const TOOL_DISPLAY_NAMES: Record = { skill: "load skill", generate_image: "generate images", custom_tool: "use custom tools", + subagent: "run subagent", } as const // Define available tool groups. @@ -322,6 +326,7 @@ export const ALWAYS_AVAILABLE_TOOLS: ToolName[] = [ "update_todo_list", "run_slash_command", "skill", + "subagent", ] as const /** diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 1f3f8f5a4f2..1d5135f3221 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -76,6 +76,50 @@ import { cn } from "@/lib/utils" import { PathTooltip } from "../ui/PathTooltip" import { OpenMarkdownPreviewButton } from "./OpenMarkdownPreviewButton" +/** i18n keys backend sends as currentTask; we pass to t() for display. */ +const SUBAGENT_STATUS_KEYS = ["chat:subagents.starting", "chat:subagents.thinking"] as const + +interface ParsedSubagentTask { + toolLabel: string + purpose: string | null + isGeneric: boolean +} + +/** + * Parses backend progress strings like "[read_file for 15 files]" or "Thinking..." into + * tool label and purpose for highlighted display. getToolLabel(key) returns the localized tool name. + */ +function parseSubagentCurrentTask(raw: string, getToolLabel: (toolKey: string) => string): ParsedSubagentTask { + const s = raw.trim() + if (!s) return { toolLabel: "", purpose: "...", isGeneric: true } + + const fallbackFormat = (str: string) => str.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) + + // Generic status (no brackets) – show as-is + if (!s.startsWith("[")) { + return { toolLabel: "", purpose: s.endsWith("...") ? s : s + "...", isGeneric: true } + } + + // Strip brackets and parse "[tool_name for purpose]" + const inner = s.slice(1, s.endsWith("]") ? s.length - 1 : s.length).trim() + const forIndex = inner.indexOf(" for ") + if (forIndex === -1) { + const toolKey = inner.replace(/\s+/g, "_").toLowerCase() + const toolLabel = getToolLabel(toolKey) || fallbackFormat(inner) + return { toolLabel, purpose: null, isGeneric: false } + } + + const toolPart = inner.slice(0, forIndex).trim() + const purposePart = inner.slice(forIndex + 5).trim() + const toolKey = toolPart.replace(/\s+/g, "_").toLowerCase() + const toolLabel = getToolLabel(toolKey) || fallbackFormat(toolPart) + let purpose = purposePart + if ((purpose.startsWith("'") && purpose.endsWith("'")) || (purpose.startsWith('"') && purpose.endsWith('"'))) { + purpose = purpose.slice(1, -1) + } + return { toolLabel, purpose: purpose || null, isGeneric: false } +} + // Helper function to get previous todos before a specific message function getPreviousTodos(messages: ClineMessage[], currentMessageTs: number): any[] { // Find the previous updateTodoList message before the current one @@ -1518,6 +1562,91 @@ export const ChatRowContent = ({ ) } + case "subagentRunning": { + const desc = sayTool.description ?? "" + const currentTask = sayTool.currentTask + const getToolLabel = (key: string) => + t("chat:subagents.toolDisplayNames." + key, { + defaultValue: key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()), + }) + const parsed = currentTask ? parseSubagentCurrentTask(currentTask, getToolLabel) : null + const purposeKey = parsed?.purpose?.replace(/\.\.\.$/, "") + const displayPurpose = + purposeKey && + SUBAGENT_STATUS_KEYS.includes(purposeKey as (typeof SUBAGENT_STATUS_KEYS)[number]) + ? t(purposeKey) + : parsed?.purpose + return ( +
+
+ {message.progressStatus?.icon && ( + + )} + {t("chat:subagents.runningLabel", { description: desc })} +
+ {parsed && ( +
+ {parsed.toolLabel ? ( + displayPurpose ? ( + <> + {parsed.toolLabel}: {displayPurpose} + {!parsed.purpose?.endsWith("...") && "..."} + + ) : ( + <>{parsed.toolLabel}... + ) + ) : ( + <> + {displayPurpose?.endsWith("...") + ? displayPurpose + : `${displayPurpose ?? ""}...`} + + )} +
+ )} +
+ ) + } + case "subagentCompleted": { + const hasError = !!sayTool.error + return ( + <> +
+ + {t("chat:subagents.completedLabel")} + {sayTool.description && ( + + {sayTool.description} + + )} +
+ {(sayTool.result || + sayTool.error || + (sayTool.resultCode && sayTool.messageKey)) && ( +
+ {sayTool.resultCode === "CANCELLED" && sayTool.messageKey + ? t(sayTool.messageKey) + : hasError + ? sayTool.error + : sayTool.result} +
+ )} + + ) + } default: return null } diff --git a/webview-ui/src/components/common/CodeAccordion.tsx b/webview-ui/src/components/common/CodeAccordion.tsx index 479e94a8846..c3c96598146 100644 --- a/webview-ui/src/components/common/CodeAccordion.tsx +++ b/webview-ui/src/components/common/CodeAccordion.tsx @@ -1,6 +1,7 @@ import { memo, useMemo } from "react" import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react" import { type ToolProgressStatus } from "@roo-code/types" +import { cn } from "@src/lib/utils" import { getLanguageFromPath } from "@src/utils/getLanguageFromPath" import { formatPathTooltip } from "@src/utils/formatPathTooltip" @@ -90,7 +91,14 @@ const CodeAccordion = ({ progressStatus.text && ( <> {progressStatus.icon && ( - + )} {progressStatus.text} diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx index 2a5e74c40d2..23f0aa2d86e 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx @@ -234,6 +234,7 @@ describe("mergeExtensionState", () => { imageGeneration: false, runSlashCommand: false, customTools: false, + subagent: false, } as Record, checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS + 5, } @@ -250,6 +251,7 @@ describe("mergeExtensionState", () => { imageGeneration: false, runSlashCommand: false, customTools: false, + subagent: false, }) }) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index f029391a379..2cc4554dfa4 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -269,6 +269,36 @@ "completionInstructions": "Subtasca completada! Pots revisar els resultats i suggerir correccions o següents passos. Si tot sembla correcte, confirma per tornar el resultat a la tasca principal.", "goToSubtask": "Veure tasca" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo té una pregunta" }, diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 2c83cabbbcb..2f3973e111d 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "Eines actualitzades correctament", "refreshError": "Error en actualitzar les eines", "toolParameters": "Paràmetres" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 2301f966b15..40310d59ffb 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -269,6 +269,36 @@ "completionInstructions": "Teilaufgabe abgeschlossen! Du kannst die Ergebnisse überprüfen und Korrekturen oder nächste Schritte vorschlagen. Wenn alles gut aussieht, bestätige, um das Ergebnis an die übergeordnete Aufgabe zurückzugeben.", "goToSubtask": "Aufgabe anzeigen" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo hat eine Frage" }, diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index c31d29147d4..9319e4347a6 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "Tools erfolgreich aktualisiert", "refreshError": "Fehler beim Aktualisieren der Tools", "toolParameters": "Parameter" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 4899859e3a6..9f348f28493 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -311,6 +311,36 @@ "completionInstructions": "You can review the results and suggest any corrections or next steps. If everything looks good, confirm to return the result to the parent task.", "goToSubtask": "View task" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo has a question" }, diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 3b2497aaee7..d285f31172a 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -886,6 +886,10 @@ "refreshSuccess": "Tools refreshed successfully", "refreshError": "Failed to refresh tools", "toolParameters": "Parameters" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 1b2bd24a55d..0cede0fc0b8 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -269,6 +269,36 @@ "completionInstructions": "¡Subtarea completada! Puedes revisar los resultados y sugerir correcciones o próximos pasos. Si todo se ve bien, confirma para devolver el resultado a la tarea principal.", "goToSubtask": "Ver tarea" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo tiene una pregunta" }, diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 6595c4f9079..24b712d653a 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "Herramientas actualizadas correctamente", "refreshError": "Error al actualizar las herramientas", "toolParameters": "Parámetros" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index adb77f40b9a..1fc5fa6c5d5 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -269,6 +269,36 @@ "completionInstructions": "Sous-tâche terminée ! Vous pouvez examiner les résultats et suggérer des corrections ou les prochaines étapes. Si tout semble bon, confirmez pour retourner le résultat à la tâche parente.", "goToSubtask": "Afficher la tâche" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo a une question" }, diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 56337bda14c..9f763c36e09 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "Outils actualisés avec succès", "refreshError": "Échec de l'actualisation des outils", "toolParameters": "Paramètres" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index b58481f9108..5f9ed179ad7 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -269,6 +269,36 @@ "completionInstructions": "उपकार्य पूर्ण! आप परिणामों की समीक्षा कर सकते हैं और सुधार या अगले चरण सुझा सकते हैं। यदि सब कुछ ठीक लगता है, तो मुख्य कार्य को परिणाम वापस करने के लिए पुष्टि करें।", "goToSubtask": "कार्य देखें" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo का एक प्रश्न है" }, diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index abd334bec09..113cc32eea9 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "टूल्स सफलतापूर्वक रिफ्रेश हुए", "refreshError": "टूल्स रिफ्रेश करने में विफल", "toolParameters": "पैरामीटर्स" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 98b08d48771..b46b4703127 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -320,6 +320,36 @@ "completionInstructions": "Subtugas selesai! Kamu bisa meninjau hasilnya dan menyarankan koreksi atau langkah selanjutnya. Jika semuanya terlihat baik, konfirmasi untuk mengembalikan hasil ke tugas induk.", "goToSubtask": "Lihat tugas" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo punya pertanyaan" }, diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 1ebcf2073b6..107da42c76a 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "Tool berhasil direfresh", "refreshError": "Gagal merefresh tool", "toolParameters": "Parameter" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index b2b9adf6851..bbacbba675d 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -269,6 +269,36 @@ "completionInstructions": "Sottoattività completata! Puoi rivedere i risultati e suggerire correzioni o prossimi passi. Se tutto sembra a posto, conferma per restituire il risultato all'attività principale.", "goToSubtask": "Visualizza attività" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo ha una domanda" }, diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 4a0c7161654..a2a52bad404 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "Strumenti aggiornati con successo", "refreshError": "Impossibile aggiornare gli strumenti", "toolParameters": "Parametri" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index e0e8ff24600..054eb7e5a93 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -269,6 +269,36 @@ "completionInstructions": "サブタスク完了!結果を確認し、修正や次のステップを提案できます。問題なければ、親タスクに結果を返すために確認してください。", "goToSubtask": "タスクを表示" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Rooは質問があります" }, diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index b0d921571af..a27447da471 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "ツールが正常に更新されました", "refreshError": "ツールの更新に失敗しました", "toolParameters": "パラメーター" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index ff3e419975d..ed40e22758b 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -269,6 +269,36 @@ "completionInstructions": "하위 작업 완료! 결과를 검토하고 수정 사항이나 다음 단계를 제안할 수 있습니다. 모든 것이 괜찮아 보이면, 부모 작업에 결과를 반환하기 위해 확인해주세요.", "goToSubtask": "작업 보기" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo에게 질문이 있습니다" }, diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 88fc8e6d79e..5df728e7427 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "도구가 성공적으로 새로고침되었습니다", "refreshError": "도구 새로고침에 실패했습니다", "toolParameters": "매개변수" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index d79c95d9e97..3c1201cb390 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -264,6 +264,36 @@ "completionInstructions": "Subtaak voltooid! Je kunt de resultaten bekijken en eventuele correcties of volgende stappen voorstellen. Als alles goed is, bevestig dan om het resultaat terug te sturen naar de hoofdtaak.", "goToSubtask": "Taak weergeven" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo heeft een vraag" }, diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index fcfad37d376..e91bc30fa0c 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "Tools succesvol vernieuwd", "refreshError": "Fout bij vernieuwen van tools", "toolParameters": "Parameters" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index ddf225bae07..d10d2b733a5 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -269,6 +269,36 @@ "completionInstructions": "Podzadanie zakończone! Możesz przejrzeć wyniki i zasugerować poprawki lub następne kroki. Jeśli wszystko wygląda dobrze, potwierdź, aby zwrócić wynik do zadania nadrzędnego.", "goToSubtask": "Wyświetl zadanie" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo ma pytanie" }, diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index fa48bc6b212..ed65a2d03d3 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "Narzędzia odświeżone pomyślnie", "refreshError": "Nie udało się odświeżyć narzędzi", "toolParameters": "Parametry" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index b0ad26bfbd2..c39f09fa8fa 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -269,6 +269,36 @@ "completionInstructions": "Subtarefa concluída! Você pode revisar os resultados e sugerir correções ou próximos passos. Se tudo parecer bom, confirme para retornar o resultado à tarefa principal.", "goToSubtask": "Ver tarefa" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo tem uma pergunta" }, diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index a8387e05121..ec445c1d9ef 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "Ferramentas atualizadas com sucesso", "refreshError": "Falha ao atualizar ferramentas", "toolParameters": "Parâmetros" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 3681d3d648f..b70a5b6af0f 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -265,6 +265,36 @@ "completionInstructions": "Подзадача завершена! Вы можете просмотреть результаты и предложить исправления или следующие шаги. Если всё в порядке, подтвердите для возврата результата в родительскую задачу.", "goToSubtask": "Просмотреть задачу" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "У Roo есть вопрос" }, diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index fe24ebee299..034d208a63a 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "Инструменты успешно обновлены", "refreshError": "Не удалось обновить инструменты", "toolParameters": "Параметры" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 216d8e07975..4075337ceac 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -270,6 +270,36 @@ "completionInstructions": "Alt görev tamamlandı! Sonuçları inceleyebilir ve düzeltmeler veya sonraki adımlar önerebilirsiniz. Her şey iyi görünüyorsa, sonucu üst göreve döndürmek için onaylayın.", "goToSubtask": "Görevi görüntüle" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo'nun bir sorusu var" }, diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 7171718f1c5..4523af05e1a 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "Araçlar başarıyla yenilendi", "refreshError": "Araçlar yenilenemedi", "toolParameters": "Parametreler" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 83cb5a03f73..6d40a572e0f 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -270,6 +270,36 @@ "completionInstructions": "Nhiệm vụ phụ đã hoàn thành! Bạn có thể xem lại kết quả và đề xuất các sửa đổi hoặc bước tiếp theo. Nếu mọi thứ có vẻ tốt, hãy xác nhận để trả kết quả về nhiệm vụ chính.", "goToSubtask": "Xem nhiệm vụ" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo có một câu hỏi" }, diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 95b4f2d6863..a45046c433d 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "Làm mới công cụ thành công", "refreshError": "Không thể làm mới công cụ", "toolParameters": "Thông số" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 049a06a7ce8..e07fe4c136f 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -270,6 +270,36 @@ "completionInstructions": "子任务已完成!您可以查看结果并提出修改或下一步建议。如果一切正常,请确认以将结果返回给主任务。", "goToSubtask": "查看任务" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo有一个问题" }, diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index eeba6bb079d..50ee04ee8f0 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -823,6 +823,10 @@ "refreshSuccess": "工具刷新成功", "refreshError": "工具刷新失败", "toolParameters": "参数" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": { diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index e01b86f52bc..68d4430b952 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -314,6 +314,36 @@ "completionInstructions": "子任務已完成!您可以檢閱結果並提出修正或後續步驟。如果一切順利,請確認以將結果回傳給主任務。", "goToSubtask": "查看工作" }, + "subagents": { + "runningLabel": "Running subagent: {{description}}", + "completedLabel": "Subagent completed", + "cancelledByUser": "Subagent was cancelled by the user.", + "starting": "Starting...", + "thinking": "Thinking...", + "toolDisplayNames": { + "read_file": "Read file", + "write_to_file": "Write file", + "apply_diff": "Apply diff", + "search_files": "Search files", + "search_and_replace": "Search and replace", + "search_replace": "Search replace", + "edit_file": "Edit file", + "list_files": "List files", + "codebase_search": "Codebase search", + "execute_command": "Run command", + "run_slash_command": "Slash command", + "ask_followup_question": "Ask question", + "attempt_completion": "Complete", + "update_todo_list": "Update todos", + "switch_mode": "Switch mode", + "new_task": "New task", + "generate_image": "Generate image", + "use_mcp_tool": "MCP tool", + "access_mcp_resource": "MCP resource", + "apply_patch": "Apply patch", + "subagent": "Subagent" + } + }, "questions": { "hasQuestion": "Roo 有一個問題" }, diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 9f4241c3dd9..c4f3fa769d1 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -833,6 +833,10 @@ "refreshSuccess": "工具重新整理成功", "refreshError": "工具重新整理失敗", "toolParameters": "參數" + }, + "SUBAGENT": { + "name": "Subagent tool", + "description": "When enabled, the model can run subagents in the background for research or sub-tasks. The subagent's result is returned to the main task. Use \"explore\" for read-only sub-tasks or \"general\" when the sub-task may edit files or run commands." } }, "promptCaching": {