mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-08 22:21:23 +00:00
display error and refactor
This commit is contained in:
parent
c0c7878497
commit
28ab1c20ca
11 changed files with 543 additions and 248 deletions
|
|
@ -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)
|
||||
|
|
|
|||
135
src/core/prompts/__tests__/timeout-fallback-responses.spec.ts
Normal file
135
src/core/prompts/__tests__/timeout-fallback-responses.spec.ts
Normal 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")
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
134
src/core/prompts/instructions/__tests__/timeout-fallback.spec.ts
Normal file
134
src/core/prompts/instructions/__tests__/timeout-fallback.spec.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
106
src/core/prompts/instructions/timeout-fallback.ts
Normal file
106
src/core/prompts/instructions/timeout-fallback.ts
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,6 +230,7 @@
|
|||
"hasQuestion": "Roo has a question:"
|
||||
},
|
||||
"taskCompleted": "Task Completed",
|
||||
"toolTimeout": "Tool Timeout",
|
||||
"error": "Error",
|
||||
"diffError": {
|
||||
"title": "Edit Unsuccessful"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue