├── .nvmrc
├── webview-ui
├── public
│ └── .gitkeep
├── src
│ ├── stories
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── Welcome.mdx
│ │ ├── Progress.stories.tsx
│ │ ├── AutosizeTextarea.stories.tsx
│ │ ├── Slider.stories.tsx
│ │ ├── Badge.stories.tsx
│ │ ├── Collapsible.stories.tsx
│ │ ├── Chat.stories.tsx
│ │ └── Button.stories.ts
│ ├── vite-env.d.ts
│ ├── components
│ │ ├── ui
│ │ │ ├── hooks
│ │ │ │ ├── index.ts
│ │ │ │ └── useClipboard.ts
│ │ │ ├── markdown
│ │ │ │ ├── index.ts
│ │ │ │ ├── Blockquote.tsx
│ │ │ │ └── CodeBlock.tsx
│ │ │ ├── chat
│ │ │ │ ├── index.ts
│ │ │ │ ├── ChatProvider.ts
│ │ │ │ ├── useChatUI.ts
│ │ │ │ ├── useChatInput.ts
│ │ │ │ ├── ChatMessageProvider.ts
│ │ │ │ ├── useChatMessage.ts
│ │ │ │ ├── ChatInputProvider.ts
│ │ │ │ ├── Chat.tsx
│ │ │ │ ├── types.ts
│ │ │ │ └── ChatMessages.tsx
│ │ │ ├── collapsible.tsx
│ │ │ ├── index.ts
│ │ │ ├── textarea.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── input.tsx
│ │ │ ├── progress.tsx
│ │ │ ├── badge.tsx
│ │ │ ├── slider.tsx
│ │ │ ├── tooltip.tsx
│ │ │ ├── popover.tsx
│ │ │ └── button.tsx
│ │ ├── chat
│ │ │ ├── checkpoints
│ │ │ │ ├── schema.ts
│ │ │ │ └── CheckpointSaved.tsx
│ │ │ └── Announcement.tsx
│ │ ├── common
│ │ │ ├── __mocks__
│ │ │ │ ├── CodeBlock.tsx
│ │ │ │ └── MarkdownBlock.tsx
│ │ │ ├── CaretIcon.tsx
│ │ │ └── VSCodeButtonLink.tsx
│ │ ├── history
│ │ │ ├── ExportButton.tsx
│ │ │ ├── CopyButton.tsx
│ │ │ └── DeleteTaskDialog.tsx
│ │ ├── settings
│ │ │ ├── ApiErrorMessage.tsx
│ │ │ ├── ExperimentalFeature.tsx
│ │ │ └── styles.ts
│ │ ├── mcp
│ │ │ ├── McpEnabledToggle.tsx
│ │ │ └── McpResourceRow.tsx
│ │ └── welcome
│ │ │ └── WelcomeView.tsx
│ ├── lib
│ │ └── utils.ts
│ ├── utils
│ │ ├── formatPrice.ts
│ │ ├── format.ts
│ │ ├── highlight.ts
│ │ ├── useDebounceEffect.ts
│ │ ├── path-mentions.ts
│ │ ├── clipboard.ts
│ │ ├── mcp.ts
│ │ ├── __tests__
│ │ │ ├── format.test.ts
│ │ │ └── path-mentions.test.ts
│ │ └── getLanguageFromPath.ts
│ ├── __mocks__
│ │ ├── lucide-react.ts
│ │ ├── pretty-bytes.js
│ │ └── vscrui.ts
│ ├── index.tsx
│ └── setupTests.ts
├── .npmrc
├── .eslintrc.json
├── .storybook
│ ├── preview.ts
│ ├── main.ts
│ └── vscode.css
├── index.html
├── .gitignore
├── components.json
├── tsconfig.json
├── vite.config.ts
└── jest.config.cjs
├── .npmrc
├── .gitconfig
├── e2e
├── .env.integration.example
├── tsconfig.json
├── .vscode-test.mjs
├── package.json
└── src
│ ├── runTest.ts
│ └── suite
│ ├── extension.test.ts
│ └── task.test.ts
├── .prettierignore
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── config.yml
│ └── bug_report.yml
├── dependabot.yml
├── workflows
│ ├── discord-pr-notify.yml
│ └── marketplace-publish.yml
├── pull_request_template.md
└── scripts
│ └── get_prev_version_refs.py
├── audio
├── celebration.wav
├── notification.wav
└── progress_loop.wav
├── assets
├── icons
│ └── rocket.png
└── docs
│ └── demo.gif
├── .gitattributes
├── .prettierrc.json
├── src
├── __mocks__
│ ├── os-name.js
│ ├── delay.js
│ ├── strip-ansi.js
│ ├── globby.js
│ ├── get-folder-size.js
│ ├── @modelcontextprotocol
│ │ └── sdk
│ │ │ ├── client
│ │ │ ├── index.js
│ │ │ └── stdio.js
│ │ │ ├── index.js
│ │ │ └── types.js
│ ├── default-shell.js
│ ├── p-wait-for.js
│ ├── McpHub.ts
│ ├── serialize-error.js
│ ├── jest.setup.ts
│ └── vscode.js
├── api
│ ├── providers
│ │ ├── constants.ts
│ │ └── deepseek.ts
│ └── transform
│ │ ├── stream.ts
│ │ └── simple-format.ts
├── activate
│ ├── index.ts
│ └── handleUri.ts
├── shared
│ ├── HistoryItem.ts
│ ├── vsCodeSelectorUtils.ts
│ ├── globalFileNames.ts
│ ├── checkExistApiConfig.ts
│ ├── array.ts
│ ├── mcp.ts
│ ├── __tests__
│ │ ├── vsCodeSelectorUtils.test.ts
│ │ ├── experiments.test.ts
│ │ └── checkExistApiConfig.test.ts
│ ├── tool-groups.ts
│ └── experiments.ts
├── services
│ ├── tree-sitter
│ │ └── queries
│ │ │ ├── python.ts
│ │ │ ├── php.ts
│ │ │ ├── java.ts
│ │ │ ├── rust.ts
│ │ │ ├── c.ts
│ │ │ ├── c-sharp.ts
│ │ │ ├── index.ts
│ │ │ ├── go.ts
│ │ │ ├── cpp.ts
│ │ │ ├── typescript.ts
│ │ │ ├── swift.ts
│ │ │ ├── ruby.ts
│ │ │ └── javascript.ts
│ ├── checkpoints
│ │ ├── constants.ts
│ │ └── types.ts
│ └── ripgrep
│ │ └── __tests__
│ │ └── index.test.ts
├── core
│ ├── prompts
│ │ ├── tools
│ │ │ ├── types.ts
│ │ │ ├── switch-mode.ts
│ │ │ ├── new-task.ts
│ │ │ ├── read-file.ts
│ │ │ ├── ask-followup-question.ts
│ │ │ ├── list-code-definition-names.ts
│ │ │ ├── list-files.ts
│ │ │ ├── execute-command.ts
│ │ │ ├── access-mcp-resource.ts
│ │ │ ├── search-files.ts
│ │ │ ├── use-mcp-tool.ts
│ │ │ ├── attempt-completion.ts
│ │ │ ├── write-to-file.ts
│ │ │ ├── insert-content.ts
│ │ │ ├── search-and-replace.ts
│ │ │ └── browser-action.ts
│ │ ├── sections
│ │ │ ├── index.ts
│ │ │ ├── tool-use.ts
│ │ │ ├── objective.ts
│ │ │ ├── tool-use-guidelines.ts
│ │ │ ├── system-info.ts
│ │ │ ├── custom-system-prompt.ts
│ │ │ ├── capabilities.ts
│ │ │ └── modes.ts
│ │ └── __tests__
│ │ │ └── sections.test.ts
│ ├── diff
│ │ ├── strategies
│ │ │ └── new-unified
│ │ │ │ └── types.ts
│ │ ├── DiffStrategy.ts
│ │ ├── insert-groups.ts
│ │ └── types.ts
│ ├── webview
│ │ ├── getNonce.ts
│ │ └── getUri.ts
│ ├── mode-validator.ts
│ └── config
│ │ ├── CustomModesSchema.ts
│ │ └── __tests__
│ │ └── GroupConfigSchema.test.ts
├── utils
│ ├── logging
│ │ ├── index.ts
│ │ └── __tests__
│ │ │ └── MockTransport.ts
│ ├── single-completion-handler.ts
│ ├── cost.ts
│ ├── fs.ts
│ └── sound.ts
├── integrations
│ ├── terminal
│ │ ├── __tests__
│ │ │ └── TerminalRegistry.test.ts
│ │ └── get-latest-output.ts
│ ├── misc
│ │ └── process-images.ts
│ └── editor
│ │ └── detect-omission.ts
└── exports
│ ├── README.md
│ └── index.ts
├── .git-blame-ignore-revs
├── .husky
├── pre-commit
└── pre-push
├── .changeset
├── config.json
├── README.md
└── changelog-config.js
├── .vscode
├── extensions.json
├── settings.json
├── launch.json
└── tasks.json
├── .gitignore
├── CHANGELOG.md
├── .eslintrc.json
├── flake.lock
├── knip.json
├── flake.nix
├── tsconfig.json
├── ellipsis.yaml
├── .vscodeignore
├── .clinerules
└── jest.config.js
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/*
--------------------------------------------------------------------------------
/webview-ui/public/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/webview-ui/src/stories/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
--------------------------------------------------------------------------------
/webview-ui/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
--------------------------------------------------------------------------------
/.gitconfig:
--------------------------------------------------------------------------------
1 | [blame]
2 | ignoreRevsFile = .git-blame-ignore-revs
3 |
--------------------------------------------------------------------------------
/e2e/.env.integration.example:
--------------------------------------------------------------------------------
1 | OPENROUTER_API_KEY=sk-or-v1-...
2 |
--------------------------------------------------------------------------------
/webview-ui/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./useClipboard"
2 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/markdown/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Markdown"
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules
3 | webview-ui/build/
4 | CHANGELOG.md
5 | package-lock.json
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/chat/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./types"
2 | export * from "./Chat"
3 |
--------------------------------------------------------------------------------
/webview-ui/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app",
3 | "ignorePatterns": ["!.storybook"]
4 | }
5 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # These owners will be the default owners for everything in the repo
2 | * @mrubens @cte
3 |
--------------------------------------------------------------------------------
/audio/celebration.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HybridTalentComputing/Roo-Code-Chinese/HEAD/audio/celebration.wav
--------------------------------------------------------------------------------
/audio/notification.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HybridTalentComputing/Roo-Code-Chinese/HEAD/audio/notification.wav
--------------------------------------------------------------------------------
/assets/icons/rocket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HybridTalentComputing/Roo-Code-Chinese/HEAD/assets/icons/rocket.png
--------------------------------------------------------------------------------
/audio/progress_loop.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HybridTalentComputing/Roo-Code-Chinese/HEAD/audio/progress_loop.wav
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | demo.gif filter=lfs diff=lfs merge=lfs -text
2 | assets/docs/demo.gif filter=lfs diff=lfs merge=lfs -text
3 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "useTabs": true,
4 | "printWidth": 120,
5 | "semi": false,
6 | "bracketSameLine": true
7 | }
8 |
--------------------------------------------------------------------------------
/src/__mocks__/os-name.js:
--------------------------------------------------------------------------------
1 | function osName() {
2 | return "macOS"
3 | }
4 |
5 | module.exports = osName
6 | module.exports.default = osName
7 |
--------------------------------------------------------------------------------
/src/api/providers/constants.ts:
--------------------------------------------------------------------------------
1 | export const ANTHROPIC_DEFAULT_MAX_TOKENS = 8192
2 |
3 | export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6
4 |
--------------------------------------------------------------------------------
/assets/docs/demo.gif:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:d426d600fa80e9ac237cbad6b3f7f47a4aa2005d5218184e6b9565a8ee46d1ba
3 | size 19108207
4 |
--------------------------------------------------------------------------------
/src/__mocks__/delay.js:
--------------------------------------------------------------------------------
1 | function delay(ms) {
2 | return new Promise((resolve) => setTimeout(resolve, ms))
3 | }
4 |
5 | module.exports = delay
6 | module.exports.default = delay
7 |
--------------------------------------------------------------------------------
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | # Ran Prettier on all files - https://github.com/RooVetGit/Roo-Code/pull/404
2 | 60a0a824b96a0b326af4d8871b6903f4ddcfe114
3 | 579bdd9dbf6d2d569e5e7adb5ff6292b1e42ea34
4 |
--------------------------------------------------------------------------------
/src/__mocks__/strip-ansi.js:
--------------------------------------------------------------------------------
1 | function stripAnsi(string) {
2 | // Simple mock that just returns the input string
3 | return string
4 | }
5 |
6 | module.exports = stripAnsi
7 | module.exports.default = stripAnsi
8 |
--------------------------------------------------------------------------------
/webview-ui/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | branch="$(git rev-parse --abbrev-ref HEAD)"
2 |
3 | if [ "$branch" = "main" ]; then
4 | echo "You can't commit directly to main - please check out a branch."
5 | exit 1
6 | fi
7 |
8 | npx lint-staged
9 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/markdown/Blockquote.tsx:
--------------------------------------------------------------------------------
1 | export const Blockquote = ({ children }: { children: React.ReactNode }) => {
2 | return
{children}
3 | }
4 |
--------------------------------------------------------------------------------
/webview-ui/src/stories/Welcome.mdx:
--------------------------------------------------------------------------------
1 | import { Meta } from "@storybook/blocks";
2 |
3 |
4 |
5 | # Welcome
6 |
7 | This Roo Code storybook is used to independently develop components for the Roo Code webview UI.
8 |
--------------------------------------------------------------------------------
/src/__mocks__/globby.js:
--------------------------------------------------------------------------------
1 | function globby(patterns, options) {
2 | return Promise.resolve([])
3 | }
4 |
5 | globby.sync = function (patterns, options) {
6 | return []
7 | }
8 |
9 | module.exports = globby
10 | module.exports.default = globby
11 |
--------------------------------------------------------------------------------
/src/activate/index.ts:
--------------------------------------------------------------------------------
1 | export { handleUri } from "./handleUri"
2 | export { registerCommands } from "./registerCommands"
3 | export { registerCodeActions } from "./registerCodeActions"
4 | export { registerTerminalActions } from "./registerTerminalActions"
5 |
--------------------------------------------------------------------------------
/src/shared/HistoryItem.ts:
--------------------------------------------------------------------------------
1 | export type HistoryItem = {
2 | id: string
3 | ts: number
4 | task: string
5 | tokensIn: number
6 | tokensOut: number
7 | cacheWrites?: number
8 | cacheReads?: number
9 | totalCost: number
10 | size?: number
11 | }
12 |
--------------------------------------------------------------------------------
/webview-ui/src/utils/formatPrice.ts:
--------------------------------------------------------------------------------
1 | export const formatPrice = (price: number) => {
2 | return new Intl.NumberFormat("en-US", {
3 | style: "currency",
4 | currency: "USD",
5 | minimumFractionDigits: 2,
6 | maximumFractionDigits: 2,
7 | }).format(price)
8 | }
9 |
--------------------------------------------------------------------------------
/webview-ui/src/components/chat/checkpoints/schema.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod"
2 |
3 | export const checkpointSchema = z.object({
4 | isFirst: z.boolean(),
5 | from: z.string(),
6 | to: z.string(),
7 | })
8 |
9 | export type Checkpoint = z.infer
10 |
--------------------------------------------------------------------------------
/src/__mocks__/get-folder-size.js:
--------------------------------------------------------------------------------
1 | module.exports = async function getFolderSize() {
2 | return {
3 | size: 1000,
4 | errors: [],
5 | }
6 | }
7 |
8 | module.exports.loose = async function getFolderSizeLoose() {
9 | return {
10 | size: 1000,
11 | errors: [],
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/webview-ui/src/__mocks__/lucide-react.ts:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export const Check = () => React.createElement("div")
4 | export const ChevronsUpDown = () => React.createElement("div")
5 | export const Loader = () => React.createElement("div")
6 | export const X = () => React.createElement("div")
7 |
--------------------------------------------------------------------------------
/src/__mocks__/@modelcontextprotocol/sdk/client/index.js:
--------------------------------------------------------------------------------
1 | class Client {
2 | constructor() {
3 | this.request = jest.fn()
4 | }
5 |
6 | connect() {
7 | return Promise.resolve()
8 | }
9 |
10 | close() {
11 | return Promise.resolve()
12 | }
13 | }
14 |
15 | module.exports = {
16 | Client,
17 | }
18 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/python.ts:
--------------------------------------------------------------------------------
1 | /*
2 | - class definitions
3 | - function definitions
4 | */
5 | export default `
6 | (class_definition
7 | name: (identifier) @name.definition.class) @definition.class
8 |
9 | (function_definition
10 | name: (identifier) @name.definition.function) @definition.function
11 | `
12 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json",
3 | "changelog": "./changelog-config.js",
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "restricted",
8 | "baseBranch": "main",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
12 |
--------------------------------------------------------------------------------
/src/__mocks__/default-shell.js:
--------------------------------------------------------------------------------
1 | // Mock default shell based on platform
2 | const os = require("os")
3 |
4 | let defaultShell
5 | if (os.platform() === "win32") {
6 | defaultShell = "cmd.exe"
7 | } else {
8 | defaultShell = "/bin/bash"
9 | }
10 |
11 | module.exports = defaultShell
12 | module.exports.default = defaultShell
13 |
--------------------------------------------------------------------------------
/webview-ui/src/components/common/__mocks__/CodeBlock.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | interface CodeBlockProps {
4 | children?: React.ReactNode
5 | language?: string
6 | }
7 |
8 | const CodeBlock: React.FC = () => Mocked Code Block
9 |
10 | export default CodeBlock
11 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/types.ts:
--------------------------------------------------------------------------------
1 | import { DiffStrategy } from "../../diff/DiffStrategy"
2 | import { McpHub } from "../../../services/mcp/McpHub"
3 |
4 | export type ToolArgs = {
5 | cwd: string
6 | supportsComputerUse: boolean
7 | diffStrategy?: DiffStrategy
8 | browserViewportSize?: string
9 | mcpHub?: McpHub
10 | toolOptions?: any
11 | }
12 |
--------------------------------------------------------------------------------
/src/shared/vsCodeSelectorUtils.ts:
--------------------------------------------------------------------------------
1 | import { LanguageModelChatSelector } from "vscode"
2 |
3 | export const SELECTOR_SEPARATOR = "/"
4 |
5 | export function stringifyVsCodeLmModelSelector(selector: LanguageModelChatSelector): string {
6 | return [selector.vendor, selector.family, selector.version, selector.id].filter(Boolean).join(SELECTOR_SEPARATOR)
7 | }
8 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/chat/ChatProvider.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from "react"
2 |
3 | import { ChatHandler } from "./types"
4 |
5 | type ChatContext = ChatHandler & {
6 | assistantName: string
7 | }
8 |
9 | export const chatContext = createContext(null)
10 |
11 | export const ChatProvider = chatContext.Provider
12 |
--------------------------------------------------------------------------------
/webview-ui/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react"
2 | import { createRoot } from "react-dom/client"
3 |
4 | import "./index.css"
5 | import App from "./App"
6 | import "../../node_modules/@vscode/codicons/dist/codicon.css"
7 |
8 | createRoot(document.getElementById("root")!).render(
9 |
10 |
11 | ,
12 | )
13 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/chat/useChatUI.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from "react"
2 |
3 | import { chatContext } from "./ChatProvider"
4 |
5 | export const useChatUI = () => {
6 | const context = useContext(chatContext)
7 |
8 | if (!context) {
9 | throw new Error("useChatUI must be used within a ChatProvider")
10 | }
11 |
12 | return context
13 | }
14 |
--------------------------------------------------------------------------------
/webview-ui/.storybook/preview.ts:
--------------------------------------------------------------------------------
1 | import type { Preview } from "@storybook/react"
2 |
3 | import "./vscode.css"
4 | import "../src/index.css"
5 |
6 | const preview: Preview = {
7 | parameters: {
8 | controls: {
9 | matchers: {
10 | color: /(background|color)$/i,
11 | date: /Date$/i,
12 | },
13 | },
14 | },
15 | }
16 |
17 | export default preview
18 |
--------------------------------------------------------------------------------
/webview-ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Roo Code Chinese
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/webview-ui/src/components/common/CaretIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export const CaretIcon = () => (
4 |
15 | )
16 |
--------------------------------------------------------------------------------
/webview-ui/src/components/common/__mocks__/MarkdownBlock.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | interface MarkdownBlockProps {
4 | children?: React.ReactNode
5 | content?: string
6 | }
7 |
8 | const MarkdownBlock: React.FC = ({ content }) => (
9 | {content}
10 | )
11 |
12 | export default MarkdownBlock
13 |
--------------------------------------------------------------------------------
/src/shared/globalFileNames.ts:
--------------------------------------------------------------------------------
1 | export const GlobalFileNames = {
2 | apiConversationHistory: "api_conversation_history.json",
3 | uiMessages: "ui_messages.json",
4 | glamaModels: "glama_models.json",
5 | openRouterModels: "openrouter_models.json",
6 | requestyModels: "requesty_models.json",
7 | mcpSettings: "cline_mcp_settings.json",
8 | unboundModels: "unbound_models.json",
9 | }
10 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/chat/useChatInput.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from "react"
2 |
3 | import { chatInputContext } from "./ChatInputProvider"
4 |
5 | export const useChatInput = () => {
6 | const context = useContext(chatInputContext)
7 |
8 | if (!context) {
9 | throw new Error("useChatInput must be used within a ChatInputProvider")
10 | }
11 |
12 | return context
13 | }
14 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
2 |
3 | const Collapsible = CollapsiblePrimitive.Root
4 |
5 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
6 |
7 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
8 |
9 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
10 |
--------------------------------------------------------------------------------
/webview-ui/.storybook/main.ts:
--------------------------------------------------------------------------------
1 | import type { StorybookConfig } from "@storybook/react-vite"
2 |
3 | const config: StorybookConfig = {
4 | stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
5 | addons: ["@storybook/addon-essentials", "storybook-dark-mode"],
6 | framework: {
7 | name: "@storybook/react-vite",
8 | options: {},
9 | },
10 | }
11 | export default config
12 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/chat/ChatMessageProvider.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from "react"
2 |
3 | import { Message } from "./types"
4 |
5 | export interface ChatMessageContext {
6 | message: Message
7 | isLast: boolean
8 | }
9 |
10 | export const chatMessageContext = createContext(null)
11 |
12 | export const ChatMessageProvider = chatMessageContext.Provider
13 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": [
5 | "dbaeumer.vscode-eslint",
6 | "connor4312.esbuild-problem-matchers",
7 | "ms-vscode.extension-test-runner",
8 | "csstools.postcss",
9 | "bradlc.vscode-tailwindcss",
10 | "tobermory.es6-string-html"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/chat/useChatMessage.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from "react"
2 |
3 | import { chatMessageContext } from "./ChatMessageProvider"
4 |
5 | export const useChatMessage = () => {
6 | const context = useContext(chatMessageContext)
7 |
8 | if (!context) {
9 | throw new Error("useChatMessage must be used within a ChatMessageProvider")
10 | }
11 |
12 | return context
13 | }
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | out
3 | out-*
4 | node_modules
5 | coverage/
6 |
7 | .DS_Store
8 |
9 | # Builds
10 | bin/
11 | roo-cline-*.vsix
12 |
13 | # Local prompts and rules
14 | /local-prompts
15 |
16 | # Test environment
17 | .test_env
18 | .vscode-test/
19 |
20 | # Docs
21 | docs/_site/
22 |
23 | # Dotenv
24 | .env.integration
25 |
26 | #Local lint config
27 | .eslintrc.local.json
28 |
29 | #Logging
30 | logs
31 |
--------------------------------------------------------------------------------
/src/core/diff/strategies/new-unified/types.ts:
--------------------------------------------------------------------------------
1 | export type Change = {
2 | type: "context" | "add" | "remove"
3 | content: string
4 | indent: string
5 | originalLine?: string
6 | }
7 |
8 | export type Hunk = {
9 | changes: Change[]
10 | }
11 |
12 | export type Diff = {
13 | hunks: Hunk[]
14 | }
15 |
16 | export type EditResult = {
17 | confidence: number
18 | result: string[]
19 | strategy: string
20 | }
21 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/php.ts:
--------------------------------------------------------------------------------
1 | /*
2 | - class declarations
3 | - function definitions
4 | - method declarations
5 | */
6 | export default `
7 | (class_declaration
8 | name: (name) @name.definition.class) @definition.class
9 |
10 | (function_definition
11 | name: (name) @name.definition.function) @definition.function
12 |
13 | (method_declaration
14 | name: (name) @name.definition.function) @definition.function
15 | `
16 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/java.ts:
--------------------------------------------------------------------------------
1 | /*
2 | - class declarations
3 | - method declarations
4 | - interface declarations
5 | */
6 | export default `
7 | (class_declaration
8 | name: (identifier) @name.definition.class) @definition.class
9 |
10 | (method_declaration
11 | name: (identifier) @name.definition.method) @definition.method
12 |
13 | (interface_declaration
14 | name: (identifier) @name.definition.interface) @definition.interface
15 | `
16 |
--------------------------------------------------------------------------------
/webview-ui/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | *storybook.log
26 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/chat/ChatInputProvider.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from "react"
2 |
3 | interface ChatInputContext {
4 | isDisabled: boolean
5 | handleKeyDown: (e: React.KeyboardEvent) => void
6 | handleSubmit: (e: React.FormEvent) => void
7 | }
8 |
9 | export const chatInputContext = createContext(null)
10 |
11 | export const ChatInputProvider = chatInputContext.Provider
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Feature Request
4 | url: https://github.com/RooVetGit/Roo-Code/discussions/categories/feature-requests
5 | about: Share and vote on feature requests for Roo Code
6 | - name: Leave a Review
7 | url: https://marketplace.visualstudio.com/items?itemName=RooVeterinaryInc.roo-cline&ssr=false#review-details
8 | about: Enjoying Roo Code? Leave a review here!
9 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/rust.ts:
--------------------------------------------------------------------------------
1 | /*
2 | - struct definitions
3 | - method definitions
4 | - function definitions
5 | */
6 | export default `
7 | (struct_item
8 | name: (type_identifier) @name.definition.class) @definition.class
9 |
10 | (declaration_list
11 | (function_item
12 | name: (identifier) @name.definition.method)) @definition.method
13 |
14 | (function_item
15 | name: (identifier) @name.definition.function) @definition.function
16 | `
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Roo Code Changelog
2 |
3 | ## [3.7.12]
4 |
5 | - 将思维模型的最大令牌数扩展到 128k,最大思维预算超过 100k(感谢 @monotykamary!)
6 | - 修复键盘模式切换器未更新 API 配置文件的问题(感谢 @aheizi!)
7 | - 在 Anthropic 提供程序中使用 count_tokens API 以实现更准确的上下文窗口管理
8 | - 为 OpenRouter 默认启用中间压缩
9 | - 如果模式不支持 MCP,则从提示中排除 MCP 指令
10 | - 添加禁用浏览器工具的复选框
11 | - 当检查点加载时间过长时显示警告
12 | - 更新 VS LM API 的警告文本
13 | - 正确填充欢迎界面上的默认 OpenRouter 模型
14 |
15 | ## [3.7.11]
16 |
17 | - 不对非思维模型应用自定义最大令牌数限制
18 | - 在模式切换键盘快捷键中包含自定义模式
19 | - 支持可以运行命令的只读模式
--------------------------------------------------------------------------------
/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "CommonJS",
4 | "moduleResolution": "Node",
5 | "esModuleInterop": true,
6 | "target": "ES2022",
7 | "lib": ["ES2022", "ESNext.Disposable", "DOM"],
8 | "sourceMap": true,
9 | "strict": true,
10 | "skipLibCheck": true,
11 | "useUnknownInCatchVariables": false,
12 | "outDir": "out"
13 | },
14 | "include": ["src", "../src/exports/cline.d.ts"],
15 | "exclude": [".vscode-test", "**/node_modules/**", "out"]
16 | }
17 |
--------------------------------------------------------------------------------
/webview-ui/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": false,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/index.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/e2e/.vscode-test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * See: https://code.visualstudio.com/api/working-with-extensions/testing-extension
3 | */
4 |
5 | import { defineConfig } from '@vscode/test-cli';
6 |
7 | export default defineConfig({
8 | label: 'integrationTest',
9 | files: 'out/suite/**/*.test.js',
10 | workspaceFolder: '.',
11 | mocha: {
12 | ui: 'tdd',
13 | timeout: 60000,
14 | },
15 | launchArgs: [
16 | '--enable-proposed-api=RooVeterinaryInc.roo-cline',
17 | '--disable-extensions'
18 | ]
19 | });
20 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./alert-dialog"
2 | export * from "./autosize-textarea"
3 | export * from "./badge"
4 | export * from "./button"
5 | export * from "./collapsible"
6 | export * from "./command"
7 | export * from "./dialog"
8 | export * from "./dropdown-menu"
9 | export * from "./input"
10 | export * from "./popover"
11 | export * from "./progress"
12 | export * from "./separator"
13 | export * from "./slider"
14 | export * from "./textarea"
15 | export * from "./tooltip"
16 |
--------------------------------------------------------------------------------
/src/__mocks__/@modelcontextprotocol/sdk/client/stdio.js:
--------------------------------------------------------------------------------
1 | class StdioClientTransport {
2 | constructor() {
3 | this.start = jest.fn().mockResolvedValue(undefined)
4 | this.close = jest.fn().mockResolvedValue(undefined)
5 | this.stderr = {
6 | on: jest.fn(),
7 | }
8 | }
9 | }
10 |
11 | class StdioServerParameters {
12 | constructor() {
13 | this.command = ""
14 | this.args = []
15 | this.env = {}
16 | }
17 | }
18 |
19 | module.exports = {
20 | StdioClientTransport,
21 | StdioServerParameters,
22 | }
23 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/switch-mode.ts:
--------------------------------------------------------------------------------
1 | export function getSwitchModeDescription(): string {
2 | return `## switch_mode
3 | 描述:请求切换到不同的模式。此工具允许模式在需要时请求切换到另一个模式,例如切换到代码模式以进行代码更改。用户必须批准模式切换。
4 | 参数:
5 | - mode_slug:(必需)要切换到的模式的标识符(例如,"code"、"ask"、"architect")
6 | - reason:(可选)切换模式的原因
7 | 用法:
8 |
9 | 在此输入模式标识符
10 | 在此输入切换原因
11 |
12 |
13 | 示例:请求切换到代码模式
14 |
15 | code
16 | 需要进行代码更改
17 | `
18 | }
19 |
--------------------------------------------------------------------------------
/webview-ui/src/components/history/ExportButton.tsx:
--------------------------------------------------------------------------------
1 | import { vscode } from "@/utils/vscode"
2 | import { Button } from "@/components/ui"
3 |
4 | export const ExportButton = ({ itemId }: { itemId: string }) => (
5 |
16 | )
17 |
--------------------------------------------------------------------------------
/src/__mocks__/p-wait-for.js:
--------------------------------------------------------------------------------
1 | function pWaitFor(condition, options = {}) {
2 | return new Promise((resolve, reject) => {
3 | const interval = setInterval(() => {
4 | if (condition()) {
5 | clearInterval(interval)
6 | resolve()
7 | }
8 | }, options.interval || 20)
9 |
10 | if (options.timeout) {
11 | setTimeout(() => {
12 | clearInterval(interval)
13 | reject(new Error("Timed out"))
14 | }, options.timeout)
15 | }
16 | })
17 | }
18 |
19 | module.exports = pWaitFor
20 | module.exports.default = pWaitFor
21 |
--------------------------------------------------------------------------------
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/src/__mocks__/McpHub.ts:
--------------------------------------------------------------------------------
1 | export class McpHub {
2 | connections = []
3 | isConnecting = false
4 |
5 | constructor() {
6 | this.toggleToolAlwaysAllow = jest.fn()
7 | this.callTool = jest.fn()
8 | }
9 |
10 | async toggleToolAlwaysAllow(serverName: string, toolName: string, shouldAllow: boolean): Promise {
11 | return Promise.resolve()
12 | }
13 |
14 | async callTool(serverName: string, toolName: string, toolArguments?: Record): Promise {
15 | return Promise.resolve({ result: "success" })
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/core/prompts/sections/index.ts:
--------------------------------------------------------------------------------
1 | export { getRulesSection } from "./rules"
2 | export { getSystemInfoSection } from "./system-info"
3 | export { getObjectiveSection } from "./objective"
4 | export { addCustomInstructions } from "./custom-instructions"
5 | export { getSharedToolUseSection } from "./tool-use"
6 | export { getMcpServersSection } from "./mcp-servers"
7 | export { getToolUseGuidelinesSection } from "./tool-use-guidelines"
8 | export { getCapabilitiesSection } from "./capabilities"
9 | export { getModesSection } from "./modes"
10 |
--------------------------------------------------------------------------------
/webview-ui/src/__mocks__/pretty-bytes.js:
--------------------------------------------------------------------------------
1 | module.exports = function prettyBytes(bytes) {
2 | if (typeof bytes !== "number") {
3 | throw new TypeError("Expected a number")
4 | }
5 |
6 | // Simple mock implementation that returns formatted strings.
7 | if (bytes === 0) return "0 B"
8 | if (bytes < 1024) return `${bytes} B`
9 | if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
10 | if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
11 | return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
12 | }
13 |
--------------------------------------------------------------------------------
/webview-ui/src/components/settings/ApiErrorMessage.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | interface ApiErrorMessageProps {
4 | errorMessage: string | undefined
5 | children?: React.ReactNode
6 | }
7 |
8 | export const ApiErrorMessage = ({ errorMessage, children }: ApiErrorMessageProps) => (
9 |
10 |
11 |
12 |
{errorMessage}
13 |
14 | {children}
15 |
16 | )
17 |
--------------------------------------------------------------------------------
/.changeset/changelog-config.js:
--------------------------------------------------------------------------------
1 | // Half-works to simplify the format but needs 'overwrite_changeset_changelog.py' in GHA to finish formatting
2 |
3 | const getReleaseLine = async (changeset) => {
4 | const [firstLine] = changeset.summary
5 | .split("\n")
6 | .map((l) => l.trim())
7 | .filter(Boolean)
8 | return `- ${firstLine}`
9 | }
10 |
11 | const getDependencyReleaseLine = async () => {
12 | return ""
13 | }
14 |
15 | const changelogFunctions = {
16 | getReleaseLine,
17 | getDependencyReleaseLine,
18 | }
19 |
20 | module.exports = changelogFunctions
21 |
--------------------------------------------------------------------------------
/webview-ui/src/stories/Progress.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/react"
2 |
3 | import { Progress } from "@/components/ui"
4 |
5 | const meta: Meta = {
6 | title: "Primitives/Progress",
7 | component: Progress,
8 | parameters: {
9 | layout: "centered",
10 | },
11 | args: {
12 | className: "w-[300px]",
13 | },
14 | tags: ["autodocs"],
15 | }
16 |
17 | export default meta
18 |
19 | type Story = StoryObj
20 |
21 | export const Default: Story = {
22 | args: {
23 | value: 50,
24 | },
25 | }
26 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": {
5 | "ecmaVersion": 6,
6 | "sourceType": "module"
7 | },
8 | "plugins": ["@typescript-eslint"],
9 | "rules": {
10 | "@typescript-eslint/naming-convention": [
11 | "warn",
12 | {
13 | "selector": "import",
14 | "format": ["camelCase", "PascalCase"]
15 | }
16 | ],
17 | "@typescript-eslint/semi": "off",
18 | "eqeqeq": "warn",
19 | "no-throw-literal": "warn",
20 | "semi": "off"
21 | },
22 | "ignorePatterns": ["out", "dist", "**/*.d.ts"]
23 | }
24 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/new-task.ts:
--------------------------------------------------------------------------------
1 | import { ToolArgs } from "./types"
2 |
3 | export function getNewTaskDescription(args: ToolArgs): string {
4 | return `## new_task
5 | Description: 使用指定的启动模式和初始消息创建新任务。此工具指示系统在给定模式下创建新的 Cline 实例,并提供初始消息。
6 |
7 | Parameters:
8 | - mode: (必填) 启动新任务的模式标识符(例如:"code"、"ask"、"architect")。
9 | - message: (必填) 此新任务的初始用户消息或指令。
10 |
11 | Usage:
12 |
13 | 在此填写模式标识符
14 | 在此填写初始指令
15 |
16 |
17 | Example:
18 |
19 | code
20 | 为应用程序实现新功能。
21 |
22 | `
23 | }
24 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/c.ts:
--------------------------------------------------------------------------------
1 | /*
2 | - struct declarations
3 | - union declarations
4 | - function declarations
5 | - typedef declarations
6 | */
7 | export default `
8 | (struct_specifier name: (type_identifier) @name.definition.class body:(_)) @definition.class
9 |
10 | (declaration type: (union_specifier name: (type_identifier) @name.definition.class)) @definition.class
11 |
12 | (function_declarator declarator: (identifier) @name.definition.function) @definition.function
13 |
14 | (type_definition declarator: (type_identifier) @name.definition.type) @definition.type
15 | `
16 |
--------------------------------------------------------------------------------
/src/core/prompts/sections/tool-use.ts:
--------------------------------------------------------------------------------
1 | export function getSharedToolUseSection(): string {
2 | return `====
3 |
4 | 工具使用
5 |
6 | 你可以使用一系列工具,这些工具将在用户批准后执行。每条消息你可以使用一个工具,并且在用户的回复中会收到该工具使用的结果。你需要一步一步地使用工具来完成给定的任务,每次工具的使用都应基于前一次工具使用的结果。
7 |
8 | # 工具使用格式
9 |
10 | 工具使用采用 XML 风格的标签格式。工具名称包含在开始和结束标签中,每个参数也同样包含在其自己的标签集中。结构如下:
11 |
12 |
13 | value1
14 | value2
15 | ...
16 |
17 |
18 | 例如:
19 |
20 |
21 | src/main.js
22 |
23 |
24 | 请始终遵循此格式以确保工具使用能够正确解析和执行。`
25 | }
26 |
--------------------------------------------------------------------------------
/src/__mocks__/serialize-error.js:
--------------------------------------------------------------------------------
1 | function serializeError(error) {
2 | if (error instanceof Error) {
3 | return {
4 | name: error.name,
5 | message: error.message,
6 | stack: error.stack,
7 | }
8 | }
9 | return error
10 | }
11 |
12 | function deserializeError(errorData) {
13 | if (errorData && typeof errorData === "object") {
14 | const error = new Error(errorData.message)
15 | error.name = errorData.name
16 | error.stack = errorData.stack
17 | return error
18 | }
19 | return errorData
20 | }
21 |
22 | module.exports = {
23 | serializeError,
24 | deserializeError,
25 | }
26 |
--------------------------------------------------------------------------------
/src/api/transform/stream.ts:
--------------------------------------------------------------------------------
1 | export type ApiStream = AsyncGenerator
2 | export type ApiStreamChunk = ApiStreamTextChunk | ApiStreamUsageChunk | ApiStreamReasoningChunk
3 |
4 | export interface ApiStreamTextChunk {
5 | type: "text"
6 | text: string
7 | }
8 |
9 | export interface ApiStreamReasoningChunk {
10 | type: "reasoning"
11 | text: string
12 | }
13 |
14 | export interface ApiStreamUsageChunk {
15 | type: "usage"
16 | inputTokens: number
17 | outputTokens: number
18 | cacheWriteTokens?: number
19 | cacheReadTokens?: number
20 | totalCost?: number // openrouter
21 | }
22 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/read-file.ts:
--------------------------------------------------------------------------------
1 | import { ToolArgs } from "./types"
2 |
3 | export function getReadFileDescription(args: ToolArgs): string {
4 | return `## read_file
5 | 描述:请求读取指定路径文件的内容。当您需要检查一个您不知道内容的现有文件时使用此工具,例如分析代码、查看文本文件或从配置文件中提取信息。输出内容会在每行前添加行号(例如:"1 | const x = 1"),这样在创建差异或讨论代码时更容易引用特定行。可以自动从PDF和DOCX文件中提取原始文本。可能不适用于其他类型的二进制文件,因为它会将原始内容作为字符串返回。
6 | 参数:
7 | - path:(必需)要读取的文件路径(相对于当前工作目录 ${args.cwd})
8 | 用法:
9 |
10 | 在此处填写文件路径
11 |
12 |
13 | 示例:请求读取frontend-config.json文件
14 |
15 | frontend-config.json
16 | `
17 | }
18 |
--------------------------------------------------------------------------------
/src/core/webview/getNonce.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A helper function that returns a unique alphanumeric identifier called a nonce.
3 | *
4 | * @remarks This function is primarily used to help enforce content security
5 | * policies for resources/scripts being executed in a webview context.
6 | *
7 | * @returns A nonce
8 | */
9 | export function getNonce() {
10 | let text = ""
11 | const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
12 | for (let i = 0; i < 32; i++) {
13 | text += possible.charAt(Math.floor(Math.random() * possible.length))
14 | }
15 | return text
16 | }
17 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/c-sharp.ts:
--------------------------------------------------------------------------------
1 | /*
2 | - class declarations
3 | - interface declarations
4 | - method declarations
5 | - namespace declarations
6 | */
7 | export default `
8 | (class_declaration
9 | name: (identifier) @name.definition.class
10 | ) @definition.class
11 |
12 | (interface_declaration
13 | name: (identifier) @name.definition.interface
14 | ) @definition.interface
15 |
16 | (method_declaration
17 | name: (identifier) @name.definition.method
18 | ) @definition.method
19 |
20 | (namespace_declaration
21 | name: (identifier) @name.definition.module
22 | ) @definition.module
23 | `
24 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/ask-followup-question.ts:
--------------------------------------------------------------------------------
1 | export function getAskFollowupQuestionDescription(): string {
2 | return `## ask_followup_question
3 | Description: 向用户提出问题以收集完成任务所需的额外信息。当您遇到模糊之处、需要澄清或需要更多细节以有效推进时,应使用此工具。它通过实现与用户的直接沟通来支持交互式问题解决。请谨慎使用此工具,在收集必要信息和避免过多来回交互之间保持平衡。
4 | Parameters:
5 | - question: (必填) 要向用户提出的问题。这应该是一个明确、具体的问题,针对您需要的信息。
6 | Usage:
7 |
8 | 在此处输入您的问题
9 |
10 |
11 | Example: 请求用户提供frontend-config.json文件的路径
12 |
13 | frontend-config.json文件的路径是什么?
14 | `
15 | }
16 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/list-code-definition-names.ts:
--------------------------------------------------------------------------------
1 | import { ToolArgs } from "./types"
2 |
3 | export function getListCodeDefinitionNamesDescription(args: ToolArgs): string {
4 | return `## list_code_definition_names
5 | Description: 请求列出指定目录中源代码文件顶层的定义名称(类、函数、方法等)。该工具提供代码库结构和重要构造的洞察,封装了对理解整体架构至关重要的高层概念和关系。
6 | Parameters:
7 | - path: (必需) 要列出顶层源代码定义的目录路径(相对于当前工作目录 ${args.cwd})。
8 | Usage:
9 |
10 | 在此处填写目录路径
11 |
12 |
13 | Example: 请求列出当前目录中所有顶层源代码定义
14 |
15 | .
16 | `
17 | }
18 |
--------------------------------------------------------------------------------
/webview-ui/src/components/common/VSCodeButtonLink.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
3 |
4 | interface VSCodeButtonLinkProps {
5 | href: string
6 | children: React.ReactNode
7 | [key: string]: any
8 | }
9 |
10 | const VSCodeButtonLink: React.FC = ({ href, children, ...props }) => {
11 | return (
12 |
18 | {children}
19 |
20 | )
21 | }
22 |
23 | export default VSCodeButtonLink
24 |
--------------------------------------------------------------------------------
/webview-ui/src/stories/AutosizeTextarea.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/react"
2 | import { AutosizeTextarea } from "../components/ui/autosize-textarea"
3 |
4 | const meta: Meta = {
5 | title: "Primitives/AutosizeTextarea",
6 | component: AutosizeTextarea,
7 | tags: ["autodocs"],
8 | args: {
9 | minHeight: 40,
10 | maxHeight: 400,
11 | placeholder: "This textarea will expand as you type.",
12 | className: "p-2",
13 | },
14 | }
15 |
16 | export default meta
17 |
18 | type Story = StoryObj
19 |
20 | export const Default: Story = {
21 | args: {},
22 | }
23 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files
5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files
6 | },
7 | "search.exclude": {
8 | "out": true, // set this to false to include "out" folder in search results
9 | "dist": true // set this to false to include "dist" folder in search results
10 | },
11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
12 | "typescript.tsc.autoDetect": "off"
13 | }
14 |
--------------------------------------------------------------------------------
/e2e/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "e2e",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "cd .. && npm run build",
7 | "compile": "tsc -p tsconfig.json",
8 | "lint": "eslint src --ext ts",
9 | "check-types": "tsc --noEmit",
10 | "test": "npm run compile && npx dotenvx run -f .env.integration -- node ./out/runTest.js",
11 | "ci": "npm run build && npm run test"
12 | },
13 | "devDependencies": {
14 | "@types/jest": "^29.5.14",
15 | "@types/mocha": "^10.0.10",
16 | "@vscode/test-cli": "^0.0.9",
17 | "@vscode/test-electron": "^2.4.1",
18 | "mocha": "^11.1.0",
19 | "typescript": "^5.4.5"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/hooks/useClipboard.ts:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 |
3 | export interface UseClipboardProps {
4 | timeout?: number
5 | }
6 |
7 | export function useClipboard({ timeout = 2000 }: UseClipboardProps = {}) {
8 | const [isCopied, setIsCopied] = useState(false)
9 |
10 | const copy = (value: string) => {
11 | if (typeof window === "undefined" || !navigator.clipboard?.writeText || !value) {
12 | return
13 | }
14 |
15 | navigator.clipboard.writeText(value).then(() => {
16 | setIsCopied(true)
17 | setTimeout(() => setIsCopied(false), timeout)
18 | })
19 | }
20 |
21 | return { isCopied, copy }
22 | }
23 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/list-files.ts:
--------------------------------------------------------------------------------
1 | import { ToolArgs } from "./types"
2 |
3 | export function getListFilesDescription(args: ToolArgs): string {
4 | return `## list_files
5 | 描述:请求列出指定目录中的文件和目录。如果recursive为true,将递归列出所有文件和目录;如果recursive为false或未提供,则仅列出顶层内容。请勿使用此工具来确认您可能已创建的文件是否存在,因为用户会告知您文件是否创建成功。
6 | 参数:
7 | - path:(必需)要列出内容的目录路径(相对于当前工作目录 ${args.cwd})
8 | - recursive:(可选)是否递归列出文件。使用true进行递归列出,使用false或省略则仅列出顶层内容。
9 | 用法:
10 |
11 | 在此处填写目录路径
12 | true或false(可选)
13 |
14 |
15 | 示例:请求列出当前目录中的所有文件
16 |
17 | .
18 | false
19 | `
20 | }
21 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/index.ts:
--------------------------------------------------------------------------------
1 | export { default as phpQuery } from "./php"
2 | export { default as typescriptQuery } from "./typescript"
3 | export { default as pythonQuery } from "./python"
4 | export { default as javascriptQuery } from "./javascript"
5 | export { default as javaQuery } from "./java"
6 | export { default as rustQuery } from "./rust"
7 | export { default as rubyQuery } from "./ruby"
8 | export { default as cppQuery } from "./cpp"
9 | export { default as cQuery } from "./c"
10 | export { default as csharpQuery } from "./c-sharp"
11 | export { default as goQuery } from "./go"
12 | export { default as swiftQuery } from "./swift"
13 |
--------------------------------------------------------------------------------
/src/core/mode-validator.ts:
--------------------------------------------------------------------------------
1 | import { Mode, isToolAllowedForMode, getModeConfig, ModeConfig, FileRestrictionError } from "../shared/modes"
2 | import { ToolName } from "../shared/tool-groups"
3 |
4 | export { isToolAllowedForMode }
5 | export type { ToolName }
6 |
7 | export function validateToolUse(
8 | toolName: ToolName,
9 | mode: Mode,
10 | customModes?: ModeConfig[],
11 | toolRequirements?: Record,
12 | toolParams?: Record,
13 | ): void {
14 | if (!isToolAllowedForMode(toolName, mode, customModes ?? [], toolRequirements, toolParams)) {
15 | throw new Error(`Tool "${toolName}" is not allowed in ${mode} mode.`)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/webview-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "baseUrl": ".",
19 | "paths": {
20 | "@/*": ["./src/*"]
21 | }
22 | },
23 | "include": ["src", "../src/shared"]
24 | }
25 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/execute-command.ts:
--------------------------------------------------------------------------------
1 | import { ToolArgs } from "./types"
2 |
3 | export function getExecuteCommandDescription(args: ToolArgs): string | undefined {
4 | return `## execute_command
5 | Description: 请求在系统上执行CLI命令。当您需要执行系统操作或运行特定命令来完成用户任务中的任何步骤时,请使用此工具。您必须根据用户的系统定制命令,并清楚地解释命令的功能。对于命令链接,请使用适合用户shell的链接语法。相比创建可执行脚本,更推荐执行复杂的CLI命令,因为它们更灵活且更容易运行。命令将在当前工作目录执行:${args.cwd}
6 | Parameters:
7 | - command: (必需) 要执行的CLI命令。这应该是适用于当前操作系统的有效命令。确保命令格式正确且不包含任何有害指令。
8 | Usage:
9 |
10 | 在此处输入您的命令
11 |
12 |
13 | Example: 请求执行 npm run dev
14 |
15 | npm run dev
16 | `
17 | }
18 |
--------------------------------------------------------------------------------
/webview-ui/src/stories/Slider.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/react"
2 |
3 | import { Slider } from "@/components/ui"
4 |
5 | const meta: Meta = {
6 | title: "Primitives/Slider",
7 | component: Slider,
8 | parameters: {
9 | layout: "centered",
10 | },
11 | args: {
12 | defaultValue: [50],
13 | max: 100,
14 | min: 0,
15 | step: 1,
16 | className: "w-[300px]",
17 | },
18 | tags: ["autodocs"],
19 | }
20 |
21 | export default meta
22 |
23 | type Story = StoryObj
24 |
25 | export const Default: Story = {
26 | args: {
27 | defaultValue: [50],
28 | max: 100,
29 | min: 0,
30 | step: 1,
31 | },
32 | }
33 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "nixpkgs": {
4 | "locked": {
5 | "lastModified": 1737569578,
6 | "narHash": "sha256-6qY0pk2QmUtBT9Mywdvif0i/CLVgpCjMUn6g9vB+f3M=",
7 | "owner": "nixos",
8 | "repo": "nixpkgs",
9 | "rev": "47addd76727f42d351590c905d9d1905ca895b82",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "nixos",
14 | "ref": "nixos-24.11",
15 | "repo": "nixpkgs",
16 | "type": "github"
17 | }
18 | },
19 | "root": {
20 | "inputs": {
21 | "nixpkgs": "nixpkgs"
22 | }
23 | }
24 | },
25 | "root": "root",
26 | "version": 7
27 | }
28 |
--------------------------------------------------------------------------------
/webview-ui/src/utils/format.ts:
--------------------------------------------------------------------------------
1 | export function formatLargeNumber(num: number): string {
2 | if (num >= 1e9) {
3 | return (num / 1e9).toFixed(1) + "b"
4 | }
5 | if (num >= 1e6) {
6 | return (num / 1e6).toFixed(1) + "m"
7 | }
8 | if (num >= 1e3) {
9 | return (num / 1e3).toFixed(1) + "k"
10 | }
11 | return num.toString()
12 | }
13 |
14 | export const formatDate = (timestamp: number) => {
15 | const date = new Date(timestamp)
16 | return date
17 | .toLocaleString("en-US", {
18 | month: "long",
19 | day: "numeric",
20 | hour: "numeric",
21 | minute: "2-digit",
22 | hour12: true,
23 | })
24 | .replace(", ", " ")
25 | .replace(" at", ",")
26 | .toUpperCase()
27 | }
28 |
--------------------------------------------------------------------------------
/src/__mocks__/@modelcontextprotocol/sdk/index.js:
--------------------------------------------------------------------------------
1 | const { Client } = require("./client/index.js")
2 | const { StdioClientTransport, StdioServerParameters } = require("./client/stdio.js")
3 | const {
4 | CallToolResultSchema,
5 | ListToolsResultSchema,
6 | ListResourcesResultSchema,
7 | ListResourceTemplatesResultSchema,
8 | ReadResourceResultSchema,
9 | ErrorCode,
10 | McpError,
11 | } = require("./types.js")
12 |
13 | module.exports = {
14 | Client,
15 | StdioClientTransport,
16 | StdioServerParameters,
17 | CallToolResultSchema,
18 | ListToolsResultSchema,
19 | ListResourcesResultSchema,
20 | ListResourceTemplatesResultSchema,
21 | ReadResourceResultSchema,
22 | ErrorCode,
23 | McpError,
24 | }
25 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/access-mcp-resource.ts:
--------------------------------------------------------------------------------
1 | import { ToolArgs } from "./types"
2 |
3 | export function getAccessMcpResourceDescription(args: ToolArgs): string | undefined {
4 | if (!args.mcpHub) {
5 | return undefined
6 | }
7 | return `## access_mcp_resource
8 | 描述:请求访问由已连接的MCP服务器提供的资源。资源代表可用作上下文的数据源,如文件、API响应或系统信息。
9 | 参数:
10 | - server_name:(必需)提供资源的MCP服务器名称
11 | - uri:(必需)用于标识要访问的特定资源的URI
12 | 使用方法:
13 |
14 | 在此填写服务器名称
15 | 在此填写资源URI
16 |
17 |
18 | 示例:请求访问MCP资源
19 |
20 |
21 | weather-server
22 | weather://san-francisco/current
23 | `
24 | }
25 |
--------------------------------------------------------------------------------
/src/shared/checkExistApiConfig.ts:
--------------------------------------------------------------------------------
1 | import { ApiConfiguration } from "../shared/api"
2 |
3 | export function checkExistKey(config: ApiConfiguration | undefined) {
4 | return config
5 | ? [
6 | config.apiKey,
7 | config.glamaApiKey,
8 | config.openRouterApiKey,
9 | config.awsRegion,
10 | config.vertexProjectId,
11 | config.openAiApiKey,
12 | config.ollamaModelId,
13 | config.lmStudioModelId,
14 | config.geminiApiKey,
15 | config.openAiNativeApiKey,
16 | config.deepSeekApiKey,
17 | config.mistralApiKey,
18 | config.vsCodeLmModelSelector,
19 | config.requestyApiKey,
20 | config.unboundApiKey,
21 | ].some((key) => key !== undefined)
22 | : false
23 | }
24 |
--------------------------------------------------------------------------------
/src/core/webview/getUri.ts:
--------------------------------------------------------------------------------
1 | import { Uri, Webview } from "vscode"
2 | /**
3 | * A helper function which will get the webview URI of a given file or resource.
4 | *
5 | * @remarks This URI can be used within a webview's HTML as a link to the
6 | * given file/resource.
7 | *
8 | * @param webview A reference to the extension webview
9 | * @param extensionUri The URI of the directory containing the extension
10 | * @param pathList An array of strings representing the path to a file/resource
11 | * @returns A URI pointing to the file/resource
12 | */
13 | export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) {
14 | return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList))
15 | }
16 |
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | branch="$(git rev-parse --abbrev-ref HEAD)"
2 |
3 | if [ "$branch" = "main" ]; then
4 | echo "You can't push directly to main - please check out a branch."
5 | exit 1
6 | fi
7 |
8 | npm run compile
9 |
10 | # Check for new changesets.
11 | NEW_CHANGESETS=$(find .changeset -name "*.md" ! -name "README.md" | wc -l | tr -d ' ')
12 | echo "Changeset files: $NEW_CHANGESETS"
13 |
14 | if [ "$NEW_CHANGESETS" == "0" ]; then
15 | echo "-------------------------------------------------------------------------------------"
16 | echo "Changes detected. Please run 'npm run changeset' to create a changeset if applicable."
17 | echo "-------------------------------------------------------------------------------------"
18 | fi
19 |
--------------------------------------------------------------------------------
/e2e/src/runTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path"
2 |
3 | import { runTests } from "@vscode/test-electron"
4 |
5 | async function main() {
6 | try {
7 | // The folder containing the Extension Manifest package.json
8 | // Passed to `--extensionDevelopmentPath`
9 | const extensionDevelopmentPath = path.resolve(__dirname, "../../")
10 |
11 | // The path to the extension test script
12 | // Passed to --extensionTestsPath
13 | const extensionTestsPath = path.resolve(__dirname, "./suite/index")
14 |
15 | // Download VS Code, unzip it and run the integration test
16 | await runTests({ extensionDevelopmentPath, extensionTestsPath })
17 | } catch {
18 | console.error("Failed to run tests")
19 | process.exit(1)
20 | }
21 | }
22 |
23 | main()
24 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/search-files.ts:
--------------------------------------------------------------------------------
1 | import { ToolArgs } from "./types"
2 |
3 | export function getSearchFilesDescription(args: ToolArgs): string {
4 | return `## search_files
5 | 描述:请求在指定目录中执行正则表达式搜索,提供上下文丰富的结果。此工具在多个文件中搜索模式或特定内容,显示每个匹配项及其上下文。
6 | 参数:
7 | - path:(必需)要搜索的目录路径(相对于当前工作目录 ${args.cwd})。将递归搜索此目录。
8 | - regex:(必需)要搜索的正则表达式模式。使用 Rust 正则表达式语法。
9 | - file_pattern:(可选)用于过滤文件的 Glob 模式(例如,'*.ts' 表示 TypeScript 文件)。如果未提供,将搜索所有文件 (*)。
10 | 用法:
11 |
12 | 在此处填写目录路径
13 | 在此处填写正则表达式模式
14 | 在此处填写文件模式(可选)
15 |
16 |
17 | 示例:请求搜索当前目录中的所有 .ts 文件
18 |
19 | .
20 | .*
21 | *.ts
22 | `
23 | }
24 |
--------------------------------------------------------------------------------
/webview-ui/src/__mocks__/vscrui.ts:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export const Checkbox = ({ children, checked, onChange }: any) =>
4 | React.createElement("div", { "data-testid": "mock-checkbox", onClick: onChange }, children)
5 |
6 | export const Dropdown = ({ children, value, onChange }: any) =>
7 | React.createElement("div", { "data-testid": "mock-dropdown", onClick: onChange }, children)
8 |
9 | export const Pane = ({ children }: any) => React.createElement("div", { "data-testid": "mock-pane" }, children)
10 |
11 | export const Button = ({ children, ...props }: any) =>
12 | React.createElement("div", { "data-testid": "mock-button", ...props }, children)
13 |
14 | export type DropdownOption = {
15 | label: string
16 | value: string
17 | }
18 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/go.ts:
--------------------------------------------------------------------------------
1 | /*
2 | - function declarations (with associated comments)
3 | - method declarations (with associated comments)
4 | - type specifications
5 | */
6 | export default `
7 | (
8 | (comment)* @doc
9 | .
10 | (function_declaration
11 | name: (identifier) @name.definition.function) @definition.function
12 | (#strip! @doc "^//\\s*")
13 | (#set-adjacent! @doc @definition.function)
14 | )
15 |
16 | (
17 | (comment)* @doc
18 | .
19 | (method_declaration
20 | name: (field_identifier) @name.definition.method) @definition.method
21 | (#strip! @doc "^//\\s*")
22 | (#set-adjacent! @doc @definition.method)
23 | )
24 |
25 | (type_spec
26 | name: (type_identifier) @name.definition.type) @definition.type
27 | `
28 |
--------------------------------------------------------------------------------
/knip.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/knip@latest/schema.json",
3 | "entry": ["src/extension.ts", "src/activate/index.ts", "webview-ui/src/index.tsx"],
4 | "project": ["src/**/*.ts", "webview-ui/src/**/*.{ts,tsx}"],
5 | "ignore": [
6 | "**/__mocks__/**",
7 | "**/__tests__/**",
8 | "**/test/**",
9 | "**/*.test.ts",
10 | "**/*.test.tsx",
11 | "**/stories/**",
12 | "coverage/**",
13 | "dist/**",
14 | "out/**",
15 | "bin/**",
16 | "src/activate/**",
17 | "src/exports/**",
18 | "src/extension.ts",
19 | "e2e/.vscode-test.mjs",
20 | "e2e/src/runTest.ts",
21 | "e2e/src/suite/index.ts"
22 | ],
23 | "workspaces": {
24 | "webview-ui": {
25 | "entry": ["src/index.tsx"],
26 | "project": ["src/**/*.{ts,tsx}"]
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/utils/logging/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Main entry point for the compact logging system
3 | * Provides a default logger instance with Jest environment detection
4 | */
5 |
6 | import { CompactLogger } from "./CompactLogger"
7 |
8 | /**
9 | * No-operation logger implementation for production environments
10 | */
11 | const noopLogger = {
12 | debug: () => {},
13 | info: () => {},
14 | warn: () => {},
15 | error: () => {},
16 | fatal: () => {},
17 | child: () => noopLogger,
18 | close: () => {},
19 | }
20 |
21 | /**
22 | * Default logger instance
23 | * Uses CompactLogger for normal operation, switches to noop logger in Jest test environment
24 | */
25 | export const logger = process.env.JEST_WORKER_ID !== undefined ? new CompactLogger() : noopLogger
26 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "Roo Code development environment";
3 |
4 | inputs = {
5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
6 | };
7 |
8 | outputs = { self, nixpkgs, ... }: let
9 | systems = [ "aarch64-darwin" "x86_64-linux" ];
10 |
11 | forAllSystems = nixpkgs.lib.genAttrs systems;
12 |
13 | mkDevShell = system: let
14 | pkgs = import nixpkgs { inherit system; };
15 | in pkgs.mkShell {
16 | name = "roo-code";
17 |
18 | packages = with pkgs; [
19 | zsh
20 | nodejs_18
21 | corepack_18
22 | ];
23 |
24 | shellHook = ''
25 | exec zsh
26 | '';
27 | };
28 | in {
29 | devShells = forAllSystems (system: {
30 | default = mkDevShell system;
31 | });
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/webview-ui/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | import "@testing-library/jest-dom"
2 |
3 | // Mock crypto.getRandomValues
4 | Object.defineProperty(window, "crypto", {
5 | value: {
6 | getRandomValues: function (buffer: Uint8Array) {
7 | for (let i = 0; i < buffer.length; i++) {
8 | buffer[i] = Math.floor(Math.random() * 256)
9 | }
10 | return buffer
11 | },
12 | },
13 | })
14 |
15 | // Mock matchMedia
16 | Object.defineProperty(window, "matchMedia", {
17 | writable: true,
18 | value: jest.fn().mockImplementation((query) => ({
19 | matches: false,
20 | media: query,
21 | onchange: null,
22 | addListener: jest.fn(), // deprecated
23 | removeListener: jest.fn(), // deprecated
24 | addEventListener: jest.fn(),
25 | removeEventListener: jest.fn(),
26 | dispatchEvent: jest.fn(),
27 | })),
28 | })
29 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Textarea = React.forwardRef>(
6 | ({ className, ...props }, ref) => {
7 | return (
8 |
16 | )
17 | },
18 | )
19 | Textarea.displayName = "Textarea"
20 |
21 | export { Textarea }
22 |
--------------------------------------------------------------------------------
/src/activate/handleUri.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 |
3 | import { ClineProvider } from "../core/webview/ClineProvider"
4 |
5 | export const handleUri = async (uri: vscode.Uri) => {
6 | const path = uri.path
7 | const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
8 | const visibleProvider = ClineProvider.getVisibleInstance()
9 |
10 | if (!visibleProvider) {
11 | return
12 | }
13 |
14 | switch (path) {
15 | case "/glama": {
16 | const code = query.get("code")
17 | if (code) {
18 | await visibleProvider.handleGlamaCallback(code)
19 | }
20 | break
21 | }
22 | case "/openrouter": {
23 | const code = query.get("code")
24 | if (code) {
25 | await visibleProvider.handleOpenRouterCallback(code)
26 | }
27 | break
28 | }
29 | default:
30 | break
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "esModuleInterop": true,
4 | "experimentalDecorators": true,
5 | "forceConsistentCasingInFileNames": true,
6 | "isolatedModules": true,
7 | "lib": ["es2022", "esnext.disposable", "DOM"],
8 | "module": "esnext",
9 | "moduleResolution": "Bundler",
10 | "noFallthroughCasesInSwitch": true,
11 | "noImplicitOverride": true,
12 | "noImplicitReturns": true,
13 | "noUnusedLocals": false,
14 | "resolveJsonModule": true,
15 | "rootDir": ".",
16 | "skipLibCheck": true,
17 | "sourceMap": true,
18 | "strict": true,
19 | "target": "es2022",
20 | "useDefineForClassFields": true,
21 | "useUnknownInCatchVariables": false
22 | },
23 | "include": ["src/**/*", "scripts/**/*", ".changeset/**/*"],
24 | "exclude": ["node_modules", ".vscode-test", "webview-ui"]
25 | }
26 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Separator = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
10 |
21 | ))
22 | Separator.displayName = SeparatorPrimitive.Root.displayName
23 |
24 | export { Separator }
25 |
--------------------------------------------------------------------------------
/src/core/prompts/sections/objective.ts:
--------------------------------------------------------------------------------
1 | export function getObjectiveSection(): string {
2 | return `====
3 |
4 | 目标
5 |
6 | 你需要通过迭代的方式完成给定的任务,将其分解为清晰的步骤并有条理地执行。
7 |
8 | 1. 分析用户的任务并设定清晰、可实现的目标。按照逻辑顺序对这些目标进行优先级排序。
9 | 2. 按顺序完成这些目标,根据需要逐一使用可用的工具。每个目标都应对应问题解决过程中的一个明确步骤。在执行过程中,你将了解到已完成的工作和剩余的任务。
10 | 3. 请记住,你拥有广泛的能力,可以访问各种工具,这些工具可以根据需要以强大和巧妙的方式用于完成每个目标。在调用工具之前,请在 标签内进行分析。首先,分析环境详情中提供的文件结构,以获得有效推进的背景和见解。然后,思考哪个提供的工具最适合完成用户的任务。接下来,检查相关工具的每个必需参数,确定用户是否直接提供或给出了足够的信息来推断参数值。在决定参数是否可以推断时,仔细考虑所有上下文是否支持特定值。如果所有必需的参数都存在或可以合理推断,则关闭思考标签并继续使用工具。但是,如果缺少某个必需参数的值,不要调用工具(甚至不要使用缺失参数的填充值),而是使用 ask_followup_question 工具请求用户提供缺失的参数。如果未提供可选参数的信息,不要询问更多信息。
11 | 4. 一旦完成用户的任务,你必须使用 attempt_completion 工具向用户展示任务的结果。你还可以提供一个命令行命令来展示你的任务结果;这在网页开发任务中特别有用,例如可以运行 \`open index.html\` 来显示你创建的网站。
12 | 5. 用户可能会提供反馈,你可以利用这些反馈进行改进并重试。但不要陷入无意义的来回对话中,即不要以问题或提供进一步帮助的方式结束你的回应。`
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/logging/__tests__/MockTransport.ts:
--------------------------------------------------------------------------------
1 | // __tests__/MockTransport.ts
2 | import { CompactTransport } from "../CompactTransport"
3 | import type { CompactLogEntry, CompactTransportConfig } from "../types"
4 |
5 | const TEST_CONFIG: CompactTransportConfig = {
6 | level: "fatal",
7 | fileOutput: {
8 | enabled: false,
9 | path: "",
10 | },
11 | }
12 |
13 | export class MockTransport extends CompactTransport {
14 | public entries: CompactLogEntry[] = []
15 | public closed = false
16 |
17 | constructor() {
18 | super(TEST_CONFIG)
19 | }
20 |
21 | override async write(entry: CompactLogEntry): Promise {
22 | this.entries.push(entry)
23 | }
24 |
25 | override async close(): Promise {
26 | this.closed = true
27 | await super.close()
28 | }
29 |
30 | clear(): void {
31 | this.entries = []
32 | this.closed = false
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/webview-ui/src/stories/Badge.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/react"
2 |
3 | import { Badge } from "@/components/ui"
4 |
5 | const meta: Meta = {
6 | title: "Primitives/Badge",
7 | component: Badge,
8 | tags: ["autodocs"],
9 | args: {
10 | children: "Badge",
11 | },
12 | }
13 |
14 | export default meta
15 |
16 | type Story = StoryObj
17 |
18 | export const Default: Story = {
19 | args: {
20 | children: "Default",
21 | },
22 | }
23 |
24 | export const Secondary: Story = {
25 | args: {
26 | variant: "secondary",
27 | children: "Secondary",
28 | },
29 | }
30 |
31 | export const Destructive: Story = {
32 | args: {
33 | variant: "destructive",
34 | children: "Destructive",
35 | },
36 | }
37 |
38 | export const Outline: Story = {
39 | args: {
40 | variant: "outline",
41 | children: "Outline",
42 | },
43 | }
44 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Input = React.forwardRef>(
6 | ({ className, type, ...props }, ref) => {
7 | return (
8 |
17 | )
18 | },
19 | )
20 | Input.displayName = "Input"
21 |
22 | export { Input }
23 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ProgressPrimitive from "@radix-ui/react-progress"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Progress = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, value, ...props }, ref) => (
10 |
14 |
18 |
19 | ))
20 | Progress.displayName = ProgressPrimitive.Root.displayName
21 |
22 | export { Progress }
23 |
--------------------------------------------------------------------------------
/src/core/prompts/sections/tool-use-guidelines.ts:
--------------------------------------------------------------------------------
1 | export function getToolUseGuidelinesSection(): string {
2 | return `# 工具使用指南
3 |
4 | 1. 在 标签中,评估你已有的信息和完成任务所需的信息。
5 | 2. 根据任务和提供的工具描述选择最合适的工具。评估是否需要额外信息来继续,以及哪些可用工具最适合收集这些信息。例如,使用 list_files 工具比在终端中运行 \`ls\` 命令更有效。重要的是要思考每个可用工具,并使用最适合任务当前步骤的工具。
6 | 3. 如果需要多个操作,每条消息使用一个工具来迭代完成任务,每次工具使用都应基于前一次工具使用的结果。不要假设任何工具使用的结果。每个步骤都必须基于前一步骤的结果。
7 | 4. 按照为每个工具指定的 XML 格式来使用工具。
8 | 5. 每次使用工具后,用户将回复该工具使用的结果。这个结果将为你提供继续任务或做出进一步决策所需的信息。这个回复可能包括:
9 | - 关于工具是否成功或失败的信息,以及失败的原因。
10 | - 由于你做出的更改而可能出现的代码检查错误,你需要解决这些错误。
11 | - 对更改的新终端输出,你可能需要考虑或采取行动。
12 | - 与工具使用相关的任何其他相关反馈或信息。
13 | 6. 在继续之前始终等待用户确认每次工具使用。在没有用户明确确认结果的情况下,切勿假设工具使用成功。
14 |
15 | 在继续任务之前,逐步等待用户在每次工具使用后的消息是至关重要的。这种方法使你能够:
16 | 1. 在继续之前确认每个步骤的成功。
17 | 2. 立即解决出现的任何问题或错误。
18 | 3. 根据新信息或意外结果调整方法。
19 | 4. 确保每个操作都正确地建立在前面的基础上。
20 |
21 | 通过等待并仔细考虑用户对每次工具使用的回复,你可以相应地做出反应并做出关于如何继续任务的明智决定。这个迭代过程有助于确保工作的整体成功和准确性。`
22 | }
23 |
--------------------------------------------------------------------------------
/webview-ui/src/components/history/CopyButton.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback } from "react"
2 |
3 | import { useClipboard } from "@/components/ui/hooks"
4 | import { Button } from "@/components/ui"
5 | import { cn } from "@/lib/utils"
6 |
7 | type CopyButtonProps = {
8 | itemTask: string
9 | }
10 |
11 | export const CopyButton = ({ itemTask }: CopyButtonProps) => {
12 | const { isCopied, copy } = useClipboard()
13 |
14 | const onCopy = useCallback(
15 | (e: React.MouseEvent) => {
16 | e.stopPropagation()
17 | !isCopied && copy(itemTask)
18 | },
19 | [isCopied, copy, itemTask],
20 | )
21 |
22 | return (
23 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/use-mcp-tool.ts:
--------------------------------------------------------------------------------
1 | import { ToolArgs } from "./types"
2 |
3 | export function getUseMcpToolDescription(args: ToolArgs): string | undefined {
4 | if (!args.mcpHub) {
5 | return undefined
6 | }
7 | return `## use_mcp_tool
8 | 描述:请求使用由已连接的MCP服务器提供的工具。每个MCP服务器可以提供具有不同功能的多个工具。工具具有定义的输入模式,用于指定必需和可选参数。
9 | 参数:
10 | - server_name:(必需)提供工具的MCP服务器名称
11 | - tool_name:(必需)要执行的工具名称
12 | - arguments:(必需)包含工具输入参数的JSON对象,遵循工具的输入模式
13 | 使用方法:
14 |
15 | 在此填写服务器名称
16 | 在此填写工具名称
17 |
18 | {
19 | "param1": "value1",
20 | "param2": "value2"
21 | }
22 |
23 |
24 |
25 | 示例:请求使用MCP工具
26 |
27 |
28 | weather-server
29 | get_forecast
30 |
31 | {
32 | "city": "San Francisco",
33 | "days": 5
34 | }
35 |
36 | `
37 | }
38 |
--------------------------------------------------------------------------------
/src/core/diff/DiffStrategy.ts:
--------------------------------------------------------------------------------
1 | import type { DiffStrategy } from "./types"
2 | import { UnifiedDiffStrategy } from "./strategies/unified"
3 | import { SearchReplaceDiffStrategy } from "./strategies/search-replace"
4 | import { NewUnifiedDiffStrategy } from "./strategies/new-unified"
5 | /**
6 | * Get the appropriate diff strategy for the given model
7 | * @param model The name of the model being used (e.g., 'gpt-4', 'claude-3-opus')
8 | * @returns The appropriate diff strategy for the model
9 | */
10 | export function getDiffStrategy(
11 | model: string,
12 | fuzzyMatchThreshold?: number,
13 | experimentalDiffStrategy: boolean = false,
14 | ): DiffStrategy {
15 | if (experimentalDiffStrategy) {
16 | return new NewUnifiedDiffStrategy(fuzzyMatchThreshold)
17 | }
18 | return new SearchReplaceDiffStrategy(fuzzyMatchThreshold)
19 | }
20 |
21 | export type { DiffStrategy }
22 | export { UnifiedDiffStrategy, SearchReplaceDiffStrategy }
23 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/chat/Chat.tsx:
--------------------------------------------------------------------------------
1 | import { HTMLAttributes } from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | import { ChatHandler } from "./types"
6 | import { ChatProvider } from "./ChatProvider"
7 | import { ChatMessages } from "./ChatMessages"
8 | import { ChatInput } from "./ChatInput"
9 |
10 | type ChatProps = HTMLAttributes & {
11 | assistantName: string
12 | handler: ChatHandler
13 | }
14 |
15 | export const Chat = ({ assistantName, handler, ...props }: ChatProps) => (
16 |
17 |
18 |
19 | )
20 |
21 | type InnerChatProps = HTMLAttributes
22 |
23 | const InnerChat = ({ className, children, ...props }: InnerChatProps) => (
24 |
25 |
26 | {children}
27 |
28 |
29 | )
30 |
--------------------------------------------------------------------------------
/.github/workflows/discord-pr-notify.yml:
--------------------------------------------------------------------------------
1 | name: Discord PR Notifier
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request_target:
6 | types: [opened]
7 |
8 | jobs:
9 | notify:
10 | runs-on: ubuntu-latest
11 | if: github.head_ref != 'changeset-release/main'
12 | steps:
13 | - name: Send Discord Notification
14 | run: |
15 | PAYLOAD=$(jq -n \
16 | --arg title "${{ github.event.pull_request.title }}" \
17 | --arg url "${{ github.event.pull_request.html_url }}" \
18 | --arg author "${{ github.event.pull_request.user.login }}" \
19 | '{
20 | content: ("🚀 **New PR:** " + $title + "\n🔗 <" + $url + ">\n👤 **Author:** " + $author),
21 | thread_name: ($title + " by " + $author)
22 | }')
23 |
24 | curl -X POST "${{ secrets.DISCORD_WEBHOOK }}" \
25 | -H "Content-Type: application/json" \
26 | -d "$PAYLOAD"
27 |
--------------------------------------------------------------------------------
/src/api/providers/deepseek.ts:
--------------------------------------------------------------------------------
1 | import { OpenAiHandler, OpenAiHandlerOptions } from "./openai"
2 | import { ModelInfo } from "../../shared/api"
3 | import { deepSeekModels, deepSeekDefaultModelId } from "../../shared/api"
4 |
5 | export class DeepSeekHandler extends OpenAiHandler {
6 | constructor(options: OpenAiHandlerOptions) {
7 | super({
8 | ...options,
9 | openAiApiKey: options.deepSeekApiKey ?? "not-provided",
10 | openAiModelId: options.apiModelId ?? deepSeekDefaultModelId,
11 | openAiBaseUrl: options.deepSeekBaseUrl ?? "https://api.deepseek.com/v1",
12 | openAiStreamingEnabled: true,
13 | includeMaxTokens: true,
14 | })
15 | }
16 |
17 | override getModel(): { id: string; info: ModelInfo } {
18 | const modelId = this.options.apiModelId ?? deepSeekDefaultModelId
19 | return {
20 | id: modelId,
21 | info: deepSeekModels[modelId as keyof typeof deepSeekModels] || deepSeekModels[deepSeekDefaultModelId],
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/shared/array.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns the index of the last element in the array where predicate is true, and -1
3 | * otherwise.
4 | * @param array The source array to search in
5 | * @param predicate find calls predicate once for each element of the array, in descending
6 | * order, until it finds one where predicate returns true. If such an element is found,
7 | * findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1.
8 | */
9 | export function findLastIndex(array: Array, predicate: (value: T, index: number, obj: T[]) => boolean): number {
10 | let l = array.length
11 | while (l--) {
12 | if (predicate(array[l], l, array)) {
13 | return l
14 | }
15 | }
16 | return -1
17 | }
18 |
19 | export function findLast(array: Array, predicate: (value: T, index: number, obj: T[]) => boolean): T | undefined {
20 | const index = findLastIndex(array, predicate)
21 | return index === -1 ? undefined : array[index]
22 | }
23 |
--------------------------------------------------------------------------------
/webview-ui/src/components/settings/ExperimentalFeature.tsx:
--------------------------------------------------------------------------------
1 | import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
2 |
3 | interface ExperimentalFeatureProps {
4 | name: string
5 | description: string
6 | enabled: boolean
7 | onChange: (value: boolean) => void
8 | }
9 |
10 | const ExperimentalFeature = ({ name, description, enabled, onChange }: ExperimentalFeatureProps) => {
11 | return (
12 |
13 |
14 | ⚠️
15 | onChange(e.target.checked)}>
16 | {name}
17 |
18 |
19 |
25 | {description}
26 |
27 |
28 | )
29 | }
30 |
31 | export default ExperimentalFeature
32 |
--------------------------------------------------------------------------------
/webview-ui/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from "path"
2 |
3 | import { defineConfig } from "vite"
4 | import react from "@vitejs/plugin-react"
5 | import tailwindcss from "@tailwindcss/vite"
6 |
7 | // https://vitejs.dev/config/
8 | export default defineConfig({
9 | plugins: [react(), tailwindcss()],
10 | resolve: {
11 | alias: {
12 | "@": path.resolve(__dirname, "./src"),
13 | },
14 | },
15 | build: {
16 | outDir: "build",
17 | rollupOptions: {
18 | output: {
19 | entryFileNames: `assets/[name].js`,
20 | chunkFileNames: `assets/[name].js`,
21 | assetFileNames: `assets/[name].[ext]`,
22 | },
23 | },
24 | },
25 | server: {
26 | hmr: {
27 | host: "localhost",
28 | protocol: "ws",
29 | },
30 | cors: {
31 | origin: "*",
32 | methods: "*",
33 | allowedHeaders: "*",
34 | },
35 | },
36 | define: {
37 | "process.platform": JSON.stringify(process.platform),
38 | "process.env.VSCODE_TEXTMATE_DEBUG": JSON.stringify(process.env.VSCODE_TEXTMATE_DEBUG),
39 | },
40 | })
41 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "runtimeExecutable": "${execPath}",
13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"],
14 | "sourceMaps": true,
15 | "outFiles": ["${workspaceFolder}/dist/**/*.js"],
16 | "preLaunchTask": "${defaultBuildTask}",
17 | "env": {
18 | "NODE_ENV": "development",
19 | "VSCODE_DEBUG_MODE": "true"
20 | },
21 | "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"],
22 | "presentation": {
23 | "hidden": false,
24 | "group": "tasks",
25 | "order": 1
26 | }
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/cpp.ts:
--------------------------------------------------------------------------------
1 | /*
2 | - struct declarations
3 | - union declarations
4 | - function declarations
5 | - method declarations (with namespace scope)
6 | - typedef declarations
7 | - class declarations
8 | */
9 | export default `
10 | (struct_specifier name: (type_identifier) @name.definition.class body:(_)) @definition.class
11 |
12 | (declaration type: (union_specifier name: (type_identifier) @name.definition.class)) @definition.class
13 |
14 | (function_declarator declarator: (identifier) @name.definition.function) @definition.function
15 |
16 | (function_declarator declarator: (field_identifier) @name.definition.function) @definition.function
17 |
18 | (function_declarator declarator: (qualified_identifier scope: (namespace_identifier) @scope name: (identifier) @name.definition.method)) @definition.method
19 |
20 | (type_definition declarator: (type_identifier) @name.definition.type) @definition.type
21 |
22 | (class_specifier name: (type_identifier) @name.definition.class) @definition.class
23 | `
24 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/attempt-completion.ts:
--------------------------------------------------------------------------------
1 | export function getAttemptCompletionDescription(): string {
2 | return `## attempt_completion
3 | Description: 在每次使用工具后,用户会对工具使用的结果进行响应,即说明是成功还是失败,以及失败的原因。当你收到工具使用的结果并确认任务已完成后,使用此工具向用户展示你的工作成果。你可以选择性地提供一个CLI命令来展示你的工作成果。如果用户对结果不满意,他们可能会提供反馈,你可以据此进行改进并重试。
4 | IMPORTANT NOTE: 在确认用户已确认之前的所有工具使用都成功之前,不能使用此工具。如果不这样做会导致代码损坏和系统故障。在使用此工具之前,你必须在 标签中问自己是否已经从用户那里确认之前的所有工具使用都成功了。如果没有,那么不要使用此工具。
5 | Parameters:
6 | - result: (必需) 任务的结果。以最终的方式表述结果,不需要用户进一步的输入。不要以问题或提供进一步帮助的方式结束你的结果。
7 | - command: (可选) 用于向用户展示结果实时演示的CLI命令。例如,使用 \`open index.html\` 来显示创建的html网站,或使用 \`open localhost:3000\` 来显示本地运行的开发服务器。但不要使用像 \`echo\` 或 \`cat\` 这样仅打印文本的命令。此命令应该对当前操作系统有效。确保命令格式正确且不包含任何有害指令。
8 | Usage:
9 |
10 |
11 | 在此处描述你的最终结果
12 |
13 | 用于演示结果的命令(可选)
14 |
15 |
16 | Example: 请求尝试完成,包含结果和命令
17 |
18 |
19 | 我已更新了CSS
20 |
21 | open index.html
22 | `
23 | }
24 |
--------------------------------------------------------------------------------
/src/utils/single-completion-handler.ts:
--------------------------------------------------------------------------------
1 | import { ApiConfiguration } from "../shared/api"
2 | import { buildApiHandler, SingleCompletionHandler } from "../api"
3 |
4 | /**
5 | * Enhances a prompt using the configured API without creating a full Cline instance or task history.
6 | * This is a lightweight alternative that only uses the API's completion functionality.
7 | */
8 | export async function singleCompletionHandler(apiConfiguration: ApiConfiguration, promptText: string): Promise {
9 | if (!promptText) {
10 | throw new Error("No prompt text provided")
11 | }
12 | if (!apiConfiguration || !apiConfiguration.apiProvider) {
13 | throw new Error("No valid API configuration provided")
14 | }
15 |
16 | const handler = buildApiHandler(apiConfiguration)
17 |
18 | // Check if handler supports single completions
19 | if (!("completePrompt" in handler)) {
20 | throw new Error("The selected API provider does not support prompt enhancement")
21 | }
22 |
23 | return (handler as SingleCompletionHandler).completePrompt(promptText)
24 | }
25 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/chat/types.ts:
--------------------------------------------------------------------------------
1 | export interface Message {
2 | role: "system" | "user" | "assistant" | "data"
3 | content: string
4 | annotations?: MessageAnnotation[]
5 | }
6 |
7 | export type ChatHandler = {
8 | isLoading: boolean
9 | setIsLoading: (isLoading: boolean, message?: string) => void
10 |
11 | loadingMessage?: string
12 | setLoadingMessage?: (message: string) => void
13 |
14 | input: string
15 | setInput: (input: string) => void
16 |
17 | messages: Message[]
18 |
19 | reload?: (options?: { data?: any }) => void
20 | stop?: () => void
21 | append: (message: Message, options?: { data?: any }) => Promise
22 | reset?: () => void
23 | }
24 |
25 | export enum MessageAnnotationType {
26 | BADGE = "badge",
27 | }
28 |
29 | export type BadgeData = {
30 | label: string
31 | variant?: "default" | "secondary" | "destructive" | "outline"
32 | }
33 |
34 | export type AnnotationData = BadgeData
35 |
36 | export type MessageAnnotation = {
37 | type: MessageAnnotationType
38 | data: AnnotationData
39 | }
40 |
--------------------------------------------------------------------------------
/src/core/diff/insert-groups.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Inserts multiple groups of elements at specified indices in an array
3 | * @param original Array to insert into, split by lines
4 | * @param insertGroups Array of groups to insert, each with an index and elements to insert
5 | * @returns New array with all insertions applied
6 | */
7 | export interface InsertGroup {
8 | index: number
9 | elements: string[]
10 | }
11 |
12 | export function insertGroups(original: string[], insertGroups: InsertGroup[]): string[] {
13 | // Sort groups by index to maintain order
14 | insertGroups.sort((a, b) => a.index - b.index)
15 |
16 | let result: string[] = []
17 | let lastIndex = 0
18 |
19 | insertGroups.forEach(({ index, elements }) => {
20 | // Add elements from original array up to insertion point
21 | result.push(...original.slice(lastIndex, index))
22 | // Add the group of elements
23 | result.push(...elements)
24 | lastIndex = index
25 | })
26 |
27 | // Add remaining elements from original array
28 | result.push(...original.slice(lastIndex))
29 |
30 | return result
31 | }
32 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/typescript.ts:
--------------------------------------------------------------------------------
1 | /*
2 | - function signatures and declarations
3 | - method signatures and definitions
4 | - abstract method signatures
5 | - class declarations (including abstract classes)
6 | - module declarations
7 | */
8 | export default `
9 | (function_signature
10 | name: (identifier) @name.definition.function) @definition.function
11 |
12 | (method_signature
13 | name: (property_identifier) @name.definition.method) @definition.method
14 |
15 | (abstract_method_signature
16 | name: (property_identifier) @name.definition.method) @definition.method
17 |
18 | (abstract_class_declaration
19 | name: (type_identifier) @name.definition.class) @definition.class
20 |
21 | (module
22 | name: (identifier) @name.definition.module) @definition.module
23 |
24 | (function_declaration
25 | name: (identifier) @name.definition.function) @definition.function
26 |
27 | (method_definition
28 | name: (property_identifier) @name.definition.method) @definition.method
29 |
30 | (class_declaration
31 | name: (type_identifier) @name.definition.class) @definition.class
32 | `
33 |
--------------------------------------------------------------------------------
/src/integrations/terminal/__tests__/TerminalRegistry.test.ts:
--------------------------------------------------------------------------------
1 | // npx jest src/integrations/terminal/__tests__/TerminalRegistry.test.ts
2 |
3 | import { TerminalRegistry } from "../TerminalRegistry"
4 |
5 | // Mock vscode.window.createTerminal
6 | const mockCreateTerminal = jest.fn()
7 | jest.mock("vscode", () => ({
8 | window: {
9 | createTerminal: (...args: any[]) => {
10 | mockCreateTerminal(...args)
11 | return {
12 | exitStatus: undefined,
13 | }
14 | },
15 | },
16 | ThemeIcon: jest.fn(),
17 | }))
18 |
19 | describe("TerminalRegistry", () => {
20 | beforeEach(() => {
21 | mockCreateTerminal.mockClear()
22 | })
23 |
24 | describe("createTerminal", () => {
25 | it("creates terminal with PAGER set to cat", () => {
26 | TerminalRegistry.createTerminal("/test/path")
27 |
28 | expect(mockCreateTerminal).toHaveBeenCalledWith({
29 | cwd: "/test/path",
30 | name: "Roo Code",
31 | iconPath: expect.any(Object),
32 | env: {
33 | PAGER: "cat",
34 | PROMPT_COMMAND: "sleep 0.050",
35 | VTE_VERSION: "0",
36 | },
37 | })
38 | })
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/webview-ui/src/stories/Collapsible.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/react"
2 | import { useState } from "react"
3 |
4 | import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../components/ui/collapsible"
5 | import { Button } from "../components/ui/button"
6 | import { ChevronsUpDown } from "lucide-react"
7 |
8 | const meta: Meta = {
9 | title: "Primitives/Collapsible",
10 | component: Collapsible,
11 | tags: ["autodocs"],
12 | }
13 |
14 | export default meta
15 |
16 | type Story = StoryObj
17 |
18 | export const Default: Story = {
19 | render: () => ,
20 | }
21 |
22 | const CollapsibleDemo = () => {
23 | const [isOpen, setIsOpen] = useState(false)
24 |
25 | return (
26 |
27 |
28 |
32 |
33 | 👋
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/src/utils/cost.ts:
--------------------------------------------------------------------------------
1 | import { ModelInfo } from "../shared/api"
2 |
3 | export function calculateApiCost(
4 | modelInfo: ModelInfo,
5 | inputTokens: number,
6 | outputTokens: number,
7 | cacheCreationInputTokens?: number,
8 | cacheReadInputTokens?: number,
9 | ): number {
10 | const modelCacheWritesPrice = modelInfo.cacheWritesPrice
11 | let cacheWritesCost = 0
12 | if (cacheCreationInputTokens && modelCacheWritesPrice) {
13 | cacheWritesCost = (modelCacheWritesPrice / 1_000_000) * cacheCreationInputTokens
14 | }
15 | const modelCacheReadsPrice = modelInfo.cacheReadsPrice
16 | let cacheReadsCost = 0
17 | if (cacheReadInputTokens && modelCacheReadsPrice) {
18 | cacheReadsCost = (modelCacheReadsPrice / 1_000_000) * cacheReadInputTokens
19 | }
20 | const baseInputCost = ((modelInfo.inputPrice || 0) / 1_000_000) * inputTokens
21 | const outputCost = ((modelInfo.outputPrice || 0) / 1_000_000) * outputTokens
22 | const totalCost = cacheWritesCost + cacheReadsCost + baseInputCost + outputCost
23 | return totalCost
24 | }
25 |
26 | export const parseApiPrice = (price: any) => (price ? parseFloat(price) * 1_000_000 : undefined)
27 |
--------------------------------------------------------------------------------
/ellipsis.yaml:
--------------------------------------------------------------------------------
1 | version: 1.3
2 | pr_review:
3 |
4 | # Modify confidence_threshold to show fewer/more comments. Increase this to show fewer, but higher quality comments.
5 | # If there’s too much noise, we suggest 0.9. The default value is 0.7.
6 | confidence_threshold: 0.7
7 |
8 | # If quiet mode is enabled, Ellipsis will only leave reviews when it has comments, so “Looks good to me” reviews
9 | # will be skipped. This can reduce clutter.
10 | quiet: true
11 |
12 | # You can disable automatic code review using auto_review_enabled. This will override any global settings you
13 | # have configured via the web UI.
14 | auto_review_enabled: true
15 |
16 | # You can enable auto-review on draft PRs using auto_review_draft. This will override any global settings you
17 | # have configured via the web UI.
18 | auto_review_draft: false
19 |
20 | # You can allow Ellipsis to approve PRs using enable_approve_prs. Note: in common branch GitHub protection configurations,
21 | # the Ellipsis approval will count towards the approval total and allow the PR to be merged when it otherwise may not be.
22 | enable_approve_prs: false
23 |
--------------------------------------------------------------------------------
/src/core/prompts/sections/system-info.ts:
--------------------------------------------------------------------------------
1 | import defaultShell from "default-shell"
2 | import os from "os"
3 | import osName from "os-name"
4 | import { Mode, ModeConfig, getModeBySlug, defaultModeSlug, isToolAllowedForMode } from "../../../shared/modes"
5 | import { getShell } from "../../../utils/shell"
6 |
7 | export function getSystemInfoSection(cwd: string, currentMode: Mode, customModes?: ModeConfig[]): string {
8 | const findModeBySlug = (slug: string, modes?: ModeConfig[]) => modes?.find((m) => m.slug === slug)
9 |
10 | const currentModeName = findModeBySlug(currentMode, customModes)?.name || currentMode
11 | const codeModeName = findModeBySlug(defaultModeSlug, customModes)?.name || "Code"
12 |
13 | let details = `====
14 |
15 | 系统信息
16 |
17 | 操作系统:${osName()}
18 | 默认Shell:${getShell()}
19 | 主目录:${os.homedir().toPosix()}
20 | 当前工作目录:${cwd.toPosix()}
21 |
22 | 当用户最初给你一个任务时,当前工作目录('/test/path')中所有文件路径的递归列表将包含在environment_details中。这提供了项目文件结构的概览,通过目录/文件名(开发人员如何概念化和组织他们的代码)和文件扩展名(使用的编程语言)提供了项目的关键信息。这也可以指导决定需要进一步探索哪些文件。如果你需要探索当前工作目录之外的目录,你可以使用list_files工具。如果为recursive参数传递'true',它将递归列出文件。否则,它将只列出顶层文件,这更适合于不一定需要嵌套结构的通用目录,比如桌面。`
23 |
24 | return details
25 | }
26 |
--------------------------------------------------------------------------------
/webview-ui/src/utils/highlight.ts:
--------------------------------------------------------------------------------
1 | export function highlightFzfMatch(
2 | text: string,
3 | positions: number[],
4 | highlightClassName: string = "history-item-highlight",
5 | ) {
6 | if (!positions.length) return text
7 |
8 | const parts: { text: string; highlight: boolean }[] = []
9 | let lastIndex = 0
10 |
11 | // Sort positions to ensure we process them in order
12 | positions.sort((a, b) => a - b)
13 |
14 | positions.forEach((pos) => {
15 | // Add non-highlighted text before this position
16 | if (pos > lastIndex) {
17 | parts.push({
18 | text: text.substring(lastIndex, pos),
19 | highlight: false,
20 | })
21 | }
22 |
23 | // Add highlighted character
24 | parts.push({
25 | text: text[pos],
26 | highlight: true,
27 | })
28 |
29 | lastIndex = pos + 1
30 | })
31 |
32 | // Add any remaining text
33 | if (lastIndex < text.length) {
34 | parts.push({
35 | text: text.substring(lastIndex),
36 | highlight: false,
37 | })
38 | }
39 |
40 | // Build final string
41 | return parts
42 | .map((part) => (part.highlight ? `${part.text}` : part.text))
43 | .join("")
44 | }
45 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/write-to-file.ts:
--------------------------------------------------------------------------------
1 | import { ToolArgs } from "./types"
2 |
3 | export function getWriteToFileDescription(args: ToolArgs): string {
4 | return `## write_to_file
5 | 描述:请求将完整内容写入指定路径的文件。如果文件已存在,将用提供的内容覆盖它。如果文件不存在,将创建新文件。此工具会自动创建写入文件所需的所有目录。
6 | 参数:
7 | - path:(必需)要写入的文件路径(相对于当前工作目录 ${args.cwd})
8 | - content:(必需)要写入文件的内容。始终提供文件的完整预期内容,不要有任何截断或遗漏。您必须包含文件的所有部分,即使它们没有被修改。但不要在内容中包含行号,只需提供文件的实际内容。
9 | - line_count:(必需)文件中的行数。确保根据文件的实际内容计算行数,而不是根据您提供的内容中的行数计算。
10 | 用法:
11 |
12 | 在此处填写文件路径
13 |
14 | 在此处填写文件内容
15 |
16 | 文件的总行数,包括空行
17 |
18 |
19 | 示例:请求写入 frontend-config.json
20 |
21 | frontend-config.json
22 |
23 | {
24 | "apiEndpoint": "https://api.example.com",
25 | "theme": {
26 | "primaryColor": "#007bff",
27 | "secondaryColor": "#6c757d",
28 | "fontFamily": "Arial, sans-serif"
29 | },
30 | "features": {
31 | "darkMode": true,
32 | "notifications": true,
33 | "analytics": false
34 | },
35 | "version": "1.0.0"
36 | }
37 |
38 | 14
39 | `
40 | }
41 |
--------------------------------------------------------------------------------
/src/__mocks__/@modelcontextprotocol/sdk/types.js:
--------------------------------------------------------------------------------
1 | const CallToolResultSchema = {
2 | parse: jest.fn().mockReturnValue({}),
3 | }
4 |
5 | const ListToolsResultSchema = {
6 | parse: jest.fn().mockReturnValue({
7 | tools: [],
8 | }),
9 | }
10 |
11 | const ListResourcesResultSchema = {
12 | parse: jest.fn().mockReturnValue({
13 | resources: [],
14 | }),
15 | }
16 |
17 | const ListResourceTemplatesResultSchema = {
18 | parse: jest.fn().mockReturnValue({
19 | resourceTemplates: [],
20 | }),
21 | }
22 |
23 | const ReadResourceResultSchema = {
24 | parse: jest.fn().mockReturnValue({
25 | contents: [],
26 | }),
27 | }
28 |
29 | const ErrorCode = {
30 | InvalidRequest: "InvalidRequest",
31 | MethodNotFound: "MethodNotFound",
32 | InvalidParams: "InvalidParams",
33 | InternalError: "InternalError",
34 | }
35 |
36 | class McpError extends Error {
37 | constructor(code, message) {
38 | super(message)
39 | this.code = code
40 | }
41 | }
42 |
43 | module.exports = {
44 | CallToolResultSchema,
45 | ListToolsResultSchema,
46 | ListResourcesResultSchema,
47 | ListResourceTemplatesResultSchema,
48 | ReadResourceResultSchema,
49 | ErrorCode,
50 | McpError,
51 | }
52 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-xs border border-transparent shadow px-2 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-primary text-primary-foreground hover:bg-primary/80",
12 | secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
13 | destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/80",
14 | outline: "text-muted-foreground border-vscode-input-border",
15 | },
16 | },
17 | defaultVariants: {
18 | variant: "default",
19 | },
20 | },
21 | )
22 |
23 | export interface BadgeProps extends React.HTMLAttributes, VariantProps {}
24 |
25 | function Badge({ className, variant, ...props }: BadgeProps) {
26 | return
27 | }
28 |
29 | export { Badge, badgeVariants }
30 |
--------------------------------------------------------------------------------
/webview-ui/src/components/mcp/McpEnabledToggle.tsx:
--------------------------------------------------------------------------------
1 | import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
2 | import { FormEvent } from "react"
3 | import { useExtensionState } from "../../context/ExtensionStateContext"
4 | import { vscode } from "../../utils/vscode"
5 |
6 | const McpEnabledToggle = () => {
7 | const { mcpEnabled, setMcpEnabled } = useExtensionState()
8 |
9 | const handleChange = (e: Event | FormEvent) => {
10 | const target = ("target" in e ? e.target : null) as HTMLInputElement | null
11 | if (!target) return
12 | setMcpEnabled(target.checked)
13 | vscode.postMessage({ type: "mcpEnabled", bool: target.checked })
14 | }
15 |
16 | return (
17 |
18 |
19 | 启用 MCP 服务器
20 |
21 |
27 | 启用后,Roo 将能够与 MCP 服务器交互以获得高级功能。如果你不使用 MCP,可以禁用此功能以减少 Roo
28 | 的令牌使用量。
29 |
30 |
31 | )
32 | }
33 |
34 | export default McpEnabledToggle
35 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SliderPrimitive from "@radix-ui/react-slider"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Slider = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
14 |
15 |
16 |
17 |
18 |
19 | ))
20 | Slider.displayName = SliderPrimitive.Root.displayName
21 |
22 | export { Slider }
23 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/insert-content.ts:
--------------------------------------------------------------------------------
1 | import { ToolArgs } from "./types"
2 |
3 | export function getInsertContentDescription(args: ToolArgs): string {
4 | return `## insert_content
5 | Description: 在文件的特定行位置插入内容。这是添加新内容和代码(函数/方法/类/导入语句等)的主要工具,因为它允许精确插入而不会覆盖现有内容。该工具使用高效的基于行的插入系统,保持文件完整性和多个插入操作的正确顺序。注意使用正确的缩进。这是向文件添加新内容和代码的首选方式。
6 | Parameters:
7 | - path: (必需) 要插入内容的文件路径(相对于当前工作目录 ${args.cwd.toPosix()})
8 | - operations: (必需) 插入操作的JSON数组。每个操作是一个包含以下字段的对象:
9 | * start_line: (必需) 要插入内容的行号。当前在该行的内容将会移到插入内容的下方。
10 | * content: (必需) 要在指定位置插入的内容。重要提示:如果内容是单行的,可以是字符串。如果是多行内容,应该是包含换行符(\n)的字符串。确保包含正确的缩进。
11 | Usage:
12 |
13 | 在此处填写文件路径
14 | [
15 | {
16 | "start_line": 10,
17 | "content": "在此处填写您的内容"
18 | }
19 | ]
20 |
21 | Example: 插入一个新函数及其导入语句
22 |
23 | 在此处填写文件路径
24 | [
25 | {
26 | "start_line": 1,
27 | "content": "import { sum } from './utils';"
28 | },
29 | {
30 | "start_line": 10,
31 | "content": "function calculateTotal(items: number[]): number {\n return items.reduce((sum, item) => sum + item, 0);\n}"
32 | }
33 | ]
34 | `
35 | }
36 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/chat/ChatMessages.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react"
2 | import { Virtuoso, VirtuosoHandle } from "react-virtuoso"
3 |
4 | import { useChatUI } from "./useChatUI"
5 | import { ChatMessage } from "./ChatMessage"
6 |
7 | export function ChatMessages() {
8 | const { messages, isLoading, append } = useChatUI()
9 | const messageCount = messages.length
10 | const virtuoso = useRef(null)
11 |
12 | useEffect(() => {
13 | if (!virtuoso.current) {
14 | return
15 | }
16 |
17 | requestAnimationFrame(() =>
18 | virtuoso.current?.scrollToIndex({ index: messageCount - 1, align: "end", behavior: "smooth" }),
19 | )
20 | }, [messageCount])
21 |
22 | return (
23 | (
28 |
38 | )}
39 | />
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/swift.ts:
--------------------------------------------------------------------------------
1 | /*
2 | - class declarations
3 | - method declarations (including initializers and deinitializers)
4 | - property declarations
5 | - function declarations
6 | */
7 | export default `
8 | (class_declaration
9 | name: (type_identifier) @name) @definition.class
10 |
11 | (protocol_declaration
12 | name: (type_identifier) @name) @definition.interface
13 |
14 | (class_declaration
15 | (class_body
16 | [
17 | (function_declaration
18 | name: (simple_identifier) @name
19 | )
20 | (subscript_declaration
21 | (parameter (simple_identifier) @name)
22 | )
23 | (init_declaration "init" @name)
24 | (deinit_declaration "deinit" @name)
25 | ]
26 | )
27 | ) @definition.method
28 |
29 | (class_declaration
30 | (class_body
31 | [
32 | (property_declaration
33 | (pattern (simple_identifier) @name)
34 | )
35 | ]
36 | )
37 | ) @definition.property
38 |
39 | (property_declaration
40 | (pattern (simple_identifier) @name)
41 | ) @definition.property
42 |
43 | (function_declaration
44 | name: (simple_identifier) @name) @definition.function
45 | `
46 |
--------------------------------------------------------------------------------
/webview-ui/src/utils/useDebounceEffect.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react"
2 |
3 | type VoidFn = () => void
4 |
5 | /**
6 | * Runs `effectRef.current()` after `delay` ms whenever any of the `deps` change,
7 | * but cancels/re-schedules if they change again before the delay.
8 | */
9 | export function useDebounceEffect(effect: VoidFn, delay: number, deps: any[]) {
10 | const callbackRef = useRef(effect)
11 | const timeoutRef = useRef(null)
12 |
13 | // Keep callbackRef current
14 | useEffect(() => {
15 | callbackRef.current = effect
16 | }, [effect])
17 |
18 | useEffect(() => {
19 | // Clear any queued call
20 | if (timeoutRef.current) {
21 | clearTimeout(timeoutRef.current)
22 | }
23 |
24 | // Schedule a new call
25 | timeoutRef.current = setTimeout(() => {
26 | // always call the *latest* version of effect
27 | callbackRef.current()
28 | }, delay)
29 |
30 | // Cleanup on unmount or next effect
31 | return () => {
32 | if (timeoutRef.current) {
33 | clearTimeout(timeoutRef.current)
34 | }
35 | }
36 |
37 | // We want to re‐schedule if any item in `deps` changed,
38 | // or if `delay` changed.
39 |
40 | // eslint-disable-next-line react-hooks/exhaustive-deps
41 | }, [delay, ...deps])
42 | }
43 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | # Default
2 | .github/**
3 | .husky/**
4 | .vscode/**
5 | .vscode-test/**
6 | out/**
7 | out-integration/**
8 | e2e/**
9 | node_modules/**
10 | src/**
11 | .gitignore
12 | .yarnrc
13 | esbuild.js
14 | vsc-extension-quickstart.md
15 | **/tsconfig.json
16 | **/.eslintrc.json
17 | **/*.map
18 | **/*.ts
19 | **/.vscode-test.*
20 |
21 | # Custom
22 | demo.gif
23 | .nvmrc
24 | .gitattributes
25 | .prettierignore
26 | .clinerules*
27 | .roomodes
28 | cline_docs/**
29 | coverage/**
30 |
31 | # Ignore all webview-ui files except the build directory (https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-react-cra/.vscodeignore)
32 | webview-ui/src/**
33 | webview-ui/public/**
34 | webview-ui/scripts/**
35 | webview-ui/index.html
36 | webview-ui/README.md
37 | webview-ui/package.json
38 | webview-ui/package-lock.json
39 | webview-ui/node_modules/**
40 | **/.gitignore
41 |
42 | # Fix issue where codicons don't get packaged (https://github.com/microsoft/vscode-extension-samples/issues/692)
43 | !node_modules/@vscode/codicons/dist/codicon.css
44 | !node_modules/@vscode/codicons/dist/codicon.ttf
45 |
46 | # Include default themes JSON files used in getTheme
47 | !src/integrations/theme/default-themes/**
48 |
49 | # Include icons
50 | !assets/icons/**
51 |
--------------------------------------------------------------------------------
/webview-ui/src/components/chat/checkpoints/CheckpointSaved.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from "react"
2 |
3 | import { CheckpointMenu } from "./CheckpointMenu"
4 | import { checkpointSchema } from "./schema"
5 |
6 | type CheckpointSavedProps = {
7 | ts: number
8 | commitHash: string
9 | currentHash?: string
10 | checkpoint?: Record
11 | }
12 |
13 | export const CheckpointSaved = ({ checkpoint, ...props }: CheckpointSavedProps) => {
14 | const isCurrent = props.currentHash === props.commitHash
15 |
16 | const metadata = useMemo(() => {
17 | if (!checkpoint) {
18 | return undefined
19 | }
20 |
21 | const result = checkpointSchema.safeParse(checkpoint)
22 |
23 | if (!result.success) {
24 | return undefined
25 | }
26 |
27 | return result.data
28 | }, [checkpoint])
29 |
30 | if (!metadata) {
31 | return null
32 | }
33 |
34 | return (
35 |
36 |
37 |
38 | {metadata.isFirst ? "Initial Checkpoint" : "Checkpoint"}
39 | {isCurrent && Current}
40 |
41 |
42 |
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/src/integrations/misc/process-images.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 | import fs from "fs/promises"
3 | import * as path from "path"
4 |
5 | export async function selectImages(): Promise {
6 | const options: vscode.OpenDialogOptions = {
7 | canSelectMany: true,
8 | openLabel: "Select",
9 | filters: {
10 | Images: ["png", "jpg", "jpeg", "webp"], // supported by anthropic and openrouter
11 | },
12 | }
13 |
14 | const fileUris = await vscode.window.showOpenDialog(options)
15 |
16 | if (!fileUris || fileUris.length === 0) {
17 | return []
18 | }
19 |
20 | return await Promise.all(
21 | fileUris.map(async (uri) => {
22 | const imagePath = uri.fsPath
23 | const buffer = await fs.readFile(imagePath)
24 | const base64 = buffer.toString("base64")
25 | const mimeType = getMimeType(imagePath)
26 | const dataUrl = `data:${mimeType};base64,${base64}`
27 | return dataUrl
28 | }),
29 | )
30 | }
31 |
32 | function getMimeType(filePath: string): string {
33 | const ext = path.extname(filePath).toLowerCase()
34 | switch (ext) {
35 | case ".png":
36 | return "image/png"
37 | case ".jpeg":
38 | case ".jpg":
39 | return "image/jpeg"
40 | case ".webp":
41 | return "image/webp"
42 | default:
43 | throw new Error(`Unsupported file type: ${ext}`)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Context
2 |
3 |
4 |
5 | ## Implementation
6 |
7 |
12 |
13 | ## Screenshots
14 |
15 | | before | after |
16 | | ------ | ----- |
17 | | | |
18 |
19 | ## How to Test
20 |
21 |
32 |
33 | ## Get in Touch
34 |
35 |
36 |
--------------------------------------------------------------------------------
/webview-ui/src/utils/path-mentions.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Utilities for handling path-related operations in mentions
3 | */
4 |
5 | /**
6 | * Converts an absolute path to a mention-friendly path
7 | * If the provided path starts with the current working directory,
8 | * it's converted to a relative path prefixed with @
9 | *
10 | * @param path The path to convert
11 | * @param cwd The current working directory
12 | * @returns A mention-friendly path
13 | */
14 | export function convertToMentionPath(path: string, cwd?: string): string {
15 | const normalizedPath = path.replace(/\\/g, "/")
16 | let normalizedCwd = cwd ? cwd.replace(/\\/g, "/") : ""
17 |
18 | if (!normalizedCwd) {
19 | return path
20 | }
21 |
22 | // Remove trailing slash from cwd if it exists
23 | if (normalizedCwd.endsWith("/")) {
24 | normalizedCwd = normalizedCwd.slice(0, -1)
25 | }
26 |
27 | // Always use case-insensitive comparison for path matching
28 | const lowerPath = normalizedPath.toLowerCase()
29 | const lowerCwd = normalizedCwd.toLowerCase()
30 |
31 | if (lowerPath.startsWith(lowerCwd)) {
32 | const relativePath = normalizedPath.substring(normalizedCwd.length)
33 | // Ensure there's a slash after the @ symbol when we create the mention path
34 | return "@" + (relativePath.startsWith("/") ? relativePath : "/" + relativePath)
35 | }
36 |
37 | return path
38 | }
39 |
--------------------------------------------------------------------------------
/src/__mocks__/jest.setup.ts:
--------------------------------------------------------------------------------
1 | // Mock the logger globally for all tests
2 | jest.mock("../utils/logging", () => ({
3 | logger: {
4 | debug: jest.fn(),
5 | info: jest.fn(),
6 | warn: jest.fn(),
7 | error: jest.fn(),
8 | fatal: jest.fn(),
9 | child: jest.fn().mockReturnValue({
10 | debug: jest.fn(),
11 | info: jest.fn(),
12 | warn: jest.fn(),
13 | error: jest.fn(),
14 | fatal: jest.fn(),
15 | }),
16 | },
17 | }))
18 |
19 | // Add toPosix method to String prototype for all tests, mimicking src/utils/path.ts
20 | // This is needed because the production code expects strings to have this method
21 | // Note: In production, this is added via import in the entry point (extension.ts)
22 | export {}
23 |
24 | declare global {
25 | interface String {
26 | toPosix(): string
27 | }
28 | }
29 |
30 | // Implementation that matches src/utils/path.ts
31 | function toPosixPath(p: string) {
32 | // Extended-Length Paths in Windows start with "\\?\" to allow longer paths
33 | // and bypass usual parsing. If detected, we return the path unmodified.
34 | const isExtendedLengthPath = p.startsWith("\\\\?\\")
35 |
36 | if (isExtendedLengthPath) {
37 | return p
38 | }
39 |
40 | return p.replace(/\\/g, "/")
41 | }
42 |
43 | if (!String.prototype.toPosix) {
44 | String.prototype.toPosix = function (this: string): string {
45 | return toPosixPath(this)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/ruby.ts:
--------------------------------------------------------------------------------
1 | /*
2 | - method definitions (including singleton methods and aliases, with associated comments)
3 | - class definitions (including singleton classes, with associated comments)
4 | - module definitions
5 | */
6 | export default `
7 | (
8 | (comment)* @doc
9 | .
10 | [
11 | (method
12 | name: (_) @name.definition.method) @definition.method
13 | (singleton_method
14 | name: (_) @name.definition.method) @definition.method
15 | ]
16 | (#strip! @doc "^#\\s*")
17 | (#select-adjacent! @doc @definition.method)
18 | )
19 |
20 | (alias
21 | name: (_) @name.definition.method) @definition.method
22 |
23 | (
24 | (comment)* @doc
25 | .
26 | [
27 | (class
28 | name: [
29 | (constant) @name.definition.class
30 | (scope_resolution
31 | name: (_) @name.definition.class)
32 | ]) @definition.class
33 | (singleton_class
34 | value: [
35 | (constant) @name.definition.class
36 | (scope_resolution
37 | name: (_) @name.definition.class)
38 | ]) @definition.class
39 | ]
40 | (#strip! @doc "^#\\s*")
41 | (#select-adjacent! @doc @definition.class)
42 | )
43 |
44 | (
45 | (module
46 | name: [
47 | (constant) @name.definition.module
48 | (scope_resolution
49 | name: (_) @name.definition.module)
50 | ]) @definition.module
51 | )
52 | `
53 |
--------------------------------------------------------------------------------
/src/shared/mcp.ts:
--------------------------------------------------------------------------------
1 | export type McpServer = {
2 | name: string
3 | config: string
4 | status: "connected" | "connecting" | "disconnected"
5 | error?: string
6 | tools?: McpTool[]
7 | resources?: McpResource[]
8 | resourceTemplates?: McpResourceTemplate[]
9 | disabled?: boolean
10 | timeout?: number
11 | }
12 |
13 | export type McpTool = {
14 | name: string
15 | description?: string
16 | inputSchema?: object
17 | alwaysAllow?: boolean
18 | }
19 |
20 | export type McpResource = {
21 | uri: string
22 | name: string
23 | mimeType?: string
24 | description?: string
25 | }
26 |
27 | export type McpResourceTemplate = {
28 | uriTemplate: string
29 | name: string
30 | description?: string
31 | mimeType?: string
32 | }
33 |
34 | export type McpResourceResponse = {
35 | _meta?: Record
36 | contents: Array<{
37 | uri: string
38 | mimeType?: string
39 | text?: string
40 | blob?: string
41 | }>
42 | }
43 |
44 | export type McpToolCallResponse = {
45 | _meta?: Record
46 | content: Array<
47 | | {
48 | type: "text"
49 | text: string
50 | }
51 | | {
52 | type: "image"
53 | data: string
54 | mimeType: string
55 | }
56 | | {
57 | type: "resource"
58 | resource: {
59 | uri: string
60 | mimeType?: string
61 | text?: string
62 | blob?: string
63 | }
64 | }
65 | >
66 | isError?: boolean
67 | }
68 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const TooltipProvider = TooltipPrimitive.Provider
7 |
8 | const Tooltip = TooltipPrimitive.Root
9 |
10 | const TooltipTrigger = TooltipPrimitive.Trigger
11 |
12 | const TooltipContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, sideOffset = 4, ...props }, ref) => (
16 |
17 |
26 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/.clinerules:
--------------------------------------------------------------------------------
1 | # Code Quality Rules
2 |
3 | 1. Test Coverage:
4 | - Before attempting completion, always make sure that any code changes have test coverage
5 | - Ensure all tests pass before submitting changes
6 |
7 | 2. Lint Rules:
8 | - Never disable any lint rules without explicit user approval
9 | - If a lint rule needs to be disabled, ask the user first and explain why
10 | - Prefer fixing the underlying issue over disabling the lint rule
11 | - Document any approved lint rule disabling with a comment explaining the reason
12 |
13 | 3. Logging Guidelines:
14 | - Always instrument code changes using the logger exported from `src\utils\logging\index.ts`.
15 | - This will facilitate efficient debugging without impacting production (as the logger no-ops outside of a test environment.)
16 | - Logs can be found in `logs\app.log`
17 | - Logfile is overwritten on each run to keep it to a manageable volume.
18 |
19 | 4. Styling Guidelines:
20 | - Use Tailwind CSS classes instead of inline style objects for new markup
21 | - VSCode CSS variables must be added to webview-ui/src/index.css before using them in Tailwind classes
22 | - Example: `` instead of style objects
23 |
24 |
25 | # Adding a New Setting
26 |
27 | To add a new setting that persists its state, follow the steps in cline_docs/settings.md
28 |
--------------------------------------------------------------------------------
/src/core/diff/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Interface for implementing different diff strategies
3 | */
4 |
5 | export type DiffResult =
6 | | { success: true; content: string }
7 | | {
8 | success: false
9 | error: string
10 | details?: {
11 | similarity?: number
12 | threshold?: number
13 | matchedRange?: { start: number; end: number }
14 | searchContent?: string
15 | bestMatch?: string
16 | }
17 | }
18 |
19 | export interface DiffStrategy {
20 | /**
21 | * Get the tool description for this diff strategy
22 | * @param args The tool arguments including cwd and toolOptions
23 | * @returns The complete tool description including format requirements and examples
24 | */
25 | getToolDescription(args: { cwd: string; toolOptions?: { [key: string]: string } }): string
26 |
27 | /**
28 | * Apply a diff to the original content
29 | * @param originalContent The original file content
30 | * @param diffContent The diff content in the strategy's format
31 | * @param startLine Optional line number where the search block starts. If not provided, searches the entire file.
32 | * @param endLine Optional line number where the search block ends. If not provided, searches the entire file.
33 | * @returns A DiffResult object containing either the successful result or error details
34 | */
35 | applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): Promise
36 | }
37 |
--------------------------------------------------------------------------------
/webview-ui/jest.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
2 | module.exports = {
3 | preset: "ts-jest",
4 | testEnvironment: "jsdom",
5 | injectGlobals: true,
6 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
7 | transform: { "^.+\\.(ts|tsx)$": ["ts-jest", { tsconfig: { jsx: "react-jsx" } }] },
8 | testMatch: ["/src/**/__tests__/**/*.{js,jsx,ts,tsx}", "/src/**/*.{spec,test}.{js,jsx,ts,tsx}"],
9 | setupFilesAfterEnv: ["/src/setupTests.ts"],
10 | moduleNameMapper: {
11 | "\\.(css|less|scss|sass)$": "identity-obj-proxy",
12 | "^vscrui$": "/src/__mocks__/vscrui.ts",
13 | "^@vscode/webview-ui-toolkit/react$": "/src/__mocks__/@vscode/webview-ui-toolkit/react.ts",
14 | "^@/(.*)$": "/src/$1",
15 | },
16 | reporters: [["jest-simple-dot-reporter", {}]],
17 | transformIgnorePatterns: [
18 | "/node_modules/(?!(rehype-highlight|react-remark|unist-util-visit|unist-util-find-after|vfile|unified|bail|is-plain-obj|trough|vfile-message|unist-util-stringify-position|mdast-util-from-markdown|mdast-util-to-string|micromark|decode-named-character-reference|character-entities|markdown-table|zwitch|longest-streak|escape-string-regexp|unist-util-is|hast-util-to-text|@vscode/webview-ui-toolkit|@microsoft/fast-react-wrapper|@microsoft/fast-element|@microsoft/fast-foundation|@microsoft/fast-web-utilities|exenv-es6|vscrui)/)",
19 | ],
20 | roots: ["/src"],
21 | moduleDirectories: ["node_modules", "src"],
22 | }
23 |
--------------------------------------------------------------------------------
/webview-ui/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as PopoverPrimitive from "@radix-ui/react-popover"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Popover = PopoverPrimitive.Root
7 |
8 | const PopoverTrigger = PopoverPrimitive.Trigger
9 |
10 | const PopoverAnchor = PopoverPrimitive.Anchor
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef & {
15 | container?: HTMLElement
16 | }
17 | >(({ className, align = "center", sideOffset = 4, container, ...props }, ref) => (
18 |
19 |
29 |
30 | ))
31 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
32 |
33 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
34 |
--------------------------------------------------------------------------------
/src/shared/__tests__/vsCodeSelectorUtils.test.ts:
--------------------------------------------------------------------------------
1 | import { stringifyVsCodeLmModelSelector, SELECTOR_SEPARATOR } from "../vsCodeSelectorUtils"
2 | import { LanguageModelChatSelector } from "vscode"
3 |
4 | describe("vsCodeSelectorUtils", () => {
5 | describe("stringifyVsCodeLmModelSelector", () => {
6 | it("should join all defined selector properties with separator", () => {
7 | const selector: LanguageModelChatSelector = {
8 | vendor: "test-vendor",
9 | family: "test-family",
10 | version: "v1",
11 | id: "test-id",
12 | }
13 |
14 | const result = stringifyVsCodeLmModelSelector(selector)
15 | expect(result).toBe("test-vendor/test-family/v1/test-id")
16 | })
17 |
18 | it("should skip undefined properties", () => {
19 | const selector: LanguageModelChatSelector = {
20 | vendor: "test-vendor",
21 | family: "test-family",
22 | }
23 |
24 | const result = stringifyVsCodeLmModelSelector(selector)
25 | expect(result).toBe("test-vendor/test-family")
26 | })
27 |
28 | it("should handle empty selector", () => {
29 | const selector: LanguageModelChatSelector = {}
30 |
31 | const result = stringifyVsCodeLmModelSelector(selector)
32 | expect(result).toBe("")
33 | })
34 |
35 | it("should handle selector with only one property", () => {
36 | const selector: LanguageModelChatSelector = {
37 | vendor: "test-vendor",
38 | }
39 |
40 | const result = stringifyVsCodeLmModelSelector(selector)
41 | expect(result).toBe("test-vendor")
42 | })
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/webview-ui/src/stories/Chat.stories.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import type { Meta, StoryObj } from "@storybook/react"
3 |
4 | import { Chat, ChatHandler, Message } from "@/components/ui/chat"
5 |
6 | const meta = {
7 | title: "ui/Chat",
8 | component: Chat,
9 | parameters: { layout: "centered" },
10 | tags: ["autodocs"],
11 | } satisfies Meta
12 |
13 | export default meta
14 |
15 | type Story = StoryObj
16 |
17 | export const Default: Story = {
18 | name: "Chat",
19 | args: {
20 | assistantName: "Assistant",
21 | handler: {} as ChatHandler,
22 | },
23 | render: function StorybookChat() {
24 | const handler = useStorybookChat()
25 | return (
26 |
31 | )
32 | },
33 | }
34 |
35 | const useStorybookChat = (): ChatHandler => {
36 | const [isLoading, setIsLoading] = useState(false)
37 | const [input, setInput] = useState("")
38 | const [messages, setMessages] = useState([])
39 |
40 | const append = async (message: Message, options?: { data?: any }) => {
41 | const echo: Message = {
42 | ...message,
43 | role: "assistant",
44 | content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
45 | }
46 | setMessages((prev) => [...prev, message, echo])
47 | return Promise.resolve(null)
48 | }
49 |
50 | return { isLoading, setIsLoading, input, setInput, messages, append }
51 | }
52 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "watch",
8 | "dependsOn": ["npm: dev", "npm: watch:tsc", "npm: watch:esbuild"],
9 | "presentation": {
10 | "reveal": "never"
11 | },
12 | "group": {
13 | "kind": "build",
14 | "isDefault": true
15 | }
16 | },
17 | {
18 | "label": "npm: dev",
19 | "type": "npm",
20 | "script": "dev",
21 | "group": "build",
22 | "problemMatcher": {
23 | "owner": "vite",
24 | "pattern": {
25 | "regexp": "^$"
26 | },
27 | "background": {
28 | "activeOnStart": true,
29 | "beginsPattern": ".*VITE.*",
30 | "endsPattern": ".*Local:.*"
31 | }
32 | },
33 | "isBackground": true,
34 | "presentation": {
35 | "group": "webview-ui",
36 | "reveal": "always"
37 | }
38 | },
39 | {
40 | "label": "npm: watch:esbuild",
41 | "type": "npm",
42 | "script": "watch:esbuild",
43 | "group": "build",
44 | "problemMatcher": "$esbuild-watch",
45 | "isBackground": true,
46 | "presentation": {
47 | "group": "watch",
48 | "reveal": "always"
49 | }
50 | },
51 | {
52 | "label": "npm: watch:tsc",
53 | "type": "npm",
54 | "script": "watch:tsc",
55 | "group": "build",
56 | "problemMatcher": "$tsc-watch",
57 | "isBackground": true,
58 | "presentation": {
59 | "group": "watch",
60 | "reveal": "always"
61 | }
62 | }
63 | ]
64 | }
65 |
--------------------------------------------------------------------------------
/src/core/prompts/tools/search-and-replace.ts:
--------------------------------------------------------------------------------
1 | import { ToolArgs } from "./types"
2 |
3 | export function getSearchAndReplaceDescription(args: ToolArgs): string {
4 | return `## search_and_replace
5 | Description: 请求对文件执行搜索和替换操作。每个操作可以指定搜索模式(字符串或正则表达式)和替换文本,并可选择性地限制行范围和正则表达式标志。在应用更改前会显示差异预览。
6 | Parameters:
7 | - path: (必需) 要修改的文件路径(相对于当前工作目录 ${args.cwd.toPosix()})
8 | - operations: (必需) 搜索/替换操作的 JSON 数组。每个操作是一个包含以下字段的对象:
9 | * search: (必需) 要搜索的文本或模式
10 | * replace: (必需) 用于替换匹配项的文本。如果需要替换多行,使用 "\n" 表示换行
11 | * start_line: (可选) 限制替换的起始行号
12 | * end_line: (可选) 限制替换的结束行号
13 | * use_regex: (可选) 是否将搜索内容作为正则表达式模式处理
14 | * ignore_case: (可选) 是否在匹配时忽略大小写
15 | * regex_flags: (可选) 当 use_regex 为 true 时的额外正则表达式标志
16 | Usage:
17 |
18 | 文件路径
19 | [
20 | {
21 | "search": "要查找的文本",
22 | "replace": "替换文本",
23 | "start_line": 1,
24 | "end_line": 10
25 | }
26 | ]
27 |
28 | 示例:在 example.ts 的第 1-10 行中将 "foo" 替换为 "bar"
29 |
30 | example.ts
31 | [
32 | {
33 | "search": "foo",
34 | "replace": "bar",
35 | "start_line": 1,
36 | "end_line": 10
37 | }
38 | ]
39 |
40 | 示例:使用正则表达式替换所有 "old" 开头的单词
41 |
42 | example.ts
43 | [
44 | {
45 | "search": "old\\w+",
46 | "replace": "new$&",
47 | "use_regex": true,
48 | "ignore_case": true
49 | }
50 | ]
51 | `
52 | }
53 |
--------------------------------------------------------------------------------
/e2e/src/suite/extension.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert"
2 | import * as vscode from "vscode"
3 |
4 | suite("Roo Code Extension", () => {
5 | test("OPENROUTER_API_KEY environment variable is set", () => {
6 | if (!process.env.OPENROUTER_API_KEY) {
7 | assert.fail("OPENROUTER_API_KEY environment variable is not set")
8 | }
9 | })
10 |
11 | test("Commands should be registered", async () => {
12 | const timeout = 10 * 1_000
13 | const interval = 1_000
14 | const startTime = Date.now()
15 |
16 | const expectedCommands = [
17 | "roo-cline.plusButtonClicked",
18 | "roo-cline.mcpButtonClicked",
19 | "roo-cline.historyButtonClicked",
20 | "roo-cline.popoutButtonClicked",
21 | "roo-cline.settingsButtonClicked",
22 | "roo-cline.openInNewTab",
23 | "roo-cline.explainCode",
24 | "roo-cline.fixCode",
25 | "roo-cline.improveCode",
26 | ]
27 |
28 | while (Date.now() - startTime < timeout) {
29 | const commands = await vscode.commands.getCommands(true)
30 | const missingCommands = []
31 |
32 | for (const cmd of expectedCommands) {
33 | if (!commands.includes(cmd)) {
34 | missingCommands.push(cmd)
35 | }
36 | }
37 |
38 | if (missingCommands.length === 0) {
39 | break
40 | }
41 |
42 | await new Promise((resolve) => setTimeout(resolve, interval))
43 | }
44 |
45 | const commands = await vscode.commands.getCommands(true)
46 |
47 | for (const cmd of expectedCommands) {
48 | assert.ok(commands.includes(cmd), `Command ${cmd} should be registered`)
49 | }
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/src/integrations/terminal/get-latest-output.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 |
3 | /**
4 | * Gets the contents of the active terminal
5 | * @returns The terminal contents as a string
6 | */
7 | export async function getLatestTerminalOutput(): Promise {
8 | // Store original clipboard content to restore later
9 | const originalClipboard = await vscode.env.clipboard.readText()
10 |
11 | try {
12 | // Select terminal content
13 | await vscode.commands.executeCommand("workbench.action.terminal.selectAll")
14 |
15 | // Copy selection to clipboard
16 | await vscode.commands.executeCommand("workbench.action.terminal.copySelection")
17 |
18 | // Clear the selection
19 | await vscode.commands.executeCommand("workbench.action.terminal.clearSelection")
20 |
21 | // Get terminal contents from clipboard
22 | let terminalContents = (await vscode.env.clipboard.readText()).trim()
23 |
24 | // Check if there's actually a terminal open
25 | if (terminalContents === originalClipboard) {
26 | return ""
27 | }
28 |
29 | // Clean up command separation
30 | const lines = terminalContents.split("\n")
31 | const lastLine = lines.pop()?.trim()
32 | if (lastLine) {
33 | let i = lines.length - 1
34 | while (i >= 0 && !lines[i].trim().startsWith(lastLine)) {
35 | i--
36 | }
37 | terminalContents = lines.slice(Math.max(i, 0)).join("\n")
38 | }
39 |
40 | return terminalContents
41 | } finally {
42 | // Restore original clipboard content
43 | await vscode.env.clipboard.writeText(originalClipboard)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/api/transform/simple-format.ts:
--------------------------------------------------------------------------------
1 | import { Anthropic } from "@anthropic-ai/sdk"
2 |
3 | /**
4 | * 将复杂的内容块转换为简单的字符串内容
5 | */
6 | export function convertToSimpleContent(content: Anthropic.Messages.MessageParam["content"]): string {
7 | if (typeof content === "string") {
8 | return content
9 | }
10 |
11 | // 从内容块中提取文本
12 | return content
13 | .map((block) => {
14 | if (block.type === "text") {
15 | return block.text
16 | }
17 | if (block.type === "image") {
18 | return `[图片: ${block.source.media_type}]`
19 | }
20 | if (block.type === "tool_use") {
21 | return `[工具使用: ${block.name}]`
22 | }
23 | if (block.type === "tool_result") {
24 | if (typeof block.content === "string") {
25 | return block.content
26 | }
27 | if (Array.isArray(block.content)) {
28 | return block.content
29 | .map((part) => {
30 | if (part.type === "text") {
31 | return part.text
32 | }
33 | if (part.type === "image") {
34 | return `[图片: ${part.source.media_type}]`
35 | }
36 | return ""
37 | })
38 | .join("\n")
39 | }
40 | return ""
41 | }
42 | return ""
43 | })
44 | .filter(Boolean)
45 | .join("\n")
46 | }
47 |
48 | /**
49 | * 将Anthropic消息转换为带有字符串内容的简单格式
50 | */
51 | export function convertToSimpleMessages(
52 | messages: Anthropic.Messages.MessageParam[],
53 | ): Array<{ role: "user" | "assistant"; content: string }> {
54 | return messages.map((message) => ({
55 | role: message.role,
56 | content: convertToSimpleContent(message.content),
57 | }))
58 | }
59 |
--------------------------------------------------------------------------------
/src/utils/fs.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs/promises"
2 | import * as path from "path"
3 |
4 | /**
5 | * Asynchronously creates all non-existing subdirectories for a given file path
6 | * and collects them in an array for later deletion.
7 | *
8 | * @param filePath - The full path to a file.
9 | * @returns A promise that resolves to an array of newly created directories.
10 | */
11 | export async function createDirectoriesForFile(filePath: string): Promise {
12 | const newDirectories: string[] = []
13 | const normalizedFilePath = path.normalize(filePath) // Normalize path for cross-platform compatibility
14 | const directoryPath = path.dirname(normalizedFilePath)
15 |
16 | let currentPath = directoryPath
17 | const dirsToCreate: string[] = []
18 |
19 | // Traverse up the directory tree and collect missing directories
20 | while (!(await fileExistsAtPath(currentPath))) {
21 | dirsToCreate.push(currentPath)
22 | currentPath = path.dirname(currentPath)
23 | }
24 |
25 | // Create directories from the topmost missing one down to the target directory
26 | for (let i = dirsToCreate.length - 1; i >= 0; i--) {
27 | await fs.mkdir(dirsToCreate[i])
28 | newDirectories.push(dirsToCreate[i])
29 | }
30 |
31 | return newDirectories
32 | }
33 |
34 | /**
35 | * Helper function to check if a path exists.
36 | *
37 | * @param path - The path to check.
38 | * @returns A promise that resolves to true if the path exists, false otherwise.
39 | */
40 | export async function fileExistsAtPath(filePath: string): Promise {
41 | try {
42 | await fs.access(filePath)
43 | return true
44 | } catch {
45 | return false
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/webview-ui/src/components/mcp/McpResourceRow.tsx:
--------------------------------------------------------------------------------
1 | import { McpResource, McpResourceTemplate } from "../../../../src/shared/mcp"
2 |
3 | type McpResourceRowProps = {
4 | item: McpResource | McpResourceTemplate
5 | }
6 |
7 | const McpResourceRow = ({ item }: McpResourceRowProps) => {
8 | const hasUri = "uri" in item
9 | const uri = hasUri ? item.uri : item.uriTemplate
10 |
11 | return (
12 |
17 |
23 |
24 | {uri}
25 |
26 |
32 | {item.name && item.description
33 | ? `${item.name}: ${item.description}`
34 | : !item.name && item.description
35 | ? item.description
36 | : !item.description && item.name
37 | ? item.name
38 | : "No description"}
39 |
40 |
44 | Returns
45 |
52 | {item.mimeType || "Unknown"}
53 |
54 |
55 |
56 | )
57 | }
58 |
59 | export default McpResourceRow
60 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
2 | module.exports = {
3 | preset: "ts-jest",
4 | testEnvironment: "node",
5 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
6 | transform: {
7 | "^.+\\.tsx?$": [
8 | "ts-jest",
9 | {
10 | tsconfig: {
11 | module: "CommonJS",
12 | moduleResolution: "node",
13 | esModuleInterop: true,
14 | allowJs: true,
15 | },
16 | diagnostics: false,
17 | isolatedModules: true,
18 | },
19 | ],
20 | },
21 | testMatch: ["**/__tests__/**/*.test.ts"],
22 | moduleNameMapper: {
23 | "^vscode$": "/src/__mocks__/vscode.js",
24 | "@modelcontextprotocol/sdk$": "/src/__mocks__/@modelcontextprotocol/sdk/index.js",
25 | "@modelcontextprotocol/sdk/(.*)": "/src/__mocks__/@modelcontextprotocol/sdk/$1",
26 | "^delay$": "/src/__mocks__/delay.js",
27 | "^p-wait-for$": "/src/__mocks__/p-wait-for.js",
28 | "^globby$": "/src/__mocks__/globby.js",
29 | "^serialize-error$": "/src/__mocks__/serialize-error.js",
30 | "^strip-ansi$": "/src/__mocks__/strip-ansi.js",
31 | "^default-shell$": "/src/__mocks__/default-shell.js",
32 | "^os-name$": "/src/__mocks__/os-name.js",
33 | },
34 | transformIgnorePatterns: [
35 | "node_modules/(?!(@modelcontextprotocol|delay|p-wait-for|globby|serialize-error|strip-ansi|default-shell|os-name)/)",
36 | ],
37 | roots: ["/src", "/webview-ui/src"],
38 | modulePathIgnorePatterns: [".vscode-test"],
39 | reporters: [["jest-simple-dot-reporter", {}]],
40 | setupFiles: ["/src/__mocks__/jest.setup.ts"],
41 | }
42 |
--------------------------------------------------------------------------------
/src/core/prompts/sections/custom-system-prompt.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs/promises"
2 | import path from "path"
3 | import { Mode } from "../../../shared/modes"
4 | import { fileExistsAtPath } from "../../../utils/fs"
5 |
6 | /**
7 | * 安全地读取文件,如果文件不存在则返回空字符串
8 | */
9 | async function safeReadFile(filePath: string): Promise {
10 | try {
11 | const content = await fs.readFile(filePath, "utf-8")
12 | // 使用 "utf-8" 编码读取时,内容应该是字符串
13 | return content.trim()
14 | } catch (err) {
15 | const errorCode = (err as NodeJS.ErrnoException).code
16 | if (!errorCode || !["ENOENT", "EISDIR"].includes(errorCode)) {
17 | throw err
18 | }
19 | return ""
20 | }
21 | }
22 |
23 | /**
24 | * 获取特定模式的系统提示词文件路径
25 | */
26 | export function getSystemPromptFilePath(cwd: string, mode: Mode): string {
27 | return path.join(cwd, ".roo", `system-prompt-${mode}`)
28 | }
29 |
30 | /**
31 | * 从 .roo/system-prompt-[mode slug] 文件加载自定义系统提示词
32 | * 如果文件不存在,则返回空字符串
33 | */
34 | export async function loadSystemPromptFile(cwd: string, mode: Mode): Promise {
35 | const filePath = getSystemPromptFilePath(cwd, mode)
36 | return safeReadFile(filePath)
37 | }
38 |
39 | /**
40 | * 确保 .roo 目录存在,如果不存在则创建
41 | */
42 | export async function ensureRooDirectory(cwd: string): Promise {
43 | const rooDir = path.join(cwd, ".roo")
44 |
45 | // 检查目录是否已存在
46 | if (await fileExistsAtPath(rooDir)) {
47 | return
48 | }
49 |
50 | // 创建目录
51 | try {
52 | await fs.mkdir(rooDir, { recursive: true })
53 | } catch (err) {
54 | // 如果目录已存在(竞态条件),忽略错误
55 | const errorCode = (err as NodeJS.ErrnoException).code
56 | if (errorCode !== "EEXIST") {
57 | throw err
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/webview-ui/src/utils/clipboard.ts:
--------------------------------------------------------------------------------
1 | import { useState, useCallback } from "react"
2 |
3 | /**
4 | * Options for copying text to clipboard
5 | */
6 | interface CopyOptions {
7 | /** Duration in ms to show success feedback (default: 2000) */
8 | feedbackDuration?: number
9 | /** Optional callback when copy succeeds */
10 | onSuccess?: () => void
11 | /** Optional callback when copy fails */
12 | onError?: (error: Error) => void
13 | }
14 |
15 | /**
16 | * Copy text to clipboard with error handling
17 | */
18 | export const copyToClipboard = async (text: string, options?: CopyOptions): Promise => {
19 | try {
20 | await navigator.clipboard.writeText(text)
21 | options?.onSuccess?.()
22 | return true
23 | } catch (error) {
24 | const err = error instanceof Error ? error : new Error("Failed to copy to clipboard")
25 | options?.onError?.(err)
26 | console.error("Failed to copy to clipboard:", err)
27 | return false
28 | }
29 | }
30 |
31 | /**
32 | * React hook for managing clipboard copy state with feedback
33 | */
34 | export const useCopyToClipboard = (feedbackDuration = 2000) => {
35 | const [showCopyFeedback, setShowCopyFeedback] = useState(false)
36 |
37 | const copyWithFeedback = useCallback(
38 | async (text: string, e?: React.MouseEvent) => {
39 | e?.stopPropagation()
40 |
41 | const success = await copyToClipboard(text, {
42 | onSuccess: () => {
43 | setShowCopyFeedback(true)
44 | setTimeout(() => setShowCopyFeedback(false), feedbackDuration)
45 | },
46 | })
47 |
48 | return success
49 | },
50 | [feedbackDuration],
51 | )
52 |
53 | return {
54 | showCopyFeedback,
55 | copyWithFeedback,
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/services/checkpoints/constants.ts:
--------------------------------------------------------------------------------
1 | export const GIT_DISABLED_SUFFIX = "_disabled"
2 |
3 | export const GIT_EXCLUDES = [
4 | ".git/", // Ignore the user's .git.
5 | `.git${GIT_DISABLED_SUFFIX}/`, // Ignore the disabled nested git repos.
6 | ".DS_Store",
7 | "*.log",
8 | "node_modules/",
9 | "__pycache__/",
10 | "env/",
11 | "venv/",
12 | "target/dependency/",
13 | "build/dependencies/",
14 | "dist/",
15 | "out/",
16 | "bundle/",
17 | "vendor/",
18 | "tmp/",
19 | "temp/",
20 | "deps/",
21 | "pkg/",
22 | "Pods/",
23 | // Media files.
24 | "*.jpg",
25 | "*.jpeg",
26 | "*.png",
27 | "*.gif",
28 | "*.bmp",
29 | "*.ico",
30 | // "*.svg",
31 | "*.mp3",
32 | "*.mp4",
33 | "*.wav",
34 | "*.avi",
35 | "*.mov",
36 | "*.wmv",
37 | "*.webm",
38 | "*.webp",
39 | "*.m4a",
40 | "*.flac",
41 | // Build and dependency directories.
42 | "build/",
43 | "bin/",
44 | "obj/",
45 | ".gradle/",
46 | ".idea/",
47 | ".vscode/",
48 | ".vs/",
49 | "coverage/",
50 | ".next/",
51 | ".nuxt/",
52 | // Cache and temporary files.
53 | "*.cache",
54 | "*.tmp",
55 | "*.temp",
56 | "*.swp",
57 | "*.swo",
58 | "*.pyc",
59 | "*.pyo",
60 | ".pytest_cache/",
61 | ".eslintcache",
62 | // Environment and config files.
63 | ".env*",
64 | "*.local",
65 | "*.development",
66 | "*.production",
67 | // Large data files.
68 | "*.zip",
69 | "*.tar",
70 | "*.gz",
71 | "*.rar",
72 | "*.7z",
73 | "*.iso",
74 | "*.bin",
75 | "*.exe",
76 | "*.dll",
77 | "*.so",
78 | "*.dylib",
79 | // Database files.
80 | "*.sqlite",
81 | "*.db",
82 | "*.sql",
83 | // Log files.
84 | "*.logs",
85 | "*.error",
86 | "npm-debug.log*",
87 | "yarn-debug.log*",
88 | "yarn-error.log*",
89 | ]
90 |
--------------------------------------------------------------------------------
/e2e/src/suite/task.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from "assert"
2 |
3 | suite("Roo Code Task", () => {
4 | test("Should handle prompt and response correctly", async function () {
5 | const timeout = 30000
6 | const interval = 1000
7 |
8 | if (!globalThis.extension) {
9 | assert.fail("Extension not found")
10 | }
11 |
12 | // Ensure the webview is launched.
13 | let startTime = Date.now()
14 |
15 | while (Date.now() - startTime < timeout) {
16 | if (globalThis.provider.viewLaunched) {
17 | break
18 | }
19 |
20 | await new Promise((resolve) => setTimeout(resolve, interval))
21 | }
22 |
23 | await globalThis.provider.updateGlobalState("mode", "Code")
24 | await globalThis.provider.updateGlobalState("alwaysAllowModeSwitch", true)
25 | await globalThis.provider.updateGlobalState("autoApprovalEnabled", true)
26 |
27 | await globalThis.api.startNewTask("Hello world, what is your name? Respond with 'My name is ...'")
28 |
29 | // Wait for task to appear in history with tokens.
30 | startTime = Date.now()
31 |
32 | while (Date.now() - startTime < timeout) {
33 | const messages = globalThis.provider.messages
34 |
35 | if (messages.some(({ type, text }) => type === "say" && text?.includes("My name is Roo"))) {
36 | break
37 | }
38 |
39 | await new Promise((resolve) => setTimeout(resolve, interval))
40 | }
41 |
42 | if (globalThis.provider.messages.length === 0) {
43 | assert.fail("No messages received")
44 | }
45 |
46 | assert.ok(
47 | globalThis.provider.messages.some(({ type, text }) => type === "say" && text?.includes("My name is Roo")),
48 | "Did not receive expected response containing 'My name is Roo'",
49 | )
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/src/services/tree-sitter/queries/javascript.ts:
--------------------------------------------------------------------------------
1 | /*
2 | - class definitions
3 | - method definitions
4 | - named function declarations
5 | - arrow functions and function expressions assigned to variables
6 | */
7 | export default `
8 | (
9 | (comment)* @doc
10 | .
11 | (method_definition
12 | name: (property_identifier) @name) @definition.method
13 | (#not-eq? @name "constructor")
14 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
15 | (#select-adjacent! @doc @definition.method)
16 | )
17 |
18 | (
19 | (comment)* @doc
20 | .
21 | [
22 | (class
23 | name: (_) @name)
24 | (class_declaration
25 | name: (_) @name)
26 | ] @definition.class
27 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
28 | (#select-adjacent! @doc @definition.class)
29 | )
30 |
31 | (
32 | (comment)* @doc
33 | .
34 | [
35 | (function_declaration
36 | name: (identifier) @name)
37 | (generator_function_declaration
38 | name: (identifier) @name)
39 | ] @definition.function
40 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
41 | (#select-adjacent! @doc @definition.function)
42 | )
43 |
44 | (
45 | (comment)* @doc
46 | .
47 | (lexical_declaration
48 | (variable_declarator
49 | name: (identifier) @name
50 | value: [(arrow_function) (function_expression)]) @definition.function)
51 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
52 | (#select-adjacent! @doc @definition.function)
53 | )
54 |
55 | (
56 | (comment)* @doc
57 | .
58 | (variable_declaration
59 | (variable_declarator
60 | name: (identifier) @name
61 | value: [(arrow_function) (function_expression)]) @definition.function)
62 | (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
63 | (#select-adjacent! @doc @definition.function)
64 | )
65 | `
66 |
--------------------------------------------------------------------------------
/src/utils/sound.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode"
2 | import * as path from "path"
3 |
4 | /**
5 | * Minimum interval (in milliseconds) to prevent continuous playback
6 | */
7 | const MIN_PLAY_INTERVAL = 500
8 |
9 | /**
10 | * Timestamp of when sound was last played
11 | */
12 | let lastPlayedTime = 0
13 |
14 | /**
15 | * Determine if a file is a WAV file
16 | * @param filepath string
17 | * @returns boolean
18 | */
19 | export const isWAV = (filepath: string): boolean => {
20 | return path.extname(filepath).toLowerCase() === ".wav"
21 | }
22 |
23 | let isSoundEnabled = false
24 | let volume = 0.5
25 |
26 | /**
27 | * Set sound configuration
28 | * @param enabled boolean
29 | */
30 | export const setSoundEnabled = (enabled: boolean): void => {
31 | isSoundEnabled = enabled
32 | }
33 |
34 | /**
35 | * Set sound volume
36 | * @param volume number
37 | */
38 | export const setSoundVolume = (newVolume: number): void => {
39 | volume = newVolume
40 | }
41 |
42 | /**
43 | * Play a sound file
44 | * @param filepath string
45 | * @return void
46 | */
47 | export const playSound = (filepath: string): void => {
48 | try {
49 | if (!isSoundEnabled) {
50 | return
51 | }
52 |
53 | if (!filepath) {
54 | return
55 | }
56 |
57 | if (!isWAV(filepath)) {
58 | throw new Error("Only wav files are supported.")
59 | }
60 |
61 | const currentTime = Date.now()
62 | if (currentTime - lastPlayedTime < MIN_PLAY_INTERVAL) {
63 | return // Skip playback within minimum interval to prevent continuous playback
64 | }
65 |
66 | const sound = require("sound-play")
67 | sound.play(filepath, volume).catch(() => {
68 | throw new Error("Failed to play sound effect")
69 | })
70 |
71 | lastPlayedTime = currentTime
72 | } catch (error: any) {
73 | vscode.window.showErrorMessage(error.message)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/webview-ui/src/utils/mcp.ts:
--------------------------------------------------------------------------------
1 | import { McpResource, McpResourceTemplate } from "../../../src/shared/mcp"
2 |
3 | /**
4 | * Matches a URI against an array of URI templates and returns the matching template
5 | * @param uri The URI to match
6 | * @param templates Array of URI templates to match against
7 | * @returns The matching template or undefined if no match is found
8 | */
9 | export function findMatchingTemplate(
10 | uri: string,
11 | templates: McpResourceTemplate[] = [],
12 | ): McpResourceTemplate | undefined {
13 | return templates.find((template) => {
14 | // Convert template to regex pattern
15 | const pattern = String(template.uriTemplate)
16 | // First escape special regex characters
17 | .replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
18 | // Then replace {param} with ([^/]+) to match any non-slash characters
19 | // We need to use \{ and \} because we just escaped them
20 | .replace(/\\\{([^}]+)\\\}/g, "([^/]+)")
21 |
22 | const regex = new RegExp(`^${pattern}$`)
23 | return regex.test(uri)
24 | })
25 | }
26 |
27 | /**
28 | * Finds either an exact resource match or a matching template for a given URI
29 | * @param uri The URI to find a match for
30 | * @param resources Array of concrete resources
31 | * @param templates Array of resource templates
32 | * @returns The matching resource, template, or undefined
33 | */
34 | export function findMatchingResourceOrTemplate(
35 | uri: string,
36 | resources: McpResource[] = [],
37 | templates: McpResourceTemplate[] = [],
38 | ): McpResource | McpResourceTemplate | undefined {
39 | // First try to find an exact resource match
40 | const exactMatch = resources.find((resource) => resource.uri === uri)
41 | if (exactMatch) return exactMatch
42 |
43 | // If no exact match, try to find a matching template
44 | return findMatchingTemplate(uri, templates)
45 | }
46 |
--------------------------------------------------------------------------------
/webview-ui/src/components/settings/styles.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 |
3 | export const DROPDOWN_Z_INDEX = 1_000
4 |
5 | export const DropdownWrapper = styled.div`
6 | position: relative;
7 | width: 100%;
8 | `
9 |
10 | export const DropdownList = styled.div<{ $zIndex: number }>`
11 | position: absolute;
12 | top: calc(100% - 3px);
13 | left: 0;
14 | width: calc(100% - 2px);
15 | max-height: 200px;
16 | overflow-y: auto;
17 | background-color: var(--vscode-dropdown-background);
18 | border: 1px solid var(--vscode-list-activeSelectionBackground);
19 | z-index: ${({ $zIndex }) => $zIndex};
20 | border-bottom-left-radius: 3px;
21 | border-bottom-right-radius: 3px;
22 | `
23 |
24 | export const DropdownItem = styled.div<{ $selected: boolean }>`
25 | padding: 5px 10px;
26 | cursor: pointer;
27 | word-break: break-all;
28 | white-space: normal;
29 |
30 | background-color: ${({ $selected }) => ($selected ? "var(--vscode-list-activeSelectionBackground)" : "inherit")};
31 |
32 | &:hover {
33 | background-color: var(--vscode-list-activeSelectionBackground);
34 | }
35 | `
36 |
37 | export const StyledMarkdown = styled.div`
38 | font-family:
39 | var(--vscode-font-family),
40 | system-ui,
41 | -apple-system,
42 | BlinkMacSystemFont,
43 | "Segoe UI",
44 | Roboto,
45 | Oxygen,
46 | Ubuntu,
47 | Cantarell,
48 | "Open Sans",
49 | "Helvetica Neue",
50 | sans-serif;
51 | font-size: 12px;
52 | color: var(--vscode-descriptionForeground);
53 |
54 | p,
55 | li,
56 | ol,
57 | ul {
58 | line-height: 1.25;
59 | margin: 0;
60 | }
61 |
62 | ol,
63 | ul {
64 | padding-left: 1.5em;
65 | margin-left: 0;
66 | }
67 |
68 | p {
69 | white-space: pre-wrap;
70 | }
71 |
72 | a {
73 | text-decoration: none;
74 | }
75 | a {
76 | &:hover {
77 | text-decoration: underline;
78 | }
79 | }
80 | `
81 |
--------------------------------------------------------------------------------
/.github/workflows/marketplace-publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish Extension
2 | on:
3 | pull_request:
4 | types: [closed]
5 | workflow_dispatch:
6 |
7 | env:
8 | GIT_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || 'main' }}
9 |
10 | jobs:
11 | publish-extension:
12 | runs-on: ubuntu-latest
13 | if: >
14 | ( github.event_name == 'pull_request' &&
15 | github.event.pull_request.base.ref == 'main' &&
16 | contains(github.event.pull_request.title, 'Changeset version bump') ) ||
17 | github.event_name == 'workflow_dispatch'
18 | steps:
19 | - uses: actions/checkout@v4
20 | with:
21 | ref: ${{ env.GIT_REF }}
22 |
23 | - uses: actions/setup-node@v4
24 | with:
25 | node-version: 18
26 | - run: |
27 | git config user.name github-actions
28 | git config user.email github-actions@github.com
29 | - name: Install Dependencies
30 | run: |
31 | npm install -g vsce ovsx
32 | npm run install:ci
33 | - name: Package and Publish Extension
34 | env:
35 | VSCE_PAT: ${{ secrets.VSCE_PAT }}
36 | OVSX_PAT: ${{ secrets.OVSX_PAT }}
37 | run: |
38 | current_package_version=$(node -p "require('./package.json').version")
39 |
40 | npm run vsix
41 | package=$(unzip -l bin/roo-cline-${current_package_version}.vsix)
42 | echo "$package"
43 | echo "$package" | grep -q "dist/extension.js" || exit 1
44 | echo "$package" | grep -q "extension/webview-ui/build/assets/index.js" || exit 1
45 | echo "$package" | grep -q "extension/node_modules/@vscode/codicons/dist/codicon.ttf" || exit 1
46 |
47 | npm run publish:marketplace
48 | echo "Successfully published version $current_package_version to VS Code Marketplace"
49 |
--------------------------------------------------------------------------------
/webview-ui/src/stories/Button.stories.ts:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/react"
2 |
3 | import { Button } from "@/components/ui"
4 |
5 | const meta = {
6 | title: "primitives/Button",
7 | component: Button,
8 | parameters: { layout: "centered" },
9 | tags: ["autodocs"],
10 | argTypes: {
11 | variant: {
12 | control: "select",
13 | options: ["default", "secondary", "outline", "ghost", "link", "destructive"],
14 | type: "string",
15 | table: {
16 | defaultValue: {
17 | summary: "default",
18 | },
19 | },
20 | },
21 | size: {
22 | control: "select",
23 | options: ["default", "sm", "lg", "icon"],
24 | type: "string",
25 | table: {
26 | defaultValue: {
27 | summary: "default",
28 | },
29 | },
30 | },
31 | children: {
32 | table: {
33 | disable: true,
34 | },
35 | },
36 | asChild: {
37 | table: {
38 | disable: true,
39 | },
40 | },
41 | },
42 | args: {
43 | children: "Button",
44 | },
45 | } satisfies Meta
46 |
47 | export default meta
48 |
49 | type Story = StoryObj
50 |
51 | export const Default: Story = {
52 | name: "Default",
53 | }
54 |
55 | export const Secondary: Story = {
56 | name: "Secondary",
57 | args: {
58 | variant: "secondary",
59 | },
60 | }
61 |
62 | export const Destructive: Story = {
63 | name: "Destructive",
64 | args: {
65 | variant: "destructive",
66 | },
67 | }
68 |
69 | export const Outline: Story = {
70 | name: "Outline",
71 | args: {
72 | variant: "outline",
73 | },
74 | }
75 |
76 | export const Ghost: Story = {
77 | name: "Ghost",
78 | args: {
79 | variant: "ghost",
80 | },
81 | }
82 |
83 | export const Link: Story = {
84 | name: "Link",
85 | args: {
86 | variant: "link",
87 | },
88 | }
89 |
90 | export const Combobox: Story = {
91 | name: "Combobox",
92 | args: {
93 | variant: "combobox",
94 | },
95 | }
96 |
--------------------------------------------------------------------------------
/webview-ui/src/utils/__tests__/format.test.ts:
--------------------------------------------------------------------------------
1 | // npx jest src/utils/__tests__/format.test.ts
2 |
3 | import { formatDate } from "../format"
4 |
5 | describe("formatDate", () => {
6 | it("formats a timestamp correctly", () => {
7 | // January 15, 2023, 10:30 AM
8 | const timestamp = new Date(2023, 0, 15, 10, 30).getTime()
9 | const result = formatDate(timestamp)
10 |
11 | expect(result).toBe("JANUARY 15, 10:30 AM")
12 | })
13 |
14 | it("handles different months correctly", () => {
15 | // February 28, 2023, 3:45 PM
16 | const timestamp1 = new Date(2023, 1, 28, 15, 45).getTime()
17 | expect(formatDate(timestamp1)).toBe("FEBRUARY 28, 3:45 PM")
18 |
19 | // December 31, 2023, 11:59 PM
20 | const timestamp2 = new Date(2023, 11, 31, 23, 59).getTime()
21 | expect(formatDate(timestamp2)).toBe("DECEMBER 31, 11:59 PM")
22 | })
23 |
24 | it("handles AM/PM correctly", () => {
25 | // Morning time - 7:05 AM
26 | const morningTimestamp = new Date(2023, 5, 15, 7, 5).getTime()
27 | expect(formatDate(morningTimestamp)).toBe("JUNE 15, 7:05 AM")
28 |
29 | // Noon - 12:00 PM
30 | const noonTimestamp = new Date(2023, 5, 15, 12, 0).getTime()
31 | expect(formatDate(noonTimestamp)).toBe("JUNE 15, 12:00 PM")
32 |
33 | // Evening time - 8:15 PM
34 | const eveningTimestamp = new Date(2023, 5, 15, 20, 15).getTime()
35 | expect(formatDate(eveningTimestamp)).toBe("JUNE 15, 8:15 PM")
36 | })
37 |
38 | it("handles single-digit minutes with leading zeros", () => {
39 | // 9:05 AM
40 | const timestamp = new Date(2023, 3, 10, 9, 5).getTime()
41 | expect(formatDate(timestamp)).toBe("APRIL 10, 9:05 AM")
42 | })
43 |
44 | it("converts the result to uppercase", () => {
45 | const timestamp = new Date(2023, 8, 21, 16, 45).getTime()
46 | const result = formatDate(timestamp)
47 |
48 | expect(result).toBe(result.toUpperCase())
49 | expect(result).toBe("SEPTEMBER 21, 4:45 PM")
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/webview-ui/src/utils/__tests__/path-mentions.test.ts:
--------------------------------------------------------------------------------
1 | import { convertToMentionPath } from "../path-mentions"
2 |
3 | describe("path-mentions", () => {
4 | describe("convertToMentionPath", () => {
5 | it("should convert an absolute path to a mention path when it starts with cwd", () => {
6 | // Windows-style paths
7 | expect(convertToMentionPath("C:\\Users\\user\\project\\file.txt", "C:\\Users\\user\\project")).toBe(
8 | "@/file.txt",
9 | )
10 |
11 | // Unix-style paths
12 | expect(convertToMentionPath("/Users/user/project/file.txt", "/Users/user/project")).toBe("@/file.txt")
13 | })
14 |
15 | it("should handle paths with trailing slashes in cwd", () => {
16 | expect(convertToMentionPath("/Users/user/project/file.txt", "/Users/user/project/")).toBe("@/file.txt")
17 | })
18 |
19 | it("should be case-insensitive when matching paths", () => {
20 | expect(convertToMentionPath("/Users/User/Project/file.txt", "/users/user/project")).toBe("@/file.txt")
21 | })
22 |
23 | it("should return the original path when cwd is not provided", () => {
24 | expect(convertToMentionPath("/Users/user/project/file.txt")).toBe("/Users/user/project/file.txt")
25 | })
26 |
27 | it("should return the original path when it does not start with cwd", () => {
28 | expect(convertToMentionPath("/Users/other/project/file.txt", "/Users/user/project")).toBe(
29 | "/Users/other/project/file.txt",
30 | )
31 | })
32 |
33 | it("should normalize backslashes to forward slashes", () => {
34 | expect(convertToMentionPath("C:\\Users\\user\\project\\subdir\\file.txt", "C:\\Users\\user\\project")).toBe(
35 | "@/subdir/file.txt",
36 | )
37 | })
38 |
39 | it("should handle nested paths correctly", () => {
40 | expect(convertToMentionPath("/Users/user/project/nested/deeply/file.txt", "/Users/user/project")).toBe(
41 | "@/nested/deeply/file.txt",
42 | )
43 | })
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/webview-ui/src/utils/getLanguageFromPath.ts:
--------------------------------------------------------------------------------
1 | const extensionToLanguage: { [key: string]: string } = {
2 | // Web technologies
3 | html: "html",
4 | htm: "html",
5 | css: "css",
6 | js: "javascript",
7 | jsx: "jsx",
8 | ts: "typescript",
9 | tsx: "tsx",
10 |
11 | // Backend languages
12 | py: "python",
13 | rb: "ruby",
14 | php: "php",
15 | java: "java",
16 | cs: "csharp",
17 | go: "go",
18 | rs: "rust",
19 | scala: "scala",
20 | kt: "kotlin",
21 | swift: "swift",
22 |
23 | // Markup and data
24 | json: "json",
25 | xml: "xml",
26 | yaml: "yaml",
27 | yml: "yaml",
28 | md: "markdown",
29 | csv: "csv",
30 |
31 | // Shell and scripting
32 | sh: "bash",
33 | bash: "bash",
34 | zsh: "bash",
35 | ps1: "powershell",
36 |
37 | // Configuration
38 | toml: "toml",
39 | ini: "ini",
40 | cfg: "ini",
41 | conf: "ini",
42 |
43 | // Other
44 | sql: "sql",
45 | graphql: "graphql",
46 | gql: "graphql",
47 | tex: "latex",
48 | svg: "svg",
49 | txt: "text",
50 |
51 | // C-family languages
52 | c: "c",
53 | cpp: "cpp",
54 | h: "c",
55 | hpp: "cpp",
56 |
57 | // Functional languages
58 | hs: "haskell",
59 | lhs: "haskell",
60 | elm: "elm",
61 | clj: "clojure",
62 | cljs: "clojure",
63 | erl: "erlang",
64 | ex: "elixir",
65 | exs: "elixir",
66 |
67 | // Mobile development
68 | dart: "dart",
69 | m: "objectivec",
70 | mm: "objectivec",
71 |
72 | // Game development
73 | lua: "lua",
74 | gd: "gdscript", // Godot
75 | unity: "csharp", // Unity (using C#)
76 |
77 | // Data science and ML
78 | r: "r",
79 | jl: "julia",
80 | ipynb: "jupyter", // Jupyter notebooks
81 | }
82 |
83 | // Example usage:
84 | // console.log(getLanguageFromPath('/path/to/file.js')); // Output: javascript
85 |
86 | export function getLanguageFromPath(path: string): string | undefined {
87 | const extension = path.split(".").pop()?.toLowerCase() || ""
88 | return extensionToLanguage[extension]
89 | }
90 |
--------------------------------------------------------------------------------
/webview-ui/src/components/history/DeleteTaskDialog.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect } from "react"
2 | import { useKeyPress } from "react-use"
3 | import { AlertDialogProps } from "@radix-ui/react-alert-dialog"
4 |
5 | import {
6 | AlertDialog,
7 | AlertDialogAction,
8 | AlertDialogCancel,
9 | AlertDialogContent,
10 | AlertDialogDescription,
11 | AlertDialogFooter,
12 | AlertDialogHeader,
13 | AlertDialogTitle,
14 | Button,
15 | } from "@/components/ui"
16 |
17 | import { vscode } from "@/utils/vscode"
18 |
19 | interface DeleteTaskDialogProps extends AlertDialogProps {
20 | taskId: string
21 | }
22 |
23 | export const DeleteTaskDialog = ({ taskId, ...props }: DeleteTaskDialogProps) => {
24 | const [isEnterPressed] = useKeyPress("Enter")
25 |
26 | const { onOpenChange } = props
27 |
28 | const onDelete = useCallback(() => {
29 | if (taskId) {
30 | vscode.postMessage({ type: "deleteTaskWithId", text: taskId })
31 | onOpenChange?.(false)
32 | }
33 | }, [taskId, onOpenChange])
34 |
35 | useEffect(() => {
36 | if (taskId && isEnterPressed) {
37 | onDelete()
38 | }
39 | }, [taskId, isEnterPressed, onDelete])
40 |
41 | return (
42 |
43 | onOpenChange?.(false)}>
44 |
45 | Delete Task
46 |
47 | Are you sure you want to delete this task? This action cannot be undone.
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
58 |
59 |
60 |
61 |
62 | )
63 | }
64 |
--------------------------------------------------------------------------------
/src/shared/__tests__/experiments.test.ts:
--------------------------------------------------------------------------------
1 | import { EXPERIMENT_IDS, experimentConfigsMap, experiments as Experiments, ExperimentId } from "../experiments"
2 |
3 | describe("experiments", () => {
4 | describe("POWER_STEERING", () => {
5 | it("is configured correctly", () => {
6 | expect(EXPERIMENT_IDS.POWER_STEERING).toBe("powerSteering")
7 | expect(experimentConfigsMap.POWER_STEERING).toMatchObject({
8 | name: 'Use experimental "power steering" mode',
9 | description:
10 | "When enabled, Roo will remind the model about the details of its current mode definition more frequently. This will lead to stronger adherence to role definitions and custom instructions, but will use more tokens per message.",
11 | enabled: false,
12 | })
13 | })
14 | })
15 |
16 | describe("isEnabled", () => {
17 | it("returns false when experiment is not enabled", () => {
18 | const experiments: Record = {
19 | powerSteering: false,
20 | experimentalDiffStrategy: false,
21 | search_and_replace: false,
22 | insert_content: false,
23 | }
24 | expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false)
25 | })
26 |
27 | it("returns true when experiment is enabled", () => {
28 | const experiments: Record = {
29 | powerSteering: true,
30 | experimentalDiffStrategy: false,
31 | search_and_replace: false,
32 | insert_content: false,
33 | }
34 | expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(true)
35 | })
36 |
37 | it("returns false when experiment is not present", () => {
38 | const experiments: Record = {
39 | experimentalDiffStrategy: false,
40 | search_and_replace: false,
41 | insert_content: false,
42 | powerSteering: false,
43 | }
44 | expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false)
45 | })
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/src/core/prompts/sections/capabilities.ts:
--------------------------------------------------------------------------------
1 | import { DiffStrategy } from "../../diff/DiffStrategy"
2 | import { McpHub } from "../../../services/mcp/McpHub"
3 |
4 | export function getCapabilitiesSection(
5 | cwd: string,
6 | supportsComputerUse: boolean,
7 | mcpHub?: McpHub,
8 | diffStrategy?: DiffStrategy,
9 | ): string {
10 | return `====
11 |
12 | 功能特性
13 |
14 | - 你可以使用各种工具在用户的计算机上执行CLI命令、列出文件、查看源代码定义、正则搜索${supportsComputerUse ? "、使用浏览器" : ""}、读写文件,以及提出后续问题。这些工具可以帮助你有效地完成各种任务,例如编写代码、编辑或改进现有文件、了解项目的当前状态、执行系统操作等等。
15 | - 当用户最初给你一个任务时,当前工作目录('${cwd}')中的所有文件路径的递归列表将包含在environment_details中。这提供了项目文件结构的概览,从目录/文件名(开发人员如何概念化和组织他们的代码)和文件扩展名(使用的语言)中获取关键信息。这也可以指导你决定需要进一步探索哪些文件。如果你需要探索当前工作目录之外的目录,你可以使用list_files工具。如果将recursive参数设置为'true',它将递归列出文件。否则,它将只列出顶层文件,这更适合于不一定需要嵌套结构的通用目录,比如桌面。
16 | - 你可以使用search_files在指定目录中执行正则表达式搜索,输出包含周围行的上下文丰富的结果。这对于理解代码模式、查找特定实现或识别需要重构的区域特别有用。
17 | - 你可以使用list_code_definition_names工具获取指定目录顶层所有文件的源代码定义概览。当你需要理解代码的不同部分之间的更广泛的上下文和关系时,这特别有用。你可能需要多次调用这个工具来理解与任务相关的代码库的各个部分。
18 | - 例如,当被要求进行编辑或改进时,你可以首先分析初始environment_details中的文件结构以获取项目概览,然后使用list_code_definition_names获取相关目录中文件的源代码定义以获得更深入的见解,然后使用read_file检查相关文件的内容,分析代码并提出改进建议或进行必要的编辑,然后使用${diffStrategy ? "apply_diff或write_to_file" : "write_to_file"}工具应用更改。如果你重构的代码可能影响代码库的其他部分,你可以使用search_files确保更新其他相关文件。
19 | - 当你觉得可以帮助完成用户的任务时,你可以使用execute_command工具在用户的计算机上运行命令。当你需要执行CLI命令时,你必须提供清晰的命令说明。优先使用复杂的CLI命令而不是创建可执行脚本,因为它们更灵活且更容易运行。允许交互式和长时间运行的命令,因为这些命令在用户的VSCode终端中运行。用户可以在后台保持命令运行,你将随时了解它们的状态。你执行的每个命令都在新的终端实例中运行。${supportsComputerUse ? "\n- 当你觉得在完成用户任务时有必要时,你可以使用browser_action工具通过Puppeteer控制的浏览器与网站(包括html文件和本地运行的开发服务器)进行交互。这个工具对于Web开发任务特别有用,因为它允许你启动浏览器、导航到页面、通过点击和键盘输入与元素交互,并通过截图和控制台日志捕获结果。这个工具在Web开发任务的关键阶段可能很有用——比如在实现新功能后、进行重大更改时、排除问题时,或验证你的工作结果时。你可以分析提供的截图以确保正确渲染或识别错误,并查看控制台日志以了解运行时问题。\n - 例如,如果被要求向React网站添加组件,你可以创建必要的文件,使用execute_command在本地运行站点,然后使用browser_action启动浏览器,导航到本地服务器,并验证组件是否正确渲染和功能正常,然后关闭浏览器。" : ""}${mcpHub ? `\n- 你可以访问MCP服务器,它们可能提供额外的工具和资源。每个服务器可能提供不同的功能,你可以使用这些功能更有效地完成任务。\n` : ""}`
20 | }
21 |
--------------------------------------------------------------------------------
/src/shared/__tests__/checkExistApiConfig.test.ts:
--------------------------------------------------------------------------------
1 | import { checkExistKey } from "../checkExistApiConfig"
2 | import { ApiConfiguration } from "../api"
3 |
4 | describe("checkExistKey", () => {
5 | it("should return false for undefined config", () => {
6 | expect(checkExistKey(undefined)).toBe(false)
7 | })
8 |
9 | it("should return false for empty config", () => {
10 | const config: ApiConfiguration = {}
11 | expect(checkExistKey(config)).toBe(false)
12 | })
13 |
14 | it("should return true when one key is defined", () => {
15 | const config: ApiConfiguration = {
16 | apiKey: "test-key",
17 | }
18 | expect(checkExistKey(config)).toBe(true)
19 | })
20 |
21 | it("should return true when multiple keys are defined", () => {
22 | const config: ApiConfiguration = {
23 | apiKey: "test-key",
24 | glamaApiKey: "glama-key",
25 | openRouterApiKey: "openrouter-key",
26 | }
27 | expect(checkExistKey(config)).toBe(true)
28 | })
29 |
30 | it("should return true when only non-key fields are undefined", () => {
31 | const config: ApiConfiguration = {
32 | apiKey: "test-key",
33 | apiProvider: undefined,
34 | anthropicBaseUrl: undefined,
35 | modelMaxThinkingTokens: undefined,
36 | }
37 | expect(checkExistKey(config)).toBe(true)
38 | })
39 |
40 | it("should return false when all key fields are undefined", () => {
41 | const config: ApiConfiguration = {
42 | apiKey: undefined,
43 | glamaApiKey: undefined,
44 | openRouterApiKey: undefined,
45 | awsRegion: undefined,
46 | vertexProjectId: undefined,
47 | openAiApiKey: undefined,
48 | ollamaModelId: undefined,
49 | lmStudioModelId: undefined,
50 | geminiApiKey: undefined,
51 | openAiNativeApiKey: undefined,
52 | deepSeekApiKey: undefined,
53 | mistralApiKey: undefined,
54 | vsCodeLmModelSelector: undefined,
55 | requestyApiKey: undefined,
56 | unboundApiKey: undefined,
57 | }
58 | expect(checkExistKey(config)).toBe(false)
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/src/services/ripgrep/__tests__/index.test.ts:
--------------------------------------------------------------------------------
1 | // npx jest src/services/ripgrep/__tests__/index.test.ts
2 |
3 | import { describe, expect, it } from "@jest/globals"
4 | import { truncateLine } from "../index"
5 |
6 | describe("Ripgrep line truncation", () => {
7 | // The default MAX_LINE_LENGTH is 500 in the implementation
8 | const MAX_LINE_LENGTH = 500
9 |
10 | it("should truncate lines longer than MAX_LINE_LENGTH", () => {
11 | const longLine = "a".repeat(600) // Line longer than MAX_LINE_LENGTH
12 | const truncated = truncateLine(longLine)
13 |
14 | expect(truncated).toContain("[truncated...]")
15 | expect(truncated.length).toBeLessThan(longLine.length)
16 | expect(truncated.length).toEqual(MAX_LINE_LENGTH + " [truncated...]".length)
17 | })
18 |
19 | it("should not truncate lines shorter than MAX_LINE_LENGTH", () => {
20 | const shortLine = "Short line of text"
21 | const truncated = truncateLine(shortLine)
22 |
23 | expect(truncated).toEqual(shortLine)
24 | expect(truncated).not.toContain("[truncated...]")
25 | })
26 |
27 | it("should correctly truncate a line at exactly MAX_LINE_LENGTH characters", () => {
28 | const exactLine = "a".repeat(MAX_LINE_LENGTH)
29 | const exactPlusOne = exactLine + "x"
30 |
31 | // Should not truncate when exactly MAX_LINE_LENGTH
32 | expect(truncateLine(exactLine)).toEqual(exactLine)
33 |
34 | // Should truncate when exceeding MAX_LINE_LENGTH by even 1 character
35 | expect(truncateLine(exactPlusOne)).toContain("[truncated...]")
36 | })
37 |
38 | it("should handle empty lines without errors", () => {
39 | expect(truncateLine("")).toEqual("")
40 | })
41 |
42 | it("should allow custom maximum length", () => {
43 | const customLength = 100
44 | const line = "a".repeat(customLength + 50)
45 |
46 | const truncated = truncateLine(line, customLength)
47 |
48 | expect(truncated.length).toEqual(customLength + " [truncated...]".length)
49 | expect(truncated).toContain("[truncated...]")
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/src/__mocks__/vscode.js:
--------------------------------------------------------------------------------
1 | const vscode = {
2 | window: {
3 | showInformationMessage: jest.fn(),
4 | showErrorMessage: jest.fn(),
5 | createTextEditorDecorationType: jest.fn().mockReturnValue({
6 | dispose: jest.fn(),
7 | }),
8 | tabGroups: {
9 | onDidChangeTabs: jest.fn(() => {
10 | return {
11 | dispose: jest.fn(),
12 | }
13 | }),
14 | all: [],
15 | },
16 | },
17 | workspace: {
18 | onDidSaveTextDocument: jest.fn(),
19 | createFileSystemWatcher: jest.fn().mockReturnValue({
20 | onDidCreate: jest.fn().mockReturnValue({ dispose: jest.fn() }),
21 | onDidDelete: jest.fn().mockReturnValue({ dispose: jest.fn() }),
22 | dispose: jest.fn(),
23 | }),
24 | fs: {
25 | stat: jest.fn(),
26 | },
27 | },
28 | Disposable: class {
29 | dispose() {}
30 | },
31 | Uri: {
32 | file: (path) => ({
33 | fsPath: path,
34 | scheme: "file",
35 | authority: "",
36 | path: path,
37 | query: "",
38 | fragment: "",
39 | with: jest.fn(),
40 | toJSON: jest.fn(),
41 | }),
42 | },
43 | EventEmitter: class {
44 | constructor() {
45 | this.event = jest.fn()
46 | this.fire = jest.fn()
47 | }
48 | },
49 | ConfigurationTarget: {
50 | Global: 1,
51 | Workspace: 2,
52 | WorkspaceFolder: 3,
53 | },
54 | Position: class {
55 | constructor(line, character) {
56 | this.line = line
57 | this.character = character
58 | }
59 | },
60 | Range: class {
61 | constructor(startLine, startCharacter, endLine, endCharacter) {
62 | this.start = new vscode.Position(startLine, startCharacter)
63 | this.end = new vscode.Position(endLine, endCharacter)
64 | }
65 | },
66 | ThemeColor: class {
67 | constructor(id) {
68 | this.id = id
69 | }
70 | },
71 | ExtensionMode: {
72 | Production: 1,
73 | Development: 2,
74 | Test: 3,
75 | },
76 | FileType: {
77 | Unknown: 0,
78 | File: 1,
79 | Directory: 2,
80 | SymbolicLink: 64,
81 | },
82 | TabInputText: class {
83 | constructor(uri) {
84 | this.uri = uri
85 | }
86 | },
87 | }
88 |
89 | module.exports = vscode
90 |
--------------------------------------------------------------------------------
/.github/scripts/get_prev_version_refs.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import subprocess
4 |
5 | def run_git_command(command):
6 | result = subprocess.getoutput(command)
7 | print(f"Git Command: {command}")
8 | print(f"Git Output: {result}")
9 | return result
10 |
11 | def parse_merge_commit(line):
12 | # Parse merge commit messages like:
13 | # "355dc82 Merge pull request #71 from RooVetGit/better-error-handling"
14 | pattern = r"([a-f0-9]+)\s+Merge pull request #(\d+) from (.+)"
15 | match = re.match(pattern, line)
16 | if match:
17 | sha, pr_number, branch = match.groups()
18 | return {
19 | 'sha': sha,
20 | 'pr_number': pr_number,
21 | 'branch': branch
22 | }
23 | return None
24 |
25 | def get_version_refs():
26 | # Get the merge commits with full message
27 | command = 'git log --merges --pretty=oneline -n 3'
28 | result = run_git_command(command)
29 |
30 | if result:
31 | commits = result.split('\n')
32 | if len(commits) >= 3:
33 | # Parse HEAD~1 (PR to generate notes for)
34 | head_info = parse_merge_commit(commits[1])
35 | # Parse HEAD~2 (previous PR to compare against)
36 | base_info = parse_merge_commit(commits[2])
37 |
38 | if head_info and base_info:
39 | # Set output for GitHub Actions
40 | with open(os.environ['GITHUB_OUTPUT'], 'a') as gha_outputs:
41 | gha_outputs.write(f"head_ref={head_info['sha']}\n")
42 | gha_outputs.write(f"base_ref={base_info['sha']}")
43 |
44 | print(f"Head ref (PR #{head_info['pr_number']}): {head_info['sha']}")
45 | print(f"Base ref (PR #{base_info['pr_number']}): {base_info['sha']}")
46 | return head_info, base_info
47 |
48 | print("Could not find or parse sufficient merge history")
49 | return None, None
50 |
51 | if __name__ == "__main__":
52 | head_info, base_info = get_version_refs()
--------------------------------------------------------------------------------
/webview-ui/src/components/welcome/WelcomeView.tsx:
--------------------------------------------------------------------------------
1 | import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
2 | import { useCallback, useState } from "react"
3 | import { useExtensionState } from "../../context/ExtensionStateContext"
4 | import { validateApiConfiguration } from "../../utils/validate"
5 | import { vscode } from "../../utils/vscode"
6 | import ApiOptions from "../settings/ApiOptions"
7 |
8 | const WelcomeView = () => {
9 | const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme } = useExtensionState()
10 |
11 | const [errorMessage, setErrorMessage] = useState(undefined)
12 |
13 | const handleSubmit = useCallback(() => {
14 | const error = validateApiConfiguration(apiConfiguration)
15 |
16 | if (error) {
17 | setErrorMessage(error)
18 | return
19 | }
20 |
21 | setErrorMessage(undefined)
22 | vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
23 | }, [apiConfiguration, currentApiConfigName])
24 |
25 | return (
26 |
27 |
你好,我是 Roo!
28 |
29 | 得益于最新的智能编程技术突破和各种工具的支持,我可以完成各种任务。我能够创建和编辑文件、
30 | 探索复杂项目、使用浏览器,以及执行终端命令(当然,需要您的许可)。我甚至可以使用 MCP
31 | 来创建新工具并扩展自己的能力。
32 |
33 |
34 |
首先,这个扩展需要一个 API 提供者。
35 |
36 |
37 |
setApiConfiguration({ [field]: value })}
42 | errorMessage={errorMessage}
43 | setErrorMessage={setErrorMessage}
44 | />
45 |
46 |
47 |
48 |
49 | 开始使用!
50 | {errorMessage && {errorMessage}}
51 |
52 |
53 |
54 | )
55 | }
56 |
57 | export default WelcomeView
58 |
--------------------------------------------------------------------------------
/src/services/checkpoints/types.ts:
--------------------------------------------------------------------------------
1 | import EventEmitter from "events"
2 | import { CommitResult } from "simple-git"
3 |
4 | export type CheckpointResult = Partial & Pick
5 |
6 | export type CheckpointDiff = {
7 | paths: {
8 | relative: string
9 | absolute: string
10 | }
11 | content: {
12 | before: string
13 | after: string
14 | }
15 | }
16 |
17 | export interface CheckpointService {
18 | saveCheckpoint(message: string): Promise
19 | restoreCheckpoint(commit: string): Promise
20 | getDiff(range: { from?: string; to?: string }): Promise
21 | workspaceDir: string
22 | baseHash?: string
23 | version: number
24 | }
25 |
26 | export interface CheckpointServiceOptions {
27 | taskId: string
28 | workspaceDir: string
29 | log?: (message: string) => void
30 | }
31 |
32 | /**
33 | * EventEmitter
34 | */
35 |
36 | export interface CheckpointEventMap {
37 | initialize: { type: "initialize"; workspaceDir: string; baseHash: string; created: boolean; duration: number }
38 | checkpoint: {
39 | type: "checkpoint"
40 | isFirst: boolean
41 | fromHash: string
42 | toHash: string
43 | duration: number
44 | }
45 | restore: { type: "restore"; commitHash: string; duration: number }
46 | error: { type: "error"; error: Error }
47 | }
48 |
49 | export class CheckpointEventEmitter extends EventEmitter {
50 | override emit(event: K, data: CheckpointEventMap[K]): boolean {
51 | return super.emit(event, data)
52 | }
53 |
54 | override on(event: K, listener: (data: CheckpointEventMap[K]) => void): this {
55 | return super.on(event, listener)
56 | }
57 |
58 | override off(event: K, listener: (data: CheckpointEventMap[K]) => void): this {
59 | return super.off(event, listener)
60 | }
61 |
62 | override once(event: K, listener: (data: CheckpointEventMap[K]) => void): this {
63 | return super.once(event, listener)
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/integrations/editor/detect-omission.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Detects potential AI-generated code omissions in the given file content.
3 | * @param originalFileContent The original content of the file.
4 | * @param newFileContent The new content of the file to check.
5 | * @param predictedLineCount The predicted number of lines in the new content.
6 | * @returns True if a potential omission is detected, false otherwise.
7 | */
8 | export function detectCodeOmission(
9 | originalFileContent: string,
10 | newFileContent: string,
11 | predictedLineCount: number,
12 | ): boolean {
13 | // Skip all checks if predictedLineCount is less than 100
14 | if (!predictedLineCount || predictedLineCount < 100) {
15 | return false
16 | }
17 |
18 | const actualLineCount = newFileContent.split("\n").length
19 | const lengthRatio = actualLineCount / predictedLineCount
20 |
21 | const originalLines = originalFileContent.split("\n")
22 | const newLines = newFileContent.split("\n")
23 | const omissionKeywords = [
24 | "remain",
25 | "remains",
26 | "unchanged",
27 | "rest",
28 | "previous",
29 | "existing",
30 | "content",
31 | "same",
32 | "...",
33 | ]
34 |
35 | const commentPatterns = [
36 | /^\s*\/\//, // Single-line comment for most languages
37 | /^\s*#/, // Single-line comment for Python, Ruby, etc.
38 | /^\s*\/\*/, // Multi-line comment opening
39 | /^\s*{\s*\/\*/, // JSX comment opening
40 | /^\s*