├── .changeset ├── README.md ├── changelog-config.js └── config.json ├── .clinerules ├── .eslintrc.json ├── .git-blame-ignore-revs ├── .gitattributes ├── .gitconfig ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml ├── actions │ └── ai-release-notes │ │ └── action.yml ├── dependabot.yml ├── pull_request_template.md ├── scripts │ ├── ai-release-notes.py │ ├── get_prev_version_refs.py │ ├── overwrite_changeset_changelog.py │ ├── parse_changeset_changelog.py │ └── release-notes-prompt.py └── workflows │ ├── changeset-release.yml │ ├── code-qa.yml │ ├── codeql.yml │ ├── discord-pr-notify.yml │ └── marketplace-publish.yml ├── .gitignore ├── .husky ├── pre-commit └── pre-push ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── docs │ └── demo.gif └── icons │ └── rocket.png ├── audio ├── celebration.wav ├── notification.wav └── progress_loop.wav ├── cline_docs └── settings.md ├── e2e ├── .env.integration.example ├── .vscode-test.mjs ├── VSCODE_INTEGRATION_TESTS.md ├── package-lock.json ├── package.json ├── src │ ├── runTest.ts │ └── suite │ │ ├── extension.test.ts │ │ ├── index.ts │ │ ├── modes.test.ts │ │ └── task.test.ts └── tsconfig.json ├── ellipsis.yaml ├── esbuild.js ├── flake.lock ├── flake.nix ├── jest.config.js ├── knip.json ├── package-lock.json ├── package.json ├── src ├── __mocks__ │ ├── @modelcontextprotocol │ │ └── sdk │ │ │ ├── client │ │ │ ├── index.js │ │ │ └── stdio.js │ │ │ ├── index.js │ │ │ └── types.js │ ├── McpHub.ts │ ├── default-shell.js │ ├── delay.js │ ├── fs │ │ └── promises.ts │ ├── get-folder-size.js │ ├── globby.js │ ├── jest.setup.ts │ ├── os-name.js │ ├── p-wait-for.js │ ├── serialize-error.js │ ├── strip-ansi.js │ └── vscode.js ├── activate │ ├── handleUri.ts │ ├── index.ts │ ├── registerCodeActions.ts │ ├── registerCommands.ts │ └── registerTerminalActions.ts ├── api │ ├── __tests__ │ │ └── index.test.ts │ ├── index.ts │ ├── providers │ │ ├── __tests__ │ │ │ ├── anthropic.test.ts │ │ │ ├── bedrock.test.ts │ │ │ ├── deepseek.test.ts │ │ │ ├── gemini.test.ts │ │ │ ├── glama.test.ts │ │ │ ├── lmstudio.test.ts │ │ │ ├── mistral.test.ts │ │ │ ├── ollama.test.ts │ │ │ ├── openai-native.test.ts │ │ │ ├── openai.test.ts │ │ │ ├── openrouter.test.ts │ │ │ ├── requesty.test.ts │ │ │ ├── unbound.test.ts │ │ │ ├── vertex.test.ts │ │ │ └── vscode-lm.test.ts │ │ ├── anthropic.ts │ │ ├── base-provider.ts │ │ ├── bedrock.ts │ │ ├── constants.ts │ │ ├── deepseek.ts │ │ ├── gemini.ts │ │ ├── glama.ts │ │ ├── lmstudio.ts │ │ ├── mistral.ts │ │ ├── ollama.ts │ │ ├── openai-native.ts │ │ ├── openai.ts │ │ ├── openrouter.ts │ │ ├── requesty.ts │ │ ├── unbound.ts │ │ ├── vertex.ts │ │ └── vscode-lm.ts │ └── transform │ │ ├── __tests__ │ │ ├── bedrock-converse-format.test.ts │ │ ├── gemini-format.test.ts │ │ ├── mistral-format.test.ts │ │ ├── openai-format.test.ts │ │ ├── r1-format.test.ts │ │ ├── simple-format.test.ts │ │ ├── stream.test.ts │ │ ├── vertex-gemini-format.test.ts │ │ └── vscode-lm-format.test.ts │ │ ├── bedrock-converse-format.ts │ │ ├── gemini-format.ts │ │ ├── mistral-format.ts │ │ ├── openai-format.ts │ │ ├── r1-format.ts │ │ ├── simple-format.ts │ │ ├── stream.ts │ │ ├── vertex-gemini-format.ts │ │ └── vscode-lm-format.ts ├── core │ ├── Cline.ts │ ├── CodeActionProvider.ts │ ├── EditorUtils.ts │ ├── __tests__ │ │ ├── Cline.test.ts │ │ ├── CodeActionProvider.test.ts │ │ ├── EditorUtils.test.ts │ │ └── mode-validator.test.ts │ ├── assistant-message │ │ ├── index.ts │ │ └── parse-assistant-message.ts │ ├── config │ │ ├── ConfigManager.ts │ │ ├── CustomModesManager.ts │ │ ├── CustomModesSchema.ts │ │ └── __tests__ │ │ │ ├── ConfigManager.test.ts │ │ │ ├── CustomModesManager.test.ts │ │ │ ├── CustomModesSchema.test.ts │ │ │ ├── CustomModesSettings.test.ts │ │ │ └── GroupConfigSchema.test.ts │ ├── diff │ │ ├── DiffStrategy.ts │ │ ├── insert-groups.ts │ │ ├── strategies │ │ │ ├── __tests__ │ │ │ │ ├── new-unified.test.ts │ │ │ │ ├── search-replace.test.ts │ │ │ │ └── unified.test.ts │ │ │ ├── new-unified │ │ │ │ ├── __tests__ │ │ │ │ │ ├── edit-strategies.test.ts │ │ │ │ │ └── search-strategies.test.ts │ │ │ │ ├── edit-strategies.ts │ │ │ │ ├── index.ts │ │ │ │ ├── search-strategies.ts │ │ │ │ └── types.ts │ │ │ ├── search-replace.ts │ │ │ └── unified.ts │ │ └── types.ts │ ├── mentions │ │ ├── __tests__ │ │ │ └── index.test.ts │ │ └── index.ts │ ├── mode-validator.ts │ ├── prompts │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── system.test.ts.snap │ │ │ ├── custom-system-prompt.test.ts │ │ │ ├── sections.test.ts │ │ │ └── system.test.ts │ │ ├── responses.ts │ │ ├── sections │ │ │ ├── __tests__ │ │ │ │ └── custom-instructions.test.ts │ │ │ ├── capabilities.ts │ │ │ ├── custom-instructions.ts │ │ │ ├── custom-system-prompt.ts │ │ │ ├── index.ts │ │ │ ├── mcp-servers.ts │ │ │ ├── modes.ts │ │ │ ├── objective.ts │ │ │ ├── rules.ts │ │ │ ├── system-info.ts │ │ │ ├── tool-use-guidelines.ts │ │ │ └── tool-use.ts │ │ ├── system.ts │ │ └── tools │ │ │ ├── access-mcp-resource.ts │ │ │ ├── ask-followup-question.ts │ │ │ ├── attempt-completion.ts │ │ │ ├── browser-action.ts │ │ │ ├── execute-command.ts │ │ │ ├── index.ts │ │ │ ├── insert-content.ts │ │ │ ├── list-code-definition-names.ts │ │ │ ├── list-files.ts │ │ │ ├── new-task.ts │ │ │ ├── read-file.ts │ │ │ ├── search-and-replace.ts │ │ │ ├── search-files.ts │ │ │ ├── switch-mode.ts │ │ │ ├── types.ts │ │ │ ├── use-mcp-tool.ts │ │ │ └── write-to-file.ts │ ├── sliding-window │ │ ├── __tests__ │ │ │ └── sliding-window.test.ts │ │ └── index.ts │ └── webview │ │ ├── ClineProvider.ts │ │ ├── __tests__ │ │ └── ClineProvider.test.ts │ │ ├── getNonce.ts │ │ └── getUri.ts ├── exports │ ├── README.md │ ├── cline.d.ts │ └── index.ts ├── extension.ts ├── integrations │ ├── diagnostics │ │ └── index.ts │ ├── editor │ │ ├── DecorationController.ts │ │ ├── DiffViewProvider.ts │ │ ├── __tests__ │ │ │ ├── DiffViewProvider.test.ts │ │ │ └── detect-omission.test.ts │ │ └── detect-omission.ts │ ├── misc │ │ ├── __tests__ │ │ │ └── extract-text.test.ts │ │ ├── export-markdown.ts │ │ ├── extract-text.ts │ │ ├── open-file.ts │ │ └── process-images.ts │ ├── terminal │ │ ├── TerminalManager.ts │ │ ├── TerminalProcess.ts │ │ ├── TerminalRegistry.ts │ │ ├── __tests__ │ │ │ ├── TerminalProcess.test.ts │ │ │ └── TerminalRegistry.test.ts │ │ └── get-latest-output.ts │ ├── theme │ │ ├── default-themes │ │ │ ├── dark_modern.json │ │ │ ├── dark_plus.json │ │ │ ├── dark_vs.json │ │ │ ├── hc_black.json │ │ │ ├── hc_light.json │ │ │ ├── light_modern.json │ │ │ ├── light_plus.json │ │ │ └── light_vs.json │ │ └── getTheme.ts │ └── workspace │ │ ├── WorkspaceTracker.ts │ │ └── __tests__ │ │ └── WorkspaceTracker.test.ts ├── services │ ├── browser │ │ ├── BrowserSession.ts │ │ └── UrlContentFetcher.ts │ ├── checkpoints │ │ ├── ShadowCheckpointService.ts │ │ ├── __tests__ │ │ │ └── ShadowCheckpointService.test.ts │ │ ├── constants.ts │ │ └── types.ts │ ├── glob │ │ └── list-files.ts │ ├── mcp │ │ ├── McpHub.ts │ │ ├── McpServerManager.ts │ │ └── __tests__ │ │ │ └── McpHub.test.ts │ ├── ripgrep │ │ ├── __tests__ │ │ │ └── index.test.ts │ │ └── index.ts │ └── tree-sitter │ │ ├── __tests__ │ │ ├── index.test.ts │ │ └── languageParser.test.ts │ │ ├── index.ts │ │ ├── languageParser.ts │ │ └── queries │ │ ├── c-sharp.ts │ │ ├── c.ts │ │ ├── cpp.ts │ │ ├── go.ts │ │ ├── index.ts │ │ ├── java.ts │ │ ├── javascript.ts │ │ ├── php.ts │ │ ├── python.ts │ │ ├── ruby.ts │ │ ├── rust.ts │ │ ├── swift.ts │ │ └── typescript.ts ├── shared │ ├── ExtensionMessage.ts │ ├── HistoryItem.ts │ ├── WebviewMessage.ts │ ├── __tests__ │ │ ├── checkExistApiConfig.test.ts │ │ ├── context-mentions.test.ts │ │ ├── experiments.test.ts │ │ ├── modes.test.ts │ │ ├── support-prompts.test.ts │ │ └── vsCodeSelectorUtils.test.ts │ ├── api.ts │ ├── array.ts │ ├── checkExistApiConfig.ts │ ├── combineApiRequests.ts │ ├── combineCommandSequences.ts │ ├── context-mentions.ts │ ├── experiments.ts │ ├── getApiMetrics.ts │ ├── globalFileNames.ts │ ├── globalState.ts │ ├── mcp.ts │ ├── modes.ts │ ├── support-prompt.ts │ ├── tool-groups.ts │ └── vsCodeSelectorUtils.ts └── utils │ ├── __tests__ │ ├── cost.test.ts │ ├── enhance-prompt.test.ts │ ├── git.test.ts │ ├── path.test.ts │ ├── shell.test.ts │ └── xml-matcher.test.ts │ ├── cost.ts │ ├── fs.ts │ ├── git.ts │ ├── logging │ ├── CompactLogger.ts │ ├── CompactTransport.ts │ ├── __tests__ │ │ ├── CompactLogger.test.ts │ │ ├── CompactTransport.test.ts │ │ └── MockTransport.ts │ ├── index.ts │ └── types.ts │ ├── path.ts │ ├── shell.ts │ ├── single-completion-handler.ts │ ├── sound.ts │ └── xml-matcher.ts ├── tsconfig.json └── webview-ui ├── .eslintrc.json ├── .gitignore ├── .npmrc ├── .storybook ├── main.ts ├── preview.ts └── vscode.css ├── components.json ├── index.html ├── jest.config.cjs ├── package-lock.json ├── package.json ├── public └── .gitkeep ├── src ├── App.tsx ├── __mocks__ │ ├── @vscode │ │ └── webview-ui-toolkit │ │ │ └── react.ts │ ├── lucide-react.ts │ ├── pretty-bytes.js │ └── vscrui.ts ├── __tests__ │ └── App.test.tsx ├── components │ ├── chat │ │ ├── Announcement.tsx │ │ ├── AutoApproveMenu.tsx │ │ ├── BrowserSessionRow.tsx │ │ ├── ChatRow.tsx │ │ ├── ChatTextArea.tsx │ │ ├── ChatView.tsx │ │ ├── ContextMenu.tsx │ │ ├── ReasoningBlock.tsx │ │ ├── TaskHeader.tsx │ │ ├── __tests__ │ │ │ ├── ChatTextArea.test.tsx │ │ │ ├── ChatView.auto-approve.test.tsx │ │ │ └── ChatView.test.tsx │ │ └── checkpoints │ │ │ ├── CheckpointMenu.tsx │ │ │ ├── CheckpointSaved.tsx │ │ │ └── schema.ts │ ├── common │ │ ├── CaretIcon.tsx │ │ ├── CodeAccordian.tsx │ │ ├── CodeBlock.tsx │ │ ├── MarkdownBlock.tsx │ │ ├── MermaidBlock.tsx │ │ ├── Thumbnails.tsx │ │ ├── VSCodeButtonLink.tsx │ │ └── __mocks__ │ │ │ ├── CodeBlock.tsx │ │ │ └── MarkdownBlock.tsx │ ├── history │ │ ├── CopyButton.tsx │ │ ├── DeleteTaskDialog.tsx │ │ ├── ExportButton.tsx │ │ ├── HistoryPreview.tsx │ │ ├── HistoryView.tsx │ │ └── __tests__ │ │ │ └── HistoryView.test.tsx │ ├── mcp │ │ ├── McpEnabledToggle.tsx │ │ ├── McpResourceRow.tsx │ │ ├── McpToolRow.tsx │ │ ├── McpView.tsx │ │ └── __tests__ │ │ │ └── McpToolRow.test.tsx │ ├── prompts │ │ ├── PromptsView.tsx │ │ └── __tests__ │ │ │ └── PromptsView.test.tsx │ ├── settings │ │ ├── ApiConfigManager.tsx │ │ ├── ApiErrorMessage.tsx │ │ ├── ApiOptions.tsx │ │ ├── ExperimentalFeature.tsx │ │ ├── ModelDescriptionMarkdown.tsx │ │ ├── ModelInfoView.tsx │ │ ├── ModelPicker.tsx │ │ ├── SettingsView.tsx │ │ ├── TemperatureControl.tsx │ │ ├── ThinkingBudget.tsx │ │ ├── __tests__ │ │ │ ├── ApiConfigManager.test.tsx │ │ │ ├── ApiOptions.test.tsx │ │ │ ├── ModelPicker.test.tsx │ │ │ ├── SettingsView.test.tsx │ │ │ ├── TemperatureControl.test.tsx │ │ │ └── ThinkingBudget.test.tsx │ │ └── styles.ts │ ├── ui │ │ ├── alert-dialog.tsx │ │ ├── autosize-textarea.tsx │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── chat │ │ │ ├── Chat.tsx │ │ │ ├── ChatInput.tsx │ │ │ ├── ChatInputProvider.ts │ │ │ ├── ChatMessage.tsx │ │ │ ├── ChatMessageProvider.ts │ │ │ ├── ChatMessages.tsx │ │ │ ├── ChatProvider.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── useChatInput.ts │ │ │ ├── useChatMessage.ts │ │ │ └── useChatUI.ts │ │ ├── collapsible.tsx │ │ ├── combobox-primitive.tsx │ │ ├── combobox.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── hooks │ │ │ ├── index.ts │ │ │ └── useClipboard.ts │ │ ├── index.ts │ │ ├── input-base.tsx │ │ ├── input.tsx │ │ ├── markdown │ │ │ ├── Blockquote.tsx │ │ │ ├── CodeBlock.tsx │ │ │ ├── Markdown.tsx │ │ │ └── index.ts │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── separator.tsx │ │ ├── slider.tsx │ │ ├── textarea.tsx │ │ └── tooltip.tsx │ └── welcome │ │ └── WelcomeView.tsx ├── context │ ├── ExtensionStateContext.tsx │ └── __tests__ │ │ └── ExtensionStateContext.test.tsx ├── index.css ├── index.tsx ├── lib │ └── utils.ts ├── preflight.css ├── setupTests.ts ├── stories │ ├── AutosizeTextarea.stories.tsx │ ├── Badge.stories.tsx │ ├── Button.stories.ts │ ├── Chat.stories.tsx │ ├── Collapsible.stories.tsx │ ├── Combobox.stories.tsx │ ├── DropdownMenu.stories.tsx │ ├── Progress.stories.tsx │ ├── Slider.stories.tsx │ ├── Welcome.mdx │ └── assets │ │ └── .gitkeep ├── utils │ ├── __tests__ │ │ ├── command-validation.test.ts │ │ ├── context-mentions.test.ts │ │ ├── format.test.ts │ │ └── path-mentions.test.ts │ ├── clipboard.ts │ ├── command-validation.ts │ ├── context-mentions.ts │ ├── format.ts │ ├── formatPrice.ts │ ├── getLanguageFromPath.ts │ ├── highlight.ts │ ├── mcp.ts │ ├── path-mentions.ts │ ├── textMateToHljs.ts │ ├── useDebounceEffect.ts │ ├── validate.ts │ └── vscode.ts └── vite-env.d.ts ├── tsconfig.json └── vite.config.ts /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Ran Prettier on all files - https://github.com/RooVetGit/Roo-Code/pull/404 2 | 60a0a824b96a0b326af4d8871b6903f4ddcfe114 3 | 579bdd9dbf6d2d569e5e7adb5ff6292b1e42ea34 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | demo.gif filter=lfs diff=lfs merge=lfs -text 2 | assets/docs/demo.gif filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [blame] 2 | ignoreRevsFile = .git-blame-ignore-revs 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in the repo 2 | * @mrubens @cte 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | labels: ["bug"] 4 | body: 5 | - type: input 6 | id: version 7 | attributes: 8 | label: Which version of the app are you using? 9 | description: Please specify the app version you're using (e.g. v3.3.1) 10 | validations: 11 | required: true 12 | - type: dropdown 13 | id: provider 14 | attributes: 15 | label: Which API Provider are you using? 16 | multiple: false 17 | options: 18 | - OpenRouter 19 | - Anthropic 20 | - Google Gemini 21 | - DeepSeek 22 | - OpenAI 23 | - OpenAI Compatible 24 | - GCP Vertex AI 25 | - AWS Bedrock 26 | - Glama 27 | - VS Code LM API 28 | - LM Studio 29 | - Ollama 30 | validations: 31 | required: true 32 | - type: input 33 | id: model 34 | attributes: 35 | label: Which Model are you using? 36 | description: Please specify the model you're using (e.g. Claude 3.7 Sonnet) 37 | validations: 38 | required: true 39 | - type: textarea 40 | id: what-happened 41 | attributes: 42 | label: What happened? 43 | description: Also tell us, what did you expect to happen? 44 | placeholder: Tell us what you see! 45 | validations: 46 | required: true 47 | - type: textarea 48 | id: steps 49 | attributes: 50 | label: Steps to reproduce 51 | description: How do you trigger this bug? Please walk us through it step by step. 52 | value: | 53 | 1. 54 | 2. 55 | 3. 56 | validations: 57 | required: true 58 | - type: textarea 59 | id: logs 60 | attributes: 61 | label: Relevant API REQUEST output 62 | description: Please copy and paste any relevant output. This will be automatically formatted into code, so no need for backticks. 63 | render: shell 64 | - type: textarea 65 | id: additional-context 66 | attributes: 67 | label: Additional context 68 | description: Add any other context about the problem here, such as screenshots or related issues. 69 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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() -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules 3 | webview-ui/build/ 4 | CHANGELOG.md 5 | package-lock.json -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "useTabs": true, 4 | "printWidth": 120, 5 | "semi": false, 6 | "bracketSameLine": true 7 | } 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | - 支持可以运行命令的只读模式 -------------------------------------------------------------------------------- /assets/docs/demo.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d426d600fa80e9ac237cbad6b3f7f47a4aa2005d5218184e6b9565a8ee46d1ba 3 | size 19108207 4 | -------------------------------------------------------------------------------- /assets/icons/rocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HybridTalentComputing/Roo-Code-Chinese/e981f86f1b454c3b9d2c1d225d45e72822af0f22/assets/icons/rocket.png -------------------------------------------------------------------------------- /audio/celebration.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HybridTalentComputing/Roo-Code-Chinese/e981f86f1b454c3b9d2c1d225d45e72822af0f22/audio/celebration.wav -------------------------------------------------------------------------------- /audio/notification.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HybridTalentComputing/Roo-Code-Chinese/e981f86f1b454c3b9d2c1d225d45e72822af0f22/audio/notification.wav -------------------------------------------------------------------------------- /audio/progress_loop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HybridTalentComputing/Roo-Code-Chinese/e981f86f1b454c3b9d2c1d225d45e72822af0f22/audio/progress_loop.wav -------------------------------------------------------------------------------- /e2e/.env.integration.example: -------------------------------------------------------------------------------- 1 | OPENROUTER_API_KEY=sk-or-v1-... 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/__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/__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/__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/__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 | -------------------------------------------------------------------------------- /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/__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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/__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/__mocks__/os-name.js: -------------------------------------------------------------------------------- 1 | function osName() { 2 | return "macOS" 3 | } 4 | 5 | module.exports = osName 6 | module.exports.default = osName 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/__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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/api/providers/constants.ts: -------------------------------------------------------------------------------- 1 | export const ANTHROPIC_DEFAULT_MAX_TOKENS = 8192 2 | 3 | export const DEEP_SEEK_DEFAULT_TEMPERATURE = 0.6 4 | -------------------------------------------------------------------------------- /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/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/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/config/CustomModesSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod" 2 | import { ModeConfig } from "../../shared/modes" 3 | import { TOOL_GROUPS, ToolGroup } from "../../shared/tool-groups" 4 | 5 | // 使用 TOOL_GROUPS 的键创建有效工具组的模式 6 | const ToolGroupSchema = z.enum(Object.keys(TOOL_GROUPS) as [ToolGroup, ...ToolGroup[]]) 7 | 8 | // 带有正则表达式验证的分组选项模式 9 | const GroupOptionsSchema = z.object({ 10 | fileRegex: z 11 | .string() 12 | .optional() 13 | .refine( 14 | (pattern) => { 15 | if (!pattern) return true // 可选的,所以空值有效 16 | try { 17 | new RegExp(pattern) 18 | return true 19 | } catch { 20 | return false 21 | } 22 | }, 23 | { message: "无效的正则表达式模式" }, 24 | ), 25 | description: z.string().optional(), 26 | }) 27 | 28 | // 分组条目的模式 - 可以是工具组字符串或 [组, 选项] 的元组 29 | const GroupEntrySchema = z.union([ToolGroupSchema, z.tuple([ToolGroupSchema, GroupOptionsSchema])]) 30 | 31 | // 分组数组的模式 32 | const GroupsArraySchema = z.array(GroupEntrySchema).refine( 33 | (groups) => { 34 | const seen = new Set() 35 | return groups.every((group) => { 36 | // 对于元组,检查组名(第一个元素) 37 | const groupName = Array.isArray(group) ? group[0] : group 38 | if (seen.has(groupName)) return false 39 | seen.add(groupName) 40 | return true 41 | }) 42 | }, 43 | { message: "不允许重复的分组" }, 44 | ) 45 | 46 | // 模式配置的模式 47 | export const CustomModeSchema = z.object({ 48 | slug: z.string().regex(/^[a-zA-Z0-9-]+$/, "标识符只能包含字母、数字和连字符"), 49 | name: z.string().min(1, "名称是必需的"), 50 | roleDefinition: z.string().min(1, "角色定义是必需的"), 51 | customInstructions: z.string().optional(), 52 | groups: GroupsArraySchema, 53 | }) satisfies z.ZodType 54 | 55 | // 整个自定义模式设置文件的模式 56 | export const CustomModesSettingsSchema = z.object({ 57 | customModes: z.array(CustomModeSchema).refine( 58 | (modes) => { 59 | const slugs = new Set() 60 | return modes.every((mode) => { 61 | if (slugs.has(mode.slug)) { 62 | return false 63 | } 64 | slugs.add(mode.slug) 65 | return true 66 | }) 67 | }, 68 | { 69 | message: "不允许重复的模式标识符", 70 | }, 71 | ), 72 | }) 73 | 74 | export type CustomModesSettings = z.infer 75 | 76 | /** 77 | * 根据模式验证自定义模式配置 78 | * @throws {z.ZodError} 如果验证失败 79 | */ 80 | export function validateCustomMode(mode: unknown): asserts mode is ModeConfig { 81 | CustomModeSchema.parse(mode) 82 | } 83 | -------------------------------------------------------------------------------- /src/core/config/__tests__/GroupConfigSchema.test.ts: -------------------------------------------------------------------------------- 1 | import { CustomModeSchema } from "../CustomModesSchema" 2 | import { ModeConfig } from "../../../shared/modes" 3 | 4 | describe("GroupConfigSchema", () => { 5 | const validBaseMode = { 6 | slug: "123e4567-e89b-12d3-a456-426614174000", 7 | name: "Test Mode", 8 | roleDefinition: "Test role definition", 9 | } 10 | 11 | describe("group format validation", () => { 12 | test("accepts single group", () => { 13 | const mode = { 14 | ...validBaseMode, 15 | groups: ["read"] as const, 16 | } satisfies ModeConfig 17 | 18 | expect(() => CustomModeSchema.parse(mode)).not.toThrow() 19 | }) 20 | 21 | test("accepts multiple groups", () => { 22 | const mode = { 23 | ...validBaseMode, 24 | groups: ["read", "edit", "browser"] as const, 25 | } satisfies ModeConfig 26 | 27 | expect(() => CustomModeSchema.parse(mode)).not.toThrow() 28 | }) 29 | 30 | test("accepts all available groups", () => { 31 | const mode = { 32 | ...validBaseMode, 33 | groups: ["read", "edit", "browser", "command", "mcp"] as const, 34 | } satisfies ModeConfig 35 | 36 | expect(() => CustomModeSchema.parse(mode)).not.toThrow() 37 | }) 38 | 39 | test("rejects non-array group format", () => { 40 | const mode = { 41 | ...validBaseMode, 42 | groups: "not-an-array" as any, 43 | } 44 | 45 | expect(() => CustomModeSchema.parse(mode)).toThrow() 46 | }) 47 | 48 | test("rejects invalid group names", () => { 49 | const mode = { 50 | ...validBaseMode, 51 | groups: ["invalid_group"] as any, 52 | } 53 | 54 | expect(() => CustomModeSchema.parse(mode)).toThrow() 55 | }) 56 | 57 | test("rejects duplicate groups", () => { 58 | const mode = { 59 | ...validBaseMode, 60 | groups: ["read", "read"] as any, 61 | } 62 | 63 | expect(() => CustomModeSchema.parse(mode)).toThrow("Duplicate groups are not allowed") 64 | }) 65 | 66 | test("rejects null or undefined groups", () => { 67 | const modeWithNull = { 68 | ...validBaseMode, 69 | groups: null as any, 70 | } 71 | 72 | const modeWithUndefined = { 73 | ...validBaseMode, 74 | groups: undefined as any, 75 | } 76 | 77 | expect(() => CustomModeSchema.parse(modeWithNull)).toThrow() 78 | expect(() => CustomModeSchema.parse(modeWithUndefined)).toThrow() 79 | }) 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/core/prompts/__tests__/sections.test.ts: -------------------------------------------------------------------------------- 1 | import { addCustomInstructions } from "../sections/custom-instructions" 2 | import { getCapabilitiesSection } from "../sections/capabilities" 3 | import { DiffStrategy, DiffResult } from "../../diff/types" 4 | 5 | describe("addCustomInstructions", () => { 6 | test("adds preferred language to custom instructions", async () => { 7 | const result = await addCustomInstructions( 8 | "mode instructions", 9 | "global instructions", 10 | "/test/path", 11 | "test-mode", 12 | { preferredLanguage: "French" }, 13 | ) 14 | 15 | expect(result).toContain("Language Preference:") 16 | expect(result).toContain("You should always speak and think in the French language") 17 | }) 18 | 19 | test("works without preferred language", async () => { 20 | const result = await addCustomInstructions( 21 | "mode instructions", 22 | "global instructions", 23 | "/test/path", 24 | "test-mode", 25 | ) 26 | 27 | expect(result).not.toContain("Language Preference:") 28 | expect(result).not.toContain("You should always speak and think in") 29 | }) 30 | }) 31 | 32 | describe("getCapabilitiesSection", () => { 33 | const cwd = "/test/path" 34 | const mcpHub = undefined 35 | const mockDiffStrategy: DiffStrategy = { 36 | getToolDescription: () => "apply_diff tool description", 37 | applyDiff: async (originalContent: string, diffContent: string): Promise => { 38 | return { success: true, content: "mock result" } 39 | }, 40 | } 41 | 42 | test("includes apply_diff in capabilities when diffStrategy is provided", () => { 43 | const result = getCapabilitiesSection(cwd, false, mcpHub, mockDiffStrategy) 44 | 45 | expect(result).toContain("apply_diff or") 46 | expect(result).toContain("then use the apply_diff or write_to_file tool") 47 | }) 48 | 49 | test("excludes apply_diff from capabilities when diffStrategy is undefined", () => { 50 | const result = getCapabilitiesSection(cwd, false, mcpHub, undefined) 51 | 52 | expect(result).not.toContain("apply_diff or") 53 | expect(result).toContain("then use the write_to_file tool") 54 | expect(result).not.toContain("apply_diff or write_to_file") 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/core/prompts/sections/modes.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path" 2 | import * as vscode from "vscode" 3 | import { promises as fs } from "fs" 4 | import { ModeConfig, getAllModesWithPrompts } from "../../../shared/modes" 5 | 6 | export async function getModesSection(context: vscode.ExtensionContext): Promise { 7 | const settingsDir = path.join(context.globalStorageUri.fsPath, "settings") 8 | await fs.mkdir(settingsDir, { recursive: true }) 9 | const customModesPath = path.join(settingsDir, "cline_custom_modes.json") 10 | 11 | // Get all modes with their overrides from extension state 12 | const allModes = await getAllModesWithPrompts(context) 13 | 14 | return `==== 15 | 16 | 模式 17 | 18 | - 当前可用的模式: 19 | ${allModes.map((mode: ModeConfig) => ` * "${mode.name}" 模式 (${mode.slug}) - ${mode.roleDefinition.split(".")[0]}`).join("\n")} 20 | 21 | - 自定义模式可以通过以下两种方式配置: 22 | 1. 全局配置:通过 '${customModesPath}' 文件(启动时自动创建) 23 | 2. 工作区配置:通过工作区根目录下的 '.roomodes' 文件 24 | 25 | 当两个文件中存在相同 slug 的模式时,工作区特定的 .roomodes 版本优先。这允许项目覆盖全局模式或定义项目特定的模式。 26 | 27 | 如果要创建项目模式,请在工作区根目录的 .roomodes 中创建。如果要创建全局模式,请使用全局自定义模式文件。 28 | 29 | - 以下字段为必填项且不能为空: 30 | * slug:有效的标识符(小写字母、数字和连字符)。必须唯一,越短越好。 31 | * name:模式的显示名称 32 | * roleDefinition:模式角色和功能的详细描述 33 | * groups:允许的工具组数组(可以为空)。每个组可以指定为字符串(例如,"edit" 允许编辑任何文件)或带有文件限制(例如,["edit", { fileRegex: "\\.md$", description: "仅限 Markdown 文件" }] 仅允许编辑 markdown 文件) 34 | 35 | - customInstructions 字段是可选的。 36 | 37 | - 对于多行文本,请在字符串中包含换行符,如 "这是第一行。\\n这是下一行。\\n\\n这是双行间隔。" 38 | 39 | 两个文件都应遵循以下结构: 40 | { 41 | "customModes": [ 42 | { 43 | "slug": "designer", // 必填:由小写字母、数字和连字符组成的唯一标识符 44 | "name": "设计师", // 必填:模式显示名称 45 | "roleDefinition": "你是 Roo,一位专注于设计系统和前端开发的 UI/UX 专家。你的专长包括:\\n- 创建和维护设计系统\\n- 实现响应式和无障碍的网页界面\\n- 使用 CSS、HTML 和现代前端框架\\n- 确保跨平台的一致用户体验", // 必填:不能为空 46 | "groups": [ // 必填:工具组数组(可以为空) 47 | "read", // 读取文件组(read_file, search_files, list_files, list_code_definition_names) 48 | "edit", // 编辑文件组(apply_diff, write_to_file)- 允许编辑任何文件 49 | // 或者带有文件限制: 50 | // ["edit", { fileRegex: "\\.md$", description: "仅限 Markdown 文件" }], // 仅允许编辑 markdown 文件的编辑组 51 | "browser", // 浏览器组(browser_action) 52 | "command", // 命令组(execute_command) 53 | "mcp" // MCP 组(use_mcp_tool, access_mcp_resource) 54 | ], 55 | "customInstructions": "设计师模式的附加说明" // 可选 56 | } 57 | ] 58 | }` 59 | } 60 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/core/prompts/tools/browser-action.ts: -------------------------------------------------------------------------------- 1 | import { ToolArgs } from "./types" 2 | 3 | export function getBrowserActionDescription(args: ToolArgs): string | undefined { 4 | if (!args.supportsComputerUse) { 5 | return undefined 6 | } 7 | return `## browser_action 8 | Description: 请求与Puppeteer控制的浏览器进行交互。除了 \`close\` 操作外,每个操作都会返回浏览器当前状态的截图以及任何新的控制台日志。每条消息只能执行一个浏览器操作,并等待包含截图和日志的用户响应来确定下一步操作。 9 | - 操作序列**必须始终以**在URL上启动浏览器开始,并**必须以**关闭浏览器结束。如果需要访问当前网页无法导航到的新URL,必须先关闭浏览器,然后在新URL重新启动。 10 | - 当浏览器处于活动状态时,只能使用 \`browser_action\` 工具。在此期间不应调用其他工具。例如,如果遇到错误需要修复文件,必须先关闭浏览器,然后使用其他工具进行必要的更改,最后重新启动浏览器以验证结果。 11 | - 浏览器窗口分辨率为 **${args.browserViewportSize}** 像素。执行任何点击操作时,确保坐标在此分辨率范围内。 12 | - 在点击图标、链接或按钮等元素之前,必须查看页面的截图以确定元素的坐标。点击应该针对**元素的中心**,而不是边缘。 13 | Parameters: 14 | - action: (必需) 要执行的操作。可用的操作有: 15 | * launch: 在指定URL启动新的Puppeteer控制的浏览器实例。这**必须是第一个操作**。 16 | - 使用 \`url\` 参数提供URL。 17 | - 确保URL有效且包含适当的协议(例如 http://localhost:3000/page, file:///path/to/file.html 等) 18 | * click: 在特定的x,y坐标处点击。 19 | - 使用 \`coordinate\` 参数指定位置。 20 | - 始终根据截图中的坐标点击元素(图标、按钮、链接等)的中心。 21 | * type: 在键盘上输入文本字符串。可以在点击文本框后使用此操作输入文本。 22 | - 使用 \`text\` 参数提供要输入的字符串。 23 | * scroll_down: 向下滚动一个页面高度。 24 | * scroll_up: 向上滚动一个页面高度。 25 | * close: 关闭Puppeteer控制的浏览器实例。这**必须是最后一个浏览器操作**。 26 | - 示例: \`close\` 27 | - url: (可选) 用于为 \`launch\` 操作提供URL。 28 | * 示例: https://example.com 29 | - coordinate: (可选) \`click\` 操作的X和Y坐标。坐标必须在 **${args.browserViewportSize}** 分辨率范围内。 30 | * 示例: 450,300 31 | - text: (可选) 用于为 \`type\` 操作提供文本。 32 | * 示例: Hello, world! 33 | 使用方法: 34 | 35 | 要执行的操作(如 launch, click, type, scroll_down, scroll_up, close) 36 | 启动浏览器的URL(可选) 37 | x,y坐标(可选) 38 | 要输入的文本(可选) 39 | 40 | 41 | 示例:请求在 https://example.com 启动浏览器 42 | 43 | launch 44 | https://example.com 45 | 46 | 47 | 示例:请求点击坐标 450,300 处的元素 48 | 49 | click 50 | 450,300 51 | ` 52 | } 53 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /src/exports/README.md: -------------------------------------------------------------------------------- 1 | # Cline API 2 | 3 | The Cline extension exposes an API that can be used by other extensions. To use this API in your extension: 4 | 5 | 1. Copy `src/extension-api/cline.d.ts` to your extension's source directory. 6 | 2. Include `cline.d.ts` in your extension's compilation. 7 | 3. Get access to the API with the following code: 8 | 9 | ```ts 10 | const clineExtension = vscode.extensions.getExtension("rooveterinaryinc.roo-cline") 11 | 12 | if (!clineExtension?.isActive) { 13 | throw new Error("Cline extension is not activated") 14 | } 15 | 16 | const cline = clineExtension.exports 17 | 18 | if (cline) { 19 | // Now you can use the API 20 | 21 | // Set custom instructions 22 | await cline.setCustomInstructions("Talk like a pirate") 23 | 24 | // Get custom instructions 25 | const instructions = await cline.getCustomInstructions() 26 | console.log("Current custom instructions:", instructions) 27 | 28 | // Start a new task with an initial message 29 | await cline.startNewTask("Hello, Cline! Let's make a new project...") 30 | 31 | // Start a new task with an initial message and images 32 | await cline.startNewTask("Use this design language", ["data:image/webp;base64,..."]) 33 | 34 | // Send a message to the current task 35 | await cline.sendMessage("Can you fix the @problems?") 36 | 37 | // Simulate pressing the primary button in the chat interface (e.g. 'Save' or 'Proceed While Running') 38 | await cline.pressPrimaryButton() 39 | 40 | // Simulate pressing the secondary button in the chat interface (e.g. 'Reject') 41 | await cline.pressSecondaryButton() 42 | } else { 43 | console.error("Cline API is not available") 44 | } 45 | ``` 46 | 47 | **Note:** To ensure that the `rooveterinaryinc.roo-cline` extension is activated before your extension, add it to the `extensionDependencies` in your `package.json`: 48 | 49 | ```json 50 | "extensionDependencies": [ 51 | "rooveterinaryinc.roo-cline" 52 | ] 53 | ``` 54 | 55 | For detailed information on the available methods and their usage, refer to the `cline.d.ts` file. 56 | -------------------------------------------------------------------------------- /src/exports/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | import { ClineProvider } from "../core/webview/ClineProvider" 3 | import { ClineAPI } from "./cline" 4 | 5 | export function createClineAPI(outputChannel: vscode.OutputChannel, sidebarProvider: ClineProvider): ClineAPI { 6 | const api: ClineAPI = { 7 | setCustomInstructions: async (value: string) => { 8 | await sidebarProvider.updateCustomInstructions(value) 9 | outputChannel.appendLine("Custom instructions set") 10 | }, 11 | 12 | getCustomInstructions: async () => { 13 | return (await sidebarProvider.getGlobalState("customInstructions")) as string | undefined 14 | }, 15 | 16 | startNewTask: async (task?: string, images?: string[]) => { 17 | outputChannel.appendLine("Starting new task") 18 | await sidebarProvider.clearTask() 19 | await sidebarProvider.postStateToWebview() 20 | await sidebarProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" }) 21 | await sidebarProvider.postMessageToWebview({ 22 | type: "invoke", 23 | invoke: "sendMessage", 24 | text: task, 25 | images: images, 26 | }) 27 | outputChannel.appendLine( 28 | `Task started with message: ${task ? `"${task}"` : "undefined"} and ${images?.length || 0} image(s)`, 29 | ) 30 | }, 31 | 32 | sendMessage: async (message?: string, images?: string[]) => { 33 | outputChannel.appendLine( 34 | `Sending message: ${message ? `"${message}"` : "undefined"} with ${images?.length || 0} image(s)`, 35 | ) 36 | await sidebarProvider.postMessageToWebview({ 37 | type: "invoke", 38 | invoke: "sendMessage", 39 | text: message, 40 | images: images, 41 | }) 42 | }, 43 | 44 | pressPrimaryButton: async () => { 45 | outputChannel.appendLine("Pressing primary button") 46 | await sidebarProvider.postMessageToWebview({ 47 | type: "invoke", 48 | invoke: "primaryButtonClick", 49 | }) 50 | }, 51 | 52 | pressSecondaryButton: async () => { 53 | outputChannel.appendLine("Pressing secondary button") 54 | await sidebarProvider.postMessageToWebview({ 55 | type: "invoke", 56 | invoke: "secondaryButtonClick", 57 | }) 58 | }, 59 | 60 | sidebarProvider: sidebarProvider, 61 | } 62 | 63 | return api 64 | } 65 | -------------------------------------------------------------------------------- /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*