mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-10 03:28:03 +00:00
Support customize storage path (#1941)
Co-authored-by: Your Name <your.email@example.com>
This commit is contained in:
parent
92850023c9
commit
1c9daf5a48
6 changed files with 174 additions and 11 deletions
1
package-lock.json
generated
1
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -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')"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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[]> {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
147
src/shared/storagePathManager.ts
Normal file
147
src/shared/storagePathManager.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue