mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-09 03:17:58 +00:00
code review
This commit is contained in:
parent
b019b4c794
commit
3c7a54b3cd
27 changed files with 597 additions and 46 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
256
src/core/timeout/__tests__/timeout-telemetry.spec.ts
Normal file
256
src/core/timeout/__tests__/timeout-telemetry.spec.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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}} आपके विकास वर्कफ़्लो को बेहतर बनाने के लिए शक्तिशाली नई सुविधाएं और महत्वपूर्ण सुधार लेकर आया है।",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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}}は、開発ワークフローを向上させる強力な新機能と重要な改善をもたらします。",
|
||||
|
|
|
|||
|
|
@ -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}}은 개발 워크플로우를 향상시키는 강력한 새 기능과 중요한 개선사항을 제공합니다.",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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}}с",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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ế..."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": "自动工具超时:正在建议替代方案..."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": "自動工具逾時:正在建議替代方案..."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue