display error and refactor

This commit is contained in:
Will Li 2025-07-08 08:48:49 -07:00
parent c0c7878497
commit 28ab1c20ca
11 changed files with 543 additions and 248 deletions

View file

@ -80,6 +80,7 @@ export type ClineAsk = z.infer<typeof clineAskSchema>
* - `condense_context`: Context condensation/summarization has started
* - `condense_context_error`: Error occurred during context condensation
* - `codebase_search_result`: Results from searching the codebase
* - `tool_timeout`: Indicates a tool operation has timed out
*/
export const clineSays = [
"error",
@ -107,6 +108,7 @@ export const clineSays = [
"condense_context_error",
"codebase_search_result",
"user_edit_todos",
"tool_timeout",
] as const
export const clineSaySchema = z.enum(clineSays)

View file

@ -0,0 +1,135 @@
import { describe, it, expect } from "vitest"
import { formatResponse } from "../responses"
describe("timeout fallback responses", () => {
describe("generateContextualSuggestions", () => {
it("should generate execute_command suggestions", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateContextualSuggestions(
"execute_command",
{ command: "npm install" },
)
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("npm install")
expect(suggestions[0].text).toContain("smaller, sequential steps")
})
it("should generate read_file suggestions", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateContextualSuggestions("read_file", {
path: "/large/file.txt",
})
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("/large/file.txt")
expect(suggestions[0].text).toContain("smaller chunks")
})
it("should generate write_to_file suggestions", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateContextualSuggestions(
"write_to_file",
{ path: "/output/file.js" },
)
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("/output/file.js")
expect(suggestions[0].text).toContain("insert_content")
})
it("should generate browser_action suggestions", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateContextualSuggestions(
"browser_action",
{ action: "click" },
)
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("click")
expect(suggestions[0].text).toContain("smaller, more targeted steps")
})
it("should generate search_files suggestions", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateContextualSuggestions(
"search_files",
{ regex: "complex.*pattern" },
)
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("Narrow the search scope")
})
it("should generate generic suggestions for unknown tools", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateContextualSuggestions(
"unknown_tool" as any,
)
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("unknown_tool operation")
expect(suggestions[0].text).toContain("smaller steps")
})
})
describe("individual suggestion generators", () => {
it("should generate command suggestions with default command name", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateCommandSuggestions()
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("the command")
})
it("should generate read file suggestions with default file name", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateReadFileSuggestions()
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("the file")
})
it("should generate write file suggestions with default file name", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateWriteFileSuggestions()
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("the file")
})
it("should generate browser suggestions with default action name", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateBrowserSuggestions()
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("browser action")
})
it("should generate search suggestions", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateSearchSuggestions()
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("Narrow the search scope")
})
it("should generate generic suggestions", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateGenericSuggestions("new_task")
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toContain("new_task operation")
})
})
describe("suggestion structure", () => {
it("should return suggestions with text property", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateGenericSuggestions("new_task")
suggestions.forEach((suggestion) => {
expect(suggestion).toHaveProperty("text")
expect(typeof suggestion.text).toBe("string")
expect(suggestion.text.length).toBeGreaterThan(0)
})
})
it("should optionally include mode property", () => {
const suggestions = formatResponse.timeoutFallbackSuggestions.generateGenericSuggestions("new_task")
suggestions.forEach((suggestion) => {
if (suggestion.mode) {
expect(typeof suggestion.mode).toBe("string")
}
})
})
})
})

View file

@ -0,0 +1,134 @@
import { describe, it, expect } from "vitest"
import {
createTimeoutFallbackPrompt,
parseTimeoutFallbackResponse,
type TimeoutFallbackContext,
} from "../timeout-fallback"
describe("timeout-fallback", () => {
describe("createTimeoutFallbackPrompt", () => {
it("should create a basic prompt with required context", () => {
const context: TimeoutFallbackContext = {
toolName: "execute_command",
timeoutMs: 30000,
executionTimeMs: 32000,
}
const prompt = createTimeoutFallbackPrompt(context)
expect(prompt).toContain("execute_command operation has timed out after 30 seconds")
expect(prompt).toContain("actual execution time: 32 seconds")
expect(prompt).toContain("Tool: execute_command")
expect(prompt).toContain("Generate exactly 3-4 specific, actionable suggestions")
})
it("should include tool parameters when provided", () => {
const context: TimeoutFallbackContext = {
toolName: "read_file",
timeoutMs: 15000,
executionTimeMs: 16000,
toolParams: {
path: "/large/file.txt",
line_range: "1-10000",
},
}
const prompt = createTimeoutFallbackPrompt(context)
expect(prompt).toContain("Parameters:")
expect(prompt).toContain("/large/file.txt")
expect(prompt).toContain("1-10000")
})
it("should include task context when provided", () => {
const context: TimeoutFallbackContext = {
toolName: "write_to_file",
timeoutMs: 20000,
executionTimeMs: 22000,
taskContext: {
currentStep: "Creating configuration file",
workingDirectory: "/project/config",
},
}
const prompt = createTimeoutFallbackPrompt(context)
expect(prompt).toContain("Current step: Creating configuration file")
expect(prompt).toContain("Working directory: /project/config")
})
})
describe("parseTimeoutFallbackResponse", () => {
it("should parse numbered list responses", () => {
const response = `Here are the suggestions:
1. Break the command into smaller parts
2. Use background execution with nohup
3. Try an alternative approach
4. Increase the timeout setting`
const suggestions = parseTimeoutFallbackResponse(response)
expect(suggestions).toHaveLength(4)
expect(suggestions[0].text).toBe("Break the command into smaller parts")
expect(suggestions[1].text).toBe("Use background execution with nohup")
expect(suggestions[2].text).toBe("Try an alternative approach")
expect(suggestions[3].text).toBe("Increase the timeout setting")
})
it("should parse numbered list with parentheses", () => {
const response = `Suggestions:
1) Check file permissions
2) Use smaller chunks
3) Try a different tool`
const suggestions = parseTimeoutFallbackResponse(response)
expect(suggestions).toHaveLength(3)
expect(suggestions[0].text).toBe("Check file permissions")
expect(suggestions[1].text).toBe("Use smaller chunks")
expect(suggestions[2].text).toBe("Try a different tool")
})
it("should fallback to sentence parsing when no numbered list found", () => {
const response = `You should try breaking the operation into smaller parts. Consider using an alternative approach. Check system resources and try again.`
const suggestions = parseTimeoutFallbackResponse(response)
expect(suggestions.length).toBeGreaterThan(0)
expect(suggestions[0].text).toBe("You should try breaking the operation into smaller parts")
})
it("should limit suggestions to 4 items", () => {
const response = `1. First suggestion
2. Second suggestion
3. Third suggestion
4. Fourth suggestion
5. Fifth suggestion
6. Sixth suggestion`
const suggestions = parseTimeoutFallbackResponse(response)
expect(suggestions).toHaveLength(4)
})
it("should filter out suggestions that are too long", () => {
const response = `1. Good suggestion
2. This is a very long suggestion that exceeds the maximum character limit and should be filtered out because it's too verbose
3. Another good suggestion`
const suggestions = parseTimeoutFallbackResponse(response)
expect(suggestions).toHaveLength(2)
expect(suggestions[0].text).toBe("Good suggestion")
expect(suggestions[1].text).toBe("Another good suggestion")
})
it("should return empty array for invalid responses", () => {
const response = ""
const suggestions = parseTimeoutFallbackResponse(response)
expect(suggestions).toHaveLength(0)
})
})
})

View file

@ -0,0 +1,106 @@
import type { ToolName } from "@roo-code/types"
export interface TimeoutFallbackContext {
toolName: ToolName
timeoutMs: number
executionTimeMs: number
toolParams?: Record<string, any>
errorMessage?: string
taskContext?: {
currentStep?: string
previousActions?: string[]
workingDirectory?: string
}
}
/**
* Create a prompt for the AI to generate contextual timeout fallback suggestions
*/
export function createTimeoutFallbackPrompt(context: TimeoutFallbackContext): string {
const { toolName, timeoutMs, executionTimeMs, toolParams, taskContext } = context
const timeoutSeconds = Math.round(timeoutMs / 1000)
const executionSeconds = Math.round(executionTimeMs / 1000)
let prompt = `A ${toolName} operation has timed out after ${timeoutSeconds} seconds (actual execution time: ${executionSeconds} seconds).
Context:
- Tool: ${toolName}
- Timeout limit: ${timeoutSeconds}s
- Actual execution time: ${executionSeconds}s`
// Add tool-specific context
if (toolParams) {
prompt += `\n- Parameters: ${JSON.stringify(toolParams, null, 2)}`
}
// Add task context if available
if (taskContext) {
if (taskContext.currentStep) {
prompt += `\n- Current step: ${taskContext.currentStep}`
}
if (taskContext.workingDirectory) {
prompt += `\n- Working directory: ${taskContext.workingDirectory}`
}
}
prompt += `
Generate exactly 3-4 specific, actionable suggestions for how to proceed after this timeout. Each suggestion should be:
1. Contextually relevant to the specific ${toolName} operation that timed out
2. Actionable and specific (not generic advice)
3. Focused on solving the immediate problem
4. Ordered by likelihood of success
Format your response as a simple numbered list:
1. [First suggestion]
2. [Second suggestion]
3. [Third suggestion]
4. [Fourth suggestion (optional)]
Focus on practical solutions like:
- Breaking the operation into smaller parts
- Using alternative tools or methods
- Adjusting parameters or settings
- Checking for underlying issues
- Optimizing the approach
Keep each suggestion concise (under 80 characters) and actionable.`
return prompt
}
/**
* Parse AI response to extract suggestions
*/
export function parseTimeoutFallbackResponse(response: string): Array<{ text: string; mode?: string }> {
const suggestions: Array<{ text: string; mode?: string }> = []
// Look for numbered list items
const lines = response.split("\n")
for (const line of lines) {
const trimmed = line.trim()
// Match patterns like "1. suggestion", "2) suggestion", etc.
const match = trimmed.match(/^(\d+)[.)]\s*(.+)$/)
if (match && match[2]) {
const suggestionText = match[2].trim()
if (suggestionText.length > 0 && suggestionText.length <= 120) {
suggestions.push({ text: suggestionText })
}
}
}
// If no numbered list found, try to extract sentences
if (suggestions.length === 0) {
const sentences = response
.split(/[.!?]+/)
.map((s) => s.trim())
.filter((s) => s.length > 10 && s.length <= 120)
for (let i = 0; i < Math.min(4, sentences.length); i++) {
suggestions.push({ text: sentences[i] })
}
}
return suggestions.slice(0, 4) // Limit to 4 suggestions
}

View file

@ -3,6 +3,7 @@ import * as path from "path"
import * as diff from "diff"
import { RooIgnoreController, LOCK_TEXT_SYMBOL } from "../ignore/RooIgnoreController"
import { RooProtectedController } from "../protect/RooProtectedController"
import type { ToolName } from "@roo-code/types"
export const formatResponse = {
toolDenied: () => `The user denied this operation.`,
@ -185,6 +186,141 @@ Otherwise, if you have not completed the task and do not need additional informa
const prettyPatchLines = lines.slice(4)
return prettyPatchLines.join("\n")
},
/**
* Generate contextual timeout fallback suggestions based on tool type and parameters
*/
timeoutFallbackSuggestions: {
generateContextualSuggestions: (
toolName: ToolName,
toolParams?: Record<string, any>,
): Array<{ text: string; mode?: string }> => {
switch (toolName) {
case "execute_command":
return formatResponse.timeoutFallbackSuggestions.generateCommandSuggestions(toolParams)
case "read_file":
return formatResponse.timeoutFallbackSuggestions.generateReadFileSuggestions(toolParams)
case "write_to_file":
return formatResponse.timeoutFallbackSuggestions.generateWriteFileSuggestions(toolParams)
case "browser_action":
return formatResponse.timeoutFallbackSuggestions.generateBrowserSuggestions(toolParams)
case "search_files":
return formatResponse.timeoutFallbackSuggestions.generateSearchSuggestions(toolParams)
default:
return formatResponse.timeoutFallbackSuggestions.generateGenericSuggestions(toolName)
}
},
generateCommandSuggestions: (params?: Record<string, any>): Array<{ text: string; mode?: string }> => {
const command = params?.command || "the command"
return [
{
text: `Break "${command}" into smaller, sequential steps that can complete faster`,
},
{
text: `Run "${command}" in the background using '&' or 'nohup' to avoid blocking`,
},
{
text: `Try an alternative approach or tool to accomplish the same goal`,
},
{
text: `Increase the timeout setting if this operation legitimately needs more time`,
},
]
},
generateReadFileSuggestions: (params?: Record<string, any>): Array<{ text: string; mode?: string }> => {
const filePath = params?.path || "the file"
return [
{
text: `Read "${filePath}" in smaller chunks using line ranges`,
},
{
text: `Check if "${filePath}" is accessible and not locked by another process`,
},
{
text: `Use a different approach to access the file content`,
},
{
text: `Increase the timeout if this is a legitimately large file`,
},
]
},
generateWriteFileSuggestions: (params?: Record<string, any>): Array<{ text: string; mode?: string }> => {
const filePath = params?.path || "the file"
return [
{
text: `Write to "${filePath}" incrementally using insert_content instead`,
},
{
text: `Check if "${filePath}" is writable and not locked`,
},
{
text: `Use apply_diff for targeted changes instead of full file replacement`,
},
{
text: `Break the content into smaller write operations`,
},
]
},
generateBrowserSuggestions: (params?: Record<string, any>): Array<{ text: string; mode?: string }> => {
const action = params?.action || "browser action"
return [
{
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`,
},
]
},
generateSearchSuggestions: (params?: Record<string, any>): Array<{ text: string; mode?: string }> => {
return [
{
text: `Narrow the search scope to specific directories`,
},
{
text: `Use simpler search patterns or literal strings`,
},
{
text: `Apply file type filters to reduce search space`,
},
{
text: `Search incrementally in smaller batches`,
},
]
},
generateGenericSuggestions: (toolName: ToolName): Array<{ text: string; mode?: string }> => {
return [
{
text: `Break the ${toolName} operation into smaller steps`,
},
{
text: `Try an alternative approach to accomplish the same goal`,
},
{
text: `Check system resources and try again`,
},
{
text: `Increase the timeout setting for this operation`,
},
]
},
},
}
// to avoid circular dependency

View file

@ -1,19 +1,12 @@
import type { ToolName } from "@roo-code/types"
import type { Task } from "../task/Task"
import type { SingleCompletionHandler } from "../../api"
export interface TimeoutFallbackContext {
toolName: ToolName
timeoutMs: number
executionTimeMs: number
toolParams?: Record<string, any>
errorMessage?: string
taskContext?: {
currentStep?: string
previousActions?: string[]
workingDirectory?: string
}
}
import {
createTimeoutFallbackPrompt,
parseTimeoutFallbackResponse,
type TimeoutFallbackContext,
} from "../prompts/instructions/timeout-fallback"
import { formatResponse } from "../prompts/responses"
export interface TimeoutFallbackResult {
success: boolean
@ -67,11 +60,11 @@ export class TimeoutFallbackGenerator {
apiHandler: SingleCompletionHandler,
): Promise<TimeoutFallbackResult> {
try {
const prompt = this.createAiPrompt(context)
const prompt = createTimeoutFallbackPrompt(context)
const aiResponse = await apiHandler.completePrompt(prompt)
// Parse the AI response to extract suggestions
const suggestions = this.parseAiResponse(aiResponse)
const suggestions = parseTimeoutFallbackResponse(aiResponse)
if (suggestions.length === 0) {
throw new Error("No valid suggestions generated by AI")
@ -105,103 +98,14 @@ export class TimeoutFallbackGenerator {
}
}
/**
* Create a prompt for the AI to generate contextual timeout fallback suggestions
*/
private static createAiPrompt(context: TimeoutFallbackContext): string {
const { toolName, timeoutMs, executionTimeMs, toolParams, taskContext } = context
const timeoutSeconds = Math.round(timeoutMs / 1000)
const executionSeconds = Math.round(executionTimeMs / 1000)
let prompt = `A ${toolName} operation has timed out after ${timeoutSeconds} seconds (actual execution time: ${executionSeconds} seconds).
Context:
- Tool: ${toolName}
- Timeout limit: ${timeoutSeconds}s
- Actual execution time: ${executionSeconds}s`
// Add tool-specific context
if (toolParams) {
prompt += `\n- Parameters: ${JSON.stringify(toolParams, null, 2)}`
}
// Add task context if available
if (taskContext) {
if (taskContext.currentStep) {
prompt += `\n- Current step: ${taskContext.currentStep}`
}
if (taskContext.workingDirectory) {
prompt += `\n- Working directory: ${taskContext.workingDirectory}`
}
}
prompt += `
Generate exactly 3-4 specific, actionable suggestions for how to proceed after this timeout. Each suggestion should be:
1. Contextually relevant to the specific ${toolName} operation that timed out
2. Actionable and specific (not generic advice)
3. Focused on solving the immediate problem
4. Ordered by likelihood of success
Format your response as a simple numbered list:
1. [First suggestion]
2. [Second suggestion]
3. [Third suggestion]
4. [Fourth suggestion (optional)]
Focus on practical solutions like:
- Breaking the operation into smaller parts
- Using alternative tools or methods
- Adjusting parameters or settings
- Checking for underlying issues
- Optimizing the approach
Keep each suggestion concise (under 80 characters) and actionable.`
return prompt
}
/**
* Parse AI response to extract suggestions
*/
private static parseAiResponse(response: string): Array<{ text: string; mode?: string }> {
const suggestions: Array<{ text: string; mode?: string }> = []
// Look for numbered list items
const lines = response.split("\n")
for (const line of lines) {
const trimmed = line.trim()
// Match patterns like "1. suggestion", "2) suggestion", etc.
const match = trimmed.match(/^(\d+)[.)]\s*(.+)$/)
if (match && match[2]) {
const suggestionText = match[2].trim()
if (suggestionText.length > 0 && suggestionText.length <= 120) {
suggestions.push({ text: suggestionText })
}
}
}
// If no numbered list found, try to extract sentences
if (suggestions.length === 0) {
const sentences = response
.split(/[.!?]+/)
.map((s) => s.trim())
.filter((s) => s.length > 10 && s.length <= 120)
for (let i = 0; i < Math.min(4, sentences.length); i++) {
suggestions.push({ text: sentences[i] })
}
}
return suggestions.slice(0, 4) // Limit to 4 suggestions
}
/**
* Generate static fallback suggestions when AI is unavailable
*/
private static generateStaticToolCall(context: TimeoutFallbackContext): TimeoutFallbackResult["toolCall"] {
const suggestions = this.generateContextualSuggestions(context)
const suggestions = formatResponse.timeoutFallbackSuggestions.generateContextualSuggestions(
context.toolName,
context.toolParams,
)
const question = `The ${context.toolName} operation timed out after ${Math.round(context.timeoutMs / 1000)} seconds. How would you like to proceed?`
@ -221,138 +125,4 @@ Keep each suggestion concise (under 80 characters) and actionable.`
},
}
}
/**
* Generate contextual suggestions based on tool type and parameters
*/
private static generateContextualSuggestions(
context: TimeoutFallbackContext,
): Array<{ text: string; mode?: string }> {
const { toolName, toolParams } = context
switch (toolName) {
case "execute_command":
return this.generateCommandSuggestions(toolParams)
case "read_file":
return this.generateReadFileSuggestions(toolParams)
case "write_to_file":
return this.generateWriteFileSuggestions(toolParams)
case "browser_action":
return this.generateBrowserSuggestions(toolParams)
case "search_files":
return this.generateSearchSuggestions(toolParams)
default:
return this.generateGenericSuggestions(context)
}
}
private static generateCommandSuggestions(params?: Record<string, any>): Array<{ text: string; mode?: string }> {
const command = params?.command || "the command"
return [
{
text: `Break "${command}" into smaller, sequential steps that can complete faster`,
},
{
text: `Run "${command}" in the background using '&' or 'nohup' to avoid blocking`,
},
{
text: `Try an alternative approach or tool to accomplish the same goal`,
},
{
text: `Increase the timeout setting if this operation legitimately needs more time`,
},
]
}
private static generateReadFileSuggestions(params?: Record<string, any>): Array<{ text: string; mode?: string }> {
const filePath = params?.path || "the file"
return [
{
text: `Read "${filePath}" in smaller chunks using line ranges`,
},
{
text: `Check if "${filePath}" is accessible and not locked by another process`,
},
{
text: `Use a different approach to access the file content`,
},
{
text: `Increase the timeout if this is a legitimately large file`,
},
]
}
private static generateWriteFileSuggestions(params?: Record<string, any>): Array<{ text: string; mode?: string }> {
const filePath = params?.path || "the file"
return [
{
text: `Write to "${filePath}" incrementally using insert_content instead`,
},
{
text: `Check if "${filePath}" is writable and not locked`,
},
{
text: `Use apply_diff for targeted changes instead of full file replacement`,
},
{
text: `Break the content into smaller write operations`,
},
]
}
private static generateBrowserSuggestions(params?: Record<string, any>): Array<{ text: string; mode?: string }> {
const action = params?.action || "browser action"
return [
{
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`,
},
]
}
private static generateSearchSuggestions(params?: Record<string, any>): Array<{ text: string; mode?: string }> {
return [
{
text: `Narrow the search scope to specific directories`,
},
{
text: `Use simpler search patterns or literal strings`,
},
{
text: `Apply file type filters to reduce search space`,
},
{
text: `Search incrementally in smaller batches`,
},
]
}
private static generateGenericSuggestions(context: TimeoutFallbackContext): Array<{ text: string; mode?: string }> {
return [
{
text: `Break the ${context.toolName} operation into smaller steps`,
},
{
text: `Try an alternative approach to accomplish the same goal`,
},
{
text: `Check system resources and try again`,
},
{
text: `Increase the timeout setting for this operation`,
},
]
}
}

View file

@ -1,10 +1,7 @@
import type { ToolName } from "@roo-code/types"
import { formatResponse } from "../prompts/responses"
import {
TimeoutFallbackGenerator,
type TimeoutFallbackContext,
type TimeoutFallbackResult,
} from "./TimeoutFallbackGenerator"
import type { TimeoutFallbackContext } from "../prompts/instructions/timeout-fallback"
import { TimeoutFallbackGenerator, type TimeoutFallbackResult } from "./TimeoutFallbackGenerator"
import type { Task } from "../task/Task"
import { parseAssistantMessage } from "../assistant-message/parseAssistantMessage"
@ -24,6 +21,13 @@ export class TimeoutFallbackHandler {
): Promise<string> {
const baseResponse = formatResponse.toolTimeout(toolName, timeoutMs, executionTimeMs)
// Create a timeout message for display in the chat
if (task) {
await task.say("tool_timeout", "", undefined, false, undefined, undefined, {
isNonInteractive: true,
})
}
// Create context for AI fallback generation
const aiContext: TimeoutFallbackContext = {
toolName,

View file

@ -106,6 +106,7 @@ describe("Timeout Integration Tests", () => {
const mockTask = {
assistantMessageContent: [],
cwd: "/test/dir",
say: vitest.fn().mockResolvedValue(undefined),
} as unknown as Task
const response = await TimeoutFallbackHandler.createTimeoutResponse(

View file

@ -5,4 +5,5 @@ export { TimeoutFallbackGenerator } from "./TimeoutFallbackGenerator"
export type { TimeoutConfig, TimeoutResult, TimeoutEvent } from "./TimeoutManager"
export type { ToolExecutionOptions } from "./ToolExecutionWrapper"
export type { TimeoutFallbackContext, TimeoutFallbackResult } from "./TimeoutFallbackGenerator"
export type { TimeoutFallbackResult } from "./TimeoutFallbackGenerator"
export type { TimeoutFallbackContext } from "../prompts/instructions/timeout-fallback"

View file

@ -258,6 +258,11 @@ export const ChatRowContent = ({
/>,
<span style={{ color: normalColor, fontWeight: "bold" }}>{t("chat:questions.hasQuestion")}</span>,
]
case "tool_timeout":
return [
<span className="codicon codicon-clock" style={{ color: errorColor, marginBottom: "-1.5px" }} />,
<span style={{ color: errorColor, fontWeight: "bold" }}>{t("chat:toolTimeout")}</span>,
]
default:
return [null, null]
}

View file

@ -230,6 +230,7 @@
"hasQuestion": "Roo has a question:"
},
"taskCompleted": "Task Completed",
"toolTimeout": "Tool Timeout",
"error": "Error",
"diffError": {
"title": "Edit Unsuccessful"