code review

This commit is contained in:
Will Li 2025-07-16 11:23:35 -07:00
parent b019b4c794
commit 3c7a54b3cd
27 changed files with 597 additions and 46 deletions

View file

@ -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

View file

@ -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,
}),

View file

@ -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("<timeout_details>")
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("</timeout_details>")
})
})
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", () => {

View file

@ -16,17 +16,26 @@ export const formatResponse = {
toolError: (error?: string) => `The tool execution failed with the following error:\n<error>\n${error}\n</error>`,
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.
<timeout_details>
Tool: ${toolName}
Configured Timeout: ${Math.round(timeoutMs / 1000)}s
Execution Time: ${Math.round(executionTimeMs / 1000)}s
Status: Canceled
</timeout_details>
</timeout_details>${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`,
},

View file

@ -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<TimeoutFallbackResult> {
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<never>((_, 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) =>

View file

@ -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
*/

View file

@ -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")
})
})

View file

@ -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<typeof vi.fn>
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)
})
})
})

View file

@ -259,9 +259,27 @@ export const ChatRowContent = ({
<span style={{ color: normalColor, fontWeight: "bold" }}>{t("chat:questions.hasQuestion")}</span>,
]
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 [
<span className="codicon codicon-clock" style={{ color: errorColor, marginBottom: "-1.5px" }} />,
<span style={{ color: errorColor, fontWeight: "bold" }}>{t("chat:toolTimeout")}</span>,
<span style={{ color: errorColor }}>
<span style={{ fontWeight: "bold" }}>{t("chat:toolTimeout.title")}</span>{" "}
<span style={{ fontStyle: "italic" }}>{t("chat:toolTimeout.subtitle")}</span>
{showBackgroundWarning && (
<>
<br />
<span style={{ fontSize: "0.9em", opacity: 0.8 }}>
{t("chat:toolTimeout.backgroundWarning")}
</span>
</>
)}
</span>,
]
default:
return [null, null]
@ -1115,6 +1133,16 @@ export const ChatRowContent = ({
case "user_edit_todos":
return <UpdateTodoListToolBlock userEdited onChange={() => {}} />
default:
// Don't render message text for tool_timeout as it contains JSON metadata
if (message.say === "tool_timeout") {
return title ? (
<div style={headerStyle}>
{icon}
{title}
</div>
) : null
}
return (
<>
{title && (

View file

@ -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.",

View file

@ -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.",

View file

@ -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"

View file

@ -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.",

View file

@ -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.",

View file

@ -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}} आपके विकास वर्कफ़्लो को बेहतर बनाने के लिए शक्तिशाली नई सुविधाएं और महत्वपूर्ण सुधार लेकर आया है।",

View file

@ -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": {

View file

@ -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.",

View file

@ -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}}は、開発ワークフローを向上させる強力な新機能と重要な改善をもたらします。",

View file

@ -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}}은 개발 워크플로우를 향상시키는 강력한 새 기능과 중요한 개선사항을 제공합니다.",

View file

@ -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",

View file

@ -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.",

View file

@ -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.",

View file

@ -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}}с",

View file

@ -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.",

View file

@ -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ế..."
}
}

View file

@ -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": "自动工具超时:正在建议替代方案..."
}
}

View file

@ -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": "自動工具逾時:正在建議替代方案..."
}
}