Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions src/core/prompts/tools/execute-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,33 @@ export function getExecuteCommandDescription(args: ToolArgs): string | undefined
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.
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})
- cwd: (optional) The working directory to execute the command in (default: ${sanitizeHtmlEscapes(args.cwd)})
Usage:
<execute_command>
<command>Your command here</command>
<cwd>Working directory path (optional)</cwd>
<command>${sanitizeHtmlEscapes("Your command here")}</command>
<cwd>${sanitizeHtmlEscapes("Working directory path (optional)")}</cwd>
</execute_command>

Example: Requesting to execute npm run dev
<execute_command>
<command>npm run dev</command>
<command>${sanitizeHtmlEscapes("npm run dev")}</command>
</execute_command>

Example: Requesting to execute ls in a specific directory if directed
<execute_command>
<command>ls -la</command>
<cwd>/home/user/projects</cwd>
<command>${sanitizeHtmlEscapes("ls -la")}</command>
<cwd>${sanitizeHtmlEscapes("/home/user/projects")}</cwd>
</execute_command>`
}

function sanitizeHtmlEscapes(input: string): string {
const htmlEscapes: Record<string, string> = {
"&amp;": "&",
"&lt;": "<",
"&gt;": ">",
"&quot;": '"',
"&#39;": "'",
}

return input.replace(/&(?:amp|lt|gt|quot|#39);/g, (match) => htmlEscapes[match] || match)
}
57 changes: 37 additions & 20 deletions src/integrations/terminal/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,33 +163,33 @@ export class Terminal {
const process = new TerminalProcess(this)

// Store the command on the process for reference
process.command = command
process.command = this.sanitizeHtmlEscapes(command)

// Set process on terminal
this.process = process

// Create a promise for command completion
const promise = new Promise<void>((resolve, reject) => {
// Set up event handlers
process.once("continue", () => resolve())
process.once("error", (error) => {
console.error(`[Terminal ${this.id}] error:`, error)
reject(error)
})

// Wait for shell integration before executing the command
pWaitFor(() => this.terminal.shellIntegration !== undefined, { timeout: Terminal.shellIntegrationTimeout })
.then(() => {
process.run(command)
})
.catch(() => {
console.log(`[Terminal ${this.id}] Shell integration not available. Command execution aborted.`)
process.emit(
"no_shell_integration",
"Shell integration initialization sequence '\\x1b]633;A' was not received within 4 seconds. Shell integration has been disabled for this terminal instance. Increase the timeout in the settings if necessary.",
)
// Set up event handlers
process.once("continue", () => resolve())
process.once("error", (error) => {
console.error(`[Terminal ${this.id}] error:`, error)
reject(error)
})
})

// Wait for shell integration before executing the command
pWaitFor(() => this.terminal.shellIntegration !== undefined, { timeout: Terminal.shellIntegrationTimeout })
.then(() => {
process.run(command)
})
.catch(() => {
console.log(`[Terminal ${this.id}] Shell integration not available. Command execution aborted.`)
process.emit(
"no_shell_integration",
"Shell integration initialization sequence '\\x1b]633;A' was not received within 4 seconds. Shell integration has been disabled for this terminal instance. Increase the timeout in the settings if necessary.",
)
})
})

return mergePromise(process, promise)
}
Expand Down Expand Up @@ -259,4 +259,21 @@ export class Terminal {
public static compressTerminalOutput(input: string, lineLimit: number): string {
return truncateOutput(applyRunLengthEncoding(input), lineLimit)
}

/**
* Sanitizes HTML escape sequences in a string.
* @param input The input string to sanitize
* @returns The sanitized string
*/
private sanitizeHtmlEscapes(input: string): string {
const htmlEscapes: Record<string, string> = {
"&amp;": "&",
"&lt;": "<",
"&gt;": ">",
"&quot;": '"',
"&#39;": "'",
}

return input.replace(/&(?:amp|lt|gt|quot|#39);/g, (match) => htmlEscapes[match] || match)
}
}
19 changes: 18 additions & 1 deletion src/integrations/terminal/TerminalProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
private hotTimer: NodeJS.Timeout | null = null

async run(command: string) {
this.command = command
this.command = this.sanitizeHtmlEscapes(command)
const terminal = this.terminalInfo.terminal

if (terminal.shellIntegration && terminal.shellIntegration.executeCommand) {
Expand Down Expand Up @@ -666,6 +666,23 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {

return match133 !== undefined ? match133 : match633
}

/**
* Sanitizes HTML escape sequences in a string.
* @param input The input string to sanitize
* @returns The sanitized string
*/
private sanitizeHtmlEscapes(input: string): string {
const htmlEscapes: Record<string, string> = {
"&amp;": "&",
"&lt;": "<",
"&gt;": ">",
"&quot;": '"',
"&#39;": "'",
}

return input.replace(/&(?:amp|lt|gt|quot|#39);/g, (match) => htmlEscapes[match] || match)
}
}

export type TerminalProcessResultPromise = TerminalProcess & Promise<void>
Expand Down
3 changes: 2 additions & 1 deletion src/integrations/terminal/TerminalRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@
})

const cwdString = cwd.toString()
const newTerminal = new Terminal(this.nextTerminalId++, terminal, cwdString)
const sanitizedCwd = TerminalProcess.sanitizeHtmlEscapes(cwdString)

Check failure on line 131 in src/integrations/terminal/TerminalRegistry.ts

View workflow job for this annotation

GitHub Actions / compile

Property 'sanitizeHtmlEscapes' does not exist on type 'typeof TerminalProcess'.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling TerminalProcess.sanitizeHtmlEscapes(cwdString) here is problematic because the sanitizeHtmlEscapes method in TerminalProcess is declared as a private instance method and not static. To reuse this functionality across modules (as needed here for HTML escape sanitization), consider extracting it to a shared utility module or making it a public static method. This follows our modular design and prevents cross-module private access.

const newTerminal = new Terminal(this.nextTerminalId++, terminal, sanitizedCwd)

this.terminals.push(newTerminal)
return newTerminal
Expand Down
Loading