Skip to content

Commit 5fd60b7

Browse files
authored
Refactor Shell Detection to Use VS Code Terminal Profiles and Fallback Hierarchy (RooCodeInc#1543)
* Provide explicit command chaining instructions * Added shell detection for powershell The default-shell library being used only returns cmd for windows users. This change will utilize VS Code API calls to determine the user's shell/terminal settings. MacOS & Linux will, for now, continue to use the existing method. Still working on tests. * Replaced default-shell, added tests Replaced default-shell with local code that replicates the old behavior on macOS & Linux Windows shell detection uses VS Code settings to get the user's default terminal profile Adjusted prompt change * One small change * Removed & attributed old package + typo * Added VSC load for other OSes, refactor, better tests * Fixed system.ts explicit git lines * Added back changes for terminal-command-chaining * One minor, but important change
1 parent a087f5e commit 5fd60b7

File tree

3 files changed

+465
-3
lines changed

3 files changed

+465
-3
lines changed

src/core/prompts/system.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import defaultShell from "default-shell"
1+
import { getShell } from "../../utils/shell"
22
import os from "os"
33
import osName from "os-name"
44
import { McpHub } from "../../services/mcp/McpHub"
@@ -38,7 +38,7 @@ Always adhere to this format for the tool use to ensure proper parsing and execu
3838
# Tools
3939
4040
## execute_command
41-
Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Commands will be executed in the current working directory: ${cwd.toPosix()}
41+
Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Commands will be executed in the current working directory: ${cwd.toPosix()}
4242
Parameters:
4343
- command: (required) The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.
4444
- requires_approval: (required) A boolean indicating whether this command requires explicit user approval before execution in case the user has auto-approve mode enabled. Set to 'true' for potentially impactful operations like installing/uninstalling packages, deleting/overwriting files, system configuration changes, network operations, or any commands that could have unintended side effects. Set to 'false' for safe operations like reading files/directories, running development servers, building projects, and other non-destructive operations.
@@ -941,7 +941,7 @@ ${
941941
SYSTEM INFORMATION
942942
943943
Operating System: ${osName()}
944-
Default Shell: ${defaultShell}
944+
Default Shell: ${getShell()}
945945
Home Directory: ${os.homedir().toPosix()}
946946
Current Working Directory: ${cwd.toPosix()}
947947

src/test/shell.test.ts

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import { describe, it, beforeEach, afterEach } from "mocha"
2+
import { expect } from "chai"
3+
import { getShell } from "../utils/shell"
4+
import * as vscode from "vscode"
5+
import { userInfo } from "os"
6+
7+
describe("Shell Detection Tests", () => {
8+
let originalPlatform: string
9+
let originalEnv: NodeJS.ProcessEnv
10+
let originalGetConfig: any
11+
let originalUserInfo: any
12+
13+
// Helper to mock VS Code configuration
14+
function mockVsCodeConfig(platformKey: string, defaultProfileName: string | null, profiles: Record<string, any>) {
15+
vscode.workspace.getConfiguration = () =>
16+
({
17+
get: (key: string) => {
18+
if (key === `defaultProfile.${platformKey}`) {
19+
return defaultProfileName
20+
}
21+
if (key === `profiles.${platformKey}`) {
22+
return profiles
23+
}
24+
return undefined
25+
},
26+
}) as any
27+
}
28+
29+
beforeEach(() => {
30+
// Store original references
31+
originalPlatform = process.platform
32+
originalEnv = { ...process.env }
33+
originalGetConfig = vscode.workspace.getConfiguration
34+
originalUserInfo = userInfo
35+
36+
// Clear environment variables for a clean test
37+
delete process.env.SHELL
38+
delete process.env.COMSPEC
39+
40+
// Default userInfo() mock
41+
;(userInfo as any) = () => ({ shell: null })
42+
})
43+
44+
afterEach(() => {
45+
// Restore everything
46+
Object.defineProperty(process, "platform", { value: originalPlatform })
47+
process.env = originalEnv
48+
vscode.workspace.getConfiguration = originalGetConfig
49+
;(userInfo as any) = originalUserInfo
50+
})
51+
52+
// --------------------------------------------------------------------------
53+
// Windows Shell Detection
54+
// --------------------------------------------------------------------------
55+
describe("Windows Shell Detection", () => {
56+
beforeEach(() => {
57+
Object.defineProperty(process, "platform", { value: "win32" })
58+
})
59+
60+
it("uses explicit PowerShell 7 path from VS Code config (profile path)", () => {
61+
mockVsCodeConfig("windows", "PowerShell", {
62+
PowerShell: { path: "C:\\Program Files\\PowerShell\\7\\pwsh.exe" },
63+
})
64+
expect(getShell()).to.equal("C:\\Program Files\\PowerShell\\7\\pwsh.exe")
65+
})
66+
67+
it("uses PowerShell 7 path if source is 'PowerShell' but no explicit path", () => {
68+
mockVsCodeConfig("windows", "PowerShell", {
69+
PowerShell: { source: "PowerShell" },
70+
})
71+
expect(getShell()).to.equal("C:\\Program Files\\PowerShell\\7\\pwsh.exe")
72+
})
73+
74+
it("falls back to legacy PowerShell if profile includes 'powershell' but no path/source", () => {
75+
mockVsCodeConfig("windows", "PowerShell", {
76+
PowerShell: {},
77+
})
78+
expect(getShell()).to.equal("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe")
79+
})
80+
81+
it("uses WSL bash when profile indicates WSL source", () => {
82+
mockVsCodeConfig("windows", "WSL", {
83+
WSL: { source: "WSL" },
84+
})
85+
expect(getShell()).to.equal("/bin/bash")
86+
})
87+
88+
it("uses WSL bash when profile name includes 'wsl'", () => {
89+
mockVsCodeConfig("windows", "Ubuntu WSL", {
90+
"Ubuntu WSL": {},
91+
})
92+
expect(getShell()).to.equal("/bin/bash")
93+
})
94+
95+
it("defaults to cmd.exe if no special profile is matched", () => {
96+
mockVsCodeConfig("windows", "CommandPrompt", {
97+
CommandPrompt: {},
98+
})
99+
expect(getShell()).to.equal("C:\\Windows\\System32\\cmd.exe")
100+
})
101+
102+
it("respects userInfo() if no VS Code config is available", () => {
103+
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
104+
;(userInfo as any) = () => ({ shell: "C:\\Custom\\PowerShell.exe" })
105+
106+
expect(getShell()).to.equal("C:\\Custom\\PowerShell.exe")
107+
})
108+
109+
it("respects an odd COMSPEC if no userInfo shell is available", () => {
110+
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
111+
process.env.COMSPEC = "D:\\CustomCmd\\cmd.exe"
112+
113+
expect(getShell()).to.equal("D:\\CustomCmd\\cmd.exe")
114+
})
115+
})
116+
117+
// --------------------------------------------------------------------------
118+
// macOS Shell Detection
119+
// --------------------------------------------------------------------------
120+
describe("macOS Shell Detection", () => {
121+
beforeEach(() => {
122+
Object.defineProperty(process, "platform", { value: "darwin" })
123+
})
124+
125+
it("uses VS Code profile path if available", () => {
126+
mockVsCodeConfig("osx", "MyCustomShell", {
127+
MyCustomShell: { path: "/usr/local/bin/fish" },
128+
})
129+
expect(getShell()).to.equal("/usr/local/bin/fish")
130+
})
131+
132+
it("falls back to userInfo().shell if no VS Code config is available", () => {
133+
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
134+
;(userInfo as any) = () => ({ shell: "/opt/homebrew/bin/zsh" })
135+
136+
expect(getShell()).to.equal("/opt/homebrew/bin/zsh")
137+
})
138+
139+
it("falls back to SHELL env var if no userInfo shell is found", () => {
140+
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
141+
process.env.SHELL = "/usr/local/bin/zsh"
142+
143+
expect(getShell()).to.equal("/usr/local/bin/zsh")
144+
})
145+
146+
it("falls back to /bin/zsh if no config, userInfo, or env variable is set", () => {
147+
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
148+
// userInfo => null, SHELL => undefined
149+
expect(getShell()).to.equal("/bin/zsh")
150+
})
151+
})
152+
153+
// --------------------------------------------------------------------------
154+
// Linux Shell Detection
155+
// --------------------------------------------------------------------------
156+
describe("Linux Shell Detection", () => {
157+
beforeEach(() => {
158+
Object.defineProperty(process, "platform", { value: "linux" })
159+
})
160+
161+
it("uses VS Code profile path if available", () => {
162+
mockVsCodeConfig("linux", "CustomProfile", {
163+
CustomProfile: { path: "/usr/bin/fish" },
164+
})
165+
expect(getShell()).to.equal("/usr/bin/fish")
166+
})
167+
168+
it("falls back to userInfo().shell if no VS Code config is available", () => {
169+
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
170+
;(userInfo as any) = () => ({ shell: "/usr/bin/zsh" })
171+
172+
expect(getShell()).to.equal("/usr/bin/zsh")
173+
})
174+
175+
it("falls back to SHELL env var if no userInfo shell is found", () => {
176+
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
177+
process.env.SHELL = "/usr/bin/fish"
178+
179+
expect(getShell()).to.equal("/usr/bin/fish")
180+
})
181+
182+
it("falls back to /bin/bash if nothing is set", () => {
183+
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
184+
// userInfo => null, SHELL => undefined
185+
expect(getShell()).to.equal("/bin/bash")
186+
})
187+
})
188+
189+
// --------------------------------------------------------------------------
190+
// Unknown Platform & Error Handling
191+
// --------------------------------------------------------------------------
192+
describe("Unknown Platform / Error Handling", () => {
193+
it("falls back to /bin/sh for unknown platforms", () => {
194+
Object.defineProperty(process, "platform", { value: "sunos" })
195+
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
196+
197+
expect(getShell()).to.equal("/bin/sh")
198+
})
199+
200+
it("handles VS Code config errors gracefully, falling back to userInfo shell if present", () => {
201+
Object.defineProperty(process, "platform", { value: "linux" })
202+
vscode.workspace.getConfiguration = () => {
203+
throw new Error("Configuration error")
204+
}
205+
;(userInfo as any) = () => ({ shell: "/bin/bash" })
206+
207+
expect(getShell()).to.equal("/bin/bash")
208+
})
209+
210+
it("handles userInfo errors gracefully, falling back to environment variable if present", () => {
211+
Object.defineProperty(process, "platform", { value: "darwin" })
212+
vscode.workspace.getConfiguration = () => ({ get: () => undefined }) as any
213+
;(userInfo as any) = () => {
214+
throw new Error("userInfo error")
215+
}
216+
process.env.SHELL = "/bin/zsh"
217+
218+
expect(getShell()).to.equal("/bin/zsh")
219+
})
220+
221+
it("falls back fully to default shell paths if everything fails", () => {
222+
Object.defineProperty(process, "platform", { value: "linux" })
223+
vscode.workspace.getConfiguration = () => {
224+
throw new Error("Configuration error")
225+
}
226+
;(userInfo as any) = () => {
227+
throw new Error("userInfo error")
228+
}
229+
// No SHELL in env
230+
delete process.env.SHELL
231+
232+
expect(getShell()).to.equal("/bin/bash")
233+
})
234+
})
235+
})

0 commit comments

Comments
 (0)