mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 20:31:37 -05:00
Send tools as says when alwaysAllowReadOnly
This commit is contained in:
116
src/ClaudeDev.ts
116
src/ClaudeDev.ts
@@ -877,17 +877,25 @@ export class ClaudeDev {
|
||||
try {
|
||||
const absolutePath = path.resolve(cwd, relPath)
|
||||
const content = await fs.readFile(absolutePath, "utf-8")
|
||||
const { response, text, images } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({ tool: "readFile", path: this.getReadablePath(relPath), content } as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "messageResponse") {
|
||||
await this.say("user_feedback", text, images)
|
||||
return this.formatIntoToolResponse(this.formatGenericToolFeedback(text), images)
|
||||
|
||||
const message = JSON.stringify({
|
||||
tool: "readFile",
|
||||
path: this.getReadablePath(relPath),
|
||||
content,
|
||||
} as ClaudeSayTool)
|
||||
if (this.alwaysAllowReadOnly) {
|
||||
await this.say("tool", message)
|
||||
} else {
|
||||
const { response, text, images } = await this.ask("tool", message)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "messageResponse") {
|
||||
await this.say("user_feedback", text, images)
|
||||
return this.formatIntoToolResponse(this.formatGenericToolFeedback(text), images)
|
||||
}
|
||||
return "The user denied this operation."
|
||||
}
|
||||
return "The user denied this operation."
|
||||
}
|
||||
|
||||
return content
|
||||
} catch (error) {
|
||||
const errorString = `Error reading file: ${JSON.stringify(serializeError(error))}`
|
||||
@@ -911,21 +919,25 @@ export class ClaudeDev {
|
||||
const absolutePath = path.resolve(cwd, relDirPath)
|
||||
const files = await listFiles(absolutePath, false)
|
||||
const result = this.formatFilesList(absolutePath, files)
|
||||
const { response, text, images } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({
|
||||
tool: "listFilesTopLevel",
|
||||
path: this.getReadablePath(relDirPath),
|
||||
content: result,
|
||||
} as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "messageResponse") {
|
||||
await this.say("user_feedback", text, images)
|
||||
return this.formatIntoToolResponse(this.formatGenericToolFeedback(text), images)
|
||||
|
||||
const message = JSON.stringify({
|
||||
tool: "listFilesTopLevel",
|
||||
path: this.getReadablePath(relDirPath),
|
||||
content: result,
|
||||
} as ClaudeSayTool)
|
||||
if (this.alwaysAllowReadOnly) {
|
||||
await this.say("tool", message)
|
||||
} else {
|
||||
const { response, text, images } = await this.ask("tool", message)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "messageResponse") {
|
||||
await this.say("user_feedback", text, images)
|
||||
return this.formatIntoToolResponse(this.formatGenericToolFeedback(text), images)
|
||||
}
|
||||
return "The user denied this operation."
|
||||
}
|
||||
return "The user denied this operation."
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
const errorString = `Error listing files and directories: ${JSON.stringify(serializeError(error))}`
|
||||
@@ -951,21 +963,25 @@ export class ClaudeDev {
|
||||
const absolutePath = path.resolve(cwd, relDirPath)
|
||||
const files = await listFiles(absolutePath, true)
|
||||
const result = this.formatFilesList(absolutePath, files)
|
||||
const { response, text, images } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({
|
||||
tool: "listFilesRecursive",
|
||||
path: this.getReadablePath(relDirPath),
|
||||
content: result,
|
||||
} as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "messageResponse") {
|
||||
await this.say("user_feedback", text, images)
|
||||
return this.formatIntoToolResponse(this.formatGenericToolFeedback(text), images)
|
||||
|
||||
const message = JSON.stringify({
|
||||
tool: "listFilesRecursive",
|
||||
path: this.getReadablePath(relDirPath),
|
||||
content: result,
|
||||
} as ClaudeSayTool)
|
||||
if (this.alwaysAllowReadOnly) {
|
||||
await this.say("tool", message)
|
||||
} else {
|
||||
const { response, text, images } = await this.ask("tool", message)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "messageResponse") {
|
||||
await this.say("user_feedback", text, images)
|
||||
return this.formatIntoToolResponse(this.formatGenericToolFeedback(text), images)
|
||||
}
|
||||
return "The user denied this operation."
|
||||
}
|
||||
return "The user denied this operation."
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
const errorString = `Error listing files recursively: ${JSON.stringify(serializeError(error))}`
|
||||
@@ -1037,21 +1053,25 @@ export class ClaudeDev {
|
||||
try {
|
||||
const absolutePath = path.resolve(cwd, relDirPath)
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel(absolutePath)
|
||||
const { response, text, images } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({
|
||||
tool: "viewSourceCodeDefinitionsTopLevel",
|
||||
path: this.getReadablePath(relDirPath),
|
||||
content: result,
|
||||
} as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "messageResponse") {
|
||||
await this.say("user_feedback", text, images)
|
||||
return this.formatIntoToolResponse(this.formatGenericToolFeedback(text), images)
|
||||
|
||||
const message = JSON.stringify({
|
||||
tool: "viewSourceCodeDefinitionsTopLevel",
|
||||
path: this.getReadablePath(relDirPath),
|
||||
content: result,
|
||||
} as ClaudeSayTool)
|
||||
if (this.alwaysAllowReadOnly) {
|
||||
await this.say("tool", message)
|
||||
} else {
|
||||
const { response, text, images } = await this.ask("tool", message)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "messageResponse") {
|
||||
await this.say("user_feedback", text, images)
|
||||
return this.formatIntoToolResponse(this.formatGenericToolFeedback(text), images)
|
||||
}
|
||||
return "The user denied this operation."
|
||||
}
|
||||
return "The user denied this operation."
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
const errorString = `Error parsing source code definitions: ${JSON.stringify(serializeError(error))}`
|
||||
|
||||
@@ -61,6 +61,7 @@ export type ClaudeSay =
|
||||
| "user_feedback"
|
||||
| "api_req_retried"
|
||||
| "command_output"
|
||||
| "tool"
|
||||
|
||||
export interface ClaudeSayTool {
|
||||
tool:
|
||||
|
||||
@@ -325,6 +325,8 @@ const ChatRow: React.FC<ChatRowProps> = ({
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
case "tool":
|
||||
return renderTool(message, headerStyle)
|
||||
default:
|
||||
return (
|
||||
<>
|
||||
@@ -341,123 +343,7 @@ const ChatRow: React.FC<ChatRowProps> = ({
|
||||
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!}
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
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!}
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
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!}
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case "listFilesTopLevel":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("folder-opened")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
Claude wants to view the top level files in this directory:
|
||||
</span>
|
||||
</div>
|
||||
<CodeBlock
|
||||
code={tool.content!}
|
||||
path={tool.path!}
|
||||
language="shell-session"
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case "listFilesRecursive":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("folder-opened")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
Claude wants to recursively view all files in this directory:
|
||||
</span>
|
||||
</div>
|
||||
<CodeBlock
|
||||
code={tool.content!}
|
||||
path={tool.path!}
|
||||
language="shell-session"
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case "viewSourceCodeDefinitionsTopLevel":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("file-code")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
Claude wants to view source code definitions in files at the top level
|
||||
of this directory:
|
||||
</span>
|
||||
</div>
|
||||
<CodeBlock
|
||||
code={tool.content!}
|
||||
path={tool.path!}
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
break
|
||||
return renderTool(message, headerStyle)
|
||||
case "request_limit_reached":
|
||||
return (
|
||||
<>
|
||||
@@ -547,6 +433,125 @@ const ChatRow: React.FC<ChatRowProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const renderTool = (message: ClaudeMessage, headerStyle: React.CSSProperties) => {
|
||||
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!}
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
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!}
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
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!}
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case "listFilesTopLevel":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("folder-opened")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
Claude wants to view the top level files in this directory:
|
||||
</span>
|
||||
</div>
|
||||
<CodeBlock
|
||||
code={tool.content!}
|
||||
path={tool.path!}
|
||||
language="shell-session"
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case "listFilesRecursive":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("folder-opened")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
Claude wants to recursively view all files in this directory:
|
||||
</span>
|
||||
</div>
|
||||
<CodeBlock
|
||||
code={tool.content!}
|
||||
path={tool.path!}
|
||||
language="shell-session"
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case "viewSourceCodeDefinitionsTopLevel":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("file-code")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
Claude wants to view source code definitions in files at the top level of this
|
||||
directory:
|
||||
</span>
|
||||
</div>
|
||||
<CodeBlock
|
||||
code={tool.content!}
|
||||
path={tool.path!}
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: we cannot return null as virtuoso does not support it, so we must use a separate visibleMessages array to filter out messages that should not be rendered
|
||||
|
||||
return (
|
||||
|
||||
@@ -165,10 +165,6 @@ const ChatView = ({
|
||||
case "say":
|
||||
// don't want to reset since there could be a "say" after an "ask" while ask is waiting for response
|
||||
switch (lastMessage.say) {
|
||||
case "task":
|
||||
break
|
||||
case "error":
|
||||
break
|
||||
case "api_req_started":
|
||||
if (messages.at(-2)?.ask === "command_output") {
|
||||
// if the last ask is a command_output, and we receive an api_req_started, then that means the command has finished and we don't need input from the user anymore (in every other case, the user has to interact with input field or buttons to continue, which does the following automatically)
|
||||
@@ -179,13 +175,13 @@ const ChatView = ({
|
||||
setEnableButtons(false)
|
||||
}
|
||||
break
|
||||
case "task":
|
||||
case "error":
|
||||
case "api_req_finished":
|
||||
break
|
||||
case "text":
|
||||
break
|
||||
case "command_output":
|
||||
break
|
||||
case "completion_result":
|
||||
case "tool":
|
||||
break
|
||||
}
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user