Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
afd7955
feat: add command risk level setting with persistence
Apr 13, 2025
a2bd68b
feat: add metadata field to message types
Apr 15, 2025
44fc2e4
feat: add risk level validation for execute_command tool
Apr 15, 2025
d6a6fd5
feat: auto-approve commands based on risk level
Apr 16, 2025
c8c5314
feat: formalize risk level definitions for compound commands
Apr 18, 2025
b8262d0
feat: require formal risk analysis to execute_command
Apr 22, 2025
8ddca46
feat: improve execute_command tool description
Apr 22, 2025
f6843c7
feat: extend command risk analysis state space
Apr 23, 2025
a351735
fix: reduce token usage in risk definition model
Apr 24, 2025
ed06fec
fix: remove unused CommandRiskLevel import
Apr 26, 2025
62d51ea
refactor: move risk validation functions to utils
Apr 26, 2025
b522cca
i18n: standardize Chinese translation for readOnly
Apr 26, 2025
f3e6019
feat: refine command risk level definitions
Apr 29, 2025
e488723
feat: refine command reversibility definition with formal injectivity…
Apr 29, 2025
2df00d0
feat: add risk level display to command execution
May 10, 2025
9fb53f2
fix: command risk level select box not showing saved value
May 11, 2025
7b8e2b0
fix: refine execute-command risk parameter docs
May 11, 2025
101a993
feat: refine execute_command risk level definitions
May 14, 2025
57acf99
feat: add SQL primitives
May 14, 2025
1e1327b
rules: create more clarity around scope and gh
Jun 27, 2025
ba4c022
fix: stream risk and risk_analysis metadata when block.partial is true
Jun 27, 2025
a15174d
fix: correct CommandRiskLevel import path in ChatView
Jun 27, 2025
f464fcf
feat: completely exclude risk analysis from system instructions when …
Jun 28, 2025
0037ccd
lang: add translations for auto-approve command risk levels
Apr 22, 2025
b622a26
lang: fix grammar, appease ellipsis
Jun 28, 2025
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
12 changes: 12 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ export const globalSettingsSchema = z.object({
alwaysAllowFollowupQuestions: z.boolean().optional(),
followupAutoApproveTimeoutMs: z.number().optional(),
allowedCommands: z.array(z.string()).optional(),
commandRiskLevel: z
.enum([
"disabled",
"none",
"readOnly",
"reversibleChanges",
"complexChanges",
"serviceInterruptingChanges",
"destructiveChanges",
])
.optional(),
allowedMaxRequests: z.number().nullish(),
autoCondenseContext: z.boolean().optional(),
autoCondenseContextPercent: z.number().optional(),
Expand Down Expand Up @@ -194,6 +205,7 @@ export const EVALS_SETTINGS: RooCodeSettings = {
alwaysAllowFollowupQuestions: true,
followupAutoApproveTimeoutMs: 0,
allowedCommands: ["*"],
commandRiskLevel: "disabled",

browserToolEnabled: false,
browserViewportSize: "900x600",
Expand Down
3 changes: 3 additions & 0 deletions packages/types/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ export const clineMessageSchema = z.object({
progressStatus: toolProgressStatusSchema.optional(),
contextCondense: contextCondenseSchema.optional(),
isProtected: z.boolean().optional(),

// metadata is used to pass additional arbitrary user data to the webview as necessary
metadata: z.record(z.string(), z.unknown()).optional(),
})

export type ClineMessage = z.infer<typeof clineMessageSchema>
Expand Down
59 changes: 59 additions & 0 deletions packages/types/src/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,62 @@ export const commandExecutionStatusSchema = z.discriminatedUnion("status", [
])

export type CommandExecutionStatus = z.infer<typeof commandExecutionStatusSchema>

/**
* CommandRiskLevel
*/

export const commandRiskLevels = [
"disabled",
"none",
"readOnly",
"reversibleChanges",
"complexChanges",
"serviceInterruptingChanges",
"destructiveChanges",
] as const

export const commandRiskLevelsSchema = z.enum(commandRiskLevels)

export type CommandRiskLevel = z.infer<typeof commandRiskLevelsSchema>

/**
* Command Risk Utility Functions
*/

/**
* Helper function to check if risk analysis is enabled based on commandRiskLevel
* @param commandRiskLevel The command risk level
* @returns true if risk analysis is enabled, false otherwise
*/
export function isRiskAnalysisEnabled(commandRiskLevel?: CommandRiskLevel): boolean {
// Risk analysis is enabled by default and only disabled when commandRiskLevel is explicitly "disabled"
return commandRiskLevel !== undefined && commandRiskLevel !== "disabled"
}

/**
* Function to validate if a risk level is valid
* @param risk The risk level to validate
* @returns true if the risk level is valid, false otherwise
*/
export function isValidRiskLevel(risk: string): boolean {
return (
risk !== undefined &&
risk !== "none" &&
risk !== "disabled" &&
commandRiskLevels.includes(risk as CommandRiskLevel)
)
}

/**
* Function to check if a command risk level is allowed based on the user's risk level setting
* @param userRiskLevel The user's risk level setting
* @param cmdRiskLevel The command's risk level
* @returns true if the command risk level is allowed, false otherwise
*/
export function isRiskAllowed(userRiskLevel: CommandRiskLevel, cmdRiskLevel: CommandRiskLevel): boolean {
if (userRiskLevel === "none") return false
const userRiskIndex = commandRiskLevels.indexOf(userRiskLevel)
const cmdRiskIndex = commandRiskLevels.indexOf(cmdRiskLevel)
return cmdRiskIndex <= userRiskIndex
}
3 changes: 3 additions & 0 deletions src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,18 +257,21 @@ export async function presentAssistantMessage(cline: Task) {
cline.didAlreadyUseTool = true
}

// metadata is used to pass additional arbitrary user data to the webview as necessary
const askApproval = async (
type: ClineAsk,
partialMessage?: string,
progressStatus?: ToolProgressStatus,
isProtected?: boolean,
metadata?: Record<string, unknown>,
) => {
const { response, text, images } = await cline.ask(
type,
partialMessage,
false,
progressStatus,
isProtected || false,
metadata,
)

if (response !== "yesButtonClicked") {
Expand Down
15 changes: 14 additions & 1 deletion src/core/prompts/sections/rules.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DiffStrategy } from "../../../shared/tools"
import { CodeIndexManager } from "../../../services/code-index/manager"
import { isRiskAnalysisEnabled } from "@roo-code/types"

function getEditingInstructions(diffStrategy?: DiffStrategy): string {
const instructions: string[] = []
Expand Down Expand Up @@ -50,6 +51,7 @@ export function getRulesSection(
supportsComputerUse: boolean,
diffStrategy?: DiffStrategy,
codeIndexManager?: CodeIndexManager,
settings?: Record<string, any>,
): string {
const isCodebaseSearchAvailable =
codeIndexManager &&
Expand Down Expand Up @@ -79,7 +81,18 @@ ${getEditingInstructions(diffStrategy)}
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the attempt_completion tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.
- You are only allowed to ask the user questions using the ask_followup_question tool. Use this tool only when you need additional details to complete a task, and be sure to use a clear and concise question that will help you move forward with the task. When you ask a question, provide the user with 2-4 suggested answers based on your question so they don't need to do so much typing. The suggestions should be specific, actionable, and directly related to the completed task. They should be ordered by priority or logical sequence. However if you can use the available tools to avoid having to ask the user questions, you should do so. For example, if the user mentions a file that may be in an outside directory like the Desktop, you should use the list_files tool to list the files in the Desktop and check if the file they are talking about is there, rather than asking the user to provide the file path themselves.
- When executing commands, if you don't see the expected output, assume the terminal executed the command successfully and proceed with the task. The user's terminal may be unable to stream the output back properly. If you absolutely need to see the actual terminal output, use the ask_followup_question tool to request the user to copy and paste it back to you.
- When executing commands, if you don't see the expected output, assume the terminal executed the command successfully and proceed with the task. The user's terminal may be unable to stream the output back properly. If you absolutely need to see the actual terminal output, use the ask_followup_question tool to request the user to copy and paste it back to you.${
isRiskAnalysisEnabled(settings?.commandRiskLevel)
? `- When using the execute_command tool, you must analyze each command to determine the appropriate risk level based on the formal definitions provided. You must:
* Apply the mathematical and plain language definitions to classify command operations
* Always select the most conservative applicable risk level when in doubt
* Specify the risk parameter for every command execution
* Consider the full command context including all arguments and potential side effects
* When executing commands that could potentially overwrite or delete files (like mv, cp, or any other), if you cannot definitively confirm the non-existence of target files, you must assume they exist and treat the operation as destructiveChanges. The burden of proof is on confirming non-existence, not on existence.
* Use a command with the least risk level to accomplish the task. In some cases you may lawfully reduce the risk level with knowledge of the system state: for example, if the operation could overwrite, you can use a lower risk level by inspecting whether the destination exists or by passing an argument that would prevent overwrite; if the destination exists then it is destructive, but if it does not exist then it is reversible via deletion.
* Be especially careful with commands that might cause permanent data loss or service interruption`
: ""
}
- The user may provide a file's contents directly in their message, in which case you shouldn't use the read_file tool to get the file contents again since you already have it.
- Your goal is to try to accomplish the user's task, NOT engage in a back and forth conversation.${
supportsComputerUse
Expand Down
2 changes: 1 addition & 1 deletion src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ ${getCapabilitiesSection(cwd, supportsComputerUse, mcpHub, effectiveDiffStrategy

${modesSection}

${getRulesSection(cwd, supportsComputerUse, effectiveDiffStrategy, codeIndexManager)}
${getRulesSection(cwd, supportsComputerUse, effectiveDiffStrategy, codeIndexManager, settings)}

${getSystemInfoSection(cwd)}

Expand Down
170 changes: 167 additions & 3 deletions src/core/prompts/tools/execute-command.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { ToolArgs } from "./types"
import { isRiskAnalysisEnabled } from "@roo-code/types"

export function getExecuteCommandDescription(args: ToolArgs): string | undefined {
return `## execute_command
// Get the command risk level from settings
const commandRiskLevel = args.settings?.commandRiskLevel
// Check if risk analysis is disabled
const isRiskAnalysisDisabled = !isRiskAnalysisEnabled(commandRiskLevel)
const baseDescription = `## execute_command
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})
- 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${isRiskAnalysisDisabled ? "." : "; the chosen command should be of the lowest risk level that accomplishes the goal and must assert R ⊆ Tm"}
- cwd: (optional) The working directory to execute the command in (default: ${args.cwd})`

if (isRiskAnalysisDisabled) {
return (
baseDescription +
`
Usage:
<execute_command>
<command>Your command here</command>
Expand All @@ -22,4 +32,158 @@ Example: Requesting to execute ls in a specific directory if directed
<command>ls -la</command>
<cwd>/home/user/projects</cwd>
</execute_command>`
)
}

// else show risk analysis rules
return (
baseDescription +
`
- risk_analysis: (required) Write one plain-verbiage terse concise sentence, prove: R ⊆ Tm, readOnly ⟺ Tm=∅, prove why you chose r(C); this sentence must not mention neither the risk level and must be plain language.
- risk: (required) The risk level of the command. You must analyze each command to determine the appropriate risk level using the formal definitions below. Always select the most conservative applicable level.

### Risk Level Definitions:

Risk level total ordering:
readOnly < reversibleChanges < complexChanges < serviceInterruptingChanges < destructiveChanges

S = (F ∪ P ∪ N ∪ H ∪ M ∪ Z ∪ E) × {local,remote,api,web,cloud,virt}
F = storage: files, databases, git repos, disks, …
P = processes, services, VMs, containers, …
N = network configs, …
H = hardware, physical controls, …
M = memory states, locks, resources, …
Z = security: permissions, keys, tokens, …
E = temporal state: tasks, cron, timers, …

s₀ ∈ S: initial state before C
s′ ∈ S′: final state after C
atoms(x): minimal addressable units
content(x,s): accessible atoms in s
a ∉ content(x,s) if deleted, truncated, overwritten, corrupted, encrypted with lost key

Let command sequence C={c₁,…,cₙ} introduce the partial function where C is ONLY the command sequence and arguments with no other knowledge.
c: S ⇀ S′
by the operational judgments
⟨C,s⟩⇓s′ iff C, started in s, terminates normally in s′
⟨C,s⟩⇑ iff C, started in s, aborts with an error.

Then
c(s)=s′ ↔ ⟨C,s⟩⇓s′
c(s)↑ ↔ ⟨C,s⟩⇑

Finally
Dom(C) ≔ dom(c)
Im(C) ≔ ran(c) = c[Dom(C)]

Assume s₀ ∈ Dom(C) for the purpose of r(C) analysis

T = Tr ∪ Tm: intended targets
Tr ⊆ S: read-only targets
Tm = {x | x ∈ S ∨ (x ∉ S ∧ x ∈ S′)}: modifiable/creatable targets
R = {x | (x ∈ s₀ ∧ c(x) ≠ x) ∨ (x ∉ s₀ ∧ x ∈ S′)}: modified/created elements
c⁻¹(R): inverse operations derivable solely from command sequence C and resulting state s′ (absent all other context), explicitly excluding data from unconfirmed backups, conversation context, user-held knowledge, or external state details uncaptured by C.

r(C)=readOnly ⟺
s′ = s₀ ∧
(Tm = ∅ ∧ |Tm| = 0) ∧
(R = ∅ ∧ |R| = 0) ∧
(Tr ≠ ∅) ∧
(∀x ∈ S: content(x,s₀) = content(x,s′)) ∧
¬reversibleChanges ∧
¬complexChanges ∧
¬serviceInterruptingChanges ∧
¬destructiveChanges


r(C)=reversibleChanges ⟺ [
(Tm ≠ ∅) ∧
(R = Tm) ∧
(∃c⁻¹_known: Im(C) → Dom(C).
(inverse_is_knowable(C, c⁻¹_known)) ∧
(∀s ∈ Dom(C). c⁻¹_known(c(s)) = s))

where:
inverse_is_knowable(C, c⁻¹_known) ⇔ c⁻¹ is inferable solely from (C, s′, I).
I: the set of all known inverse commands

r(C)=complexChanges ⟺
¬readOnly ∧
¬reversibleChanges ∧
¬serviceInterruptingChanges ∧
¬destructiveChanges

r(C)=serviceInterruptingChanges ⟺
∃p ∈ Tm ∩ P, ∃t₁ ∈ ℝ⁺:
A(p,0) ∧
¬A(p,t₁) ∧
((∃t₂ > t₁: A(p,t₂)) ∨ p ∉ s′)
where A(x,t): availability at time t

r(C)=destructiveChanges ⟺
(R ⊈ Tm) ∨
(∃x ∈ s₀: content(x,s₀) ≠ ∅ ∧ content(x,s′) = ∅) ∨
(∃m ∈ R :
(content(m,s₀) ≠ content(m,s′)) ∧
(∃d ∈ s₀, d ≠ m :
(content(d,s₀) ≠ ∅ ∧ content(d,s′) = ∅)
)
)

You MUST evaluate HIGHEST risk and analyze the ENTIRE command sequence, eg \`c₁ && c₂\`:
r(C) = max{r(c₁), r(c₂), …, r(cₙ)}

Examples by risk level
- readOnly: ls, git log, cat, ps, ip, lsblk, free, id, atq, date, dmesg, lsmod, last, iptables -L, gh [cmd] (view|diff|etc), SELECT, …
- reversibleChanges: mkdir, mv -n and cp -n (without overwrite), renice, ip addr add, swapon, groupadd, tar c, systemctl enable, chmod +x, mount, iptables -A, gzip, gh [cmd] (comment|close|etc), INSERT, …
- complexChanges: rsync, tar x, sed -i, taskset, firewall-cmd, fwupdmgr, numactl, chcon, update-alternatives, recursive operations, gh [cmd] (create|edit), UPDATE, …
- serviceInterruptingChanges: stop, restart, ip link set down, eject, swapoff -a, kill -9, setenforce 1, systemctl mask, timedatectl set-time, iptables -F, …
- destructiveChanges: rm, dd of=, nft flush, mkfs, userdel -r, crontab -r, shred -u, overwrite or truncate operations (cp, mv, >file, …), DELETE, DROP, …

### Usage:
<execute_command>
<command>Your command here</command>
<risk_analysis>[reasoning]</risk_analysis>
<risk>Risk level here</risk>
<cwd>Working directory path (optional)</cwd>
</execute_command>

### Examples:

${`Example: Requesting to execute a read-only command
<execute_command>
<command>ls -la</command>
<risk_analysis>[reasoning]</risk_analysis>
<risk>readOnly</risk>
</execute_command>

Example: Requesting to execute a reversible change
<execute_command>
<command>mkdir test_directory</command>
<risk_analysis>[reasoning]</risk_analysis>
<risk>reversibleChanges</risk>
</execute_command>

Example: Requesting to execute a complex change
<execute_command>
<command>npm install express</command>
<risk_analysis>[reasoning]</risk_analysis>
<risk>complexChanges</risk>
</execute_command>

Example: Requesting to execute a destructive change
<execute_command>
<command>rm test_file.txt</command>
<risk_analysis>[reasoning]</risk_analysis>
<risk>destructiveChanges</risk>
</execute_command>

Example: Requesting to execute a command in a specific directory
<execute_command>
<command>ls -la</command>
<risk_analysis>[reasoning]</risk_analysis>
<risk>readOnly</risk>
<cwd>/home/user/projects</cwd>
</execute_command>`}`
)
}
Loading