diff --git a/src/integrations/terminal/ExecaTerminalProcess.ts b/src/integrations/terminal/ExecaTerminalProcess.ts index cc2af9380..4f4c4f495 100644 --- a/src/integrations/terminal/ExecaTerminalProcess.ts +++ b/src/integrations/terminal/ExecaTerminalProcess.ts @@ -5,6 +5,7 @@ import process from "process" import type { RooTerminal } from "./types" import { BaseTerminal } from "./BaseTerminal" import { BaseTerminalProcess } from "./BaseTerminalProcess" +import { getShell } from "../../utils/shell" export class ExecaTerminalProcess extends BaseTerminalProcess { private terminalRef: WeakRef @@ -40,7 +41,7 @@ export class ExecaTerminalProcess extends BaseTerminalProcess { this.isHot = true this.subprocess = execa({ - shell: BaseTerminal.getExecaShellPath() || true, + shell: BaseTerminal.getExecaShellPath() || getShell(), cwd: this.terminal.getCurrentWorkingDirectory(), all: true, // Ignore stdin to ensure non-interactive mode and prevent hanging @@ -111,7 +112,7 @@ export class ExecaTerminalProcess extends BaseTerminalProcess { timeoutId = setTimeout(() => { try { this.subprocess?.kill("SIGKILL") - } catch (e) {} + } catch (e) { } resolve() }, 5_000) diff --git a/src/integrations/terminal/Terminal.ts b/src/integrations/terminal/Terminal.ts index 38ace9d4b..61b67bcab 100644 --- a/src/integrations/terminal/Terminal.ts +++ b/src/integrations/terminal/Terminal.ts @@ -6,6 +6,7 @@ import { BaseTerminal } from "./BaseTerminal" import { TerminalProcess } from "./TerminalProcess" import { ShellIntegrationManager } from "./ShellIntegrationManager" import { mergePromise } from "./mergePromise" +import { getShell } from "../../utils/shell" export class Terminal extends BaseTerminal { public terminal: vscode.Terminal @@ -17,7 +18,7 @@ export class Terminal extends BaseTerminal { const env = Terminal.getEnv() const iconPath = new vscode.ThemeIcon("rocket") - this.terminal = terminal ?? vscode.window.createTerminal({ cwd, name: "Roo Code", iconPath, env }) + this.terminal = terminal ?? vscode.window.createTerminal({ cwd, name: "Roo Code", iconPath, env, shellPath: getShell() }) if (Terminal.getTerminalZdotdir()) { ShellIntegrationManager.terminalTmpDirs.set(id, env.ZDOTDIR) diff --git a/src/integrations/terminal/__tests__/ExecaTerminalProcess.spec.ts b/src/integrations/terminal/__tests__/ExecaTerminalProcess.spec.ts index 5f0a21869..1b89638bf 100644 --- a/src/integrations/terminal/__tests__/ExecaTerminalProcess.spec.ts +++ b/src/integrations/terminal/__tests__/ExecaTerminalProcess.spec.ts @@ -25,6 +25,7 @@ import { execa } from "execa" import { ExecaTerminalProcess } from "../ExecaTerminalProcess" import { BaseTerminal } from "../BaseTerminal" import type { RooTerminal } from "../types" +import * as shellUtils from "../../../utils/shell" describe("ExecaTerminalProcess", () => { let mockTerminal: RooTerminal @@ -34,6 +35,7 @@ describe("ExecaTerminalProcess", () => { beforeEach(() => { originalEnv = { ...process.env } BaseTerminal.setExecaShellPath(undefined) + vitest.spyOn(shellUtils, "getShell").mockReturnValue("/mock/fallback-shell") mockTerminal = { provider: "execa", id: 1, @@ -54,7 +56,7 @@ describe("ExecaTerminalProcess", () => { afterEach(() => { process.env = originalEnv - vitest.clearAllMocks() + vitest.restoreAllMocks() }) describe("UTF-8 encoding fix", () => { @@ -63,7 +65,7 @@ describe("ExecaTerminalProcess", () => { const execaMock = vitest.mocked(execa) expect(execaMock).toHaveBeenCalledWith( expect.objectContaining({ - shell: true, + shell: "/mock/fallback-shell", cwd: "/test/cwd", all: true, env: expect.objectContaining({ @@ -105,13 +107,13 @@ describe("ExecaTerminalProcess", () => { ) }) - it("should fall back to shell=true when execaShellPath is undefined", async () => { + it("should fall back to getShell() when execaShellPath is undefined", async () => { BaseTerminal.setExecaShellPath(undefined) await terminalProcess.run("echo test") const execaMock = vitest.mocked(execa) expect(execaMock).toHaveBeenCalledWith( expect.objectContaining({ - shell: true, + shell: "/mock/fallback-shell", }), ) }) diff --git a/src/integrations/terminal/__tests__/TerminalRegistry.spec.ts b/src/integrations/terminal/__tests__/TerminalRegistry.spec.ts index 5039b2a32..cf434e0ef 100644 --- a/src/integrations/terminal/__tests__/TerminalRegistry.spec.ts +++ b/src/integrations/terminal/__tests__/TerminalRegistry.spec.ts @@ -3,6 +3,7 @@ import * as vscode from "vscode" import { Terminal } from "../Terminal" import { TerminalRegistry } from "../TerminalRegistry" +import * as shellUtils from "../../../utils/shell" const PAGER = process.platform === "win32" ? "" : "cat" @@ -34,6 +35,12 @@ describe("TerminalRegistry", () => { }, }) as any, ) + + vi.spyOn(shellUtils, "getShell").mockReturnValue("/mock/fallback-shell") + }) + + afterEach(() => { + vi.restoreAllMocks() }) describe("createTerminal", () => { @@ -43,13 +50,14 @@ describe("TerminalRegistry", () => { expect(mockCreateTerminal).toHaveBeenCalledWith({ cwd: "/test/path", name: "Roo Code", - iconPath: expect.any(Object), + iconPath: expect.objectContaining({ id: expect.any(String) }), env: { PAGER, ROO_ACTIVE: "true", VTE_VERSION: "0", PROMPT_EOL_MARK: "", }, + shellPath: "/mock/fallback-shell", }) }) @@ -64,7 +72,7 @@ describe("TerminalRegistry", () => { expect(mockCreateTerminal).toHaveBeenCalledWith({ cwd: "/test/path", name: "Roo Code", - iconPath: expect.any(Object), + iconPath: expect.objectContaining({ id: expect.any(String) }), env: { PAGER, ROO_ACTIVE: "true", @@ -72,6 +80,7 @@ describe("TerminalRegistry", () => { VTE_VERSION: "0", PROMPT_EOL_MARK: "", }, + shellPath: "/mock/fallback-shell", }) } finally { // Restore original delay @@ -87,7 +96,7 @@ describe("TerminalRegistry", () => { expect(mockCreateTerminal).toHaveBeenCalledWith({ cwd: "/test/path", name: "Roo Code", - iconPath: expect.any(Object), + iconPath: expect.objectContaining({ id: expect.any(String) }), env: { PAGER, ROO_ACTIVE: "true", @@ -95,6 +104,7 @@ describe("TerminalRegistry", () => { PROMPT_EOL_MARK: "", ITERM_SHELL_INTEGRATION_INSTALLED: "Yes", }, + shellPath: "/mock/fallback-shell", }) } finally { Terminal.setTerminalZshOhMy(false) @@ -109,7 +119,7 @@ describe("TerminalRegistry", () => { expect(mockCreateTerminal).toHaveBeenCalledWith({ cwd: "/test/path", name: "Roo Code", - iconPath: expect.any(Object), + iconPath: expect.objectContaining({ id: expect.any(String) }), env: { PAGER, ROO_ACTIVE: "true", @@ -117,6 +127,7 @@ describe("TerminalRegistry", () => { PROMPT_EOL_MARK: "", POWERLEVEL9K_TERM_SHELL_INTEGRATION: "true", }, + shellPath: "/mock/fallback-shell", }) } finally { Terminal.setTerminalZshP10k(false)