mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 20:31:37 -05:00
Refactor potentially relevant details; fix terminal output processing
This commit is contained in:
109
src/ClaudeDev.ts
109
src/ClaudeDev.ts
@@ -257,7 +257,6 @@ export class ClaudeDev {
|
|||||||
private askResponseImages?: string[]
|
private askResponseImages?: string[]
|
||||||
private lastMessageTs?: number
|
private lastMessageTs?: number
|
||||||
private consecutiveMistakeCount: number = 0
|
private consecutiveMistakeCount: number = 0
|
||||||
private shouldSkipNextApiReqStartedMessage = false
|
|
||||||
private providerRef: WeakRef<ClaudeDevProvider>
|
private providerRef: WeakRef<ClaudeDevProvider>
|
||||||
private abort: boolean = false
|
private abort: boolean = false
|
||||||
|
|
||||||
@@ -445,30 +444,14 @@ export class ClaudeDev {
|
|||||||
|
|
||||||
await this.say("text", task, images)
|
await this.say("text", task, images)
|
||||||
|
|
||||||
// getting verbose details is an expensive operation, it uses globby to top-down build file structure of project which for large projects can take a few seconds
|
|
||||||
// for the best UX we show a loading spinner as this happens
|
|
||||||
const taskText = `<task>\n${task}\n</task>`
|
|
||||||
let imageBlocks: Anthropic.ImageBlockParam[] = this.formatImagesIntoBlocks(images)
|
let imageBlocks: Anthropic.ImageBlockParam[] = this.formatImagesIntoBlocks(images)
|
||||||
await this.say(
|
await this.initiateTaskLoop([
|
||||||
"api_req_started",
|
{
|
||||||
JSON.stringify({
|
type: "text",
|
||||||
request: `${taskText}\n\n<potentially_relevant_details>\nLoading...\n</potentially_relevant_details>`,
|
text: `<task>\n${task}\n</task>`,
|
||||||
})
|
},
|
||||||
)
|
...imageBlocks,
|
||||||
this.shouldSkipNextApiReqStartedMessage = true
|
])
|
||||||
this.getInitialDetails().then(async (initialDetails) => {
|
|
||||||
const lastApiReqIndex = findLastIndex(this.claudeMessages, (m) => m.say === "api_req_started")
|
|
||||||
this.claudeMessages[lastApiReqIndex].text = JSON.stringify({ request: `${taskText}\n\n${initialDetails}` })
|
|
||||||
await this.saveClaudeMessages()
|
|
||||||
await this.providerRef.deref()?.postStateToWebview()
|
|
||||||
await this.initiateTaskLoop([
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `${taskText}\n\n${initialDetails}`, // cannot be sent with system prompt since it's cached and these details can change
|
|
||||||
},
|
|
||||||
...imageBlocks,
|
|
||||||
])
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async resumeTaskFromHistory() {
|
private async resumeTaskFromHistory() {
|
||||||
@@ -647,10 +630,10 @@ export class ClaudeDev {
|
|||||||
const combinedText =
|
const combinedText =
|
||||||
`Task resumption: This autonomous coding task was interrupted ${agoText}. It may or may not be complete, so please reassess the task context. Be aware that the project state may have changed since then. The current working directory is now ${cwd}. If the task has not been completed, retry the last step before interruption and proceed with completing the task.` +
|
`Task resumption: This autonomous coding task was interrupted ${agoText}. It may or may not be complete, so please reassess the task context. Be aware that the project state may have changed since then. The current working directory is now ${cwd}. If the task has not been completed, retry the last step before interruption and proceed with completing the task.` +
|
||||||
(modifiedOldUserContentText
|
(modifiedOldUserContentText
|
||||||
? `\n\nLast recorded user input before interruption:\n<previous_message>\n${modifiedOldUserContentText}\n</previous_message>\n`
|
? `\n\nLast recorded user input before interruption:\n<previous_message>\n${modifiedOldUserContentText}\n</previous_message>`
|
||||||
: "") +
|
: "") +
|
||||||
(newUserContentText
|
(newUserContentText
|
||||||
? `\n\nNew instructions for task continuation:\n<user_message>\n${newUserContentText}\n</user_message>\n`
|
? `\n\nNew instructions for task continuation:\n<user_message>\n${newUserContentText}\n</user_message>`
|
||||||
: "")
|
: "")
|
||||||
|
|
||||||
const newUserContentImages = newUserContent.filter((block) => block.type === "image")
|
const newUserContentImages = newUserContent.filter((block) => block.type === "image")
|
||||||
@@ -664,9 +647,10 @@ export class ClaudeDev {
|
|||||||
|
|
||||||
private async initiateTaskLoop(userContent: UserContent): Promise<void> {
|
private async initiateTaskLoop(userContent: UserContent): Promise<void> {
|
||||||
let nextUserContent = userContent
|
let nextUserContent = userContent
|
||||||
|
let includeFileDetails = true
|
||||||
while (!this.abort) {
|
while (!this.abort) {
|
||||||
const { didEndLoop } = await this.recursivelyMakeClaudeRequests(nextUserContent)
|
const { didEndLoop } = await this.recursivelyMakeClaudeRequests(nextUserContent, includeFileDetails)
|
||||||
|
includeFileDetails = false // we only need file details the first time
|
||||||
|
|
||||||
// The way this agentic loop works is that claude will be given a task that he then calls tools to complete. unless there's an attempt_completion call, we keep responding back to him with his tool's responses until he either attempt_completion or does not use anymore tools. If he does not use anymore tools, we ask him to consider if he's completed the task and then call attempt_completion, otherwise proceed with completing the task.
|
// The way this agentic loop works is that claude will be given a task that he then calls tools to complete. unless there's an attempt_completion call, we keep responding back to him with his tool's responses until he either attempt_completion or does not use anymore tools. If he does not use anymore tools, we ask him to consider if he's completed the task and then call attempt_completion, otherwise proceed with completing the task.
|
||||||
// There is a MAX_REQUESTS_PER_TASK limit to prevent infinite requests, but Claude is prompted to finish the task as efficiently as he can.
|
// There is a MAX_REQUESTS_PER_TASK limit to prevent infinite requests, but Claude is prompted to finish the task as efficiently as he can.
|
||||||
@@ -1371,7 +1355,6 @@ export class ClaudeDev {
|
|||||||
|
|
||||||
let result = ""
|
let result = ""
|
||||||
process.on("line", (line) => {
|
process.on("line", (line) => {
|
||||||
console.log("New line from process:", line)
|
|
||||||
result += line
|
result += line
|
||||||
sendCommandOutput(line)
|
sendCommandOutput(line)
|
||||||
})
|
})
|
||||||
@@ -1526,7 +1509,10 @@ ${this.customInstructions.trim()}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async recursivelyMakeClaudeRequests(userContent: UserContent): Promise<ClaudeRequestResult> {
|
async recursivelyMakeClaudeRequests(
|
||||||
|
userContent: UserContent,
|
||||||
|
includeFileDetails: boolean = false
|
||||||
|
): Promise<ClaudeRequestResult> {
|
||||||
if (this.abort) {
|
if (this.abort) {
|
||||||
throw new Error("ClaudeDev instance aborted")
|
throw new Error("ClaudeDev instance aborted")
|
||||||
}
|
}
|
||||||
@@ -1552,19 +1538,33 @@ ${this.customInstructions.trim()}
|
|||||||
this.consecutiveMistakeCount = 0
|
this.consecutiveMistakeCount = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getting verbose details is an expensive operation, it uses globby to top-down build file structure of project which for large projects can take a few seconds
|
||||||
|
// for the best UX we show a placeholder api_req_started message with a loading spinner as this happens
|
||||||
|
await this.say(
|
||||||
|
"api_req_started",
|
||||||
|
JSON.stringify({
|
||||||
|
request:
|
||||||
|
userContent.map(formatContentBlockToMarkdown).join("\n\n") +
|
||||||
|
"\n\n<potentially_relevant_details>\nLoading...\n</potentially_relevant_details>",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// potentially expensive operation
|
||||||
|
const potentiallyRelevantDetails = await this.getPotentiallyRelevantDetails(includeFileDetails)
|
||||||
|
|
||||||
// add potentially relevant details as its own text block, separate from tool results
|
// add potentially relevant details as its own text block, separate from tool results
|
||||||
userContent.push({ type: "text", text: await this.getPotentiallyRelevantDetails() })
|
userContent.push({ type: "text", text: potentiallyRelevantDetails })
|
||||||
|
|
||||||
await this.addToApiConversationHistory({ role: "user", content: userContent })
|
await this.addToApiConversationHistory({ role: "user", content: userContent })
|
||||||
|
|
||||||
if (!this.shouldSkipNextApiReqStartedMessage) {
|
// since we sent off a placeholder api_req_started message to update the webview while waiting to actually start the API request (to load potential details for example), we need to update the text of that message
|
||||||
await this.say(
|
const lastApiReqIndex = findLastIndex(this.claudeMessages, (m) => m.say === "api_req_started")
|
||||||
"api_req_started",
|
this.claudeMessages[lastApiReqIndex].text = JSON.stringify({
|
||||||
JSON.stringify({ request: userContent.map(formatContentBlockToMarkdown).join("\n\n") })
|
request: userContent.map(formatContentBlockToMarkdown).join("\n\n"),
|
||||||
)
|
})
|
||||||
} else {
|
await this.saveClaudeMessages()
|
||||||
this.shouldSkipNextApiReqStartedMessage = false
|
await this.providerRef.deref()?.postStateToWebview()
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await this.attemptApiRequest()
|
const response = await this.attemptApiRequest()
|
||||||
|
|
||||||
@@ -1718,23 +1718,7 @@ ${this.customInstructions.trim()}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getInitialDetails() {
|
async getPotentiallyRelevantDetails(includeFileDetails: boolean = false) {
|
||||||
let details = "<potentially_relevant_details>"
|
|
||||||
|
|
||||||
const isDesktop = cwd === path.join(os.homedir(), "Desktop")
|
|
||||||
const files = await listFiles(cwd, !isDesktop)
|
|
||||||
const result = this.formatFilesList(cwd, files)
|
|
||||||
details += `\n# Current Working Directory ('${cwd}') Files${
|
|
||||||
isDesktop
|
|
||||||
? "\n(Desktop so only top-level contents shown for brevity, use list_files to explore further if necessary)"
|
|
||||||
: ""
|
|
||||||
}\n${result}\n`
|
|
||||||
|
|
||||||
details += "</potentially_relevant_details>"
|
|
||||||
return details
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPotentiallyRelevantDetails() {
|
|
||||||
let details = `<potentially_relevant_details>
|
let details = `<potentially_relevant_details>
|
||||||
# VSCode Visible Files
|
# VSCode Visible Files
|
||||||
${
|
${
|
||||||
@@ -1791,11 +1775,22 @@ ${
|
|||||||
if (newOutput) {
|
if (newOutput) {
|
||||||
details += `\n...\n${newOutput}`
|
details += `\n...\n${newOutput}`
|
||||||
} else {
|
} else {
|
||||||
details += `\n(Still running, no new output)`
|
// details += `\n(Still running, no new output)` // don't want to show this right after running the command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (includeFileDetails) {
|
||||||
|
const isDesktop = cwd === path.join(os.homedir(), "Desktop")
|
||||||
|
const files = await listFiles(cwd, !isDesktop)
|
||||||
|
const result = this.formatFilesList(cwd, files)
|
||||||
|
details += `\n\n# Current Working Directory ('${cwd}') Files${
|
||||||
|
isDesktop
|
||||||
|
? "\n(Desktop so only top-level contents shown for brevity, use list_files to explore further if necessary)"
|
||||||
|
: ""
|
||||||
|
}\n${result}`
|
||||||
|
}
|
||||||
|
|
||||||
details += "\n</potentially_relevant_details>"
|
details += "\n</potentially_relevant_details>"
|
||||||
return details
|
return details
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,13 +277,19 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
|||||||
let isFirstChunk = true
|
let isFirstChunk = true
|
||||||
let didOutputNonCommand = false
|
let didOutputNonCommand = false
|
||||||
for await (let data of stream) {
|
for await (let data of stream) {
|
||||||
|
console.log("original chunk:", data)
|
||||||
if (isFirstChunk) {
|
if (isFirstChunk) {
|
||||||
/*
|
/*
|
||||||
The first chunk we get from this stream needs to be processed to be more human readable, ie remove vscode's custom escape sequences and identifiers, removing duplicate first char bug, etc.
|
The first chunk we get from this stream needs to be processed to be more human readable, ie remove vscode's custom escape sequences and identifiers, removing duplicate first char bug, etc.
|
||||||
*/
|
*/
|
||||||
// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
|
// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
|
||||||
const vscodeSequenceRegex = /\x1b\]633;.[^\x07]*\x07/g
|
const vscodeSequenceRegex = /\x1b\]633;.[^\x07]*\x07/g
|
||||||
data = stripAnsi(data.replace(vscodeSequenceRegex, ""))
|
const lastMatch = [...data.matchAll(vscodeSequenceRegex)].pop()
|
||||||
|
if (lastMatch && lastMatch.index !== undefined) {
|
||||||
|
data = data.slice(lastMatch.index + lastMatch[0].length)
|
||||||
|
}
|
||||||
|
// remove ansi
|
||||||
|
data = stripAnsi(data)
|
||||||
// Split data by newlines
|
// Split data by newlines
|
||||||
let lines = data ? data.split("\n") : []
|
let lines = data ? data.split("\n") : []
|
||||||
// Remove non-human readable characters from the first line
|
// Remove non-human readable characters from the first line
|
||||||
@@ -320,7 +326,7 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
|||||||
data = lines.join("\n")
|
data = lines.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Received data chunk for terminal:`, data)
|
console.log(`parsed chunk:`, data)
|
||||||
this.fullOutput += data
|
this.fullOutput += data
|
||||||
if (this.isListening) {
|
if (this.isListening) {
|
||||||
console.log(`Emitting data for terminal`)
|
console.log(`Emitting data for terminal`)
|
||||||
|
|||||||
@@ -444,25 +444,29 @@ const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessa
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
flexDirection: "column",
|
||||||
backgroundColor: "rgba(255, 191, 0, 0.1)",
|
backgroundColor: "rgba(255, 191, 0, 0.1)",
|
||||||
padding: 8,
|
padding: 8,
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
}}>
|
}}>
|
||||||
<i
|
<div style={{ display: "flex", alignItems: "center", marginBottom: 4 }}>
|
||||||
className="codicon codicon-warning"
|
<i
|
||||||
style={{
|
className="codicon codicon-warning"
|
||||||
marginRight: 8,
|
style={{
|
||||||
fontSize: 18,
|
marginRight: 8,
|
||||||
color: "#FFA500",
|
fontSize: 18,
|
||||||
}}></i>
|
color: "#FFA500",
|
||||||
<span>
|
}}></i>
|
||||||
Shell integration is not available! Claude will not be able to see the output of the
|
<span style={{ fontWeight: 500, color: "#FFA500" }}>
|
||||||
command. Please update to the latest version of VSCode (
|
Shell Integration Unavailable
|
||||||
{"CMD/CTRL + Shift + P → Update"}) and ensure you are using one of the following
|
</span>
|
||||||
shells: bash, zsh, fish, or PowerShell.
|
</div>
|
||||||
</span>
|
<div>
|
||||||
|
Claude won't be able to view the command's output. Please update VSCode (CMD/CTRL +
|
||||||
|
Shift + P → Update) and make sure you're using a supported shell: bash, zsh, fish,
|
||||||
|
or PowerShell.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user