diff --git a/.changeset/clever-news-arrive.md b/.changeset/clever-news-arrive.md new file mode 100644 index 0000000000..d490036adb --- /dev/null +++ b/.changeset/clever-news-arrive.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Disable writing in ask mode diff --git a/.changeset/orange-geese-provide.md b/.changeset/orange-geese-provide.md deleted file mode 100644 index 7a4354e84d..0000000000 --- a/.changeset/orange-geese-provide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"roo-cline": patch ---- - -Allow users to clear out custom instructions for the built-in modes diff --git a/.clinerules b/.clinerules index 4f726299d6..9bd9ff02ee 100644 --- a/.clinerules +++ b/.clinerules @@ -16,6 +16,11 @@ - Logs can be found in `logs\app.log` - Logfile is overwritten on each run to keep it to a manageable volume. +4. Styling Guidelines: + - Use Tailwind CSS classes instead of inline style objects for new markup + - VSCode CSS variables must be added to webview-ui/src/index.css before using them in Tailwind classes + - Example: `
` instead of style objects + # Adding a New Setting diff --git a/CHANGELOG.md b/CHANGELOG.md index fada04ba4d..39ce0a73f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Roo Code Changelog +## [3.3.19] + +- Fix a bug where aborting in the middle of file writes would not revert the write +- Honor the VS Code theme for dialog backgrounds +- Make it possible to clear out the default custom instructions for built-in modes +- Add a help button that links to our new documentation site (which we would love help from the community to improve!) +- Switch checkpoints logic to use a shadow git repository to work around issues with hot reloads and polluting existing repositories (thanks Cline for the inspiration!) + ## [3.3.18] - Add a per-API-configuration model temperature setting (thanks @joemanley201!) diff --git a/package-lock.json b/package-lock.json index eeea392e2f..8af12ad90c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "roo-cline", - "version": "3.3.18", + "version": "3.3.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "roo-cline", - "version": "3.3.18", + "version": "3.3.19", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", "@anthropic-ai/sdk": "^0.26.0", @@ -31,6 +31,7 @@ "diff-match-patch": "^1.0.5", "fast-deep-equal": "^3.1.3", "fastest-levenshtein": "^1.0.16", + "get-folder-size": "^5.0.0", "globby": "^14.0.2", "isbinaryfile": "^5.0.2", "mammoth": "^1.8.0", @@ -39,6 +40,7 @@ "os-name": "^6.0.0", "p-wait-for": "^5.0.2", "pdf-parse": "^1.1.1", + "pretty-bytes": "^6.1.1", "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", "serialize-error": "^11.0.3", @@ -9429,6 +9431,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-folder-size": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/get-folder-size/-/get-folder-size-5.0.0.tgz", + "integrity": "sha512-+fgtvbL83tSDypEK+T411GDBQVQtxv+qtQgbV+HVa/TYubqDhNd5ghH/D6cOHY9iC5/88GtOZB7WI8PXy2A3bg==", + "license": "MIT", + "bin": { + "get-folder-size": "bin/get-folder-size.js" + }, + "engines": { + "node": ">=18.11.0" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -13428,6 +13442,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", diff --git a/package.json b/package.json index b87b640e0d..8795b54d37 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Roo Code (prev. Roo Cline)", "description": "A VS Code plugin that enhances coding with AI-powered automation, multi-model support, and experimental features.", "publisher": "RooVeterinaryInc", - "version": "3.3.18", + "version": "3.3.19", "icon": "assets/icons/rocket.png", "galleryBanner": { "color": "#617A91", @@ -99,6 +99,11 @@ "title": "Settings", "icon": "$(settings-gear)" }, + { + "command": "roo-cline.helpButtonClicked", + "title": "Documentation", + "icon": "$(question)" + }, { "command": "roo-cline.openInNewTab", "title": "Open In New Tab", @@ -225,6 +230,11 @@ "command": "roo-cline.settingsButtonClicked", "group": "navigation@6", "when": "view == roo-cline.SidebarProvider" + }, + { + "command": "roo-cline.helpButtonClicked", + "group": "navigation@7", + "when": "view == roo-cline.SidebarProvider" } ] }, @@ -272,7 +282,9 @@ "compile:integration": "tsc -p tsconfig.integration.json", "install:all": "npm install && cd webview-ui && npm install", "lint": "eslint src --ext ts && npm run lint --prefix webview-ui", + "lint-local": "eslint -c .eslintrc.local.json src --ext ts && npm run lint --prefix webview-ui", "lint-fix": "eslint src --ext ts --fix && npm run lint-fix --prefix webview-ui", + "lint-fix-local": "eslint -c .eslintrc.local.json src --ext ts --fix && npm run lint-fix --prefix webview-ui", "package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production", "pretest": "npm run compile && npm run compile:integration", "dev": "cd webview-ui && npm run dev", @@ -314,6 +326,7 @@ "diff-match-patch": "^1.0.5", "fast-deep-equal": "^3.1.3", "fastest-levenshtein": "^1.0.16", + "get-folder-size": "^5.0.0", "globby": "^14.0.2", "isbinaryfile": "^5.0.2", "mammoth": "^1.8.0", @@ -322,6 +335,7 @@ "os-name": "^6.0.0", "p-wait-for": "^5.0.2", "pdf-parse": "^1.1.1", + "pretty-bytes": "^6.1.1", "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", "serialize-error": "^11.0.3", @@ -352,17 +366,17 @@ "@vscode/test-cli": "^0.0.9", "@vscode/test-electron": "^2.4.0", "esbuild": "^0.24.0", - "mkdirp": "^3.0.1", - "rimraf": "^6.0.1", "eslint": "^8.57.0", "glob": "^11.0.1", "husky": "^9.1.7", "jest": "^29.7.0", "jest-simple-dot-reporter": "^1.0.5", "lint-staged": "^15.2.11", + "mkdirp": "^3.0.1", "mocha": "^11.1.0", "npm-run-all": "^4.1.5", "prettier": "^3.4.2", + "rimraf": "^6.0.1", "ts-jest": "^29.2.5", "typescript": "^5.4.5" }, diff --git a/src/__mocks__/get-folder-size.js b/src/__mocks__/get-folder-size.js new file mode 100644 index 0000000000..082d5203de --- /dev/null +++ b/src/__mocks__/get-folder-size.js @@ -0,0 +1,13 @@ +module.exports = async function getFolderSize() { + return { + size: 1000, + errors: [], + } +} + +module.exports.loose = async function getFolderSizeLoose() { + return { + size: 1000, + errors: [], + } +} diff --git a/src/activate/registerCommands.ts b/src/activate/registerCommands.ts index bbfc716f6d..69e257e7a5 100644 --- a/src/activate/registerCommands.ts +++ b/src/activate/registerCommands.ts @@ -38,6 +38,9 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt "roo-cline.historyButtonClicked": () => { provider.postMessageToWebview({ type: "action", action: "historyButtonClicked" }) }, + "roo-cline.helpButtonClicked": () => { + vscode.env.openExternal(vscode.Uri.parse("https://docs.roocode.com")) + }, } } diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 33865601aa..9c2977a266 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -6,13 +6,14 @@ import delay from "delay" import fs from "fs/promises" import os from "os" import pWaitFor from "p-wait-for" +import getFolderSize from "get-folder-size" import * as path from "path" import { serializeError } from "serialize-error" import * as vscode from "vscode" import { ApiHandler, SingleCompletionHandler, buildApiHandler } from "../api" import { ApiStream } from "../api/transform/stream" import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../integrations/editor/DiffViewProvider" -import { CheckpointService } from "../services/checkpoints/CheckpointService" +import { CheckpointService, CheckpointServiceFactory } from "../services/checkpoints" import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown" import { extractTextFromFile, @@ -239,7 +240,8 @@ export class Cline { private async saveClineMessages() { try { - const filePath = path.join(await this.ensureTaskDirectoryExists(), GlobalFileNames.uiMessages) + const taskDir = await this.ensureTaskDirectoryExists() + const filePath = path.join(taskDir, GlobalFileNames.uiMessages) await fs.writeFile(filePath, JSON.stringify(this.clineMessages)) // combined as they are in ChatView const apiMetrics = getApiMetrics(combineApiRequests(combineCommandSequences(this.clineMessages.slice(1)))) @@ -251,6 +253,17 @@ export class Cline { (m) => !(m.ask === "resume_task" || m.ask === "resume_completed_task"), ) ] + + let taskDirSize = 0 + + try { + taskDirSize = await getFolderSize.loose(taskDir) + } catch (err) { + console.error( + `[saveClineMessages] failed to get task directory size (${taskDir}): ${err instanceof Error ? err.message : String(err)}`, + ) + } + await this.providerRef.deref()?.updateTaskHistory({ id: this.taskId, ts: lastRelevantMessage.ts, @@ -260,6 +273,7 @@ export class Cline { cacheWrites: apiMetrics.totalCacheWrites, cacheReads: apiMetrics.totalCacheReads, totalCost: apiMetrics.totalCost, + size: taskDirSize, }) } catch (error) { console.error("Failed to save cline messages:", error) @@ -2692,7 +2706,7 @@ export class Cline { } if (isCheckpointPossible) { - await this.checkpointSave() + await this.checkpointSave({ isFirst: false }) } /* @@ -2762,7 +2776,7 @@ export class Cline { const isFirstRequest = this.clineMessages.filter((m) => m.say === "api_req_started").length === 0 if (isFirstRequest) { - await this.checkpointSave() + await this.checkpointSave({ isFirst: true }) } // 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 @@ -3098,11 +3112,14 @@ export class Cline { } details += "\n\n# VSCode Open Tabs" + const { maxOpenTabsContext } = (await this.providerRef.deref()?.getState()) ?? {} + const maxTabs = maxOpenTabsContext ?? 20 const openTabs = vscode.window.tabGroups.all .flatMap((group) => group.tabs) .map((tab) => (tab.input as vscode.TabInputText)?.uri?.fsPath) .filter(Boolean) .map((absolutePath) => path.relative(cwd, absolutePath).toPosix()) + .slice(0, maxTabs) .join("\n") if (openTabs) { details += `\n${openTabs}` @@ -3255,11 +3272,32 @@ export class Cline { // Checkpoints private async getCheckpointService() { + if (!this.checkpointsEnabled) { + throw new Error("Checkpoints are disabled") + } + if (!this.checkpointService) { - this.checkpointService = await CheckpointService.create({ - taskId: this.taskId, - baseDir: vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? "", - log: (message) => this.providerRef.deref()?.log(message), + const workspaceDir = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) + const shadowDir = this.providerRef.deref()?.context.globalStorageUri.fsPath + + if (!workspaceDir) { + this.providerRef.deref()?.log("[getCheckpointService] workspace folder not found") + throw new Error("Workspace directory not found") + } + + if (!shadowDir) { + this.providerRef.deref()?.log("[getCheckpointService] shadowDir not found") + throw new Error("Global storage directory not found") + } + + this.checkpointService = await CheckpointServiceFactory.create({ + strategy: "shadow", + options: { + taskId: this.taskId, + workspaceDir, + shadowDir, + log: (message) => this.providerRef.deref()?.log(message), + }, }) } @@ -3318,29 +3356,25 @@ export class Cline { } } - public async checkpointSave() { + public async checkpointSave({ isFirst }: { isFirst: boolean }) { if (!this.checkpointsEnabled) { return } try { - const isFirst = !this.checkpointService const service = await this.getCheckpointService() + const strategy = service.strategy + const version = service.version + const commit = await service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`) + const fromHash = service.baseHash + const toHash = isFirst ? commit?.commit || fromHash : commit?.commit - if (commit?.commit) { - await this.providerRef - .deref() - ?.postMessageToWebview({ type: "currentCheckpointUpdated", text: service.currentCheckpoint }) + if (toHash) { + await this.providerRef.deref()?.postMessageToWebview({ type: "currentCheckpointUpdated", text: toHash }) - // Checkpoint metadata required by the UI. - const checkpoint = { - isFirst, - from: service.baseCommitHash, - to: commit.commit, - } - - await this.say("checkpoint_saved", commit.commit, undefined, undefined, checkpoint) + const checkpoint = { isFirst, from: fromHash, to: toHash, strategy, version } + await this.say("checkpoint_saved", toHash, undefined, undefined, checkpoint) } } catch (err) { this.providerRef.deref()?.log("[checkpointSave] disabling checkpoints for this task") @@ -3371,9 +3405,7 @@ export class Cline { const service = await this.getCheckpointService() await service.restoreCheckpoint(commitHash) - await this.providerRef - .deref() - ?.postMessageToWebview({ type: "currentCheckpointUpdated", text: service.currentCheckpoint }) + await this.providerRef.deref()?.postMessageToWebview({ type: "currentCheckpointUpdated", text: commitHash }) if (mode === "restore") { await this.overwriteApiConversationHistory( diff --git a/src/core/__tests__/Cline.test.ts b/src/core/__tests__/Cline.test.ts index 4031b2eb5e..3da0c8cdd3 100644 --- a/src/core/__tests__/Cline.test.ts +++ b/src/core/__tests__/Cline.test.ts @@ -475,6 +475,7 @@ describe("Cline", () => { // Mock abort state Object.defineProperty(cline, "abort", { get: () => false, + set: () => {}, configurable: true, }) @@ -603,10 +604,12 @@ describe("Cline", () => { // Mock abort state for both instances Object.defineProperty(clineWithImages, "abort", { get: () => false, + set: () => {}, configurable: true, }) Object.defineProperty(clineWithoutImages, "abort", { get: () => false, + set: () => {}, configurable: true, }) diff --git a/src/core/config/CustomModesManager.ts b/src/core/config/CustomModesManager.ts index ad3a2b8b10..70107d9e69 100644 --- a/src/core/config/CustomModesManager.ts +++ b/src/core/config/CustomModesManager.ts @@ -6,6 +6,8 @@ import { ModeConfig } from "../../shared/modes" import { fileExistsAtPath } from "../../utils/fs" import { arePathsEqual } from "../../utils/path" +const ROOMODES_FILENAME = ".roomodes" + export class CustomModesManager { private disposables: vscode.Disposable[] = [] private isWriting = false @@ -15,7 +17,7 @@ export class CustomModesManager { private readonly context: vscode.ExtensionContext, private readonly onUpdate: () => Promise+
Select the language that Cline should use for communication.
+ Maximum number of VSCode open tabs to include in context. Higher values provide more context + but increase token usage. +
+