Skip to content

Commit a9f7756

Browse files
author
Your Name
committed
Support custom storage location
1 parent 78bc0c1 commit a9f7756

File tree

5 files changed

+148
-11
lines changed

5 files changed

+148
-11
lines changed

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@
154154
"command": "roo-cline.terminalExplainCommandInCurrentTask",
155155
"title": "Roo Code: Explain This Command (Current Task)",
156156
"category": "Terminal"
157+
},
158+
{
159+
"command": "roo-cline.setCustomStoragePath",
160+
"title": "Set Custom Storage Path",
161+
"category": "Roo Code"
157162
}
158163
],
159164
"menus": {
@@ -270,6 +275,11 @@
270275
}
271276
},
272277
"description": "Settings for VSCode Language Model API"
278+
},
279+
"roo-cline.customStoragePath": {
280+
"type": "string",
281+
"default": "",
282+
"description": "Custom storage path. Leave empty to use the default location. Supports absolute paths (e.g. 'D:\\RooCodeStorage')"
273283
}
274284
}
275285
}

src/activate/registerCommands.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
8585
"roo-cline.registerHumanRelayCallback": registerHumanRelayCallback,
8686
"roo-cline.unregisterHumanRelayCallback": unregisterHumanRelayCallback,
8787
"roo-cline.handleHumanRelayResponse": handleHumanRelayResponse,
88+
"roo-cline.setCustomStoragePath": async () => {
89+
const { promptForCustomStoragePath } = await import("../shared/storagePathManager")
90+
await promptForCustomStoragePath()
91+
},
8892
}
8993
}
9094

src/core/Cline.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,10 @@ export class Cline extends EventEmitter<ClineEvents> {
288288
if (!globalStoragePath) {
289289
throw new Error("Global storage uri is invalid")
290290
}
291-
const taskDir = path.join(globalStoragePath, "tasks", this.taskId)
292-
await fs.mkdir(taskDir, { recursive: true })
293-
return taskDir
291+
292+
// Use storagePathManager to retrieve the task storage directory
293+
const { getTaskDirectoryPath } = await import("../shared/storagePathManager")
294+
return getTaskDirectoryPath(globalStoragePath, this.taskId)
294295
}
295296

296297
private async getSavedApiConversationHistory(): Promise<Anthropic.MessageParam[]> {

src/core/webview/ClineProvider.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2224,15 +2224,15 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
22242224
}
22252225

22262226
async ensureSettingsDirectoryExists(): Promise<string> {
2227-
const settingsDir = path.join(this.contextProxy.globalStorageUri.fsPath, "settings")
2228-
await fs.mkdir(settingsDir, { recursive: true })
2229-
return settingsDir
2227+
const { getSettingsDirectoryPath } = await import("../../shared/storagePathManager")
2228+
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
2229+
return getSettingsDirectoryPath(globalStoragePath)
22302230
}
22312231

22322232
private async ensureCacheDirectoryExists() {
2233-
const cacheDir = path.join(this.contextProxy.globalStorageUri.fsPath, "cache")
2234-
await fs.mkdir(cacheDir, { recursive: true })
2235-
return cacheDir
2233+
const { getCacheDirectoryPath } = await import("../../shared/storagePathManager")
2234+
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
2235+
return getCacheDirectoryPath(globalStoragePath)
22362236
}
22372237

22382238
private async readModelsFromCache(filename: string): Promise<Record<string, ModelInfo> | undefined> {
@@ -2330,7 +2330,11 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
23302330
throw new Error("Task not found in history")
23312331
}
23322332

2333-
const taskDirPath = path.join(this.contextProxy.globalStorageUri.fsPath, "tasks", id)
2333+
// 使用storagePathManager获取任务存储目录
2334+
const { getTaskDirectoryPath } = await import("../../shared/storagePathManager")
2335+
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
2336+
const taskDirPath = await getTaskDirectoryPath(globalStoragePath, id)
2337+
23342338
const apiConversationHistoryFilePath = path.join(taskDirPath, GlobalFileNames.apiConversationHistory)
23352339
const uiMessagesFilePath = path.join(taskDirPath, GlobalFileNames.uiMessages)
23362340

@@ -2862,8 +2866,11 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
28622866
const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || []
28632867
const validTasks: HistoryItem[] = []
28642868

2869+
const { getTaskDirectoryPath } = await import("../../shared/storagePathManager")
2870+
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
2871+
28652872
for (const item of history) {
2866-
const taskDirPath = path.join(this.contextProxy.globalStorageUri.fsPath, "tasks", item.id)
2873+
const taskDirPath = await getTaskDirectoryPath(globalStoragePath, item.id)
28672874
const apiConversationHistoryFilePath = path.join(taskDirPath, GlobalFileNames.apiConversationHistory)
28682875

28692876
if (await fileExistsAtPath(apiConversationHistoryFilePath)) {

src/shared/storagePathManager.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import * as vscode from "vscode"
2+
import * as path from "path"
3+
import * as fs from "fs/promises"
4+
5+
/**
6+
* Get the base path for conversation storage
7+
* If the user has configured a custom path, use the custom path
8+
* Otherwise use the default VSCode extension global storage path
9+
*/
10+
export async function getStorageBasePath(defaultPath: string): Promise<string> {
11+
// Get the user-configured custom storage path
12+
const config = vscode.workspace.getConfiguration("roo-cline")
13+
const customStoragePath = config.get<string>("customStoragePath", "")
14+
15+
// If no custom path is set, use the default path
16+
if (!customStoragePath) {
17+
return defaultPath
18+
}
19+
20+
try {
21+
// Ensure the custom path exists
22+
await fs.mkdir(customStoragePath, { recursive: true })
23+
24+
// Test if the path is writable
25+
const testFile = path.join(customStoragePath, ".write_test")
26+
await fs.writeFile(testFile, "test")
27+
await fs.rm(testFile)
28+
29+
return customStoragePath
30+
} catch (error) {
31+
// If the path cannot be used, report the error and fall back to the default path
32+
console.error(`Custom storage path cannot be used: ${error instanceof Error ? error.message : String(error)}`)
33+
vscode.window.showErrorMessage(
34+
`Custom storage path "${customStoragePath}" cannot be used, will use default path instead`,
35+
)
36+
return defaultPath
37+
}
38+
}
39+
40+
/**
41+
* Get the storage directory path for a task
42+
*/
43+
export async function getTaskDirectoryPath(globalStoragePath: string, taskId: string): Promise<string> {
44+
const basePath = await getStorageBasePath(globalStoragePath)
45+
const taskDir = path.join(basePath, "tasks", taskId)
46+
await fs.mkdir(taskDir, { recursive: true })
47+
return taskDir
48+
}
49+
50+
/**
51+
* Get the settings directory path
52+
*/
53+
export async function getSettingsDirectoryPath(globalStoragePath: string): Promise<string> {
54+
const basePath = await getStorageBasePath(globalStoragePath)
55+
const settingsDir = path.join(basePath, "settings")
56+
await fs.mkdir(settingsDir, { recursive: true })
57+
return settingsDir
58+
}
59+
60+
/**
61+
* Get the cache directory path
62+
*/
63+
export async function getCacheDirectoryPath(globalStoragePath: string): Promise<string> {
64+
const basePath = await getStorageBasePath(globalStoragePath)
65+
const cacheDir = path.join(basePath, "cache")
66+
await fs.mkdir(cacheDir, { recursive: true })
67+
return cacheDir
68+
}
69+
70+
/**
71+
* Prompt user to set a custom storage path
72+
* Display an input box allowing users to enter a custom path
73+
*/
74+
export async function promptForCustomStoragePath(): Promise<void> {
75+
const currentConfig = vscode.workspace.getConfiguration("roo-cline")
76+
const currentPath = currentConfig.get<string>("customStoragePath", "")
77+
78+
const result = await vscode.window.showInputBox({
79+
value: currentPath,
80+
placeHolder: "D:\\RooCodeStorage",
81+
prompt: "Enter custom conversation history storage path, leave empty to use default location",
82+
validateInput: (input) => {
83+
if (!input) {
84+
return null // Allow empty value (use default path)
85+
}
86+
87+
try {
88+
// Simple validation of path validity
89+
path.parse(input)
90+
return null // Path format is valid
91+
} catch (e) {
92+
return "Please enter a valid path"
93+
}
94+
},
95+
})
96+
97+
// If user canceled the operation, result will be undefined
98+
if (result !== undefined) {
99+
await currentConfig.update("customStoragePath", result, vscode.ConfigurationTarget.Global)
100+
101+
if (result) {
102+
try {
103+
// Test if the path is accessible
104+
await fs.mkdir(result, { recursive: true })
105+
vscode.window.showInformationMessage(`Custom storage path set: ${result}`)
106+
} catch (error) {
107+
vscode.window.showErrorMessage(
108+
`Cannot access path ${result}: ${error instanceof Error ? error.message : String(error)}`,
109+
)
110+
}
111+
} else {
112+
vscode.window.showInformationMessage("Restored default storage path")
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)