mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Add in permissions logic for tools; Update README with permissions section
This commit is contained in:
11
README.md
11
README.md
@@ -6,9 +6,12 @@ This project was developed for the [Build with Claude June 2024](https://docs.an
|
||||
|
||||
## How it works
|
||||
|
||||
Claude Dev uses an agentic loop style implementation using chain-of-thought prompting and access to powerful tools that give him the ability to accomplish nearly everything. From building softwware projects to running system operations, Claude Dev is only limited by your imagination.
|
||||
|
||||
|
||||
### Tools
|
||||
|
||||
Claude Dev has access to the following tools:
|
||||
Claude has access to the following tools:
|
||||
|
||||
1. **execute_command**: Execute CLI commands on the system.
|
||||
2. **list_files**: List all files and directories at the top level of the specified directory.
|
||||
@@ -17,6 +20,12 @@ Claude Dev has access to the following tools:
|
||||
5. **ask_followup_question**: Ask the user a question to gather additional information needed to complete a task.
|
||||
6. **attempt_completion**: Present the result to the user after completing a task.
|
||||
|
||||
### Only With Your Permission
|
||||
|
||||
Claude always asks for your permission first before any tools are executed or information is sent back to the API. This puts you in control of this agentic loop, every step of the way.
|
||||
|
||||

|
||||
|
||||
## Screenshots
|
||||
|
||||
### 1. Give Claude Dev any task!
|
||||
|
||||
@@ -340,12 +340,9 @@ ${openDocuments}`
|
||||
if (fileExists) {
|
||||
const originalContent = await fs.readFile(filePath, "utf-8")
|
||||
const diffResult = diff.createPatch(filePath, originalContent, newContent)
|
||||
if (diffResult) {
|
||||
await fs.writeFile(filePath, newContent)
|
||||
|
||||
// Create diff for DiffCodeView.tsx
|
||||
const diffStringRaw = diff.diffLines(originalContent, newContent)
|
||||
const diffStringConverted = diffStringRaw
|
||||
const completeDiffStringRaw = diff.diffLines(originalContent, newContent)
|
||||
const completeDiffStringConverted = completeDiffStringRaw
|
||||
.map((part, index) => {
|
||||
const prefix = part.added ? "+ " : part.removed ? "- " : " "
|
||||
return part.value
|
||||
@@ -354,7 +351,7 @@ ${openDocuments}`
|
||||
// avoid adding an extra empty line at the very end of the diff output
|
||||
if (
|
||||
line === "" &&
|
||||
index === diffStringRaw.length - 1 &&
|
||||
index === completeDiffStringRaw.length - 1 &&
|
||||
lineIndex === part.value.split("\n").length - 1
|
||||
) {
|
||||
return null
|
||||
@@ -364,34 +361,31 @@ ${openDocuments}`
|
||||
.join("")
|
||||
})
|
||||
.join("")
|
||||
this.say(
|
||||
|
||||
const { response } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({
|
||||
tool: "editedExistingFile",
|
||||
path: filePath,
|
||||
diff: diffStringConverted,
|
||||
diff: completeDiffStringConverted,
|
||||
} as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
return "This operation was not approved by the user."
|
||||
}
|
||||
|
||||
await fs.writeFile(filePath, newContent)
|
||||
return `Changes applied to ${filePath}:\n${diffResult}`
|
||||
} else {
|
||||
this.say(
|
||||
"tool",
|
||||
JSON.stringify({
|
||||
tool: "editedExistingFile",
|
||||
path: filePath,
|
||||
content: "No changes.",
|
||||
} as ClaudeSayTool)
|
||||
)
|
||||
return `Tool succeeded, however there were no changes detected to ${filePath}`
|
||||
}
|
||||
} else {
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
||||
await fs.writeFile(filePath, newContent)
|
||||
this.say(
|
||||
const { response } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({ tool: "newFileCreated", path: filePath, content: newContent } as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
return "This operation was not approved by the user."
|
||||
}
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
||||
await fs.writeFile(filePath, newContent)
|
||||
return `New file created and content written to ${filePath}`
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -404,7 +398,13 @@ ${openDocuments}`
|
||||
async readFile(filePath: string): Promise<string> {
|
||||
try {
|
||||
const content = await fs.readFile(filePath, "utf-8")
|
||||
this.say("tool", JSON.stringify({ tool: "readFile", path: filePath, content } as ClaudeSayTool))
|
||||
const { response } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({ tool: "readFile", path: filePath, content } as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
return "This operation was not approved by the user."
|
||||
}
|
||||
return content
|
||||
} catch (error) {
|
||||
const errorString = `Error reading file: ${JSON.stringify(serializeError(error))}`
|
||||
@@ -419,7 +419,13 @@ ${openDocuments}`
|
||||
const isRoot = absolutePath === root
|
||||
if (isRoot) {
|
||||
if (shouldLog) {
|
||||
this.say("tool", JSON.stringify({ tool: "listFiles", path: dirPath, content: root } as ClaudeSayTool))
|
||||
const { response } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({ tool: "listFiles", path: dirPath, content: root } as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
return "This operation was not approved by the user."
|
||||
}
|
||||
}
|
||||
return root
|
||||
}
|
||||
@@ -434,7 +440,13 @@ ${openDocuments}`
|
||||
const entries = await glob("*", options)
|
||||
const result = entries.slice(0, 500).join("\n") // truncate to 500 entries
|
||||
if (shouldLog) {
|
||||
this.say("tool", JSON.stringify({ tool: "listFiles", path: dirPath, content: result } as ClaudeSayTool))
|
||||
const { response } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({ tool: "listFiles", path: dirPath, content: result } as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
return "This operation was not approved by the user."
|
||||
}
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
|
||||
@@ -16,8 +16,8 @@ export interface ClaudeMessage {
|
||||
text?: string
|
||||
}
|
||||
|
||||
export type ClaudeAsk = "request_limit_reached" | "followup" | "command" | "completion_result"
|
||||
export type ClaudeSay = "task" | "error" | "api_req_started" | "api_req_finished" | "text" | "tool" | "command_output" | "completion_result"
|
||||
export type ClaudeAsk = "request_limit_reached" | "followup" | "command" | "completion_result" | "tool"
|
||||
export type ClaudeSay = "task" | "error" | "api_req_started" | "api_req_finished" | "text" | "command_output" | "completion_result"
|
||||
|
||||
export interface ClaudeSayTool {
|
||||
tool: "editedExistingFile" | "newFileCreated" | "readFile" | "listFiles"
|
||||
|
||||
@@ -39,7 +39,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
|
||||
<span
|
||||
className="codicon codicon-terminal"
|
||||
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>,
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>Command</span>,
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>Claude wants to execute this command:</span>,
|
||||
]
|
||||
case "completion_result":
|
||||
return [
|
||||
@@ -113,57 +113,6 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
|
||||
)
|
||||
case "api_req_finished":
|
||||
return null // Hide this message type
|
||||
case "tool":
|
||||
const tool = JSON.parse(message.text || "{}") as ClaudeSayTool
|
||||
const toolIcon = (name: string) => (
|
||||
<span
|
||||
className={`codicon codicon-${name}`}
|
||||
style={{ color: "var(--vscode-foreground)", marginBottom: "-1.5px" }}></span>
|
||||
)
|
||||
|
||||
switch (tool.tool) {
|
||||
case "editedExistingFile":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("edit")}
|
||||
Edited file...
|
||||
</div>
|
||||
<CodeBlock diff={tool.diff!} path={tool.path!} />
|
||||
</>
|
||||
)
|
||||
case "newFileCreated":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("new-file")}
|
||||
Created new file...
|
||||
</div>
|
||||
<CodeBlock code={tool.content!} path={tool.path!} />
|
||||
</>
|
||||
)
|
||||
case "readFile":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("file-code")}
|
||||
Read file...
|
||||
</div>
|
||||
<CodeBlock code={tool.content!} path={tool.path!} />
|
||||
</>
|
||||
)
|
||||
case "listFiles":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("folder-opened")}
|
||||
Viewed contents of directory...
|
||||
</div>
|
||||
<CodeBlock code={tool.content!} path={tool.path!} language="shell-session" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
break
|
||||
case "text":
|
||||
return <p style={contentStyle}>{message.text}</p>
|
||||
case "error":
|
||||
@@ -205,9 +154,59 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
|
||||
</>
|
||||
)
|
||||
}
|
||||
break
|
||||
case "ask":
|
||||
switch (message.ask) {
|
||||
case "tool":
|
||||
const tool = JSON.parse(message.text || "{}") as ClaudeSayTool
|
||||
const toolIcon = (name: string) => (
|
||||
<span
|
||||
className={`codicon codicon-${name}`}
|
||||
style={{ color: "var(--vscode-foreground)", marginBottom: "-1.5px" }}></span>
|
||||
)
|
||||
|
||||
switch (tool.tool) {
|
||||
case "editedExistingFile":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("edit")}
|
||||
<span style={{ fontWeight: "bold" }}>Claude wants to edit this file:</span>
|
||||
</div>
|
||||
<CodeBlock diff={tool.diff!} path={tool.path!} />
|
||||
</>
|
||||
)
|
||||
case "newFileCreated":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("new-file")}
|
||||
<span style={{ fontWeight: "bold" }}>Claude wants to create a new file:</span>
|
||||
</div>
|
||||
<CodeBlock code={tool.content!} path={tool.path!} />
|
||||
</>
|
||||
)
|
||||
case "readFile":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("file-code")}
|
||||
<span style={{ fontWeight: "bold" }}>Claude wants to read this file:</span>
|
||||
</div>
|
||||
<CodeBlock code={tool.content!} path={tool.path!} />
|
||||
</>
|
||||
)
|
||||
case "listFiles":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("folder-opened")}
|
||||
<span style={{ fontWeight: "bold" }}>Claude wants to view this directory:</span>
|
||||
</div>
|
||||
<CodeBlock code={tool.content!} path={tool.path!} language="shell-session" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
break
|
||||
case "request_limit_reached":
|
||||
return (
|
||||
<>
|
||||
@@ -240,11 +239,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
|
||||
{title}
|
||||
</div>
|
||||
<div style={contentStyle}>
|
||||
<p style={contentStyle}>
|
||||
Claude Dev wants to execute the following terminal command. Would you like to
|
||||
proceed?
|
||||
</p>
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
<div>
|
||||
<CodeBlock code={command} language="shell-session" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -82,11 +82,17 @@ const ChatView = ({ messages }: ChatViewProps) => {
|
||||
setPrimaryButtonText(undefined)
|
||||
setSecondaryButtonText(undefined)
|
||||
break
|
||||
case "tool":
|
||||
setTextAreaDisabled(true)
|
||||
setClaudeAsk("tool")
|
||||
setPrimaryButtonText("Approve")
|
||||
setSecondaryButtonText("Cancel")
|
||||
break
|
||||
case "command":
|
||||
setTextAreaDisabled(true)
|
||||
setClaudeAsk("command")
|
||||
setPrimaryButtonText("Yes")
|
||||
setSecondaryButtonText("No")
|
||||
setPrimaryButtonText("Run Command")
|
||||
setSecondaryButtonText("Cancel")
|
||||
break
|
||||
case "completion_result":
|
||||
// extension waiting for feedback. but we can just present a new task button
|
||||
@@ -110,8 +116,6 @@ const ChatView = ({ messages }: ChatViewProps) => {
|
||||
break
|
||||
case "text":
|
||||
break
|
||||
case "tool":
|
||||
break
|
||||
case "command_output":
|
||||
break
|
||||
case "completion_result":
|
||||
@@ -166,9 +170,8 @@ const ChatView = ({ messages }: ChatViewProps) => {
|
||||
const handlePrimaryButtonClick = () => {
|
||||
switch (claudeAsk) {
|
||||
case "request_limit_reached":
|
||||
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonTapped" })
|
||||
break
|
||||
case "command":
|
||||
case "tool":
|
||||
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonTapped" })
|
||||
break
|
||||
case "completion_result":
|
||||
@@ -185,6 +188,7 @@ const ChatView = ({ messages }: ChatViewProps) => {
|
||||
const handleSecondaryButtonClick = () => {
|
||||
switch (claudeAsk) {
|
||||
case "request_limit_reached":
|
||||
case "tool": // TODO: for now when a user cancels, it starts a new task. But we could easily just respond to the API with a "This operation failed" and let it try again.
|
||||
startNewTask()
|
||||
break
|
||||
case "command":
|
||||
@@ -267,7 +271,7 @@ const ChatView = ({ messages }: ChatViewProps) => {
|
||||
<div style={{ padding: "0 25px" }}>
|
||||
<h2>What can I do for you?</h2>
|
||||
<p>
|
||||
{/*prettier-ignore*/}
|
||||
{/* prettier-ignore */}
|
||||
Thanks to <VSCodeLink href="https://www-cdn.anthropic.com/fed9cc193a14b84131812372d8d5857f8f304c52/Model_Card_Claude_3_Addendum.pdf" style={{ display: "inline" }}>Claude 3.5 Sonnet's agentic coding capabilities</VSCodeLink>, I can handle complex software development tasks step-by-step. With tools that let me read & write files, create entire projects from scratch, and execute terminal commands (after you grant permission), I can assist you in ways that go beyond simple code completion or tech support.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -29,8 +29,8 @@ export const mockMessages: ClaudeMessage[] = [
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 3200000,
|
||||
type: "say",
|
||||
say: "tool",
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "newFileCreated",
|
||||
path: "/src/components/TodoList.tsx",
|
||||
@@ -121,8 +121,8 @@ export const mockMessages: ClaudeMessage[] = [
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 2600000,
|
||||
type: "say",
|
||||
say: "tool",
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "editedExistingFile",
|
||||
path: "/src/components/TodoList.tsx",
|
||||
@@ -238,8 +238,8 @@ export const mockMessages: ClaudeMessage[] = [
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 1700000,
|
||||
type: "say",
|
||||
say: "tool",
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "newFileCreated",
|
||||
path: "/src/app.js",
|
||||
@@ -313,8 +313,8 @@ export const mockMessages: ClaudeMessage[] = [
|
||||
},
|
||||
{
|
||||
ts: Date.now() - 1000000,
|
||||
type: "say",
|
||||
say: "tool",
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "editedExistingFile",
|
||||
path: "/src/app.js",
|
||||
|
||||
Reference in New Issue
Block a user