diff --git a/src/core/prompts/tools/execute-command.ts b/src/core/prompts/tools/execute-command.ts
index c1fc1ea3f19c..03d0e81e524d 100644
--- a/src/core/prompts/tools/execute-command.ts
+++ b/src/core/prompts/tools/execute-command.ts
@@ -2,14 +2,16 @@ import { ToolArgs } from "./types"
export function getExecuteCommandDescription(args: ToolArgs): string | undefined {
return `## execute_command
-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. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: \`touch ./testdata/example.file\`, \`dir ./examples/model1/data/yaml\`, or \`go test ./cmd/front --config ./cmd/front/config.yml\`. If directed by the user, you may open a terminal in a different directory by using the \`cwd\` parameter.
+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 your shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Prefer relative commands and paths that avoid location sensitivity for terminal consistency, e.g: \`touch ./testdata/example.file\`, \`dir ./examples/model1/data/yaml\`, or \`go test ./cmd/front --config ./cmd/front/config.yml\`. If directed by the user, you may open a terminal in a different directory by using the \`cwd\` parameter.
Parameters:
- 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.
- cwd: (optional) The working directory to execute the command in (default: ${args.cwd})
+- background: (optional) Set to "true" to run the command in the background without interrupting with questions. Default is false.
Usage:
Your command here
Working directory path (optional)
+true
Example: Requesting to execute npm run dev
@@ -21,5 +23,11 @@ Example: Requesting to execute ls in a specific directory if directed
ls -la
/home/user/projects
+
+
+Example: Running a long-running command in the background
+
+npm run build
+true
`
}
diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts
index b0ae07bf86a2..2ba640d1d3d1 100644
--- a/src/core/tools/executeCommandTool.ts
+++ b/src/core/tools/executeCommandTool.ts
@@ -30,6 +30,7 @@ export async function executeCommandTool(
) {
let command: string | undefined = block.params.command
const customCwd: string | undefined = block.params.cwd
+ const background: boolean = block.params.background === "true"
try {
if (block.partial) {
@@ -90,6 +91,7 @@ export async function executeCommandTool(
executionId,
command,
customCwd,
+ background,
terminalShellIntegrationDisabled,
terminalOutputLineLimit,
terminalOutputCharacterLimit,
@@ -137,6 +139,7 @@ export type ExecuteCommandOptions = {
executionId: string
command: string
customCwd?: string
+ background?: boolean
terminalShellIntegrationDisabled?: boolean
terminalOutputLineLimit?: number
terminalOutputCharacterLimit?: number
@@ -149,6 +152,7 @@ export async function executeCommand(
executionId,
command,
customCwd,
+ background = false,
terminalShellIntegrationDisabled = true,
terminalOutputLineLimit = 500,
terminalOutputCharacterLimit = DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
@@ -174,7 +178,7 @@ export async function executeCommand(
}
let message: { text?: string; images?: string[] } | undefined
- let runInBackground = false
+ let runInBackground = background
let completed = false
let result: string = ""
let exitDetails: ExitCodeDetails | undefined
@@ -199,15 +203,17 @@ export async function executeCommand(
return
}
- try {
- const { response, text, images } = await task.ask("command_output", "")
- runInBackground = true
+ if (!background) {
+ try {
+ const { response, text, images } = await task.ask("command_output", "")
+ runInBackground = true
- if (response === "messageResponse") {
- message = { text, images }
- process.continue()
- }
- } catch (_error) {}
+ if (response === "messageResponse") {
+ message = { text, images }
+ process.continue()
+ }
+ } catch (_error) {}
+ }
},
onCompleted: (output: string | undefined) => {
result = Terminal.compressTerminalOutput(
diff --git a/src/integrations/terminal/__tests__/background-execution.spec.ts b/src/integrations/terminal/__tests__/background-execution.spec.ts
new file mode 100644
index 000000000000..7e0f68d98f4e
--- /dev/null
+++ b/src/integrations/terminal/__tests__/background-execution.spec.ts
@@ -0,0 +1,161 @@
+// background-execution.spec.ts
+
+import { describe, it, expect, vi } from "vitest"
+import { Task } from "../../../core/task/Task"
+import { executeCommand, ExecuteCommandOptions } from "../../../core/tools/executeCommandTool"
+import { TerminalRegistry } from "../TerminalRegistry"
+import { Terminal } from "../Terminal"
+
+// Mock Task class
+class MockTask {
+ taskId = "test-task"
+ cwd = "/test/workspace"
+ consecutiveMistakeCount = 0
+ didRejectTool = false
+ lastMessageTs = undefined
+ terminalProcess = undefined
+
+ async say() {}
+ async ask() {
+ throw new Error("Should not be called for background execution")
+ }
+ recordToolError() {}
+}
+
+vi.mock("fs/promises")
+vi.mock("../TerminalRegistry")
+vi.mock("../Terminal")
+vi.mock("../../../i18n")
+
+describe("Background execution", () => {
+ let mockTask: MockTask
+ let mockProvider: any
+
+ beforeEach(() => {
+ mockTask = new MockTask()
+ mockProvider = {
+ postMessageToWebview: vi.fn(),
+ getState: vi.fn().mockResolvedValue({
+ terminalOutputLineLimit: 500,
+ terminalOutputCharacterLimit: 10000,
+ terminalShellIntegrationDisabled: true,
+ }),
+ }
+
+ // Mock terminal methods
+ vi.mocked(TerminalRegistry.getOrCreateTerminal).mockResolvedValue({
+ getCurrentWorkingDirectory: () => "/test/workspace",
+ runCommand: vi.fn().mockReturnValue(Promise.resolve()),
+ terminal: {
+ show: vi.fn(),
+ },
+ } as any)
+ })
+
+ it("should execute command in background when background parameter is true", async () => {
+ const options: ExecuteCommandOptions = {
+ executionId: "test-id",
+ command: "echo 'test'",
+ background: true,
+ terminalOutputLineLimit: 500,
+ terminalOutputCharacterLimit: 10000,
+ }
+
+ // Mock the callbacks
+ const callbacks = {
+ onLine: vi.fn(),
+ onCompleted: vi.fn(),
+ onShellExecutionStarted: vi.fn(),
+ onShellExecutionComplete: vi.fn(),
+ }
+
+ // Mock terminal instance
+ const mockTerminal = {
+ getCurrentWorkingDirectory: () => "/test/workspace",
+ runCommand: vi.fn().mockReturnValue(Promise.resolve()),
+ terminal: { show: vi.fn() },
+ }
+
+ vi.mocked(TerminalRegistry.getOrCreateTerminal).mockResolvedValue(mockTerminal as any)
+
+ // Execute command with background = true
+ const [rejected, result] = await executeCommand(mockTask, options)
+
+ // Verify that task.ask was NOT called (which means it ran in background)
+ // Since we can't easily test the internal callback behavior, we verify the command was set up for background execution
+ expect(rejected).toBe(false)
+ expect(typeof result).toBe("string")
+ })
+
+ it("should execute command with background=false by default", async () => {
+ const options: ExecuteCommandOptions = {
+ executionId: "test-id",
+ command: "echo 'test'",
+ background: false,
+ terminalOutputLineLimit: 500,
+ terminalOutputCharacterLimit: 10000,
+ }
+
+ // Mock terminal instance
+ const mockTerminal = {
+ getCurrentWorkingDirectory: () => "/test/workspace",
+ runCommand: vi.fn().mockReturnValue(Promise.resolve()),
+ terminal: { show: vi.fn() },
+ }
+
+ vi.mocked(TerminalRegistry.getOrCreateTerminal).mockResolvedValue(mockTerminal as any)
+
+ const [rejected, result] = await executeCommand(mockTask, options)
+
+ expect(rejected).toBe(false)
+ expect(typeof result).toBe("string")
+ })
+
+ it("should handle background parameter as boolean true", async () => {
+ const options: ExecuteCommandOptions = {
+ executionId: "test-id",
+ command: "echo 'test'",
+ background: true,
+ terminalOutputLineLimit: 500,
+ terminalOutputCharacterLimit: 10000,
+ }
+
+ // Mock terminal instance
+ const mockTerminal = {
+ getCurrentWorkingDirectory: () => "/test/workspace",
+ runCommand: vi.fn().mockReturnValue(Promise.resolve()),
+ terminal: { show: vi.fn() },
+ }
+
+ vi.mocked(TerminalRegistry.getOrCreateTerminal).mockResolvedValue(mockTerminal as any)
+
+ const [rejected, result] = await executeCommand(mockTask, options)
+
+ expect(rejected).toBe(false)
+ // When background is true, the command should run and complete
+ expect(result).toContain("Command executed in terminal")
+ })
+
+ it("should default background to false when not provided", async () => {
+ const options: ExecuteCommandOptions = {
+ executionId: "test-id",
+ command: "echo 'test'",
+ terminalOutputLineLimit: 500,
+ terminalOutputCharacterLimit: 10000,
+ }
+
+ // Mock terminal instance
+ const mockTerminal = {
+ getCurrentWorkingDirectory: () => "/test/workspace",
+ runCommand: vi.fn().mockReturnValue(Promise.resolve()),
+ terminal: { show: vi.fn() },
+ }
+
+ vi.mocked(TerminalRegistry.getOrCreateTerminal).mockResolvedValue(mockTerminal as any)
+
+ const [rejected, result] = await executeCommand(mockTask, options)
+
+ expect(rejected).toBe(false)
+ expect(typeof result).toBe("string")
+ })
+})
diff --git a/src/shared/tools.ts b/src/shared/tools.ts
index 61e840272726..7e5c4017e6a6 100644
--- a/src/shared/tools.ts
+++ b/src/shared/tools.ts
@@ -57,6 +57,7 @@ export const toolParamNames = [
"size",
"query",
"args",
+ "background",
"start_line",
"end_line",
"todos",
@@ -77,7 +78,7 @@ export interface ToolUse {
export interface ExecuteCommandToolUse extends ToolUse {
name: "execute_command"
// Pick, "command"> makes "command" required, but Partial<> makes it optional
- params: Partial, "command" | "cwd">>
+ params: Partial, "command" | "cwd" | "background">>
}
export interface ReadFileToolUse extends ToolUse {