Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e04901d
feat: add read_command_output tool for retrieving truncated command o…
hannesrudolph Jan 24, 2026
8d417ed
fix: address PR review comments
hannesrudolph Jan 24, 2026
07852aa
fix: bound accumulatedOutput growth in execute_command
hannesrudolph Jan 24, 2026
61025ce
fix: resolve CI failures for PR #10944
hannesrudolph Jan 25, 2026
23ee830
fix(parser): add read_command_output to NativeToolCallParser
hannesrudolph Jan 27, 2026
54fe0fd
feat(ui): add read_command_output activity indicator
hannesrudolph Jan 27, 2026
311fb4b
i18n: add readCommandOutput translations to all locales
hannesrudolph Jan 27, 2026
0195613
refactor: remove terminalOutputLineLimit and terminalOutputCharacterL…
hannesrudolph Jan 27, 2026
48948b4
Delete claude-code.md
hannesrudolph Jan 27, 2026
9a10704
Delete codex-extract-terminal-spawning-tool.md
hannesrudolph Jan 27, 2026
21d2535
Delete Roo-EXTRACTION-terminal-shell-integration.md
hannesrudolph Jan 27, 2026
d4680cd
feat: update OutputInterceptor to use 50/50 head/tail split like Codex
hannesrudolph Jan 27, 2026
b0ec581
fix: ensure lossless artifact storage and strict mode compatibility
hannesrudolph Jan 27, 2026
765528e
fix: use chunked streaming for search to avoid memory blowup
hannesrudolph Jan 27, 2026
61e7820
fix: address review feedback for lossless terminal output
hannesrudolph Jan 27, 2026
c30924e
fix: ensure onCompleted callback finishes before using persistedResult
hannesrudolph Jan 27, 2026
fe913cb
feat: align output limits with terminal integration spec
hannesrudolph Jan 28, 2026
a3a50b7
feat: update i18n labels for new preview sizes (5KB/10KB/20KB)
hannesrudolph Jan 28, 2026
0f11772
fix: display search pattern and match count in read_command_output UI
hannesrudolph Jan 28, 2026
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
3 changes: 0 additions & 3 deletions packages/types/src/cloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ export const organizationDefaultSettingsSchema = globalSettingsSchema
maxWorkspaceFiles: true,
showRooIgnoredFiles: true,
terminalCommandDelay: true,
terminalCompressProgressBar: true,
terminalOutputLineLimit: true,
terminalShellIntegrationDisabled: true,
terminalShellIntegrationTimeout: true,
terminalZshClearEolMark: true,
Expand All @@ -112,7 +110,6 @@ export const organizationDefaultSettingsSchema = globalSettingsSchema
maxReadFileLine: z.number().int().gte(-1).optional(),
maxWorkspaceFiles: z.number().int().nonnegative().optional(),
terminalCommandDelay: z.number().int().nonnegative().optional(),
terminalOutputLineLimit: z.number().int().nonnegative().optional(),
terminalShellIntegrationTimeout: z.number().int().nonnegative().optional(),
}),
)
Expand Down
44 changes: 34 additions & 10 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,40 @@ import { languagesSchema } from "./vscode.js"
export const DEFAULT_WRITE_DELAY_MS = 1000

/**
* Default terminal output character limit constant.
* This provides a reasonable default that aligns with typical terminal usage
* while preventing context window explosions from extremely long lines.
* Terminal output preview size options for persisted command output.
*
* Controls how much command output is kept in memory as a "preview" before
* the LLM decides to retrieve more via `read_command_output`. Larger previews
* mean more immediate context but consume more of the context window.
*
* - `small`: 5KB preview - Best for long-running commands with verbose output
* - `medium`: 10KB preview - Balanced default for most use cases
* - `large`: 20KB preview - Best when commands produce critical info early
*
* @see OutputInterceptor - Uses this setting to determine when to spill to disk
* @see PersistedCommandOutput - Contains the resulting preview and artifact reference
*/
export const DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT = 50_000
export type TerminalOutputPreviewSize = "small" | "medium" | "large"

/**
* Byte limits for each terminal output preview size.
*
* Maps preview size names to their corresponding byte thresholds.
* When command output exceeds these thresholds, the excess is persisted
* to disk and made available via the `read_command_output` tool.
*/
export const TERMINAL_PREVIEW_BYTES: Record<TerminalOutputPreviewSize, number> = {
small: 5 * 1024, // 5KB
medium: 10 * 1024, // 10KB
large: 20 * 1024, // 20KB
}

/**
* Default terminal output preview size.
* The "medium" (10KB) setting provides a good balance between immediate
* visibility and context window conservation for most use cases.
*/
export const DEFAULT_TERMINAL_OUTPUT_PREVIEW_SIZE: TerminalOutputPreviewSize = "medium"

/**
* Minimum checkpoint timeout in seconds.
Expand Down Expand Up @@ -147,8 +176,7 @@ export const globalSettingsSchema = z.object({
maxImageFileSize: z.number().optional(),
maxTotalImageSize: z.number().optional(),

terminalOutputLineLimit: z.number().optional(),
terminalOutputCharacterLimit: z.number().optional(),
terminalOutputPreviewSize: z.enum(["small", "medium", "large"]).optional(),
terminalShellIntegrationTimeout: z.number().optional(),
terminalShellIntegrationDisabled: z.boolean().optional(),
terminalCommandDelay: z.number().optional(),
Expand All @@ -157,7 +185,6 @@ export const globalSettingsSchema = z.object({
terminalZshOhMy: z.boolean().optional(),
terminalZshP10k: z.boolean().optional(),
terminalZdotdir: z.boolean().optional(),
terminalCompressProgressBar: z.boolean().optional(),

diagnosticsEnabled: z.boolean().optional(),

Expand Down Expand Up @@ -338,16 +365,13 @@ export const EVALS_SETTINGS: RooCodeSettings = {
soundEnabled: false,
soundVolume: 0.5,

terminalOutputLineLimit: 500,
terminalOutputCharacterLimit: DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
terminalShellIntegrationTimeout: 30000,
terminalCommandDelay: 0,
terminalPowershellCounter: false,
terminalZshOhMy: true,
terminalZshClearEolMark: true,
terminalZshP10k: false,
terminalZdotdir: true,
terminalCompressProgressBar: true,
terminalShellIntegrationDisabled: true,

diagnosticsEnabled: true,
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export const clineSays = [
"codebase_search_result",
"user_edit_todos",
"too_many_tools_warning",
"tool",
] as const

export const clineSaySchema = z.enum(clineSays)
Expand Down
66 changes: 66 additions & 0 deletions packages/types/src/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,69 @@ export const commandExecutionStatusSchema = z.discriminatedUnion("status", [
])

export type CommandExecutionStatus = z.infer<typeof commandExecutionStatusSchema>

/**
* PersistedCommandOutput
*
* Represents the result of a terminal command execution that may have been
* truncated and persisted to disk.
*
* When command output exceeds the configured preview threshold, the full
* output is saved to a disk artifact file. The LLM receives this structure
* which contains:
* - A preview of the output (for immediate display in context)
* - Metadata about the full output (size, truncation status)
* - A path to the artifact file for later retrieval via `read_command_output`
*
* ## Usage in execute_command Response
*
* The response format depends on whether truncation occurred:
*
* **Not truncated** (output fits in preview):
* ```json
* {
* "preview": "full output here...",
* "totalBytes": 1234,
* "artifactPath": null,
* "truncated": false
* }
* ```
*
* **Truncated** (output exceeded threshold):
* ```json
* {
* "preview": "first 4KB of output...",
* "totalBytes": 1048576,
* "artifactPath": "/path/to/tasks/123/command-output/cmd-1706119234567.txt",
* "truncated": true
* }
* ```
*
* @see OutputInterceptor - Creates these results during command execution
* @see ReadCommandOutputTool - Retrieves full content from artifact files
*/
export interface PersistedCommandOutput {
/**
* Preview of the command output, truncated to the preview threshold.
* Always contains the beginning of the output, even if truncated.
*/
preview: string

/**
* Total size of the command output in bytes.
* Useful for determining if additional reads are needed.
*/
totalBytes: number

/**
* Absolute path to the artifact file containing full output.
* `null` if output wasn't truncated (no artifact was created).
*/
artifactPath: string | null

/**
* Whether the output was truncated (exceeded preview threshold).
* When `true`, use `read_command_output` to retrieve full content.
*/
truncated: boolean
}
1 change: 1 addition & 0 deletions packages/types/src/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type ToolGroup = z.infer<typeof toolGroupsSchema>
export const toolNames = [
"execute_command",
"read_file",
"read_command_output",
"write_to_file",
"apply_diff",
"search_and_replace",
Expand Down
11 changes: 8 additions & 3 deletions packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,7 @@ export type ExtensionState = Pick<
| "soundEnabled"
| "soundVolume"
| "maxConcurrentFileReads"
| "terminalOutputLineLimit"
| "terminalOutputCharacterLimit"
| "terminalOutputPreviewSize"
| "terminalShellIntegrationTimeout"
| "terminalShellIntegrationDisabled"
| "terminalCommandDelay"
Expand All @@ -312,7 +311,6 @@ export type ExtensionState = Pick<
| "terminalZshOhMy"
| "terminalZshP10k"
| "terminalZdotdir"
| "terminalCompressProgressBar"
| "diagnosticsEnabled"
| "language"
| "modeApiConfigs"
Expand Down Expand Up @@ -780,6 +778,7 @@ export interface ClineSayTool {
| "newFileCreated"
| "codebaseSearch"
| "readFile"
| "readCommandOutput"
| "fetchInstructions"
| "listFilesTopLevel"
| "listFilesRecursive"
Expand All @@ -792,6 +791,12 @@ export interface ClineSayTool {
| "runSlashCommand"
| "updateTodoList"
path?: string
// For readCommandOutput
readStart?: number
readEnd?: number
totalBytes?: number
searchPattern?: string
matchCount?: number
diff?: string
content?: string
// Unified diff statistics computed by the extension
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions src/core/assistant-message/NativeToolCallParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,17 @@ export class NativeToolCallParser {
}
break

case "read_command_output":
if (args.artifact_id !== undefined) {
nativeArgs = {
artifact_id: args.artifact_id,
search: args.search,
offset: args.offset,
limit: args.limit,
} as NativeArgsFor<TName>
}
break

case "write_to_file":
if (args.path !== undefined && args.content !== undefined) {
nativeArgs = {
Expand Down
13 changes: 12 additions & 1 deletion src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Task } from "../task/Task"
import { fetchInstructionsTool } from "../tools/FetchInstructionsTool"
import { listFilesTool } from "../tools/ListFilesTool"
import { readFileTool } from "../tools/ReadFileTool"
import { readCommandOutputTool } from "../tools/ReadCommandOutputTool"
import { writeToFileTool } from "../tools/WriteToFileTool"
import { searchAndReplaceTool } from "../tools/SearchAndReplaceTool"
import { searchReplaceTool } from "../tools/SearchReplaceTool"
Expand Down Expand Up @@ -402,8 +403,10 @@ export async function presentAssistantMessage(cline: Task) {
return `[${block.name}]`
case "switch_mode":
return `[${block.name} to '${block.params.mode_slug}'${block.params.reason ? ` because: ${block.params.reason}` : ""}]`
case "codebase_search": // Add case for the new tool
case "codebase_search":
return `[${block.name} for '${block.params.query}']`
case "read_command_output":
return `[${block.name} for '${block.params.artifact_id}']`
case "update_todo_list":
return `[${block.name}]`
case "new_task": {
Expand Down Expand Up @@ -846,6 +849,13 @@ export async function presentAssistantMessage(cline: Task) {
pushToolResult,
})
break
case "read_command_output":
await readCommandOutputTool.handle(cline, block as ToolUse<"read_command_output">, {
askApproval,
handleError,
pushToolResult,
})
break
case "use_mcp_tool":
await useMcpToolTool.handle(cline, block as ToolUse<"use_mcp_tool">, {
askApproval,
Expand Down Expand Up @@ -1088,6 +1098,7 @@ function containsXmlToolMarkup(text: string): boolean {
"generate_image",
"list_files",
"new_task",
"read_command_output",
"read_file",
"search_and_replace",
"search_files",
Expand Down
19 changes: 3 additions & 16 deletions src/core/environment/getEnvironmentDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import pWaitFor from "p-wait-for"
import delay from "delay"

import type { ExperimentId } from "@roo-code/types"
import { DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT } from "@roo-code/types"

import { formatLanguage } from "../../shared/language"
import { defaultModeSlug, getFullModeDetails } from "../../shared/modes"
Expand All @@ -26,11 +25,7 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo

const clineProvider = cline.providerRef.deref()
const state = await clineProvider?.getState()
const {
terminalOutputLineLimit = 500,
terminalOutputCharacterLimit = DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
maxWorkspaceFiles = 200,
} = state ?? {}
const { maxWorkspaceFiles = 200 } = state ?? {}

// It could be useful for cline to know if the user went from one or no
// file to another between messages, so we always include this context.
Expand Down Expand Up @@ -112,11 +107,7 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
let newOutput = TerminalRegistry.getUnretrievedOutput(busyTerminal.id)

if (newOutput) {
newOutput = Terminal.compressTerminalOutput(
newOutput,
terminalOutputLineLimit,
terminalOutputCharacterLimit,
)
newOutput = Terminal.compressTerminalOutput(newOutput)
terminalDetails += `\n### New Output\n${newOutput}`
}
}
Expand Down Expand Up @@ -144,11 +135,7 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
let output = process.getUnretrievedOutput()

if (output) {
output = Terminal.compressTerminalOutput(
output,
terminalOutputLineLimit,
terminalOutputCharacterLimit,
)
output = Terminal.compressTerminalOutput(output)
terminalOutputs.push(`Command: \`${process.command}\`\n${output}`)
}
}
Expand Down
Loading
Loading