Skip to content

Commit c8ceb0d

Browse files
Merge pull request #4 from RooVetGit/main
rebase
2 parents 12adde0 + 75f4a6c commit c8ceb0d

File tree

22 files changed

+621
-310
lines changed

22 files changed

+621
-310
lines changed

.changeset/fair-boxes-smile.md

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

.changeset/orange-geese-provide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Allow users to clear out custom instructions for the built-in modes

CHANGELOG.md

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

3+
## [3.3.18]
4+
5+
- Add a per-API-configuration model temperature setting (thanks @joemanley201!)
6+
- Add retries for fetching usage stats from OpenRouter (thanks @jcbdev!)
7+
- Fix bug where disabled MCP servers would not show up in the settings on initialization (thanks @MuriloFP!)
8+
- Add the Requesty provider and clean up a lot of shared model picker code (thanks @samhvw8!)
9+
- Add a button on the Prompts tab to copy the full system prompt to the clipboard (thanks @mamertofabian!)
10+
- Fix issue where Ollama/LMStudio URLs would flicker back to previous while entering them in settings
11+
- Fix logic error where automatic retries were waiting twice as long as intended
12+
- Rework the checkpoints code to avoid conflicts with file locks on Windows (sorry for the hassle!)
13+
314
## [3.3.17]
415

516
- Fix the restore checkpoint popover

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Roo Code (prev. Roo Cline)",
44
"description": "A VS Code plugin that enhances coding with AI-powered automation, multi-model support, and experimental features.",
55
"publisher": "RooVeterinaryInc",
6-
"version": "3.3.17",
6+
"version": "3.3.18",
77
"icon": "assets/icons/rocket.png",
88
"galleryBanner": {
99
"color": "#617A91",

src/core/Cline.ts

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export class Cline {
143143
this.fuzzyMatchThreshold = fuzzyMatchThreshold ?? 1.0
144144
this.providerRef = new WeakRef(provider)
145145
this.diffViewProvider = new DiffViewProvider(cwd)
146-
this.checkpointsEnabled = process.platform !== "win32" && !!enableCheckpoints
146+
this.checkpointsEnabled = enableCheckpoints ?? false
147147

148148
if (historyItem) {
149149
this.taskId = historyItem.id
@@ -370,7 +370,13 @@ export class Cline {
370370
this.askResponseImages = images
371371
}
372372

373-
async say(type: ClineSay, text?: string, images?: string[], partial?: boolean): Promise<undefined> {
373+
async say(
374+
type: ClineSay,
375+
text?: string,
376+
images?: string[],
377+
partial?: boolean,
378+
checkpoint?: Record<string, unknown>,
379+
): Promise<undefined> {
374380
if (this.abort) {
375381
throw new Error("Roo Code instance aborted")
376382
}
@@ -423,7 +429,7 @@ export class Cline {
423429
// this is a new non-partial message, so add it like normal
424430
const sayTs = Date.now()
425431
this.lastMessageTs = sayTs
426-
await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images })
432+
await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, checkpoint })
427433
await this.providerRef.deref()?.postStateToWebview()
428434
}
429435
}
@@ -726,13 +732,18 @@ export class Cline {
726732
}
727733

728734
async abortTask() {
729-
this.abort = true // Will stop any autonomously running promises.
735+
// Will stop any autonomously running promises.
736+
this.abort = true
737+
730738
this.terminalManager.disposeAll()
731739
this.urlContentFetcher.closeBrowser()
732740
this.browserSession.closeBrowser()
733-
// Need to await for when we want to make sure directories/files are
734-
// reverted before re-starting the task from a checkpoint.
735-
await this.diffViewProvider.revertChanges()
741+
742+
// If we're not streaming then `abortStream` (which reverts the diff
743+
// view changes) won't be called, so we need to revert the changes here.
744+
if (this.isStreaming && this.diffViewProvider.isEditing) {
745+
await this.diffViewProvider.revertChanges()
746+
}
736747
}
737748

738749
// Tools
@@ -822,29 +833,21 @@ export class Cline {
822833
const { mcpEnabled, alwaysApproveResubmit, requestDelaySeconds, rateLimitSeconds } =
823834
(await this.providerRef.deref()?.getState()) ?? {}
824835

825-
let finalDelay = 0
836+
let rateLimitDelay = 0
826837

827838
// Only apply rate limiting if this isn't the first request
828839
if (this.lastApiRequestTime) {
829840
const now = Date.now()
830841
const timeSinceLastRequest = now - this.lastApiRequestTime
831842
const rateLimit = rateLimitSeconds || 0
832-
const rateLimitDelay = Math.max(0, rateLimit * 1000 - timeSinceLastRequest)
833-
finalDelay = rateLimitDelay
834-
}
835-
836-
// Add exponential backoff delay for retries
837-
if (retryAttempt > 0) {
838-
const baseDelay = requestDelaySeconds || 5
839-
const exponentialDelay = Math.ceil(baseDelay * Math.pow(2, retryAttempt)) * 1000
840-
finalDelay = Math.max(finalDelay, exponentialDelay)
843+
rateLimitDelay = Math.ceil(Math.max(0, rateLimit * 1000 - timeSinceLastRequest) / 1000)
841844
}
842845

843-
if (finalDelay > 0) {
846+
// Only show rate limiting message if we're not retrying. If retrying, we'll include the delay there.
847+
if (rateLimitDelay > 0 && retryAttempt === 0) {
844848
// Show countdown timer
845-
for (let i = Math.ceil(finalDelay / 1000); i > 0; i--) {
846-
const delayMessage =
847-
retryAttempt > 0 ? `Retrying in ${i} seconds...` : `Rate limiting for ${i} seconds...`
849+
for (let i = rateLimitDelay; i > 0; i--) {
850+
const delayMessage = `Rate limiting for ${i} seconds...`
848851
await this.say("api_req_retry_delayed", delayMessage, undefined, true)
849852
await delay(1000)
850853
}
@@ -959,9 +962,11 @@ export class Cline {
959962
const errorMsg = error.message ?? "Unknown error"
960963
const baseDelay = requestDelaySeconds || 5
961964
const exponentialDelay = Math.ceil(baseDelay * Math.pow(2, retryAttempt))
965+
// Wait for the greater of the exponential delay or the rate limit delay
966+
const finalDelay = Math.max(exponentialDelay, rateLimitDelay)
962967

963968
// Show countdown timer with exponential backoff
964-
for (let i = exponentialDelay; i > 0; i--) {
969+
for (let i = finalDelay; i > 0; i--) {
965970
await this.say(
966971
"api_req_retry_delayed",
967972
`${errorMsg}\n\nRetry attempt ${retryAttempt + 1}\nRetrying in ${i} seconds...`,
@@ -2753,6 +2758,13 @@ export class Cline {
27532758
// get previous api req's index to check token usage and determine if we need to truncate conversation history
27542759
const previousApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started")
27552760

2761+
// Save checkpoint if this is the first API request.
2762+
const isFirstRequest = this.clineMessages.filter((m) => m.say === "api_req_started").length === 0
2763+
2764+
if (isFirstRequest) {
2765+
await this.checkpointSave()
2766+
}
2767+
27562768
// 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
27572769
// for the best UX we show a placeholder api_req_started message with a loading spinner as this happens
27582770
await this.say(
@@ -2810,6 +2822,8 @@ export class Cline {
28102822
}
28112823

28122824
const abortStream = async (cancelReason: ClineApiReqCancelReason, streamingFailedMessage?: string) => {
2825+
console.log(`[Cline#abortStream] cancelReason = ${cancelReason}`)
2826+
28132827
if (this.diffViewProvider.isEditing) {
28142828
await this.diffViewProvider.revertChanges() // closes diff view
28152829
}
@@ -2864,6 +2878,7 @@ export class Cline {
28642878
const stream = this.attemptApiRequest(previousApiReqIndex) // yields only if the first chunk is successful, otherwise will allow the user to retry the request (most likely due to rate limit error, which gets thrown on the first chunk)
28652879
let assistantMessage = ""
28662880
let reasoningMessage = ""
2881+
this.isStreaming = true
28672882
try {
28682883
for await (const chunk of stream) {
28692884
if (!chunk) {
@@ -2896,11 +2911,13 @@ export class Cline {
28962911
}
28972912

28982913
if (this.abort) {
2899-
console.log("aborting stream...")
2914+
console.log(`aborting stream, this.abandoned = ${this.abandoned}`)
2915+
29002916
if (!this.abandoned) {
29012917
// only need to gracefully abort if this instance isn't abandoned (sometimes openrouter stream hangs, in which case this would affect future instances of cline)
29022918
await abortStream("user_cancelled")
29032919
}
2920+
29042921
break // aborts the stream
29052922
}
29062923

@@ -2933,6 +2950,8 @@ export class Cline {
29332950
// await this.providerRef.deref()?.postStateToWebview()
29342951
}
29352952
}
2953+
} finally {
2954+
this.isStreaming = false
29362955
}
29372956

29382957
// need to call here in case the stream was aborted
@@ -3294,12 +3313,7 @@ export class Cline {
32943313
]),
32953314
)
32963315
} catch (err) {
3297-
this.providerRef
3298-
.deref()
3299-
?.log(
3300-
`[checkpointDiff] Encountered unexpected error: $${err instanceof Error ? err.message : String(err)}`,
3301-
)
3302-
3316+
this.providerRef.deref()?.log("[checkpointDiff] disabling checkpoints for this task")
33033317
this.checkpointsEnabled = false
33043318
}
33053319
}
@@ -3310,6 +3324,7 @@ export class Cline {
33103324
}
33113325

33123326
try {
3327+
const isFirst = !this.checkpointService
33133328
const service = await this.getCheckpointService()
33143329
const commit = await service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`)
33153330

@@ -3318,15 +3333,17 @@ export class Cline {
33183333
.deref()
33193334
?.postMessageToWebview({ type: "currentCheckpointUpdated", text: service.currentCheckpoint })
33203335

3321-
await this.say("checkpoint_saved", commit.commit)
3336+
// Checkpoint metadata required by the UI.
3337+
const checkpoint = {
3338+
isFirst,
3339+
from: service.baseCommitHash,
3340+
to: commit.commit,
3341+
}
3342+
3343+
await this.say("checkpoint_saved", commit.commit, undefined, undefined, checkpoint)
33223344
}
33233345
} catch (err) {
3324-
this.providerRef
3325-
.deref()
3326-
?.log(
3327-
`[checkpointSave] Encountered unexpected error: $${err instanceof Error ? err.message : String(err)}`,
3328-
)
3329-
3346+
this.providerRef.deref()?.log("[checkpointSave] disabling checkpoints for this task")
33303347
this.checkpointsEnabled = false
33313348
}
33323349
}
@@ -3396,12 +3413,7 @@ export class Cline {
33963413
// Cline instance.
33973414
this.providerRef.deref()?.cancelTask()
33983415
} catch (err) {
3399-
this.providerRef
3400-
.deref()
3401-
?.log(
3402-
`[restoreCheckpoint] Encountered unexpected error: $${err instanceof Error ? err.message : String(err)}`,
3403-
)
3404-
3416+
this.providerRef.deref()?.log("[checkpointRestore] disabling checkpoints for this task")
34053417
this.checkpointsEnabled = false
34063418
}
34073419
}

0 commit comments

Comments
 (0)