Skip to content

Commit 409d67c

Browse files
authored
Merge branch 'RooVetGit:main' into feature/textToSpeech
2 parents be9e57e + 85dd1a1 commit 409d67c

File tree

22 files changed

+252
-55
lines changed

22 files changed

+252
-55
lines changed

.changeset/wild-dragons-leave.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Roo Code Changelog
22

3+
## [3.8.3] - 2025-03-09
4+
5+
- Fix VS Code LM API model picker truncation issue
6+
7+
## [3.8.2] - 2025-03-08
8+
9+
- Create an auto-approval toggle for subtask creation and completion (thanks @shaybc!)
10+
- Show a progress indicator when using the multi-diff editing strategy (thanks @qdaxb!)
11+
- Add o3-mini support to the OpenAI-compatible provider (thanks @yt3trees!)
12+
- Fix encoding issue where unreadable characters were sometimes getting added to the beginning of files
13+
- Fix issue where settings dropdowns were getting truncated in some cases
14+
315
## [3.8.1] - 2025-03-07
416

517
- Show the reserved output tokens in the context window visualization

jest.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ module.exports = {
3030
"^strip-ansi$": "<rootDir>/src/__mocks__/strip-ansi.js",
3131
"^default-shell$": "<rootDir>/src/__mocks__/default-shell.js",
3232
"^os-name$": "<rootDir>/src/__mocks__/os-name.js",
33+
"^strip-bom$": "<rootDir>/src/__mocks__/strip-bom.js",
3334
},
3435
transformIgnorePatterns: [
35-
"node_modules/(?!(@modelcontextprotocol|delay|p-wait-for|globby|serialize-error|strip-ansi|default-shell|os-name)/)",
36+
"node_modules/(?!(@modelcontextprotocol|delay|p-wait-for|globby|serialize-error|strip-ansi|default-shell|os-name|strip-bom)/)",
3637
],
3738
roots: ["<rootDir>/src", "<rootDir>/webview-ui/src"],
3839
modulePathIgnorePatterns: [".vscode-test"],

package-lock.json

Lines changed: 19 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Roo Code (prev. Roo Cline)",
44
"description": "A whole dev team of AI agents in your editor.",
55
"publisher": "RooVeterinaryInc",
6-
"version": "3.8.1",
6+
"version": "3.8.3",
77
"icon": "assets/icons/rocket.png",
88
"galleryBanner": {
99
"color": "#617A91",
@@ -265,8 +265,8 @@
265265
"@anthropic-ai/sdk": "^0.37.0",
266266
"@anthropic-ai/vertex-sdk": "^0.7.0",
267267
"@aws-sdk/client-bedrock-runtime": "^3.706.0",
268-
"@google/generative-ai": "^0.18.0",
269268
"@google-cloud/vertexai": "^1.9.3",
269+
"@google/generative-ai": "^0.18.0",
270270
"@mistralai/mistralai": "^1.3.6",
271271
"@modelcontextprotocol/sdk": "^1.0.1",
272272
"@types/clone-deep": "^4.0.4",
@@ -305,6 +305,7 @@
305305
"sound-play": "^1.1.0",
306306
"string-similarity": "^4.0.4",
307307
"strip-ansi": "^7.1.0",
308+
"strip-bom": "^5.0.0",
308309
"tmp": "^0.2.3",
309310
"tree-sitter-wasms": "^0.1.11",
310311
"turndown": "^7.2.0",

src/__mocks__/strip-bom.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Mock implementation of strip-bom
2+
module.exports = function stripBom(string) {
3+
if (typeof string !== "string") {
4+
throw new TypeError("Expected a string")
5+
}
6+
7+
// Removes UTF-8 BOM
8+
if (string.charCodeAt(0) === 0xfeff) {
9+
return string.slice(1)
10+
}
11+
12+
return string
13+
}

src/core/Cline.ts

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {
4848
ClineSay,
4949
ClineSayBrowserAction,
5050
ClineSayTool,
51+
ToolProgressStatus,
5152
} from "../shared/ExtensionMessage"
5253
import { getApiMetrics } from "../shared/getApiMetrics"
5354
import { HistoryItem } from "../shared/HistoryItem"
@@ -408,6 +409,7 @@ export class Cline {
408409
type: ClineAsk,
409410
text?: string,
410411
partial?: boolean,
412+
progressStatus?: ToolProgressStatus,
411413
): Promise<{ response: ClineAskResponse; text?: string; images?: string[] }> {
412414
// 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.)
413415
if (this.abort) {
@@ -423,6 +425,7 @@ export class Cline {
423425
// existing partial message, so update it
424426
lastMessage.text = text
425427
lastMessage.partial = partial
428+
lastMessage.progressStatus = progressStatus
426429
// 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
427430
// await this.saveClineMessages()
428431
// await this.providerRef.deref()?.postStateToWebview()
@@ -460,6 +463,8 @@ export class Cline {
460463
// lastMessage.ts = askTs
461464
lastMessage.text = text
462465
lastMessage.partial = false
466+
lastMessage.progressStatus = progressStatus
467+
463468
await this.saveClineMessages()
464469
// await this.providerRef.deref()?.postStateToWebview()
465470
await this.providerRef
@@ -511,6 +516,7 @@ export class Cline {
511516
images?: string[],
512517
partial?: boolean,
513518
checkpoint?: Record<string, unknown>,
519+
progressStatus?: ToolProgressStatus,
514520
): Promise<undefined> {
515521
if (this.abort) {
516522
throw new Error(`Task: ${this.taskNumber} Roo Code instance aborted (#2)`)
@@ -526,6 +532,7 @@ export class Cline {
526532
lastMessage.text = text
527533
lastMessage.images = images
528534
lastMessage.partial = partial
535+
lastMessage.progressStatus = progressStatus
529536
await this.providerRef
530537
.deref()
531538
?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
@@ -545,6 +552,7 @@ export class Cline {
545552
lastMessage.text = text
546553
lastMessage.images = images
547554
lastMessage.partial = false
555+
lastMessage.progressStatus = progressStatus
548556

549557
// instead of streaming partialMessage events, we do a save and post like normal to persist to disk
550558
await this.saveClineMessages()
@@ -1417,6 +1425,18 @@ export class Cline {
14171425
return true
14181426
}
14191427

1428+
const askFinishSubTaskApproval = async () => {
1429+
// ask the user to approve this task has completed, and he has reviewd it, and we can declare task is finished
1430+
// and return control to the parent task to continue running the rest of the sub-tasks
1431+
const toolMessage = JSON.stringify({
1432+
tool: "finishTask",
1433+
content:
1434+
"Subtask completed! You can review the results and suggest any corrections or next steps. If everything looks good, confirm to return the result to the parent task.",
1435+
})
1436+
1437+
return await askApproval("tool", toolMessage)
1438+
}
1439+
14201440
const handleError = async (action: string, error: Error) => {
14211441
const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`
14221442
await this.say(
@@ -1691,8 +1711,16 @@ export class Cline {
16911711
try {
16921712
if (block.partial) {
16931713
// update gui message
1714+
let toolProgressStatus
1715+
if (this.diffStrategy && this.diffStrategy.getProgressStatus) {
1716+
toolProgressStatus = this.diffStrategy.getProgressStatus(block)
1717+
}
1718+
16941719
const partialMessage = JSON.stringify(sharedMessageProps)
1695-
await this.ask("tool", partialMessage, block.partial).catch(() => {})
1720+
1721+
await this.ask("tool", partialMessage, block.partial, toolProgressStatus).catch(
1722+
() => {},
1723+
)
16961724
break
16971725
} else {
16981726
if (!relPath) {
@@ -1787,6 +1815,14 @@ export class Cline {
17871815
diff: diffContent,
17881816
} satisfies ClineSayTool)
17891817

1818+
let toolProgressStatus
1819+
if (this.diffStrategy && this.diffStrategy.getProgressStatus) {
1820+
toolProgressStatus = this.diffStrategy.getProgressStatus(block, diffResult)
1821+
}
1822+
await this.ask("tool", completeMessage, block.partial, toolProgressStatus).catch(
1823+
() => {},
1824+
)
1825+
17901826
const didApprove = await askApproval("tool", completeMessage)
17911827
if (!didApprove) {
17921828
await this.diffViewProvider.revertChanges() // This likely handles closing the diff view
@@ -2945,13 +2981,6 @@ export class Cline {
29452981
// havent sent a command message yet so first send completion_result then command
29462982
await this.say("completion_result", result, undefined, false)
29472983
telemetryService.captureTaskCompleted(this.taskId)
2948-
if (this.isSubTask) {
2949-
// tell the provider to remove the current subtask and resume the previous task in the stack
2950-
await this.providerRef
2951-
.deref()
2952-
?.finishSubTask(`Task complete: ${lastMessage?.text}`)
2953-
break
2954-
}
29552984
}
29562985

29572986
// complete command message
@@ -2970,13 +2999,17 @@ export class Cline {
29702999
} else {
29713000
await this.say("completion_result", result, undefined, false)
29723001
telemetryService.captureTaskCompleted(this.taskId)
2973-
if (this.isSubTask) {
2974-
// tell the provider to remove the current subtask and resume the previous task in the stack
2975-
await this.providerRef
2976-
.deref()
2977-
?.finishSubTask(`Task complete: ${lastMessage?.text}`)
3002+
}
3003+
3004+
if (this.isSubTask) {
3005+
const didApprove = await askFinishSubTaskApproval()
3006+
if (!didApprove) {
29783007
break
29793008
}
3009+
3010+
// tell the provider to remove the current subtask and resume the previous task in the stack
3011+
await this.providerRef.deref()?.finishSubTask(`Task complete: ${lastMessage?.text}`)
3012+
break
29803013
}
29813014

29823015
// we already sent completion_result says, an empty string asks relinquishes control over button and field

src/core/diff/strategies/multi-search-replace.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { DiffStrategy, DiffResult } from "../types"
22
import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text"
33
import { distance } from "fastest-levenshtein"
4+
import { ToolProgressStatus } from "../../../shared/ExtensionMessage"
5+
import { ToolUse } from "../../assistant-message"
46

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

@@ -362,4 +364,27 @@ Only use a single line of '=======' between search and replacement content, beca
362364
failParts: diffResults,
363365
}
364366
}
367+
368+
getProgressStatus(toolUse: ToolUse, result?: DiffResult): ToolProgressStatus {
369+
const diffContent = toolUse.params.diff
370+
if (diffContent) {
371+
const icon = "diff-multiple"
372+
const searchBlockCount = (diffContent.match(/SEARCH/g) || []).length
373+
if (toolUse.partial) {
374+
if (diffContent.length < 1000 || (diffContent.length / 50) % 10 === 0) {
375+
return { icon, text: `${searchBlockCount}` }
376+
}
377+
} else if (result) {
378+
if (result.failParts?.length) {
379+
return {
380+
icon,
381+
text: `${searchBlockCount - result.failParts.length}/${searchBlockCount}`,
382+
}
383+
} else {
384+
return { icon, text: `${searchBlockCount}` }
385+
}
386+
}
387+
}
388+
return {}
389+
}
365390
}

src/core/diff/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
* Interface for implementing different diff strategies
33
*/
44

5+
import { ToolProgressStatus } from "../../shared/ExtensionMessage"
6+
import { ToolUse } from "../assistant-message"
7+
58
export type DiffResult =
69
| { success: true; content: string; failParts?: DiffResult[] }
710
| ({
@@ -34,4 +37,6 @@ export interface DiffStrategy {
3437
* @returns A DiffResult object containing either the successful result or error details
3538
*/
3639
applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): Promise<DiffResult>
40+
41+
getProgressStatus?(toolUse: ToolUse, result?: any): ToolProgressStatus
3742
}

src/core/webview/ClineProvider.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
990990
await this.updateGlobalState("alwaysAllowModeSwitch", message.bool)
991991
await this.postStateToWebview()
992992
break
993+
case "alwaysAllowSubtasks":
994+
await this.updateGlobalState("alwaysAllowSubtasks", message.bool)
995+
await this.postStateToWebview()
996+
break
993997
case "askResponse":
994998
this.getCurrentCline()?.handleWebviewAskResponse(
995999
message.askResponse!,
@@ -999,9 +1003,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
9991003
break
10001004
case "clearTask":
10011005
// clear task resets the current session and allows for a new task to be started, if this session is a subtask - it allows the parent task to be resumed
1002-
await this.finishSubTask(
1003-
`new_task finished with an error!, it was stopped and canceled by the user.`,
1004-
)
1006+
await this.finishSubTask(`Task error: It was stopped and canceled by the user.`)
10051007
await this.postStateToWebview()
10061008
break
10071009
case "didShowAnnouncement":
@@ -2200,6 +2202,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
22002202
alwaysAllowBrowser,
22012203
alwaysAllowMcp,
22022204
alwaysAllowModeSwitch,
2205+
alwaysAllowSubtasks,
22032206
soundEnabled,
22042207
ttsEnabled,
22052208
ttsSpeed,
@@ -2249,6 +2252,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
22492252
alwaysAllowBrowser: alwaysAllowBrowser ?? false,
22502253
alwaysAllowMcp: alwaysAllowMcp ?? false,
22512254
alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
2255+
alwaysAllowSubtasks: alwaysAllowSubtasks ?? false,
22522256
uriScheme: vscode.env.uriScheme,
22532257
currentTaskItem: this.getCurrentCline()?.taskId
22542258
? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId)
@@ -2412,6 +2416,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
24122416
alwaysAllowBrowser: stateValues.alwaysAllowBrowser ?? false,
24132417
alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false,
24142418
alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false,
2419+
alwaysAllowSubtasks: stateValues.alwaysAllowSubtasks ?? false,
24152420
taskHistory: stateValues.taskHistory,
24162421
allowedCommands: stateValues.allowedCommands,
24172422
soundEnabled: stateValues.soundEnabled ?? false,

0 commit comments

Comments
 (0)