mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-12 23:01:21 +00:00
fix: enhance indexing error recovery to prevent workspace reload requirement
- Add resetFromError() and canStartIndexing() methods to CodeIndexStateManager - Improve startIndexing() in CodeIndexOrchestrator to handle error state recovery - Enhance CodeIndexManager.startIndexing() with better error handling and recovery - Improve webview message handler for startIndexing with detailed error feedback - Add comprehensive error recovery tests Fixes #5826: Users can now restart indexing after errors without workspace reload
This commit is contained in:
parent
80fcd1ba74
commit
aa9c89cfb2
5 changed files with 298 additions and 14 deletions
|
|
@ -2073,15 +2073,94 @@ export const webviewMessageHandler = async (
|
|||
case "startIndexing": {
|
||||
try {
|
||||
const manager = provider.codeIndexManager!
|
||||
if (manager.isFeatureEnabled && manager.isFeatureConfigured) {
|
||||
if (!manager.isInitialized) {
|
||||
await manager.initialize(provider.contextProxy)
|
||||
}
|
||||
if (!manager.isFeatureEnabled) {
|
||||
await provider.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: {
|
||||
systemStatus: "Standby",
|
||||
message: "Code indexing is disabled. Please enable it in settings.",
|
||||
processedItems: 0,
|
||||
totalItems: 0,
|
||||
currentItemUnit: "blocks",
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
manager.startIndexing()
|
||||
if (!manager.isFeatureConfigured) {
|
||||
await provider.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: {
|
||||
systemStatus: "Standby",
|
||||
message: "Code indexing is not configured. Please configure your embedder settings.",
|
||||
processedItems: 0,
|
||||
totalItems: 0,
|
||||
currentItemUnit: "blocks",
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
// Initialize manager if needed
|
||||
if (!manager.isInitialized) {
|
||||
try {
|
||||
await manager.initialize(provider.contextProxy)
|
||||
} catch (initError) {
|
||||
const errorMessage = initError instanceof Error ? initError.message : String(initError)
|
||||
provider.log(`Error initializing code index manager: ${errorMessage}`)
|
||||
await provider.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: {
|
||||
systemStatus: "Error",
|
||||
message: `Failed to initialize: ${errorMessage}`,
|
||||
processedItems: 0,
|
||||
totalItems: 0,
|
||||
currentItemUnit: "blocks",
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to start indexing with enhanced error recovery
|
||||
try {
|
||||
await manager.startIndexing()
|
||||
// Send updated status to webview
|
||||
await provider.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: manager.getCurrentStatus(),
|
||||
})
|
||||
} catch (startError) {
|
||||
const errorMessage = startError instanceof Error ? startError.message : String(startError)
|
||||
provider.log(`Error starting indexing: ${errorMessage}`)
|
||||
|
||||
// Send error status with recovery suggestion to webview
|
||||
await provider.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: {
|
||||
systemStatus: "Error",
|
||||
message: `Failed to start indexing: ${errorMessage}. You can try starting indexing again.`,
|
||||
processedItems: 0,
|
||||
totalItems: 0,
|
||||
currentItemUnit: "blocks",
|
||||
},
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
provider.log(`Error starting indexing: ${error instanceof Error ? error.message : String(error)}`)
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
provider.log(`Unexpected error in startIndexing handler: ${errorMessage}`)
|
||||
|
||||
// Send generic error status to webview
|
||||
await provider.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: {
|
||||
systemStatus: "Error",
|
||||
message: `An unexpected error occurred. Please try again or check your configuration.`,
|
||||
processedItems: 0,
|
||||
totalItems: 0,
|
||||
currentItemUnit: "blocks",
|
||||
},
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
|
|||
161
src/services/code-index/__tests__/error-recovery.spec.ts
Normal file
161
src/services/code-index/__tests__/error-recovery.spec.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import { describe, it, expect, beforeEach, vi } from "vitest"
|
||||
import { CodeIndexStateManager } from "../state-manager"
|
||||
import { CodeIndexOrchestrator } from "../orchestrator"
|
||||
import { CodeIndexManager } from "../manager"
|
||||
|
||||
describe("Code Index Error Recovery", () => {
|
||||
let stateManager: CodeIndexStateManager
|
||||
let mockConfigManager: any
|
||||
let mockVectorStore: any
|
||||
let mockScanner: any
|
||||
let mockFileWatcher: any
|
||||
let mockCacheManager: any
|
||||
|
||||
beforeEach(() => {
|
||||
stateManager = new CodeIndexStateManager()
|
||||
|
||||
mockConfigManager = {
|
||||
isFeatureConfigured: true,
|
||||
isFeatureEnabled: true,
|
||||
}
|
||||
|
||||
mockVectorStore = {
|
||||
initialize: vi.fn().mockResolvedValue(false),
|
||||
clearCollection: vi.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
|
||||
mockScanner = {
|
||||
scanDirectory: vi.fn().mockResolvedValue({
|
||||
stats: { totalFiles: 5, processedFiles: 5 }
|
||||
}),
|
||||
}
|
||||
|
||||
mockFileWatcher = {
|
||||
initialize: vi.fn().mockResolvedValue(undefined),
|
||||
onDidStartBatchProcessing: vi.fn().mockReturnValue({ dispose: vi.fn() }),
|
||||
onBatchProgressUpdate: vi.fn().mockReturnValue({ dispose: vi.fn() }),
|
||||
onDidFinishBatchProcessing: vi.fn().mockReturnValue({ dispose: vi.fn() }),
|
||||
dispose: vi.fn(),
|
||||
}
|
||||
|
||||
mockCacheManager = {
|
||||
clearCacheFile: vi.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
})
|
||||
|
||||
describe("CodeIndexStateManager", () => {
|
||||
it("should allow resetting from error state", () => {
|
||||
// Set to error state
|
||||
stateManager.setSystemState("Error", "Test error")
|
||||
expect(stateManager.state).toBe("Error")
|
||||
|
||||
// Reset from error
|
||||
stateManager.resetFromError()
|
||||
expect(stateManager.state).toBe("Standby")
|
||||
})
|
||||
|
||||
it("should not reset if not in error state", () => {
|
||||
stateManager.setSystemState("Indexed", "All good")
|
||||
stateManager.resetFromError()
|
||||
expect(stateManager.state).toBe("Indexed")
|
||||
})
|
||||
|
||||
it("should correctly identify when indexing can start", () => {
|
||||
// Test all valid states
|
||||
stateManager.setSystemState("Standby")
|
||||
expect(stateManager.canStartIndexing()).toBe(true)
|
||||
|
||||
stateManager.setSystemState("Error")
|
||||
expect(stateManager.canStartIndexing()).toBe(true)
|
||||
|
||||
stateManager.setSystemState("Indexed")
|
||||
expect(stateManager.canStartIndexing()).toBe(true)
|
||||
|
||||
// Test invalid state
|
||||
stateManager.setSystemState("Indexing")
|
||||
expect(stateManager.canStartIndexing()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("CodeIndexOrchestrator", () => {
|
||||
let orchestrator: CodeIndexOrchestrator
|
||||
|
||||
beforeEach(() => {
|
||||
orchestrator = new CodeIndexOrchestrator(
|
||||
mockConfigManager,
|
||||
stateManager,
|
||||
"/test/workspace",
|
||||
mockCacheManager,
|
||||
mockVectorStore,
|
||||
mockScanner,
|
||||
mockFileWatcher
|
||||
)
|
||||
})
|
||||
|
||||
it("should recover from error state when starting indexing", async () => {
|
||||
// Set to error state
|
||||
stateManager.setSystemState("Error", "Previous error")
|
||||
expect(stateManager.state).toBe("Error")
|
||||
|
||||
// Start indexing should recover from error
|
||||
await orchestrator.startIndexing()
|
||||
|
||||
// Should have reset from error and started indexing
|
||||
expect(stateManager.state).toBe("Indexed")
|
||||
})
|
||||
|
||||
it("should not start if already processing", async () => {
|
||||
// Start indexing once
|
||||
const firstStart = orchestrator.startIndexing()
|
||||
|
||||
// Try to start again while processing
|
||||
await orchestrator.startIndexing()
|
||||
|
||||
// Wait for first start to complete
|
||||
await firstStart
|
||||
|
||||
// Should only have been called once
|
||||
expect(mockVectorStore.initialize).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should handle configuration errors gracefully", async () => {
|
||||
mockConfigManager.isFeatureConfigured = false
|
||||
|
||||
await orchestrator.startIndexing()
|
||||
|
||||
expect(stateManager.state).toBe("Standby")
|
||||
expect(mockVectorStore.initialize).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("Integration Test", () => {
|
||||
it("should allow restarting indexing after error without workspace reload", async () => {
|
||||
const orchestrator = new CodeIndexOrchestrator(
|
||||
mockConfigManager,
|
||||
stateManager,
|
||||
"/test/workspace",
|
||||
mockCacheManager,
|
||||
mockVectorStore,
|
||||
mockScanner,
|
||||
mockFileWatcher
|
||||
)
|
||||
|
||||
// Simulate an error during indexing
|
||||
mockVectorStore.initialize.mockRejectedValueOnce(new Error("Connection failed"))
|
||||
|
||||
// First attempt should fail
|
||||
await orchestrator.startIndexing()
|
||||
expect(stateManager.state).toBe("Error")
|
||||
|
||||
// Fix the mock to succeed
|
||||
mockVectorStore.initialize.mockResolvedValueOnce(false)
|
||||
|
||||
// Second attempt should succeed without requiring workspace reload
|
||||
await orchestrator.startIndexing()
|
||||
expect(stateManager.state).toBe("Indexed")
|
||||
|
||||
// Should have attempted initialization twice
|
||||
expect(mockVectorStore.initialize).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -157,14 +157,36 @@ export class CodeIndexManager {
|
|||
|
||||
/**
|
||||
* Initiates the indexing process (initial scan and starts watcher).
|
||||
* Enhanced with error recovery to prevent users from needing to reload workspace.
|
||||
*/
|
||||
|
||||
public async startIndexing(): Promise<void> {
|
||||
if (!this.isFeatureEnabled) {
|
||||
return
|
||||
}
|
||||
this.assertInitialized()
|
||||
await this._orchestrator!.startIndexing()
|
||||
|
||||
// If we're in an error state, attempt to recover by resetting the state
|
||||
if (this._stateManager.state === "Error") {
|
||||
console.log("[CodeIndexManager] Attempting to recover from error state before starting indexing.")
|
||||
this._stateManager.resetFromError()
|
||||
}
|
||||
|
||||
try {
|
||||
await this._orchestrator!.startIndexing()
|
||||
} catch (error) {
|
||||
// Enhanced error handling with recovery suggestions
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error("[CodeIndexManager] Failed to start indexing:", errorMessage)
|
||||
|
||||
// Set a more informative error state that suggests recovery options
|
||||
this._stateManager.setSystemState(
|
||||
"Error",
|
||||
`Failed to start indexing: ${errorMessage}. Try starting indexing again or check your configuration.`
|
||||
)
|
||||
|
||||
// Re-throw the error so callers can handle it appropriately
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -100,18 +100,22 @@ export class CodeIndexOrchestrator {
|
|||
return
|
||||
}
|
||||
|
||||
if (
|
||||
this._isProcessing ||
|
||||
(this.stateManager.state !== "Standby" &&
|
||||
this.stateManager.state !== "Error" &&
|
||||
this.stateManager.state !== "Indexed")
|
||||
) {
|
||||
// Check if we can start indexing using the state manager's logic
|
||||
if (this._isProcessing || !this.stateManager.canStartIndexing()) {
|
||||
console.warn(
|
||||
`[CodeIndexOrchestrator] Start rejected: Already processing or in state ${this.stateManager.state}.`,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// If we're recovering from an error state, reset it first
|
||||
if (this.stateManager.state === "Error") {
|
||||
console.log("[CodeIndexOrchestrator] Recovering from error state, resetting to standby.")
|
||||
this.stateManager.resetFromError()
|
||||
// Stop any existing watcher to ensure clean state
|
||||
this.stopWatcher()
|
||||
}
|
||||
|
||||
this._isProcessing = true
|
||||
this.stateManager.setSystemState("Indexing", "Initializing services...")
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,24 @@ export class CodeIndexStateManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the system from an error state to standby, allowing indexing to be restarted.
|
||||
* This method provides a clean recovery path from error states without requiring workspace reload.
|
||||
*/
|
||||
public resetFromError(): void {
|
||||
if (this._systemStatus === "Error") {
|
||||
this.setSystemState("Standby", "Ready to start indexing.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the system is in a state that allows starting indexing.
|
||||
* @returns true if indexing can be started, false otherwise
|
||||
*/
|
||||
public canStartIndexing(): boolean {
|
||||
return this._systemStatus === "Standby" || this._systemStatus === "Error" || this._systemStatus === "Indexed"
|
||||
}
|
||||
|
||||
public reportBlockIndexingProgress(processedItems: number, totalItems: number): void {
|
||||
const progressChanged = processedItems !== this._processedItems || totalItems !== this._totalItems
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue