Skip to content

Commit d741193

Browse files
author
Your Name
committed
Support customize storage path
1 parent 89013dd commit d741193

File tree

6 files changed

+174
-11
lines changed

6 files changed

+174
-11
lines changed

package-lock.json

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,11 @@
164164
"command": "roo-cline.terminalExplainCommandInCurrentTask",
165165
"title": "Explain This Command (Current Task)",
166166
"category": "Terminal"
167+
},
168+
{
169+
"command": "roo-cline.setCustomStoragePath",
170+
"title": "Set Custom Storage Path",
171+
"category": "Roo Code"
167172
}
168173
],
169174
"menus": {
@@ -288,6 +293,11 @@
288293
}
289294
},
290295
"description": "Settings for VSCode Language Model API"
296+
},
297+
"roo-cline.customStoragePath": {
298+
"type": "string",
299+
"default": "",
300+
"description": "Custom storage path. Leave empty to use the default location. Supports absolute paths (e.g. 'D:\\RooCodeStorage')"
291301
}
292302
}
293303
}

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
@@ -290,9 +290,10 @@ export class Cline extends EventEmitter<ClineEvents> {
290290
if (!globalStoragePath) {
291291
throw new Error("Global storage uri is invalid")
292292
}
293-
const taskDir = path.join(globalStoragePath, "tasks", this.taskId)
294-
await fs.mkdir(taskDir, { recursive: true })
295-
return taskDir
293+
294+
// Use storagePathManager to retrieve the task storage directory
295+
const { getTaskDirectoryPath } = await import("../shared/storagePathManager")
296+
return getTaskDirectoryPath(globalStoragePath, this.taskId)
296297
}
297298

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

src/core/webview/ClineProvider.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,15 +2230,15 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
22302230
}
22312231

22322232
async ensureSettingsDirectoryExists(): Promise<string> {
2233-
const settingsDir = path.join(this.contextProxy.globalStorageUri.fsPath, "settings")
2234-
await fs.mkdir(settingsDir, { recursive: true })
2235-
return settingsDir
2233+
const { getSettingsDirectoryPath } = await import("../../shared/storagePathManager")
2234+
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
2235+
return getSettingsDirectoryPath(globalStoragePath)
22362236
}
22372237

22382238
private async ensureCacheDirectoryExists() {
2239-
const cacheDir = path.join(this.contextProxy.globalStorageUri.fsPath, "cache")
2240-
await fs.mkdir(cacheDir, { recursive: true })
2241-
return cacheDir
2239+
const { getCacheDirectoryPath } = await import("../../shared/storagePathManager")
2240+
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
2241+
return getCacheDirectoryPath(globalStoragePath)
22422242
}
22432243

22442244
private async readModelsFromCache(filename: string): Promise<Record<string, ModelInfo> | undefined> {
@@ -2368,7 +2368,9 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
23682368
const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || []
23692369
const historyItem = history.find((item) => item.id === id)
23702370
if (historyItem) {
2371-
const taskDirPath = path.join(this.contextProxy.globalStorageUri.fsPath, "tasks", id)
2371+
const { getTaskDirectoryPath } = await import("../../shared/storagePathManager")
2372+
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
2373+
const taskDirPath = await getTaskDirectoryPath(globalStoragePath, id)
23722374
const apiConversationHistoryFilePath = path.join(taskDirPath, GlobalFileNames.apiConversationHistory)
23732375
const uiMessagesFilePath = path.join(taskDirPath, GlobalFileNames.uiMessages)
23742376
const fileExists = await fileExistsAtPath(apiConversationHistoryFilePath)

src/shared/storagePathManager.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import * as vscode from "vscode"
2+
import * as path from "path"
3+
import * as fs from "fs/promises"
4+
5+
/**
6+
* Gets the base storage path for conversations
7+
* If a custom path is configured, uses that path
8+
* Otherwise uses the default VSCode extension global storage path
9+
*/
10+
export async function getStorageBasePath(defaultPath: string): Promise<string> {
11+
// Get user-configured custom storage path
12+
let customStoragePath = ""
13+
14+
try {
15+
// This is the line causing the error in tests
16+
const config = vscode.workspace.getConfiguration("roo-cline")
17+
customStoragePath = config.get<string>("customStoragePath", "")
18+
} catch (error) {
19+
console.warn("Could not access VSCode configuration - using default path")
20+
return defaultPath
21+
}
22+
23+
// If no custom path is set, use default path
24+
if (!customStoragePath) {
25+
return defaultPath
26+
}
27+
28+
try {
29+
// Ensure custom path exists
30+
await fs.mkdir(customStoragePath, { recursive: true })
31+
32+
// Test if path is writable
33+
const testFile = path.join(customStoragePath, ".write_test")
34+
await fs.writeFile(testFile, "test")
35+
await fs.rm(testFile)
36+
37+
return customStoragePath
38+
} catch (error) {
39+
// If path is unusable, report error and fall back to default path
40+
console.error(`Custom storage path is unusable: ${error instanceof Error ? error.message : String(error)}`)
41+
if (vscode.window) {
42+
vscode.window.showErrorMessage(
43+
`Custom storage path "${customStoragePath}" is unusable, will use default path`,
44+
)
45+
}
46+
return defaultPath
47+
}
48+
}
49+
50+
/**
51+
* Gets the storage directory path for a task
52+
*/
53+
export async function getTaskDirectoryPath(globalStoragePath: string, taskId: string): Promise<string> {
54+
const basePath = await getStorageBasePath(globalStoragePath)
55+
const taskDir = path.join(basePath, "tasks", taskId)
56+
await fs.mkdir(taskDir, { recursive: true })
57+
return taskDir
58+
}
59+
60+
/**
61+
* Gets the settings directory path
62+
*/
63+
export async function getSettingsDirectoryPath(globalStoragePath: string): Promise<string> {
64+
const basePath = await getStorageBasePath(globalStoragePath)
65+
const settingsDir = path.join(basePath, "settings")
66+
await fs.mkdir(settingsDir, { recursive: true })
67+
return settingsDir
68+
}
69+
70+
/**
71+
* Gets the cache directory path
72+
*/
73+
export async function getCacheDirectoryPath(globalStoragePath: string): Promise<string> {
74+
const basePath = await getStorageBasePath(globalStoragePath)
75+
const cacheDir = path.join(basePath, "cache")
76+
await fs.mkdir(cacheDir, { recursive: true })
77+
return cacheDir
78+
}
79+
80+
/**
81+
* Prompts the user to set a custom storage path
82+
* Displays an input box allowing the user to enter a custom path
83+
*/
84+
export async function promptForCustomStoragePath(): Promise<void> {
85+
if (!vscode.window || !vscode.workspace) {
86+
console.error("VS Code API not available")
87+
return
88+
}
89+
90+
let currentPath = ""
91+
try {
92+
const currentConfig = vscode.workspace.getConfiguration("roo-cline")
93+
currentPath = currentConfig.get<string>("customStoragePath", "")
94+
} catch (error) {
95+
console.error("Could not access configuration")
96+
return
97+
}
98+
99+
const result = await vscode.window.showInputBox({
100+
value: currentPath,
101+
placeHolder: "D:\\RooCodeStorage",
102+
prompt: "Enter custom conversation history storage path, leave empty to use default location",
103+
validateInput: (input) => {
104+
if (!input) {
105+
return null // Allow empty value (use default path)
106+
}
107+
108+
try {
109+
// Validate path format
110+
path.parse(input)
111+
112+
// Check if path is absolute
113+
if (!path.isAbsolute(input)) {
114+
return "Please enter an absolute path (e.g. D:\\RooCodeStorage or /home/user/storage)"
115+
}
116+
117+
return null // Path format is valid
118+
} catch (e) {
119+
return "Please enter a valid path"
120+
}
121+
},
122+
})
123+
124+
// If user canceled the operation, result will be undefined
125+
if (result !== undefined) {
126+
try {
127+
const currentConfig = vscode.workspace.getConfiguration("roo-cline")
128+
await currentConfig.update("customStoragePath", result, vscode.ConfigurationTarget.Global)
129+
130+
if (result) {
131+
try {
132+
// Test if path is accessible
133+
await fs.mkdir(result, { recursive: true })
134+
vscode.window.showInformationMessage(`Custom storage path set: ${result}`)
135+
} catch (error) {
136+
vscode.window.showErrorMessage(
137+
`Cannot access path ${result}: ${error instanceof Error ? error.message : String(error)}`,
138+
)
139+
}
140+
} else {
141+
vscode.window.showInformationMessage("Reverted to using default storage path")
142+
}
143+
} catch (error) {
144+
console.error("Failed to update configuration", error)
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)