mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
fix: resolve terminal working directory tracking after cd commands (#5424)
- Enhanced TerminalRegistry.getOrCreateTerminal() to properly track working directory changes - Added strict working directory matching in all terminal selection priorities - Implemented task isolation to prevent inappropriate terminal reuse - Added comprehensive test coverage for working directory scenarios - Ensures subsequent commands run in correct directory after cd operations Fixes #5424
This commit is contained in:
parent
5b1ca51ec0
commit
156698f965
2 changed files with 198 additions and 3 deletions
|
|
@ -177,13 +177,19 @@ export class TerminalRegistry {
|
|||
})
|
||||
}
|
||||
|
||||
// Second priority: Find any available terminal with matching directory.
|
||||
// Second priority: Find any available terminal with matching directory
|
||||
// that is not assigned to a different task.
|
||||
if (!terminal) {
|
||||
terminal = terminals.find((t) => {
|
||||
if (t.busy || t.provider !== provider) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Don't reuse terminals assigned to different tasks
|
||||
if (taskId && t.taskId && t.taskId !== taskId) {
|
||||
return false
|
||||
}
|
||||
|
||||
const terminalCwd = t.getCurrentWorkingDirectory()
|
||||
|
||||
if (!terminalCwd) {
|
||||
|
|
@ -195,9 +201,28 @@ export class TerminalRegistry {
|
|||
}
|
||||
|
||||
// Third priority: Find any non-busy terminal (only if directory is not
|
||||
// required).
|
||||
// required AND no terminals exist with different working directories).
|
||||
// This prevents reusing terminals that have changed their working directory.
|
||||
if (!terminal && !requiredCwd) {
|
||||
terminal = terminals.find((t) => !t.busy && t.provider === provider)
|
||||
terminal = terminals.find((t) => {
|
||||
if (t.busy || t.provider !== provider) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Don't reuse terminals assigned to different tasks
|
||||
if (taskId && t.taskId && t.taskId !== taskId) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Only reuse terminals that are still in the requested working directory
|
||||
// or have no shell integration (fallback to initial CWD)
|
||||
const terminalCwd = t.getCurrentWorkingDirectory()
|
||||
if (!terminalCwd) {
|
||||
return false
|
||||
}
|
||||
|
||||
return arePathsEqual(vscode.Uri.file(cwd).fsPath, terminalCwd)
|
||||
})
|
||||
}
|
||||
|
||||
// If no suitable terminal found, create a new one.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,170 @@
|
|||
// npx vitest run src/integrations/terminal/__tests__/TerminalRegistry.workingDirectory.spec.ts
|
||||
|
||||
import * as vscode from "vscode"
|
||||
import { Terminal } from "../Terminal"
|
||||
import { TerminalRegistry } from "../TerminalRegistry"
|
||||
|
||||
vi.mock("execa", () => ({
|
||||
execa: vi.fn(),
|
||||
}))
|
||||
|
||||
describe("TerminalRegistry - Working Directory Tracking", () => {
|
||||
let mockCreateTerminal: any
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear any existing terminals
|
||||
TerminalRegistry["terminals"] = []
|
||||
|
||||
mockCreateTerminal = vi.spyOn(vscode.window, "createTerminal").mockImplementation((...args: any[]) => {
|
||||
const mockShellIntegration = {
|
||||
executeCommand: vi.fn(),
|
||||
cwd: vscode.Uri.file("/test/path"), // Initial working directory
|
||||
}
|
||||
|
||||
return {
|
||||
exitStatus: undefined,
|
||||
name: "Roo Code",
|
||||
processId: Promise.resolve(123),
|
||||
creationOptions: {},
|
||||
state: {
|
||||
isInteractedWith: true,
|
||||
shell: { id: "test-shell", executable: "/bin/bash", args: [] },
|
||||
},
|
||||
dispose: vi.fn(),
|
||||
hide: vi.fn(),
|
||||
show: vi.fn(),
|
||||
sendText: vi.fn(),
|
||||
shellIntegration: mockShellIntegration,
|
||||
} as any
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe("getOrCreateTerminal with changed working directory", () => {
|
||||
it("should reuse terminal when working directory matches current directory", async () => {
|
||||
// Create a terminal with initial working directory
|
||||
const terminal1 = await TerminalRegistry.getOrCreateTerminal("/test/path", false, "task1", "vscode")
|
||||
|
||||
// Simulate the terminal's working directory changing (like after cd command)
|
||||
if (terminal1 instanceof Terminal) {
|
||||
// Mock the shell integration to return the new working directory
|
||||
Object.defineProperty(terminal1.terminal.shellIntegration!, "cwd", {
|
||||
value: vscode.Uri.file("/test/path/subdir"),
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Mark terminal as not busy
|
||||
terminal1.busy = false
|
||||
|
||||
// Request a terminal for the new working directory
|
||||
const terminal2 = await TerminalRegistry.getOrCreateTerminal("/test/path/subdir", false, "task1", "vscode")
|
||||
|
||||
// Should reuse the same terminal since its current working directory matches
|
||||
expect(terminal2).toBe(terminal1)
|
||||
expect(mockCreateTerminal).toHaveBeenCalledTimes(1) // Only one terminal created
|
||||
})
|
||||
|
||||
it("should create new terminal when no existing terminal matches current working directory", async () => {
|
||||
// Create a terminal with initial working directory
|
||||
const terminal1 = await TerminalRegistry.getOrCreateTerminal("/test/path", false, "task1", "vscode")
|
||||
|
||||
// Simulate the terminal's working directory changing (like after cd command)
|
||||
if (terminal1 instanceof Terminal) {
|
||||
// Mock the shell integration to return the new working directory
|
||||
Object.defineProperty(terminal1.terminal.shellIntegration!, "cwd", {
|
||||
value: vscode.Uri.file("/test/path/subdir"),
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Mark terminal as not busy
|
||||
terminal1.busy = false
|
||||
|
||||
// Request a terminal for a different working directory
|
||||
const terminal2 = await TerminalRegistry.getOrCreateTerminal("/test/path/other", false, "task1", "vscode")
|
||||
|
||||
// Should create a new terminal since no existing terminal matches the requested directory
|
||||
expect(terminal2).not.toBe(terminal1)
|
||||
expect(mockCreateTerminal).toHaveBeenCalledTimes(2) // Two terminals created
|
||||
})
|
||||
|
||||
it("should handle terminals without shell integration gracefully", async () => {
|
||||
// Create a terminal without shell integration
|
||||
mockCreateTerminal.mockImplementationOnce(
|
||||
(...args: any[]) =>
|
||||
({
|
||||
exitStatus: undefined,
|
||||
name: "Roo Code",
|
||||
processId: Promise.resolve(123),
|
||||
creationOptions: {},
|
||||
state: {
|
||||
isInteractedWith: true,
|
||||
shell: { id: "test-shell", executable: "/bin/bash", args: [] },
|
||||
},
|
||||
dispose: vi.fn(),
|
||||
hide: vi.fn(),
|
||||
show: vi.fn(),
|
||||
sendText: vi.fn(),
|
||||
shellIntegration: undefined, // No shell integration
|
||||
}) as any,
|
||||
)
|
||||
|
||||
const terminal1 = await TerminalRegistry.getOrCreateTerminal("/test/path", false, "task1", "vscode")
|
||||
terminal1.busy = false
|
||||
|
||||
// Request a terminal for the same working directory
|
||||
const terminal2 = await TerminalRegistry.getOrCreateTerminal("/test/path", false, "task1", "vscode")
|
||||
|
||||
// Should reuse the same terminal since it falls back to initial CWD
|
||||
expect(terminal2).toBe(terminal1)
|
||||
expect(mockCreateTerminal).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should prioritize task-specific terminals with matching current working directory", async () => {
|
||||
// Create a terminal for task1
|
||||
const terminal1 = await TerminalRegistry.getOrCreateTerminal("/test/path", false, "task1", "vscode")
|
||||
|
||||
// Create a terminal for task2
|
||||
const terminal2 = await TerminalRegistry.getOrCreateTerminal("/test/path", false, "task2", "vscode")
|
||||
|
||||
// Simulate terminal1's working directory changing
|
||||
if (terminal1 instanceof Terminal) {
|
||||
// Mock the shell integration to return the new working directory
|
||||
Object.defineProperty(terminal1.terminal.shellIntegration!, "cwd", {
|
||||
value: vscode.Uri.file("/test/path/subdir"),
|
||||
writable: true,
|
||||
configurable: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Mark both terminals as not busy
|
||||
terminal1.busy = false
|
||||
terminal2.busy = false
|
||||
|
||||
// Request a terminal for task1 with the new working directory
|
||||
const terminal3 = await TerminalRegistry.getOrCreateTerminal("/test/path/subdir", false, "task1", "vscode")
|
||||
|
||||
// Should reuse terminal1 since it's assigned to task1 and has matching current working directory
|
||||
expect(terminal3).toBe(terminal1)
|
||||
expect(mockCreateTerminal).toHaveBeenCalledTimes(2) // Only two terminals created
|
||||
})
|
||||
|
||||
it("should create separate terminals for different tasks", async () => {
|
||||
// Create a terminal for task1
|
||||
const terminal1 = await TerminalRegistry.getOrCreateTerminal("/test/path", false, "task1", "vscode")
|
||||
|
||||
// Create a terminal for task2 - should create a new terminal
|
||||
const terminal2 = await TerminalRegistry.getOrCreateTerminal("/test/path", false, "task2", "vscode")
|
||||
|
||||
// Should be different terminals
|
||||
expect(terminal2).not.toBe(terminal1)
|
||||
expect(mockCreateTerminal).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue