Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 29 additions & 4 deletions src/core/Cline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
ClineSay,
ClineSayBrowserAction,
ClineSayTool,
ToolProgressStatus,
} from "../shared/ExtensionMessage"
import { getApiMetrics } from "../shared/getApiMetrics"
import { HistoryItem } from "../shared/HistoryItem"
Expand Down Expand Up @@ -408,6 +409,7 @@ export class Cline {
type: ClineAsk,
text?: string,
partial?: boolean,
progressStatus?: ToolProgressStatus,
): Promise<{ response: ClineAskResponse; text?: string; images?: string[] }> {
// If this Cline instance was aborted by the provider, then the only thing keeping us alive is a promise still running in the background, in which case we don't want to send its result to the webview as it is attached to a new instance of Cline now. So we can safely ignore the result of any active promises, and this class will be deallocated. (Although we set Cline = undefined in provider, that simply removes the reference to this instance, but the instance is still alive until this promise resolves or rejects.)
if (this.abort) {
Expand All @@ -423,6 +425,7 @@ export class Cline {
// existing partial message, so update it
lastMessage.text = text
lastMessage.partial = partial
lastMessage.progressStatus = progressStatus
// todo be more efficient about saving and posting only new data or one whole message at a time so ignore partial for saves, and only post parts of partial message instead of whole array in new listener
// await this.saveClineMessages()
// await this.providerRef.deref()?.postStateToWebview()
Expand Down Expand Up @@ -460,6 +463,8 @@ export class Cline {
// lastMessage.ts = askTs
lastMessage.text = text
lastMessage.partial = false
lastMessage.progressStatus = progressStatus

await this.saveClineMessages()
// await this.providerRef.deref()?.postStateToWebview()
await this.providerRef
Expand Down Expand Up @@ -511,6 +516,7 @@ export class Cline {
images?: string[],
partial?: boolean,
checkpoint?: Record<string, unknown>,
progressStatus?: ToolProgressStatus,
): Promise<undefined> {
if (this.abort) {
throw new Error(`Task: ${this.taskNumber} Roo Code instance aborted (#2)`)
Expand All @@ -526,6 +532,7 @@ export class Cline {
lastMessage.text = text
lastMessage.images = images
lastMessage.partial = partial
lastMessage.progressStatus = progressStatus
await this.providerRef
.deref()
?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
Expand All @@ -545,6 +552,7 @@ export class Cline {
lastMessage.text = text
lastMessage.images = images
lastMessage.partial = false
lastMessage.progressStatus = progressStatus

// instead of streaming partialMessage events, we do a save and post like normal to persist to disk
await this.saveClineMessages()
Expand Down Expand Up @@ -1394,8 +1402,12 @@ export class Cline {
isCheckpointPossible = true
}

const askApproval = async (type: ClineAsk, partialMessage?: string) => {
const { response, text, images } = await this.ask(type, partialMessage, false)
const askApproval = async (
type: ClineAsk,
partialMessage?: string,
progressStatus?: ToolProgressStatus,
) => {
const { response, text, images } = await this.ask(type, partialMessage, false, progressStatus)
if (response !== "yesButtonClicked") {
// Handle both messageResponse and noButtonClicked with text
if (text) {
Expand Down Expand Up @@ -1703,8 +1715,16 @@ export class Cline {
try {
if (block.partial) {
// update gui message
let toolProgressStatus
if (this.diffStrategy && this.diffStrategy.getProgressStatus) {
toolProgressStatus = this.diffStrategy.getProgressStatus(block)
}

const partialMessage = JSON.stringify(sharedMessageProps)
await this.ask("tool", partialMessage, block.partial).catch(() => {})

await this.ask("tool", partialMessage, block.partial, toolProgressStatus).catch(
() => {},
)
break
} else {
if (!relPath) {
Expand Down Expand Up @@ -1799,7 +1819,12 @@ export class Cline {
diff: diffContent,
} satisfies ClineSayTool)

const didApprove = await askApproval("tool", completeMessage)
let toolProgressStatus
if (this.diffStrategy && this.diffStrategy.getProgressStatus) {
toolProgressStatus = this.diffStrategy.getProgressStatus(block, diffResult)
}

const didApprove = await askApproval("tool", completeMessage, toolProgressStatus)
if (!didApprove) {
await this.diffViewProvider.revertChanges() // This likely handles closing the diff view
break
Expand Down
25 changes: 25 additions & 0 deletions src/core/diff/strategies/multi-search-replace.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { DiffStrategy, DiffResult } from "../types"
import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text"
import { distance } from "fastest-levenshtein"
import { ToolProgressStatus } from "../../../shared/ExtensionMessage"
import { ToolUse } from "../../assistant-message"

const BUFFER_LINES = 40 // Number of extra context lines to show before and after matches

Expand Down Expand Up @@ -362,4 +364,27 @@ Only use a single line of '=======' between search and replacement content, beca
failParts: diffResults,
}
}

getProgressStatus(toolUse: ToolUse, result?: DiffResult): ToolProgressStatus {
const diffContent = toolUse.params.diff
if (diffContent) {
const icon = "diff-multiple"
const searchBlockCount = (diffContent.match(/SEARCH/g) || []).length
if (toolUse.partial) {
if (diffContent.length < 1000 || (diffContent.length / 50) % 10 === 0) {
return { icon, text: `${searchBlockCount}` }
}
} else if (result) {
if (result.failParts?.length) {
return {
icon,
text: `${searchBlockCount - result.failParts.length}/${searchBlockCount}`,
}
} else {
return { icon, text: `${searchBlockCount}` }
}
}
}
return {}
}
}
5 changes: 5 additions & 0 deletions src/core/diff/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
* Interface for implementing different diff strategies
*/

import { ToolProgressStatus } from "../../shared/ExtensionMessage"
import { ToolUse } from "../assistant-message"

export type DiffResult =
| { success: true; content: string; failParts?: DiffResult[] }
| ({
Expand Down Expand Up @@ -34,4 +37,6 @@ export interface DiffStrategy {
* @returns A DiffResult object containing either the successful result or error details
*/
applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): Promise<DiffResult>

getProgressStatus?(toolUse: ToolUse, result?: any): ToolProgressStatus
}
1 change: 1 addition & 0 deletions src/exports/roo-code.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export interface ClineMessage {
reasoning?: string
conversationHistoryIndex?: number
checkpoint?: Record<string, unknown>
progressStatus?: ToolProgressStatus
}

export interface ClineProvider {
Expand Down
5 changes: 5 additions & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,8 @@ export interface HumanRelayCancelMessage {
}

export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled"

export type ToolProgressStatus = {
icon?: string
text?: string
}
1 change: 1 addition & 0 deletions webview-ui/src/components/chat/ChatRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ export const ChatRowContent = ({
<span style={{ fontWeight: "bold" }}>Roo wants to edit this file:</span>
</div>
<CodeAccordian
progressStatus={message.progressStatus}
isLoading={message.partial}
diff={tool.diff!}
path={tool.path!}
Expand Down
11 changes: 11 additions & 0 deletions webview-ui/src/components/common/CodeAccordian.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { memo, useMemo } from "react"
import { getLanguageFromPath } from "../../utils/getLanguageFromPath"
import CodeBlock, { CODE_BLOCK_BG_COLOR } from "./CodeBlock"
import { ToolProgressStatus } from "../../../../src/shared/ExtensionMessage"

interface CodeAccordianProps {
code?: string
Expand All @@ -12,6 +13,7 @@ interface CodeAccordianProps {
isExpanded: boolean
onToggleExpand: () => void
isLoading?: boolean
progressStatus?: ToolProgressStatus
}

/*
Expand All @@ -32,6 +34,7 @@ const CodeAccordian = ({
isExpanded,
onToggleExpand,
isLoading,
progressStatus,
}: CodeAccordianProps) => {
const inferredLanguage = useMemo(
() => code && (language ?? (path ? getLanguageFromPath(path) : undefined)),
Expand Down Expand Up @@ -95,6 +98,14 @@ const CodeAccordian = ({
</>
)}
<div style={{ flexGrow: 1 }}></div>
{progressStatus && progressStatus.text && (
<>
{progressStatus.icon && <span className={`codicon codicon-${progressStatus.icon} mr-1`} />}
<span className="mr-1 ml-auto text-vscode-descriptionForeground">
{progressStatus.text}
</span>
</>
)}
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
</div>
)}
Expand Down
Loading