Support customize storage path (#1941)

Co-authored-by: Your Name <your.email@example.com>
This commit is contained in:
Jiayuan Chen 2025-03-25 03:22:55 +08:00 • committed by GitHub
parent 92850023c9
commit 1c9daf5a48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 174 additions and 11 deletions

1
package-lock.json generated
View file

@ -13024,7 +13024,6 @@
"resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz",
"integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
"chalk": "^2.4.1",

View file

@ -164,6 +164,11 @@
"command": "roo-cline.terminalExplainCommandInCurrentTask",
"title": "Explain This Command (Current Task)",
"category": "Terminal"
},
{
"command": "roo-cline.setCustomStoragePath",
"title": "Set Custom Storage Path",
"category": "Roo Code"
}
],
"menus": {
@ -288,6 +293,11 @@
}
},
"description": "Settings for VSCode Language Model API"
},
"roo-cline.customStoragePath": {
"type": "string",
"default": "",
"description": "Custom storage path. Leave empty to use the default location. Supports absolute paths (e.g. 'D:\\RooCodeStorage')"
}
}
}

View file

@ -85,6 +85,10 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
"roo-cline.registerHumanRelayCallback": registerHumanRelayCallback,
"roo-cline.unregisterHumanRelayCallback": unregisterHumanRelayCallback,
"roo-cline.handleHumanRelayResponse": handleHumanRelayResponse,
"roo-cline.setCustomStoragePath": async () => {
const { promptForCustomStoragePath } = await import("../shared/storagePathManager")
await promptForCustomStoragePath()
},
}
}

View file

@ -294,9 +294,10 @@ export class Cline extends EventEmitter<ClineEvents> {
if (!globalStoragePath) {
throw new Error("Global storage uri is invalid")
}
const taskDir = path.join(globalStoragePath, "tasks", this.taskId)
await fs.mkdir(taskDir, { recursive: true })
return taskDir
// Use storagePathManager to retrieve the task storage directory
const { getTaskDirectoryPath } = await import("../shared/storagePathManager")
return getTaskDirectoryPath(globalStoragePath, this.taskId)
}
private async getSavedApiConversationHistory(): Promise<Anthropic.MessageParam[]> {

View file

@ -2245,15 +2245,15 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
}
async ensureSettingsDirectoryExists(): Promise<string> {
const settingsDir = path.join(this.contextProxy.globalStorageUri.fsPath, "settings")
await fs.mkdir(settingsDir, { recursive: true })
return settingsDir
const { getSettingsDirectoryPath } = await import("../../shared/storagePathManager")
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
return getSettingsDirectoryPath(globalStoragePath)
}
private async ensureCacheDirectoryExists() {
const cacheDir = path.join(this.contextProxy.globalStorageUri.fsPath, "cache")
await fs.mkdir(cacheDir, { recursive: true })
return cacheDir
const { getCacheDirectoryPath } = await import("../../shared/storagePathManager")
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
return getCacheDirectoryPath(globalStoragePath)
}
private async readModelsFromCache(filename: string): Promise<Record<string, ModelInfo> | undefined> {
@ -2383,7 +2383,9 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || []
const historyItem = history.find((item) => item.id === id)
if (historyItem) {
const taskDirPath = path.join(this.contextProxy.globalStorageUri.fsPath, "tasks", id)
const { getTaskDirectoryPath } = await import("../../shared/storagePathManager")
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
const taskDirPath = await getTaskDirectoryPath(globalStoragePath, id)
const apiConversationHistoryFilePath = path.join(taskDirPath, GlobalFileNames.apiConversationHistory)
const uiMessagesFilePath = path.join(taskDirPath, GlobalFileNames.uiMessages)
const fileExists = await fileExistsAtPath(apiConversationHistoryFilePath)

View file

@ -0,0 +1,147 @@
import * as vscode from "vscode"
import * as path from "path"
import * as fs from "fs/promises"
/**
* Gets the base storage path for conversations
* If a custom path is configured, uses that path
* Otherwise uses the default VSCode extension global storage path
*/
export async function getStorageBasePath(defaultPath: string): Promise<string> {
// Get user-configured custom storage path
let customStoragePath = ""
try {
// This is the line causing the error in tests
const config = vscode.workspace.getConfiguration("roo-cline")
customStoragePath = config.get<string>("customStoragePath", "")
} catch (error) {
console.warn("Could not access VSCode configuration - using default path")
return defaultPath
}
// If no custom path is set, use default path
if (!customStoragePath) {
return defaultPath
}
try {
// Ensure custom path exists
await fs.mkdir(customStoragePath, { recursive: true })
// Test if path is writable
const testFile = path.join(customStoragePath, ".write_test")
await fs.writeFile(testFile, "test")
await fs.rm(testFile)
return customStoragePath
} catch (error) {
// If path is unusable, report error and fall back to default path
console.error(`Custom storage path is unusable: ${error instanceof Error ? error.message : String(error)}`)
if (vscode.window) {
vscode.window.showErrorMessage(
`Custom storage path "${customStoragePath}" is unusable, will use default path`,
)
}
return defaultPath
}
}
/**
* Gets the storage directory path for a task
*/
export async function getTaskDirectoryPath(globalStoragePath: string, taskId: string): Promise<string> {
const basePath = await getStorageBasePath(globalStoragePath)
const taskDir = path.join(basePath, "tasks", taskId)
await fs.mkdir(taskDir, { recursive: true })
return taskDir
}
/**
* Gets the settings directory path
*/
export async function getSettingsDirectoryPath(globalStoragePath: string): Promise<string> {
const basePath = await getStorageBasePath(globalStoragePath)
const settingsDir = path.join(basePath, "settings")
await fs.mkdir(settingsDir, { recursive: true })
return settingsDir
}
/**
* Gets the cache directory path
*/
export async function getCacheDirectoryPath(globalStoragePath: string): Promise<string> {
const basePath = await getStorageBasePath(globalStoragePath)
const cacheDir = path.join(basePath, "cache")
await fs.mkdir(cacheDir, { recursive: true })
return cacheDir
}
/**
* Prompts the user to set a custom storage path
* Displays an input box allowing the user to enter a custom path
*/
export async function promptForCustomStoragePath(): Promise<void> {
if (!vscode.window || !vscode.workspace) {
console.error("VS Code API not available")
return
}
let currentPath = ""
try {
const currentConfig = vscode.workspace.getConfiguration("roo-cline")
currentPath = currentConfig.get<string>("customStoragePath", "")
} catch (error) {
console.error("Could not access configuration")
return
}
const result = await vscode.window.showInputBox({
value: currentPath,
placeHolder: "D:\\RooCodeStorage",
prompt: "Enter custom conversation history storage path, leave empty to use default location",
validateInput: (input) => {
if (!input) {
return null // Allow empty value (use default path)
}
try {
// Validate path format
path.parse(input)
// Check if path is absolute
if (!path.isAbsolute(input)) {
return "Please enter an absolute path (e.g. D:\\RooCodeStorage or /home/user/storage)"
}
return null // Path format is valid
} catch (e) {
return "Please enter a valid path"
}
},
})
// If user canceled the operation, result will be undefined
if (result !== undefined) {
try {
const currentConfig = vscode.workspace.getConfiguration("roo-cline")
await currentConfig.update("customStoragePath", result, vscode.ConfigurationTarget.Global)
if (result) {
try {
// Test if path is accessible
await fs.mkdir(result, { recursive: true })
vscode.window.showInformationMessage(`Custom storage path set: ${result}`)
} catch (error) {
vscode.window.showErrorMessage(
`Cannot access path ${result}: ${error instanceof Error ? error.message : String(error)}`,
)
}
} else {
vscode.window.showInformationMessage("Reverted to using default storage path")
}
} catch (error) {
console.error("Failed to update configuration", error)
}
}
}