Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit cb28f75

Browse files
committed
feat: add stdin support for ExecaTerminalProcess
- Add writeStdin method to RooTerminalProcess interface - Add abstract writeStdin to BaseTerminalProcess - Implement writeStdin in ExecaTerminalProcess using stdin pipe - Implement writeStdin in TerminalProcess using terminal.sendText - Change ExecaTerminalProcess from stdin: 'ignore' to stdin: 'pipe' - Update WriteStdinTool to use unified process.writeStdin() method This enables write_stdin tool to work with both VSCode terminals and Execa terminals, providing consistent interactive terminal support.
1 parent c9af762 commit cb28f75

5 files changed

Lines changed: 62 additions & 15 deletions

File tree

src/core/tools/WriteStdinTool.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Task } from "../task/Task"
44
import { ToolUse } from "../../shared/tools"
55
import { formatResponse } from "../prompts/responses"
66
import { ProcessManager } from "../../integrations/terminal/ProcessManager"
7-
import { Terminal } from "../../integrations/terminal/Terminal"
87
import { t } from "../../i18n"
98

109
import { BaseTool, ToolCallbacks } from "./BaseTool"
@@ -122,22 +121,16 @@ export class WriteStdinTool extends BaseTool<"write_stdin"> {
122121
// Process escape sequences in input
123122
const processedChars = this.processEscapeSequences(chars)
124123

125-
// Write to stdin
124+
// Write to stdin using the unified writeStdin interface
126125
const { terminal, process } = entry
127126
let writeSuccess = false
128127

129128
try {
130-
if (terminal instanceof Terminal) {
131-
// VSCode terminal - use sendText
132-
// Note: sendText automatically adds a newline by default, so we pass false
133-
// to prevent double newlines when the input already ends with \n
134-
terminal.terminal.sendText(processedChars, false)
135-
writeSuccess = true
136-
} else {
137-
// Execa terminal - would need stdin pipe support
138-
// For now, we'll indicate this isn't supported for execa
139-
// TODO: Implement stdin support for ExecaTerminalProcess
140-
const errorMsg = `Session ${session_id} is using a non-interactive terminal. Interactive stdin is only supported for VSCode terminals.`
129+
// Use the process writeStdin method which works for both VSCode and Execa terminals
130+
writeSuccess = process.writeStdin(processedChars)
131+
132+
if (!writeSuccess) {
133+
const errorMsg = `Failed to write to session ${session_id}: stdin is not available`
141134
await task.say("error", errorMsg)
142135
pushToolResult(`Error: ${errorMsg}`)
143136
return

src/integrations/terminal/BaseTerminalProcess.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ export abstract class BaseTerminalProcess extends EventEmitter<RooTerminalProces
137137
*/
138138
abstract getUnretrievedOutput(): string
139139

140+
/**
141+
* Write characters to stdin.
142+
* @param chars The characters to write
143+
* @returns true if write was successful, false if stdin is not available
144+
*/
145+
abstract writeStdin(chars: string): boolean
146+
140147
/**
141148
* Clears the internal output buffer when all content has been retrieved.
142149
*

src/integrations/terminal/ExecaTerminalProcess.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
4242
shell: true,
4343
cwd: this.terminal.getCurrentWorkingDirectory(),
4444
all: true,
45-
// Ignore stdin to ensure non-interactive mode and prevent hanging
46-
stdin: "ignore",
45+
// Use pipe for stdin to allow interactive input via writeStdin
46+
stdin: "pipe",
4747
env: {
4848
...process.env,
4949
// Ensure UTF-8 encoding for Ruby, CocoaPods, etc.
@@ -241,6 +241,26 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
241241
return output.slice(0, index)
242242
}
243243

244+
/**
245+
* Write characters to stdin of the running process.
246+
* @param chars The characters to write
247+
* @returns true if write was successful
248+
*/
249+
public override writeStdin(chars: string): boolean {
250+
if (!this.subprocess?.stdin) {
251+
console.warn("[ExecaTerminalProcess#writeStdin] No stdin available")
252+
return false
253+
}
254+
255+
try {
256+
this.subprocess.stdin.write(chars)
257+
return true
258+
} catch (error) {
259+
console.error("[ExecaTerminalProcess#writeStdin] Failed to write:", error)
260+
return false
261+
}
262+
}
263+
244264
private emitRemainingBufferIfListening() {
245265
if (!this.isListening) {
246266
return

src/integrations/terminal/TerminalProcess.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,28 @@ export class TerminalProcess extends BaseTerminalProcess {
314314
return this.removeEscapeSequences(outputToProcess)
315315
}
316316

317+
/**
318+
* Write characters to stdin of the running process.
319+
* For VSCode terminals, this uses sendText which sends to the terminal.
320+
* @param chars The characters to write
321+
* @returns true if write was successful
322+
*/
323+
public override writeStdin(chars: string): boolean {
324+
try {
325+
const vsceTerminal = this.terminal.terminal
326+
if (!vsceTerminal) {
327+
console.warn("[TerminalProcess#writeStdin] No VSCode terminal available")
328+
return false
329+
}
330+
// sendText with addNewline=false to avoid double newlines
331+
vsceTerminal.sendText(chars, false)
332+
return true
333+
} catch (error) {
334+
console.error("[TerminalProcess#writeStdin] Failed to write:", error)
335+
return false
336+
}
337+
}
338+
317339
private emitRemainingBufferIfListening() {
318340
if (this.isListening) {
319341
const remainingBuffer = this.getUnretrievedOutput()

src/integrations/terminal/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ export interface RooTerminalProcess extends EventEmitter<RooTerminalProcessEvent
3737
hasUnretrievedOutput: () => boolean
3838
getUnretrievedOutput: () => string
3939
trimRetrievedOutput: () => void
40+
/**
41+
* Write characters to stdin. Returns true if write was successful.
42+
* May return false if stdin is not available (e.g., stdin: "ignore").
43+
*/
44+
writeStdin: (chars: string) => boolean
4045
}
4146

4247
export type RooTerminalProcessResultPromise = RooTerminalProcess & Promise<void>

0 commit comments

Comments
 (0)