-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat: interactive terminal support #11059
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…utput Implements a new tool that allows the LLM to retrieve full command output when execute_command produces output exceeding the preview threshold. Key components: - ReadCommandOutputTool: Reads persisted output with search/pagination - OutputInterceptor: Intercepts and persists large command outputs to disk - Terminal settings UI: Configuration for output interception behavior - Type definitions for output interception settings The tool supports: - Reading full output beyond the truncated preview - Search/filtering with regex patterns (like grep) - Pagination through large outputs using offset/limit Includes comprehensive tests for ReadCommandOutputTool and OutputInterceptor.
- Read terminalOutputPreviewSize from providerState instead of hardcoded default - Fix native tool schema to only require artifact_id (optional params no longer required) - Fix Buffer allocation for line numbers using chunked 64KB reads to avoid memory blowup
- Remove unused barrel file (src/integrations/terminal/index.ts) to fix knip check - Fix Windows path test in OutputInterceptor.test.ts by using path.normalize() - Add missing translations for terminal.outputPreviewSize settings to all 17 locales
chore: remove terminalCompressProgressBar setting - Fix: Add missing read_command_output case to parser (was causing 'Invalid arguments' errors) - Remove: Delete compress progress bar setting from all components (redundant with preview size control) - Clean up: Remove from global-settings, OutputInterceptor, BaseTerminal, ExecuteCommandTool, SettingsView, TerminalSettings, ExtensionStateContext - Clean up: Remove from all i18n locale files
Show read progress in chat when read_command_output tool is used: - Display 'Roo read command output (0 B - 100 KB of 263.3 KB)' - Shows the specific byte range being read each call - Uses same visual style as read_file tool - Add 'tool' to ClineSay types - Add readCommandOutput to ClineSayTool with readStart/readEnd/totalBytes
…imit settings These settings were redundant with terminalOutputPreviewSize which controls the preview shown to the LLM. The line/char limits were for UI truncation which is now handled with hardcoded defaults (500 lines, 50K chars) since they don't need to be user-configurable. - Remove settings from packages/types schemas - Remove DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT constant - Update compressTerminalOutput() to use hardcoded limits - Update ExecuteCommandTool to not pass limit parameters - Update ClineProvider state handling - Update webview context and settings - Update tests to not use removed settings
- Replace single buffer with separate headBuffer and tailBuffer - Each buffer gets 50% of the preview budget - Head captures beginning of output, tail keeps rolling end - Middle content is dropped when output exceeds threshold - Preview shows: head + [omission indicator] + tail - Add tests for head/tail split behavior This approach ensures the LLM sees both: - The beginning (command startup, environment info, early errors) - The end (final results, exit codes, error summaries)
- OutputInterceptor: Buffer ALL chunks before spilling to disk to preserve full content losslessly. Previously, the rolling tail buffer could drop middle content before the spill decision was made. - read_command_output schema: Include all properties in 'required' array for OpenAI strict mode compliance. With strict: true, all properties must be listed in required (optional ones use null union types).
Replace fs.readFile with chunked streaming in searchInArtifact() to keep memory usage bounded for large command outputs. Instead of loading the entire file into memory, reads in 64KB chunks and processes lines as they are encountered. This addresses the concern that loading 100MB+ build logs into memory defeats the purpose of the persisted output feature.
- OutputInterceptor.finalize() now awaits stream flush before returning This ensures artifact files are fully written before the artifact_id is advertised to the LLM, preventing partial reads. - Remove strict mode from read_command_output native tool schema With strict: true, OpenAI requires all params in 'required', forcing the LLM to provide explicit null values for optional params. This created verbose tool calls. Now optional params can be omitted entirely. - Update tests to handle async finalize() method
- Update RooTerminalCallbacks.onCompleted type to allow async callbacks (void | Promise<void>) - Track onCompleted completion with a promise and await it before using persistedResult - This fixes a race condition where exitDetails could be set before the async finalize() completes - Fix test callback to not return assignment value
- Update preview sizes: 2KB/4KB/8KB → 5KB/10KB/20KB (default 10KB) - Update read_command_output default limit: 32KB → 40KB - Match spec's MODEL_TRUNCATION_BYTES (10KB) for preview - Match spec's DEFAULT_MAX_OUTPUT_TOKENS (10000 tokens × 4 bytes = 40KB) for retrieval - Update all related tests and documentation
Update all 18 locale files with new preview size labels: - small: 2KB → 5KB - medium: 4KB → 10KB - large: 8KB → 20KB
When using search mode, the UI now shows the search pattern and match count instead of the misleading byte range (0 B - totalSize). - Added searchPattern and matchCount fields to ClineSayTool type - Updated ReadCommandOutputTool to return match count from search operations - Updated ChatRow to display 'search: "pattern" • N matches' for search mode
This commit implements the interactive terminal feature from the terminal integration specification (plans/extract-terminal-integration.md). Changes: - Add write_stdin to toolNames in packages/types/src/tool.ts - Create write_stdin native tool schema in src/core/prompts/tools/native-tools/ - Create WriteStdinTool handler in src/core/tools/ - Add ProcessManager to track running processes by session_id - Modify ExecuteCommandTool to register processes when still running - Add write_stdin to tool routing in presentAssistantMessage.ts - Add write_stdin to NativeToolCallParser for streaming support - Add tests for ProcessManager The write_stdin tool enables the LLM to: - Send input to running terminal processes (y/n prompts, passwords) - Send control characters like Ctrl+C (\x03) - Poll for new output from long-running processes When execute_command starts a process that's still running after the yield time, it registers the process with ProcessManager and returns a session_id. The LLM can then use write_stdin with that session_id to interact with the process.
- Add terminate_session tool to kill running terminal sessions - Add list_sessions tool to view active terminal sessions - Add listSessions and terminateSession methods to ProcessManager - Create native tool schemas for both tools - Create tool handlers (TerminateSessionTool, ListSessionsTool) - Add routing in presentAssistantMessage.ts - Add parsing support in NativeToolCallParser.ts - Add 8 new tests for ProcessManager methods (23 total tests) These tools complement write_stdin to provide complete terminal session management: - execute_command starts a process (returns session_id if still running) - write_stdin sends input to running processes - list_sessions shows all active sessions - terminate_session kills a running session
- 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.
…ring in ChatRow - Add proper UI cases for write_stdin showing session ID and input chars - Add terminate_session rendering with termination status - Add list_sessions rendering with sessions table - Add i18n translations for all stdin operations
- Add activeTerminalSessions count to ExtensionState type - Add ProcessManager query in ClineProvider.getState() to count running sessions - Add TerminalSquare icon indicator in TaskHeader showing session count - Show indicator only when there are active sessions (green icon + count) - Add i18n translations for terminal session tooltip - Update ExtensionStateContext test with new property
…text and test mock
Re-reviewed latest update (65a6dde, i18n-only). No new issues found in this update.
Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues. |
Add translations for terminal session and stdin operation strings to all 17 supported locales in chat.json: - terminal.sessionsRunning (singular/plural forms) - stdinOperations.sentToSession - stdinOperations.polledSession - stdinOperations.wantsToTerminate - stdinOperations.didTerminate - stdinOperations.wantsToListSessions - stdinOperations.didListSessions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR review: interactive terminal support (session lifecycle + stdin).
| // Ignore stdin to ensure non-interactive mode and prevent hanging | ||
| stdin: "ignore", | ||
| // Use pipe for stdin to allow interactive input via writeStdin | ||
| stdin: "pipe", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switching execa stdin from "ignore" to "pipe" enables write_stdin, but it also changes default behavior for all execa-run commands. This can make previously non-interactive commands hang if they (or a child) unexpectedly start reading from stdin.
Consider gating stdin: "pipe" behind an explicit interactive/session mode (or add a timeout / auto-abort strategy for commands that start waiting for input).
| * @returns The session ID for this process | ||
| * @throws Error if maximum process limit is reached | ||
| */ | ||
| registerProcess(terminal: RooTerminal, process: RooTerminalProcess, taskId: string, command: string): number { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ProcessManager never updates entry.running based on the underlying process lifecycle. cleanup() only checks terminal.isClosed()/!entry.running, so sessions can stick around indefinitely (especially for execa where isClosed() is always false).
Suggestion: in registerProcess(...), attach listeners like process.once("shell_execution_complete", ...) and/or process.once("completed", ...) to markCompleted()/unregisterProcess() deterministically.
| } | ||
|
|
||
| // Check if process has exited | ||
| const isStillRunning = !terminal.isClosed() && terminal.running |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const isStillRunning = !terminal.isClosed() && terminal.running is a fragile liveness check. For execa terminals, isClosed() is always false; and terminal.running is terminal-wide state (can be stale or refer to another command if terminal reuse happens).
Suggestion: base liveness on the session's RooTerminalProcess completion events tracked in ProcessManager, rather than terminal-wide flags.
| } else { | ||
| // Process is still running - register it with ProcessManager for write_stdin interaction | ||
| let sessionId: number | undefined | ||
| const currentProcess = terminal.process |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Session registration uses terminal.process as the handle to track. Since executeCommandInTerminal() already has the process returned from terminal.runCommand(...), it would be safer to register that exact instance (avoid any chance of terminal.process changing due to terminal reuse or internal state transitions).
Important
This pull request adds interactive terminal support with new tools for managing terminal sessions, updates the UI to display session information, and includes tests for session management.
write_stdin,terminate_session, andlist_sessionstools totool.tsandNativeToolCallParser.tsfor interactive terminal management.presentAssistantMessage.tsto handle new tools with appropriate handlers.ProcessManagerinProcessManager.tsto track and manage terminal sessions.ChatRow.tsxandTaskHeader.tsxto display terminal session information.ProcessManagerinProcessManager.spec.tsto ensure correct session management.This description was created by
for 65a6dde. You can customize this summary. It will automatically update as commits are pushed.