diff --git a/packages/telemetry/src/TelemetryService.ts b/packages/telemetry/src/TelemetryService.ts
index 7a11e3d388..7d59f2a417 100644
--- a/packages/telemetry/src/TelemetryService.ts
+++ b/packages/telemetry/src/TelemetryService.ts
@@ -152,6 +152,23 @@ export class TelemetryService {
this.captureEvent(TelemetryEventName.CONSECUTIVE_MISTAKE_ERROR, { taskId })
}
+ /**
+ * Captures when a tool execution times out
+ * @param taskId The task ID where the timeout occurred
+ * @param toolName The name of the tool that timed out
+ * @param timeoutMs The timeout duration in milliseconds
+ * @param executionTimeMs The actual execution time before timeout
+ */
+ public captureToolTimeout(taskId: string, toolName: string, timeoutMs: number, executionTimeMs: number): void {
+ this.captureEvent(TelemetryEventName.TOOL_TIMEOUT, {
+ taskId,
+ toolName,
+ timeoutMs,
+ executionTimeMs,
+ timeoutRatio: executionTimeMs / timeoutMs,
+ })
+ }
+
/**
* Captures when a tab is shown due to user action
* @param tab The tab that was shown
diff --git a/packages/types/src/telemetry.ts b/packages/types/src/telemetry.ts
index 6ad2eb3a7a..b714d16509 100644
--- a/packages/types/src/telemetry.ts
+++ b/packages/types/src/telemetry.ts
@@ -65,6 +65,7 @@ export enum TelemetryEventName {
DIFF_APPLICATION_ERROR = "Diff Application Error",
SHELL_INTEGRATION_ERROR = "Shell Integration Error",
CONSECUTIVE_MISTAKE_ERROR = "Consecutive Mistake Error",
+ TOOL_TIMEOUT = "Tool Timeout",
}
/**
@@ -157,6 +158,7 @@ export const rooCodeTelemetryEventSchema = z.discriminatedUnion("type", [
TelemetryEventName.TAB_SHOWN,
TelemetryEventName.MODE_SETTINGS_CHANGED,
TelemetryEventName.CUSTOM_MODE_CREATED,
+ TelemetryEventName.TOOL_TIMEOUT,
]),
properties: telemetryPropertiesSchema,
}),
diff --git a/src/core/prompts/__tests__/timeout-fallback-responses.spec.ts b/src/core/prompts/__tests__/timeout-fallback-responses.spec.ts
index 30b53f1581..3899df3923 100644
--- a/src/core/prompts/__tests__/timeout-fallback-responses.spec.ts
+++ b/src/core/prompts/__tests__/timeout-fallback-responses.spec.ts
@@ -2,6 +2,47 @@ import { describe, it, expect } from "vitest"
import { formatResponse } from "../responses"
describe("timeout fallback responses", () => {
+ describe("toolTimeout", () => {
+ it("should include background operation warning for execute_command", () => {
+ const message = formatResponse.toolTimeout("execute_command", 30000, 35000)
+
+ expect(message).toContain("timed out after 30 seconds")
+ expect(message).toContain(
+ "**Important**: The execute_command operation may still be running in the background",
+ )
+ expect(message).toContain("You might see output or effects from this operation later")
+ })
+
+ it("should include background operation warning for browser_action", () => {
+ const message = formatResponse.toolTimeout("browser_action", 15000, 20000)
+
+ expect(message).toContain("timed out after 15 seconds")
+ expect(message).toContain(
+ "**Important**: The browser_action operation may still be running in the background",
+ )
+ expect(message).toContain("You might see output or effects from this operation later")
+ })
+
+ it("should not include background operation warning for other tools", () => {
+ const message = formatResponse.toolTimeout("read_file", 10000, 12000)
+
+ expect(message).toContain("timed out after 10 seconds")
+ expect(message).not.toContain("may still be running in the background")
+ expect(message).not.toContain("**Important**")
+ })
+
+ it("should format timeout details correctly", () => {
+ const message = formatResponse.toolTimeout("write_to_file", 5000, 6000)
+
+ expect(message).toContain("")
+ expect(message).toContain("Tool: write_to_file")
+ expect(message).toContain("Configured Timeout: 5s")
+ expect(message).toContain("Execution Time: 6s")
+ expect(message).toContain("Status: Canceled")
+ expect(message).toContain("")
+ })
+ })
+
describe("generateContextualSuggestions", () => {
it("should generate execute_command suggestions", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateContextualSuggestions(
@@ -11,7 +52,8 @@ describe("timeout fallback responses", () => {
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("npm install")
- expect(suggestions[0].text).toContain("smaller, sequential steps")
+ expect(suggestions[0].text).toContain("still running in the background")
+ expect(suggestions[1].text).toContain("smaller, sequential steps")
})
it("should generate read_file suggestions", () => {
@@ -42,8 +84,9 @@ describe("timeout fallback responses", () => {
)
expect(suggestions).toHaveLength(4)
- expect(suggestions[0].text).toContain("click")
- expect(suggestions[0].text).toContain("smaller, more targeted steps")
+ expect(suggestions[0].text).toContain("browser action")
+ expect(suggestions[0].text).toContain("still processing in the background")
+ expect(suggestions[1].text).toContain("smaller, more targeted steps")
})
it("should generate search_files suggestions", () => {
@@ -73,6 +116,7 @@ describe("timeout fallback responses", () => {
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("the command")
+ expect(suggestions[0].text).toContain("still running in the background")
})
it("should generate read file suggestions with default file name", () => {
@@ -94,6 +138,7 @@ describe("timeout fallback responses", () => {
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("browser action")
+ expect(suggestions[0].text).toContain("still processing in the background")
})
it("should generate search suggestions", () => {
diff --git a/src/core/prompts/responses.ts b/src/core/prompts/responses.ts
index f3c629101b..37282e13c1 100644
--- a/src/core/prompts/responses.ts
+++ b/src/core/prompts/responses.ts
@@ -16,17 +16,26 @@ export const formatResponse = {
toolError: (error?: string) => `The tool execution failed with the following error:\n\n${error}\n`,
- toolTimeout: (toolName: string, timeoutMs: number, executionTimeMs: number) =>
- `The ${toolName} operation timed out after ${Math.round(timeoutMs / 1000)} seconds and was automatically canceled.
+ toolTimeout: (toolName: string, timeoutMs: number, executionTimeMs: number) => {
+ // Determine if this tool might continue running in the background
+ const backgroundOperationTools = ["execute_command", "browser_action"]
+ const mightContinueInBackground = backgroundOperationTools.includes(toolName)
+
+ const backgroundWarning = mightContinueInBackground
+ ? `\n\n**Important**: The ${toolName} operation may still be running in the background. You might see output or effects from this operation later.`
+ : ""
+
+ return `The ${toolName} operation timed out after ${Math.round(timeoutMs / 1000)} seconds and was automatically canceled.
Tool: ${toolName}
Configured Timeout: ${Math.round(timeoutMs / 1000)}s
Execution Time: ${Math.round(executionTimeMs / 1000)}s
Status: Canceled
-
+${backgroundWarning}
-The operation has been terminated to prevent system resource issues. Please consider one of the following approaches to complete your task.`,
+The operation has been terminated to prevent system resource issues. Please consider one of the following approaches to complete your task.`
+ },
rooIgnoreError: (path: string) =>
`Access to ${path} is blocked by the .rooignore file settings. You must try to continue in the task without using this file, or ask the user to update the .rooignore file.`,
@@ -215,6 +224,9 @@ Otherwise, if you have not completed the task and do not need additional informa
const command = params?.command || "the command"
return [
+ {
+ text: `Check if "${command}" is still running in the background and wait for it to complete`,
+ },
{
text: `Break "${command}" into smaller, sequential steps that can complete faster`,
},
@@ -224,9 +236,6 @@ Otherwise, if you have not completed the task and do not need additional informa
{
text: `Try an alternative approach or tool to accomplish the same goal`,
},
- {
- text: `Increase the timeout setting if this operation legitimately needs more time`,
- },
]
},
@@ -272,15 +281,15 @@ Otherwise, if you have not completed the task and do not need additional informa
const action = params?.action || "browser action"
return [
+ {
+ text: `Check if the browser action is still processing in the background`,
+ },
{
text: `Simplify the "${action}" into smaller, more targeted steps`,
},
{
text: `Wait for specific elements to load before proceeding`,
},
- {
- text: `Use direct API calls instead of browser automation if possible`,
- },
{
text: `Reset the browser session and try again`,
},
diff --git a/src/core/timeout/TimeoutFallbackHandler.ts b/src/core/timeout/TimeoutFallbackHandler.ts
index 5aaa2c62f1..6c9e09dc97 100644
--- a/src/core/timeout/TimeoutFallbackHandler.ts
+++ b/src/core/timeout/TimeoutFallbackHandler.ts
@@ -25,6 +25,18 @@ export interface TimeoutFallbackResult {
* and creates timeout responses in a single optimized flow
*/
export class TimeoutFallbackHandler {
+ private static readonly BACKGROUND_OPERATION_TOOLS = ["execute_command", "browser_action"]
+
+ /**
+ * Helper method to generate the timeout question message
+ */
+ private static generateTimeoutQuestion(toolName: ToolName, timeoutMs: number): string {
+ const mightContinueInBackground = this.BACKGROUND_OPERATION_TOOLS.includes(toolName)
+ const baseMessage = `The ${toolName} operation timed out after ${Math.round(timeoutMs / 1000)} seconds`
+ const backgroundSuffix = mightContinueInBackground ? " but may still be running in the background" : ""
+ return `${baseMessage}${backgroundSuffix}. How would you like to proceed?`
+ }
+
/**
* Create a timeout response with AI-generated fallback question in a single optimized query
*/
@@ -39,7 +51,11 @@ export class TimeoutFallbackHandler {
// Create a timeout message for display in the chat
if (task) {
- await task.say("tool_timeout", "", undefined, false, undefined, undefined, {
+ const mightContinueInBackground = this.BACKGROUND_OPERATION_TOOLS.includes(toolName)
+
+ // Pass a JSON string with the tool info so the UI can determine if it should show a background warning
+ const timeoutInfo = JSON.stringify({ toolName, mightContinueInBackground })
+ await task.say("tool_timeout", timeoutInfo, undefined, false, undefined, undefined, {
isNonInteractive: true,
})
}
@@ -93,10 +109,17 @@ This is required to help the user decide how to proceed after the timeout.`
// Try to use AI to generate contextual suggestions
if (task?.api && "completePrompt" in task.api) {
try {
- const aiResult = await this.generateAiSuggestions(context, task.api as SingleCompletionHandler)
+ // Pass the timeout from context to use exact tool timeout
+ const aiResult = await this.generateAiSuggestions(
+ context,
+ task.api as SingleCompletionHandler,
+ context.timeoutMs,
+ )
if (aiResult.success) {
+ console.log(`[TimeoutFallbackHandler] AI suggestions generated successfully`)
return aiResult
}
+ console.error(`[TimeoutFallbackHandler] AI suggestions failed:`, aiResult.error)
} catch (error) {
// AI failed, fall through to static suggestions
}
@@ -117,10 +140,20 @@ This is required to help the user decide how to proceed after the timeout.`
private static async generateAiSuggestions(
context: TimeoutFallbackContext,
apiHandler: SingleCompletionHandler,
+ timeoutMs: number,
): Promise {
try {
const prompt = createTimeoutFallbackPrompt(context)
- const aiResponse = await apiHandler.completePrompt(prompt)
+
+ // Create a timeout promise using exactly the same timeout as the tool
+ const timeoutPromise = new Promise((_, reject) => {
+ setTimeout(() => {
+ reject(new Error(`AI fallback generation timed out after ${timeoutMs / 1000} seconds`))
+ }, timeoutMs)
+ })
+
+ // Race between AI completion and timeout
+ const aiResponse = await Promise.race([apiHandler.completePrompt(prompt), timeoutPromise])
// Parse the AI response to extract suggestions
const suggestions = parseTimeoutFallbackResponse(aiResponse)
@@ -129,7 +162,7 @@ This is required to help the user decide how to proceed after the timeout.`
throw new Error("No valid suggestions generated by AI")
}
- const question = `The ${context.toolName} operation timed out after ${Math.round(context.timeoutMs / 1000)} seconds. How would you like to proceed?`
+ const question = this.generateTimeoutQuestion(context.toolName, context.timeoutMs)
const followUpXml = suggestions
.map((suggestion) =>
@@ -150,9 +183,12 @@ This is required to help the user decide how to proceed after the timeout.`
},
}
} catch (error) {
+ // If it's a timeout error, include that in the error message
+ const errorMessage = error instanceof Error ? error.message : "Unknown error generating AI suggestions"
+
return {
success: false,
- error: error instanceof Error ? error.message : "Unknown error generating AI suggestions",
+ error: errorMessage,
}
}
}
@@ -166,7 +202,7 @@ This is required to help the user decide how to proceed after the timeout.`
context.toolParams,
)
- const question = `The ${context.toolName} operation timed out after ${Math.round(context.timeoutMs / 1000)} seconds. How would you like to proceed?`
+ const question = this.generateTimeoutQuestion(context.toolName, context.timeoutMs)
const followUpXml = suggestions
.map((suggestion) =>
diff --git a/src/core/timeout/TimeoutManager.ts b/src/core/timeout/TimeoutManager.ts
index 805b46dc3e..fe1ae3c458 100644
--- a/src/core/timeout/TimeoutManager.ts
+++ b/src/core/timeout/TimeoutManager.ts
@@ -1,5 +1,6 @@
import { EventEmitter } from "events"
import type { ToolName } from "@roo-code/types"
+import { TelemetryService } from "@roo-code/telemetry"
export interface TimeoutConfig {
toolName: ToolName
@@ -36,14 +37,16 @@ export class TimeoutManager extends EventEmitter {
* like "Proceed While Running"
*/
private lastTimeoutEvent: TimeoutEvent | null = null
+ private logger: (...args: any[]) => void
- private constructor() {
+ private constructor(logger?: (...args: any[]) => void) {
super()
+ this.logger = logger || console.log
}
- public static getInstance(): TimeoutManager {
+ public static getInstance(logger?: (...args: any[]) => void): TimeoutManager {
if (!TimeoutManager.instance) {
- TimeoutManager.instance = new TimeoutManager()
+ TimeoutManager.instance = new TimeoutManager(logger)
}
return TimeoutManager.instance
}
@@ -59,6 +62,8 @@ export class TimeoutManager extends EventEmitter {
const controller = new AbortController()
const startTime = Date.now()
+ this.logger(`[TimeoutManager] Starting operation ${operationId} with timeout ${config.timeoutMs}ms`)
+
// Store the controller for potential cancellation
this.activeOperations.set(operationId, controller)
@@ -81,6 +86,8 @@ export class TimeoutManager extends EventEmitter {
const executionTimeMs = Date.now() - startTime
+ this.logger(`[TimeoutManager] Operation ${operationId} completed successfully in ${executionTimeMs}ms`)
+
return {
success: true,
result,
@@ -105,6 +112,21 @@ export class TimeoutManager extends EventEmitter {
this.lastTimeoutEvent = timeoutEvent
this.emit("timeout", timeoutEvent)
+ // Log timeout event
+ this.logger(
+ `[TimeoutManager] Operation ${operationId} timed out after ${executionTimeMs}ms (limit: ${config.timeoutMs}ms)`,
+ )
+
+ // Capture telemetry if TelemetryService is available
+ if (TelemetryService.hasInstance() && config.taskId) {
+ TelemetryService.instance.captureToolTimeout(
+ config.taskId,
+ config.toolName,
+ config.timeoutMs,
+ executionTimeMs,
+ )
+ }
+
return {
success: false,
timedOut: true,
@@ -114,6 +136,11 @@ export class TimeoutManager extends EventEmitter {
}
}
+ // Log non-timeout errors
+ this.logger(
+ `[TimeoutManager] Operation ${operationId} failed after ${executionTimeMs}ms: ${error instanceof Error ? error.message : String(error)}`,
+ )
+
return {
success: false,
timedOut: false,
@@ -186,6 +213,21 @@ export class TimeoutManager extends EventEmitter {
return `${toolName}:${taskId || "default"}`
}
+ /**
+ * Get timeout statistics for monitoring
+ */
+ public getTimeoutStats(): {
+ lastTimeout: TimeoutEvent | null
+ activeOperations: number
+ operationIds: string[]
+ } {
+ return {
+ lastTimeout: this.lastTimeoutEvent,
+ activeOperations: this.activeOperations.size,
+ operationIds: Array.from(this.activeOperations.keys()),
+ }
+ }
+
/**
* Cleanup method for graceful shutdown
*/
diff --git a/src/core/timeout/__tests__/timeout-fallback.spec.ts b/src/core/timeout/__tests__/timeout-fallback.spec.ts
index d835bbf6ac..ee98890f2a 100644
--- a/src/core/timeout/__tests__/timeout-fallback.spec.ts
+++ b/src/core/timeout/__tests__/timeout-fallback.spec.ts
@@ -59,6 +59,7 @@ describe("TimeoutFallbackHandler", () => {
expect(result.toolCall?.name).toBe("ask_followup_question")
expect(result.toolCall?.params.question).toContain("execute_command")
expect(result.toolCall?.params.question).toContain("30 seconds")
+ expect(result.toolCall?.params.question).toContain("may still be running in the background")
// Check that AI-generated suggestions are included
const followUp = result.toolCall?.params.follow_up || ""
@@ -93,12 +94,55 @@ describe("TimeoutFallbackHandler", () => {
// Should contain static fallback suggestions
const followUp = result.toolCall?.params.follow_up || ""
+ expect(followUp).toContain("still running in the background")
expect(followUp).toContain('Break "npm test" into smaller')
expect(followUp).toContain("background using")
expect(followUp).toContain("alternative approach")
- expect(followUp).toContain("Increase the timeout")
})
+ test("should timeout AI fallback generation using exact tool timeout", async () => {
+ // Mock AI handler that takes longer than the timeout
+ ;(mockApiHandler.completePrompt as any).mockImplementation(() => {
+ return new Promise((resolve) => {
+ // This would resolve after 5 seconds, but should timeout before that
+ setTimeout(() => {
+ resolve("This response should never be used")
+ }, 5000)
+ })
+ })
+
+ const context = {
+ toolName: "execute_command" as const,
+ timeoutMs: 2000, // 2 second timeout for faster testing
+ executionTimeMs: 2500,
+ toolParams: { command: "npm install" },
+ }
+
+ const startTime = Date.now()
+ // The timeout is now taken from context.timeoutMs
+ const result = await TimeoutFallbackHandler.generateAiFallback(context, mockTask as Task)
+ const elapsedTime = Date.now() - startTime
+
+ // Should timeout and fallback to static suggestions
+ expect(result.success).toBe(true)
+ expect(result.toolCall).toBeDefined()
+ expect(result.toolCall?.name).toBe("ask_followup_question")
+
+ // Should have timed out around 2 seconds (exact tool timeout)
+ expect(elapsedTime).toBeGreaterThanOrEqual(1900)
+ expect(elapsedTime).toBeLessThan(2500)
+
+ // Should contain static fallback suggestions since AI timed out
+ const followUp = result.toolCall?.params.follow_up || ""
+ expect(followUp).toContain("still running in the background")
+ expect(followUp).toContain('Break "npm install" into smaller')
+ expect(followUp).toContain("background using")
+ expect(followUp).toContain("alternative approach")
+
+ // Verify AI was called
+ expect(mockApiHandler.completePrompt).toHaveBeenCalled()
+ }, 5000) // Test timeout of 5 seconds
+
test("should fallback to static suggestions when API handler is unavailable", async () => {
// Task without API handler
const taskWithoutApi = {}
@@ -400,9 +444,9 @@ Based on the search_files timeout, here are my recommendations:
// Should contain static fallback suggestions for browser_action
const followUp = result.toolCall?.params.follow_up || ""
+ expect(followUp).toContain("still processing in the background")
expect(followUp).toContain('Simplify the "click"')
expect(followUp).toContain("Wait for specific elements")
- expect(followUp).toContain("direct API calls")
expect(followUp).toContain("Reset the browser session")
})
})
diff --git a/src/core/timeout/__tests__/timeout-telemetry.spec.ts b/src/core/timeout/__tests__/timeout-telemetry.spec.ts
new file mode 100644
index 0000000000..a8fd4effc9
--- /dev/null
+++ b/src/core/timeout/__tests__/timeout-telemetry.spec.ts
@@ -0,0 +1,256 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
+import { TimeoutManager } from "../TimeoutManager"
+import { TelemetryService } from "@roo-code/telemetry"
+
+// Mock TelemetryService
+vi.mock("@roo-code/telemetry", () => ({
+ TelemetryService: {
+ hasInstance: vi.fn(),
+ instance: {
+ captureToolTimeout: vi.fn(),
+ },
+ },
+}))
+
+describe("TimeoutManager Telemetry and Logging", () => {
+ let timeoutManager: TimeoutManager
+ let mockLogger: ReturnType
+ let originalInstance: TimeoutManager | undefined
+
+ beforeEach(() => {
+ // Store the original instance
+ originalInstance = (TimeoutManager as any).instance
+ // Reset the singleton instance
+ ;(TimeoutManager as any).instance = undefined
+
+ // Create mock logger
+ mockLogger = vi.fn()
+
+ // Create new instance with mock logger
+ timeoutManager = TimeoutManager.getInstance(mockLogger)
+
+ // Setup TelemetryService mock
+ vi.mocked(TelemetryService.hasInstance).mockReturnValue(true)
+ vi.mocked(TelemetryService.instance.captureToolTimeout).mockClear()
+ })
+
+ afterEach(() => {
+ // Restore the original instance
+ ;(TimeoutManager as any).instance = originalInstance
+ vi.clearAllMocks()
+ })
+
+ describe("Logging", () => {
+ it("should log operation start", async () => {
+ const operation = vi.fn().mockResolvedValue("result")
+
+ await timeoutManager.executeWithTimeout(operation, {
+ toolName: "execute_command",
+ timeoutMs: 1000,
+ enableFallback: false,
+ taskId: "task-123",
+ })
+
+ expect(mockLogger).toHaveBeenCalledWith(
+ expect.stringContaining(
+ "[TimeoutManager] Starting operation execute_command:task-123 with timeout 1000ms",
+ ),
+ )
+ })
+
+ it("should log successful operation completion", async () => {
+ const operation = vi.fn().mockResolvedValue("result")
+
+ await timeoutManager.executeWithTimeout(operation, {
+ toolName: "execute_command",
+ timeoutMs: 1000,
+ enableFallback: false,
+ taskId: "task-123",
+ })
+
+ expect(mockLogger).toHaveBeenCalledWith(
+ expect.stringMatching(
+ /\[TimeoutManager\] Operation execute_command:task-123 completed successfully in \d+ms/,
+ ),
+ )
+ })
+
+ it("should log timeout events", async () => {
+ const operation = vi.fn().mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 2000)))
+
+ await timeoutManager.executeWithTimeout(operation, {
+ toolName: "execute_command",
+ timeoutMs: 100,
+ enableFallback: false,
+ taskId: "task-123",
+ })
+
+ expect(mockLogger).toHaveBeenCalledWith(
+ expect.stringMatching(
+ /\[TimeoutManager\] Operation execute_command:task-123 timed out after \d+ms \(limit: 100ms\)/,
+ ),
+ )
+ })
+
+ it("should log non-timeout errors", async () => {
+ const error = new Error("Test error")
+ const operation = vi.fn().mockRejectedValue(error)
+
+ await timeoutManager.executeWithTimeout(operation, {
+ toolName: "execute_command",
+ timeoutMs: 1000,
+ enableFallback: false,
+ taskId: "task-123",
+ })
+
+ expect(mockLogger).toHaveBeenCalledWith(
+ expect.stringMatching(
+ /\[TimeoutManager\] Operation execute_command:task-123 failed after \d+ms: Test error/,
+ ),
+ )
+ })
+ })
+
+ describe("Telemetry", () => {
+ it("should capture telemetry for timeout events when TelemetryService is available", async () => {
+ const operation = vi.fn().mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 2000)))
+
+ const result = await timeoutManager.executeWithTimeout(operation, {
+ toolName: "execute_command",
+ timeoutMs: 100,
+ enableFallback: false,
+ taskId: "task-123",
+ })
+
+ expect(result.timedOut).toBe(true)
+ expect(TelemetryService.instance.captureToolTimeout).toHaveBeenCalledWith(
+ "task-123",
+ "execute_command",
+ 100,
+ expect.any(Number),
+ )
+ })
+
+ it("should not capture telemetry when taskId is not provided", async () => {
+ const operation = vi.fn().mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 2000)))
+
+ await timeoutManager.executeWithTimeout(operation, {
+ toolName: "execute_command",
+ timeoutMs: 100,
+ enableFallback: false,
+ })
+
+ expect(TelemetryService.instance.captureToolTimeout).not.toHaveBeenCalled()
+ })
+
+ it("should not capture telemetry when TelemetryService is not available", async () => {
+ vi.mocked(TelemetryService.hasInstance).mockReturnValue(false)
+
+ const operation = vi.fn().mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 2000)))
+
+ await timeoutManager.executeWithTimeout(operation, {
+ toolName: "execute_command",
+ timeoutMs: 100,
+ enableFallback: false,
+ taskId: "task-123",
+ })
+
+ expect(TelemetryService.instance.captureToolTimeout).not.toHaveBeenCalled()
+ })
+
+ it("should not capture telemetry for successful operations", async () => {
+ const operation = vi.fn().mockResolvedValue("result")
+
+ await timeoutManager.executeWithTimeout(operation, {
+ toolName: "execute_command",
+ timeoutMs: 1000,
+ enableFallback: false,
+ taskId: "task-123",
+ })
+
+ expect(TelemetryService.instance.captureToolTimeout).not.toHaveBeenCalled()
+ })
+
+ it("should not capture telemetry for non-timeout errors", async () => {
+ const operation = vi.fn().mockRejectedValue(new Error("Test error"))
+
+ await timeoutManager.executeWithTimeout(operation, {
+ toolName: "execute_command",
+ timeoutMs: 1000,
+ enableFallback: false,
+ taskId: "task-123",
+ })
+
+ expect(TelemetryService.instance.captureToolTimeout).not.toHaveBeenCalled()
+ })
+ })
+
+ describe("getTimeoutStats", () => {
+ it("should return timeout statistics", async () => {
+ // Execute an operation that will timeout
+ const operation = vi.fn().mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 2000)))
+
+ await timeoutManager.executeWithTimeout(operation, {
+ toolName: "execute_command",
+ timeoutMs: 100,
+ enableFallback: false,
+ taskId: "task-123",
+ })
+
+ const stats = timeoutManager.getTimeoutStats()
+
+ expect(stats.lastTimeout).toBeTruthy()
+ expect(stats.lastTimeout?.toolName).toBe("execute_command")
+ expect(stats.lastTimeout?.taskId).toBe("task-123")
+ expect(stats.lastTimeout?.timeoutMs).toBe(100)
+ expect(stats.activeOperations).toBe(0) // Should be 0 after operation completes
+ })
+
+ it("should track active operations", async () => {
+ // Start a long-running operation
+ const operation = vi.fn().mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 5000)))
+
+ const promise = timeoutManager.executeWithTimeout(operation, {
+ toolName: "execute_command",
+ timeoutMs: 10000,
+ enableFallback: false,
+ taskId: "task-123",
+ })
+
+ // Check stats while operation is running
+ const stats = timeoutManager.getTimeoutStats()
+ expect(stats.activeOperations).toBe(1)
+ expect(stats.operationIds).toContain("execute_command:task-123")
+
+ // Cancel to clean up
+ timeoutManager.cancelOperation("execute_command", "task-123")
+ await promise.catch(() => {}) // Ignore cancellation error
+ })
+ })
+
+ describe("Timeout event emission", () => {
+ it("should emit timeout event with correct data", async () => {
+ const timeoutHandler = vi.fn()
+ timeoutManager.on("timeout", timeoutHandler)
+
+ const operation = vi.fn().mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 2000)))
+
+ await timeoutManager.executeWithTimeout(operation, {
+ toolName: "execute_command",
+ timeoutMs: 100,
+ enableFallback: true,
+ taskId: "task-123",
+ })
+
+ expect(timeoutHandler).toHaveBeenCalledWith({
+ toolName: "execute_command",
+ timeoutMs: 100,
+ executionTimeMs: expect.any(Number),
+ taskId: "task-123",
+ timestamp: expect.any(Number),
+ })
+
+ timeoutManager.off("timeout", timeoutHandler)
+ })
+ })
+})
diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx
index ae19d3bb2a..397cc0c8da 100644
--- a/webview-ui/src/components/chat/ChatRow.tsx
+++ b/webview-ui/src/components/chat/ChatRow.tsx
@@ -259,9 +259,27 @@ export const ChatRowContent = ({
{t("chat:questions.hasQuestion")},
]
case "tool_timeout":
+ let showBackgroundWarning = false
+ try {
+ const timeoutInfo = message.text ? JSON.parse(message.text) : null
+ showBackgroundWarning = timeoutInfo?.mightContinueInBackground || false
+ } catch (_e) {
+ // If parsing fails, don't show the warning
+ }
return [
,
- {t("chat:toolTimeout")},
+
+ {t("chat:toolTimeout.title")}{" "}
+ {t("chat:toolTimeout.subtitle")}
+ {showBackgroundWarning && (
+ <>
+
+
+ {t("chat:toolTimeout.backgroundWarning")}
+
+ >
+ )}
+ ,
]
default:
return [null, null]
@@ -1115,6 +1133,16 @@ export const ChatRowContent = ({
case "user_edit_todos":
return {}} />
default:
+ // Don't render message text for tool_timeout as it contains JSON metadata
+ if (message.say === "tool_timeout") {
+ return title ? (
+
+ {icon}
+ {title}
+
+ ) : null
+ }
+
return (
<>
{title && (
diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json
index ac8c69a153..8c429bbb20 100644
--- a/webview-ui/src/i18n/locales/ca/chat.json
+++ b/webview-ui/src/i18n/locales/ca/chat.json
@@ -215,7 +215,6 @@
"hasQuestion": "Roo té una pregunta:"
},
"taskCompleted": "Tasca completada",
- "toolTimeout": "Temps d'espera de l'eina esgotat: Suggerint alternatives...",
"powershell": {
"issues": "Sembla que estàs tenint problemes amb Windows PowerShell, si us plau consulta aquesta documentació per a més informació."
},
@@ -239,6 +238,11 @@
"autoSelectCountdown": "Selecció automàtica en {{count}}s",
"countdownDisplay": "{{count}}s"
},
+ "toolTimeout": {
+ "title": "Temps d'espera d'eina configurat per l'usuari:",
+ "subtitle": "Suggerint alternatives...",
+ "backgroundWarning": "Nota: l'operació pot seguir executant-se en segon pla"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} Llançat",
"description": "Roo Code {{version}} porta noves funcions potents i millores significatives per millorar el vostre flux de treball de desenvolupament.",
diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json
index c98ca8a3b6..8b3ae34dc1 100644
--- a/webview-ui/src/i18n/locales/de/chat.json
+++ b/webview-ui/src/i18n/locales/de/chat.json
@@ -215,7 +215,6 @@
"hasQuestion": "Roo hat eine Frage:"
},
"taskCompleted": "Aufgabe abgeschlossen",
- "toolTimeout": "Automatische Tool-Zeitüberschreitung: Schlage Alternativen vor...",
"powershell": {
"issues": "Es scheint, dass du Probleme mit Windows PowerShell hast, bitte sieh dir dies an"
},
@@ -239,6 +238,11 @@
"autoSelectCountdown": "Automatische Auswahl in {{count}}s",
"countdownDisplay": "{{count}}s"
},
+ "toolTimeout": {
+ "title": "Benutzerdefiniertes Tool-Timeout:",
+ "subtitle": "Alternativen werden vorgeschlagen...",
+ "backgroundWarning": "Hinweis: Der Vorgang läuft möglicherweise noch im Hintergrund"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} veröffentlicht",
"description": "Roo Code {{version}} bringt mächtige neue Funktionen und bedeutende Verbesserungen, um deinen Entwicklungsworkflow zu verbessern.",
diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json
index 99b267bc6c..0add37cab5 100644
--- a/webview-ui/src/i18n/locales/en/chat.json
+++ b/webview-ui/src/i18n/locales/en/chat.json
@@ -230,7 +230,11 @@
"hasQuestion": "Roo has a question:"
},
"taskCompleted": "Task Completed",
- "toolTimeout": "Automatic Tool Timeout: Suggesting alternatives...",
+ "toolTimeout": {
+ "title": "User Configured Tool Timeout:",
+ "subtitle": "Suggesting alternatives...",
+ "backgroundWarning": "Note operation may still be running in the background"
+ },
"error": "Error",
"diffError": {
"title": "Edit Unsuccessful"
diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json
index 53cd277f3a..6b8a3a34eb 100644
--- a/webview-ui/src/i18n/locales/es/chat.json
+++ b/webview-ui/src/i18n/locales/es/chat.json
@@ -1,5 +1,4 @@
{
- "toolTimeout": "Tiempo de espera de herramienta agotado: Sugiriendo alternativas...",
"greeting": "Bienvenido a Roo Code",
"task": {
"title": "Tarea",
@@ -239,6 +238,11 @@
"autoSelectCountdown": "Selección automática en {{count}}s",
"countdownDisplay": "{{count}}s"
},
+ "toolTimeout": {
+ "title": "Tiempo de espera de herramienta configurado por el usuario:",
+ "subtitle": "Sugiriendo alternativas...",
+ "backgroundWarning": "Nota: la operación puede seguir ejecutándose en segundo plano"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} publicado",
"description": "Roo Code {{version}} trae poderosas nuevas funcionalidades y mejoras significativas para mejorar tu flujo de trabajo de desarrollo.",
diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json
index e57501c176..032a6cc54b 100644
--- a/webview-ui/src/i18n/locales/fr/chat.json
+++ b/webview-ui/src/i18n/locales/fr/chat.json
@@ -1,5 +1,4 @@
{
- "toolTimeout": "Délai d'attente de l'outil dépassé : Suggestion d'alternatives...",
"greeting": "Bienvenue sur Roo Code",
"task": {
"title": "Tâche",
@@ -239,6 +238,11 @@
"autoSelectCountdown": "Sélection automatique dans {{count}}s",
"countdownDisplay": "{{count}}s"
},
+ "toolTimeout": {
+ "title": "Délai d'expiration d'outil configuré par l'utilisateur :",
+ "subtitle": "Suggestion d'alternatives...",
+ "backgroundWarning": "Note : l'opération peut encore s'exécuter en arrière-plan"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} est sortie",
"description": "Roo Code {{version}} apporte de puissantes nouvelles fonctionnalités et des améliorations significatives pour améliorer ton flux de travail de développement.",
diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json
index dc7d82c1f2..95c7621605 100644
--- a/webview-ui/src/i18n/locales/hi/chat.json
+++ b/webview-ui/src/i18n/locales/hi/chat.json
@@ -1,5 +1,4 @@
{
- "toolTimeout": "टूल टाइमआउट: विकल्प सुझा रहा हूं...",
"greeting": "Roo Code में आपका स्वागत है",
"task": {
"title": "कार्य",
@@ -239,6 +238,11 @@
"autoSelectCountdown": "{{count}}s में स्वचालित रूप से चयन हो रहा है",
"countdownDisplay": "{{count}}सेकंड"
},
+ "toolTimeout": {
+ "title": "उपयोगकर्ता द्वारा कॉन्फ़िगर किया गया टूल टाइमआउट:",
+ "subtitle": "विकल्प सुझा रहा है...",
+ "backgroundWarning": "नोट: ऑपरेशन अभी भी बैकग्राउंड में चल रहा हो सकता है"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} रिलीज़ हुआ",
"description": "Roo Code {{version}} आपके विकास वर्कफ़्लो को बेहतर बनाने के लिए शक्तिशाली नई सुविधाएं और महत्वपूर्ण सुधार लेकर आया है।",
diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json
index a86456313a..7bf8671a79 100644
--- a/webview-ui/src/i18n/locales/id/chat.json
+++ b/webview-ui/src/i18n/locales/id/chat.json
@@ -1,5 +1,4 @@
{
- "toolTimeout": "Timeout Tool Otomatis: Menyarankan alternatif...",
"greeting": "Selamat datang di Roo Code",
"task": {
"title": "Tugas",
@@ -236,6 +235,11 @@
"questions": {
"hasQuestion": "Roo punya pertanyaan:"
},
+ "toolTimeout": {
+ "title": "Timeout Tool yang Dikonfigurasi Pengguna:",
+ "subtitle": "Menyarankan alternatif...",
+ "backgroundWarning": "Catatan: operasi mungkin masih berjalan di latar belakang"
+ },
"taskCompleted": "Tugas Selesai",
"error": "Error",
"diffError": {
diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json
index c3ccfe1f0f..c30123c0eb 100644
--- a/webview-ui/src/i18n/locales/it/chat.json
+++ b/webview-ui/src/i18n/locales/it/chat.json
@@ -1,5 +1,4 @@
{
- "toolTimeout": "Timeout automatico dello strumento: Suggerendo alternative...",
"greeting": "Benvenuto a Roo Code",
"task": {
"title": "Attività",
@@ -239,6 +238,11 @@
"autoSelectCountdown": "Selezione automatica in {{count}}s",
"countdownDisplay": "{{count}}s"
},
+ "toolTimeout": {
+ "title": "Timeout Strumento Configurato dall'Utente:",
+ "subtitle": "Suggerendo alternative...",
+ "backgroundWarning": "Nota: l'operazione potrebbe essere ancora in esecuzione in background"
+ },
"announcement": {
"title": "🎉 Rilasciato Roo Code {{version}}",
"description": "Roo Code {{version}} porta nuove potenti funzionalità e miglioramenti significativi per potenziare il tuo flusso di lavoro di sviluppo.",
diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json
index 443c63eb1a..1787fcec72 100644
--- a/webview-ui/src/i18n/locales/ja/chat.json
+++ b/webview-ui/src/i18n/locales/ja/chat.json
@@ -1,5 +1,4 @@
{
- "toolTimeout": "自動ツールタイムアウト:代替案を提案しています...",
"greeting": "Roo Code へようこそ",
"task": {
"title": "タスク",
@@ -239,6 +238,11 @@
"autoSelectCountdown": "{{count}}秒後に自動選択します",
"countdownDisplay": "{{count}}秒"
},
+ "toolTimeout": {
+ "title": "ユーザー設定ツールタイムアウト:",
+ "subtitle": "代替案を提案中...",
+ "backgroundWarning": "注意: 操作がバックグラウンドで実行中の可能性があります"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} リリース",
"description": "Roo Code {{version}}は、開発ワークフローを向上させる強力な新機能と重要な改善をもたらします。",
diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json
index 5ca6075ec3..1234935b29 100644
--- a/webview-ui/src/i18n/locales/ko/chat.json
+++ b/webview-ui/src/i18n/locales/ko/chat.json
@@ -1,5 +1,4 @@
{
- "toolTimeout": "자동 도구 타임아웃: 대안을 제안하고 있습니다...",
"greeting": "Roo Code에 오신 것을 환영합니다",
"task": {
"title": "작업",
@@ -239,6 +238,11 @@
"autoSelectCountdown": "{{count}}초 후 자동 선택",
"countdownDisplay": "{{count}}초"
},
+ "toolTimeout": {
+ "title": "사용자 구성 도구 타임아웃:",
+ "subtitle": "대안을 제안하는 중...",
+ "backgroundWarning": "참고: 작업이 백그라운드에서 계속 실행 중일 수 있습니다"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} 출시",
"description": "Roo Code {{version}}은 개발 워크플로우를 향상시키는 강력한 새 기능과 중요한 개선사항을 제공합니다.",
diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json
index 71ebaf2c28..146e27576f 100644
--- a/webview-ui/src/i18n/locales/nl/chat.json
+++ b/webview-ui/src/i18n/locales/nl/chat.json
@@ -1,5 +1,4 @@
{
- "toolTimeout": "Automatische tool timeout: Alternatieven voorstellen...",
"greeting": "Welkom bij Roo Code",
"task": {
"title": "Taak",
@@ -244,6 +243,11 @@
"errorHeader": "Context samenvatten mislukt",
"tokens": "tokens"
},
+ "toolTimeout": {
+ "title": "Door gebruiker geconfigureerde tool timeout:",
+ "subtitle": "Alternatieven voorstellen...",
+ "backgroundWarning": "Let op: de bewerking kan nog steeds op de achtergrond worden uitgevoerd"
+ },
"followUpSuggest": {
"copyToInput": "Kopiëren naar invoer (zelfde als shift + klik)",
"autoSelectCountdown": "Automatische selectie in {{count}}s",
diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json
index 35ee26990c..894f06f5eb 100644
--- a/webview-ui/src/i18n/locales/pl/chat.json
+++ b/webview-ui/src/i18n/locales/pl/chat.json
@@ -1,5 +1,4 @@
{
- "toolTimeout": "Automatyczny timeout narzędzia: Sugerowanie alternatyw...",
"greeting": "Witamy w Roo Code",
"task": {
"title": "Zadanie",
@@ -239,6 +238,11 @@
"autoSelectCountdown": "Automatyczny wybór za {{count}}s",
"countdownDisplay": "{{count}}s"
},
+ "toolTimeout": {
+ "title": "Skonfigurowany przez użytkownika timeout narzędzia:",
+ "subtitle": "Sugerowanie alternatyw...",
+ "backgroundWarning": "Uwaga: operacja może nadal działać w tle"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} wydany",
"description": "Roo Code {{version}} wprowadza potężne nowe funkcje i znaczące ulepszenia, aby ulepszyć Twój przepływ pracy programistycznej.",
diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json
index 892ed6c49d..cc7b86eafd 100644
--- a/webview-ui/src/i18n/locales/pt-BR/chat.json
+++ b/webview-ui/src/i18n/locales/pt-BR/chat.json
@@ -1,5 +1,4 @@
{
- "toolTimeout": "Timeout automático da ferramenta: Sugerindo alternativas...",
"greeting": "Bem-vindo ao Roo Code",
"task": {
"title": "Tarefa",
@@ -239,6 +238,11 @@
"autoSelectCountdown": "Seleção automática em {{count}}s",
"countdownDisplay": "{{count}}s"
},
+ "toolTimeout": {
+ "title": "Timeout de Ferramenta Configurado pelo Usuário:",
+ "subtitle": "Sugerindo alternativas...",
+ "backgroundWarning": "Nota: a operação pode ainda estar executando em segundo plano"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} Lançado",
"description": "Roo Code {{version}} traz novos recursos poderosos e melhorias significativas para aprimorar seu fluxo de trabalho de desenvolvimento.",
diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json
index d63264259d..64eed7bdfa 100644
--- a/webview-ui/src/i18n/locales/ru/chat.json
+++ b/webview-ui/src/i18n/locales/ru/chat.json
@@ -1,5 +1,4 @@
{
- "toolTimeout": "Автоматический тайм-аут инструмента: Предлагаю альтернативы...",
"greeting": "Добро пожаловать в Roo Code",
"task": {
"title": "Задача",
@@ -244,6 +243,11 @@
"errorHeader": "Не удалось сжать контекст",
"tokens": "токены"
},
+ "toolTimeout": {
+ "title": "Настроенный пользователем тайм-аут инструмента:",
+ "subtitle": "Предлагаем альтернативы...",
+ "backgroundWarning": "Примечание: операция может все еще выполняться в фоновом режиме"
+ },
"followUpSuggest": {
"copyToInput": "Скопировать во ввод (то же, что shift + клик)",
"autoSelectCountdown": "Автовыбор через {{count}}с",
diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json
index 3377c8e70f..bc44e17c00 100644
--- a/webview-ui/src/i18n/locales/tr/chat.json
+++ b/webview-ui/src/i18n/locales/tr/chat.json
@@ -1,5 +1,4 @@
{
- "toolTimeout": "Otomatik araç zaman aşımı: Alternatifler öneriliyor...",
"greeting": "Roo Code'a Hoş Geldiniz",
"task": {
"title": "Görev",
@@ -239,6 +238,11 @@
"autoSelectCountdown": "{{count}}s içinde otomatik seçilecek",
"countdownDisplay": "{{count}}sn"
},
+ "toolTimeout": {
+ "title": "Kullanıcı Tarafından Yapılandırılan Araç Zaman Aşımı:",
+ "subtitle": "Alternatifler öneriliyor...",
+ "backgroundWarning": "Not: işlem hala arka planda çalışıyor olabilir"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} Yayınlandı",
"description": "Roo Code {{version}}, geliştirme iş akışınızı geliştirmek için güçlü yeni özellikler ve önemli iyileştirmeler getiriyor.",
diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json
index 2aa199d056..e8ceb8a9d1 100644
--- a/webview-ui/src/i18n/locales/vi/chat.json
+++ b/webview-ui/src/i18n/locales/vi/chat.json
@@ -238,6 +238,11 @@
"autoSelectCountdown": "Tự động chọn sau {{count}}s",
"countdownDisplay": "{{count}}s"
},
+ "toolTimeout": {
+ "title": "Thời gian chờ công cụ do người dùng cấu hình:",
+ "subtitle": "Đang đề xuất các lựa chọn thay thế...",
+ "backgroundWarning": "Lưu ý: thao tác có thể vẫn đang chạy trong nền"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} Đã phát hành",
"description": "Roo Code {{version}} mang đến các tính năng mạnh mẽ mới và cải tiến đáng kể để nâng cao quy trình phát triển của bạn.",
@@ -316,6 +321,5 @@
},
"versionIndicator": {
"ariaLabel": "Phiên bản {{version}} - Nhấp để xem ghi chú phát hành"
- },
- "toolTimeout": "Tự động hết thời gian chờ công cụ: Đang đề xuất các lựa chọn thay thế..."
+ }
}
diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json
index de30f5e693..e5b3ec283e 100644
--- a/webview-ui/src/i18n/locales/zh-CN/chat.json
+++ b/webview-ui/src/i18n/locales/zh-CN/chat.json
@@ -238,6 +238,11 @@
"autoSelectCountdown": "{{count}}秒后自动选择",
"countdownDisplay": "{{count}}秒"
},
+ "toolTimeout": {
+ "title": "用户配置的工具超时:",
+ "subtitle": "正在建议替代方案...",
+ "backgroundWarning": "注意: 操作可能仍在后台运行"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} 已发布",
"description": "Roo Code {{version}} 带来强大的新功能和重大改进,提升您的开发工作流程。",
@@ -316,6 +321,5 @@
},
"versionIndicator": {
"ariaLabel": "版本 {{version}} - 点击查看发布说明"
- },
- "toolTimeout": "自动工具超时:正在建议替代方案..."
+ }
}
diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json
index 0ece02f426..b198111910 100644
--- a/webview-ui/src/i18n/locales/zh-TW/chat.json
+++ b/webview-ui/src/i18n/locales/zh-TW/chat.json
@@ -238,6 +238,11 @@
"autoSelectCountdown": "{{count}}秒後自動選擇",
"countdownDisplay": "{{count}}秒"
},
+ "toolTimeout": {
+ "title": "使用者設定的工具逾時:",
+ "subtitle": "正在建議替代方案...",
+ "backgroundWarning": "注意: 操作可能仍在背景執行"
+ },
"announcement": {
"title": "🎉 Roo Code {{version}} 已發布",
"description": "Roo Code {{version}} 帶來強大的新功能和重大改進,提升您的開發工作流程。",
@@ -316,6 +321,5 @@
},
"versionIndicator": {
"ariaLabel": "版本 {{version}} - 點擊查看發布說明"
- },
- "toolTimeout": "自動工具逾時:正在建議替代方案..."
+ }
}