Skip to content

Commit eadd302

Browse files
committed
Improve command execution component
1 parent 7863303 commit eadd302

File tree

18 files changed

+312
-241
lines changed

18 files changed

+312
-241
lines changed

src/core/tools/attemptCompletionTool.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,14 @@ export async function attemptCompletionTool(
7676
}
7777

7878
// Complete command message.
79-
const didApprove = await askApproval("command", command)
79+
const executionId = Date.now().toString()
80+
const didApprove = await askApproval("command", command, { id: executionId })
8081

8182
if (!didApprove) {
8283
return
8384
}
8485

85-
const options: ExecuteCommandOptions = { command }
86-
86+
const options: ExecuteCommandOptions = { executionId, command }
8787
const [userRejected, execCommandResult] = await executeCommand(cline, options)
8888

8989
if (userRejected) {

src/core/tools/executeCommandTool.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as path from "path"
44
import delay from "delay"
55

66
import { Cline } from "../Cline"
7+
import { CommandExecutionStatus } from "../../schemas"
78
import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag, ToolResponse } from "../../shared/tools"
89
import { formatResponse } from "../prompts/responses"
910
import { unescapeHtmlEntities } from "../../utils/text-normalization"
@@ -47,8 +48,9 @@ export async function executeCommandTool(
4748

4849
cline.consecutiveMistakeCount = 0
4950

51+
const executionId = Date.now().toString()
5052
command = unescapeHtmlEntities(command) // Unescape HTML entities.
51-
const didApprove = await askApproval("command", command)
53+
const didApprove = await askApproval("command", command, { id: executionId })
5254

5355
if (!didApprove) {
5456
return
@@ -59,6 +61,7 @@ export async function executeCommandTool(
5961
const { terminalOutputLineLimit = 500, terminalShellIntegrationDisabled = false } = clineProviderState ?? {}
6062

6163
const options: ExecuteCommandOptions = {
64+
executionId,
6265
command,
6366
customCwd,
6467
terminalShellIntegrationDisabled,
@@ -74,8 +77,10 @@ export async function executeCommandTool(
7477

7578
pushToolResult(result)
7679
} catch (error: unknown) {
77-
await cline.say("shell_integration_warning")
80+
const status: CommandExecutionStatus = { executionId, status: "fallback" }
81+
clineProvider?.postMessageToWebview({ type: "commandExecutionStatus", text: JSON.stringify(status) })
7882
clineProvider?.setValue("terminalShellIntegrationDisabled", true)
83+
await cline.say("shell_integration_warning")
7984

8085
if (error instanceof ShellIntegrationError) {
8186
const [rejected, result] = await executeCommand(cline, {
@@ -102,6 +107,7 @@ export async function executeCommandTool(
102107
}
103108

104109
export type ExecuteCommandOptions = {
110+
executionId: string
105111
command: string
106112
customCwd?: string
107113
terminalShellIntegrationDisabled?: boolean
@@ -111,6 +117,7 @@ export type ExecuteCommandOptions = {
111117
export async function executeCommand(
112118
cline: Cline,
113119
{
120+
executionId,
114121
command,
115122
customCwd,
116123
terminalShellIntegrationDisabled = false,
@@ -141,6 +148,7 @@ export async function executeCommand(
141148
let shellIntegrationError: string | undefined
142149

143150
const terminalProvider = terminalShellIntegrationDisabled ? "execa" : "vscode"
151+
const clineProvider = await cline.providerRef.deref()
144152

145153
const callbacks: RooTerminalCallbacks = {
146154
onLine: async (output: string, process: RooTerminalProcess) => {
@@ -165,7 +173,15 @@ export async function executeCommand(
165173
result = Terminal.compressTerminalOutput(output ?? "", terminalOutputLineLimit)
166174
completed = true
167175
},
168-
onShellExecutionComplete: (details: ExitCodeDetails) => (exitDetails = details),
176+
onShellExecutionStarted: (pid: number | undefined) => {
177+
const status: CommandExecutionStatus = { executionId, status: "running", pid }
178+
clineProvider?.postMessageToWebview({ type: "commandExecutionStatus", text: JSON.stringify(status) })
179+
},
180+
onShellExecutionComplete: (details: ExitCodeDetails) => {
181+
const status: CommandExecutionStatus = { executionId, status: "exited", exitCode: details.exitCode }
182+
clineProvider?.postMessageToWebview({ type: "commandExecutionStatus", text: JSON.stringify(status) })
183+
exitDetails = details
184+
},
169185
}
170186

171187
if (terminalProvider === "vscode") {

src/exports/roo-code.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ type ClineMessage = {
347347
| undefined
348348
progressStatus?:
349349
| {
350+
id?: string | undefined
350351
icon?: string | undefined
351352
text?: string | undefined
352353
}
@@ -422,6 +423,7 @@ type RooCodeEvents = {
422423
| undefined
423424
progressStatus?:
424425
| {
426+
id?: string | undefined
425427
icon?: string | undefined
426428
text?: string | undefined
427429
}

src/exports/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ type ClineMessage = {
352352
| undefined
353353
progressStatus?:
354354
| {
355+
id?: string | undefined
355356
icon?: string | undefined
356357
text?: string | undefined
357358
}
@@ -431,6 +432,7 @@ type RooCodeEvents = {
431432
| undefined
432433
progressStatus?:
433434
| {
435+
id?: string | undefined
434436
icon?: string | undefined
435437
text?: string | undefined
436438
}

src/integrations/terminal/BaseTerminal.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export abstract class BaseTerminal implements RooTerminal {
4444
* @param stream The stream to set, or undefined to clean up
4545
* @throws Error if process is undefined when a stream is provided
4646
*/
47-
public setActiveStream(stream: AsyncIterable<string> | undefined): void {
47+
public setActiveStream(stream: AsyncIterable<string> | undefined, pid?: number): void {
4848
if (stream) {
4949
if (!this.process) {
5050
this.running = false
@@ -58,6 +58,7 @@ export abstract class BaseTerminal implements RooTerminal {
5858

5959
this.running = true
6060
this.streamClosed = false
61+
this.process.emit("shell_execution_started", pid)
6162
this.process.emit("stream_available", stream)
6263
} else {
6364
this.streamClosed = true

src/integrations/terminal/ExecaTerminal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class ExecaTerminal extends BaseTerminal {
2424

2525
process.on("line", (line) => callbacks.onLine(line, process))
2626
process.once("completed", (output) => callbacks.onCompleted(output, process))
27+
process.once("shell_execution_started", (pid) => callbacks.onShellExecutionStarted(pid, process))
2728
process.once("shell_execution_complete", (details) => callbacks.onShellExecutionComplete(details, process))
2829

2930
const promise = new Promise<void>((resolve, reject) => {

src/integrations/terminal/ExecaTerminalProcess.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
4040
cancelSignal: this.controller.signal,
4141
})`${command}`
4242

43-
this.terminal.setActiveStream(subprocess)
43+
this.terminal.setActiveStream(subprocess, subprocess.pid)
4444
this.emit("line", "")
4545

4646
for await (const line of subprocess) {
@@ -60,10 +60,7 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
6060
} catch (error) {
6161
if (error instanceof ExecaError) {
6262
console.error(`[ExecaTerminalProcess] shell execution error: ${error.message}`)
63-
this.emit("shell_execution_complete", {
64-
exitCode: error.exitCode ?? 1,
65-
signalName: error.signal,
66-
})
63+
this.emit("shell_execution_complete", { exitCode: error.exitCode ?? 1, signalName: error.signal })
6764
} else {
6865
this.emit("shell_execution_complete", { exitCode: 1 })
6966
}

src/integrations/terminal/Terminal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export class Terminal extends BaseTerminal {
5555
// configured before the process starts.
5656
process.on("line", (line) => callbacks.onLine(line, process))
5757
process.once("completed", (output) => callbacks.onCompleted(output, process))
58+
process.once("shell_execution_started", (pid) => callbacks.onShellExecutionStarted(pid, process))
5859
process.once("shell_execution_complete", (details) => callbacks.onShellExecutionComplete(details, process))
5960
process.once("no_shell_integration", (msg) => callbacks.onNoShellIntegration?.(msg, process))
6061

src/integrations/terminal/TerminalRegistry.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,8 @@ export class TerminalRegistry {
5252
const stream = e.execution.read()
5353
const terminal = this.getTerminalByVSCETerminal(e.terminal)
5454

55-
console.info("[onDidStartTerminalShellExecution] Shell execution started:", {
56-
hasExecution: !!e.execution,
57-
command: e.execution?.commandLine?.value,
55+
console.info("[onDidStartTerminalShellExecution]", {
56+
command: e.execution.commandLine.value,
5857
terminalId: terminal?.id,
5958
})
6059

@@ -79,9 +78,8 @@ export class TerminalRegistry {
7978
const process = terminal?.process
8079
const exitDetails = TerminalProcess.interpretExitCode(e.exitCode)
8180

82-
console.info("[TerminalRegistry] Shell execution ended:", {
83-
hasExecution: !!e.execution,
84-
command: e.execution?.commandLine?.value,
81+
console.info("[onDidEndTerminalShellExecution]", {
82+
command: e.execution.commandLine.value,
8583
terminalId: terminal?.id,
8684
...exitDetails,
8785
})

src/integrations/terminal/types.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface RooTerminal {
1212
getCurrentWorkingDirectory(): string
1313
isClosed: () => boolean
1414
runCommand: (command: string, callbacks: RooTerminalCallbacks) => RooTerminalProcessResultPromise
15-
setActiveStream(stream: AsyncIterable<string> | undefined): void
15+
setActiveStream(stream: AsyncIterable<string> | undefined, pid?: number): void
1616
shellExecutionComplete(exitDetails: ExitCodeDetails): void
1717
getProcessesWithOutput(): RooTerminalProcess[]
1818
getUnretrievedOutput(): string
@@ -23,6 +23,7 @@ export interface RooTerminal {
2323
export interface RooTerminalCallbacks {
2424
onLine: (line: string, process: RooTerminalProcess) => void
2525
onCompleted: (output: string | undefined, process: RooTerminalProcess) => void
26+
onShellExecutionStarted: (pid: number | undefined, process: RooTerminalProcess) => void
2627
onShellExecutionComplete: (details: ExitCodeDetails, process: RooTerminalProcess) => void
2728
onNoShellIntegration?: (message: string, process: RooTerminalProcess) => void
2829
}
@@ -43,15 +44,11 @@ export interface RooTerminalProcessEvents {
4344
line: [line: string]
4445
continue: []
4546
completed: [output?: string]
47+
stream_available: [stream: AsyncIterable<string>]
48+
shell_execution_started: [pid: number | undefined]
49+
shell_execution_complete: [exitDetails: ExitCodeDetails]
4650
error: [error: Error]
4751
no_shell_integration: [message: string]
48-
/**
49-
* Emitted when a shell execution completes
50-
* @param id The terminal ID
51-
* @param exitDetails Contains exit code and signal information if process was terminated by signal
52-
*/
53-
shell_execution_complete: [exitDetails: ExitCodeDetails]
54-
stream_available: [stream: AsyncIterable<string>]
5552
}
5653

5754
export interface ExitCodeDetails {

0 commit comments

Comments
 (0)