feat: make fork standalone as Moo Code

- Strip all telemetry (PostHog, cloud telemetry) - no data leaves the machine

- Disable all cloud features (auth, settings sync, sharing, Roo provider)

- Rebrand extension: roo-cline -> moo-code, RooVeterinaryInc -> moo-code

- Update all commands, settings, views to moo-code.* prefix

- Remove upstream CI/CD workflows (marketplace publish, nightly, website)

- Rewrite README, PRIVACY, CONTRIBUTING, SECURITY docs

- Build verified: bin/moo-code-3.52.1.vsix (31.44 MB)
This commit is contained in:
davincidreams 2026-04-23 08:20:11 -04:00
parent c4547d25c5
commit 2fc7798424
57 changed files with 449 additions and 6220 deletions

3
.github/CODEOWNERS vendored
View file

@ -1,2 +1,3 @@
# These owners will be the default owners for everything in the repo
* @mrubens @cte @jr @hannesrudolph @daniel-lxs
# Update with your GitHub username or team
# * @your-username

View file

@ -1,92 +0,0 @@
name: Publish Extension
on:
pull_request:
types: [closed]
workflow_dispatch:
env:
GIT_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || 'main' }}
jobs:
publish-extension:
runs-on: ubuntu-latest
permissions:
contents: write # Required for pushing tags.
if: >
( github.event_name == 'pull_request' &&
github.event.pull_request.base.ref == 'main' &&
contains(github.event.pull_request.title, 'Changeset version bump') ) ||
github.event_name == 'workflow_dispatch'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ env.GIT_REF }}
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Create .env file
run: echo "POSTHOG_API_KEY=${{ secrets.POSTHOG_API_KEY }}" >> .env
- name: Package Extension
run: |
current_package_version=$(node -p "require('./src/package.json').version")
pnpm vsix
# Save VSIX contents to a temporary file to avoid broken pipe issues.
unzip -l bin/roo-cline-${current_package_version}.vsix > /tmp/roo-code-vsix-contents.txt
# Check for required files.
grep -q "extension/package.json" /tmp/roo-code-vsix-contents.txt || exit 1
grep -q "extension/package.nls.json" /tmp/roo-code-vsix-contents.txt || exit 1
grep -q "extension/dist/extension.js" /tmp/roo-code-vsix-contents.txt || exit 1
grep -q "extension/webview-ui/audio/celebration.wav" /tmp/roo-code-vsix-contents.txt || exit 1
grep -q "extension/webview-ui/build/assets/index.js" /tmp/roo-code-vsix-contents.txt || exit 1
grep -q "extension/assets/codicons/codicon.ttf" /tmp/roo-code-vsix-contents.txt || exit 1
grep -q "extension/assets/vscode-material-icons/icons/3d.svg" /tmp/roo-code-vsix-contents.txt || exit 1
grep -q ".env" /tmp/roo-code-vsix-contents.txt || exit 1
# Clean up temporary file.
rm /tmp/roo-code-vsix-contents.txt
- name: Create and Push Git Tag
run: |
current_package_version=$(node -p "require('./src/package.json').version")
git tag -a "v${current_package_version}" -m "Release v${current_package_version}"
git push origin "v${current_package_version}" --no-verify
echo "Successfully created and pushed git tag v${current_package_version}"
- name: Publish Extension
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
OVSX_PAT: ${{ secrets.OVSX_PAT }}
run: |
current_package_version=$(node -p "require('./src/package.json').version")
pnpm --filter roo-cline publish:marketplace
echo "Successfully published version $current_package_version to VS Code Marketplace"
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
current_package_version=$(node -p "require('./src/package.json').version")
# Extract changelog for current version
echo "Extracting changelog for version ${current_package_version}"
changelog_content=$(sed -n "/## \\[${current_package_version}\\]/,/## \\[/p" CHANGELOG.md | sed '$d')
# If changelog extraction failed, use a default message
if [ -z "$changelog_content" ]; then
echo "Warning: No changelog section found for version ${current_package_version}"
changelog_content="Release v${current_package_version}"
else
echo "Found changelog section for version ${current_package_version}"
fi
# Create release with changelog content
gh release create "v${current_package_version}" \
--title "Release v${current_package_version}" \
--notes "$changelog_content" \
--target ${{ env.GIT_REF }} \
bin/roo-cline-${current_package_version}.vsix
echo "Successfully created GitHub Release v${current_package_version}"

View file

@ -1,52 +0,0 @@
name: Nightly Publish
on:
push:
branches: [main]
workflow_dispatch: # Allows manual triggering.
jobs:
publish-nightly:
runs-on: ubuntu-latest
permissions:
contents: read # No tags pushed → read is enough.
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
with:
install-args: '--frozen-lockfile'
- name: Forge numeric Nightly version
id: version
env:
RUN_NUMBER: ${{ github.run_number }}
run: echo "number=$(( 5500 + ${RUN_NUMBER} ))" >> $GITHUB_OUTPUT
- name: Patch package.json version
env:
VERSION_NUMBER: ${{ steps.version.outputs.number }}
run: |
node <<'EOF'
const fs = require('fs');
const path = require('path');
const pkgPath = path.join(__dirname, 'apps', 'vscode-nightly', 'package.nightly.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath,'utf8'));
const [maj, min] = pkg.version.split('.');
pkg.version = `${maj}.${min}.${process.env.VERSION_NUMBER}`;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
console.log(`🔖 Nightly version set to ${pkg.version}`);
EOF
- name: Build VSIX
run: pnpm vsix:nightly # Produces bin/roo-code-nightly-0.0.[count].vsix
- name: Publish to VS Code Marketplace
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
run: npx vsce publish --packagePath "bin/$(/bin/ls bin | head -n1)"
- name: Publish to Open VSX Registry
env:
OVSX_PAT: ${{ secrets.OVSX_PAT }}
run: npx ovsx publish "bin/$(ls bin | head -n1)"

View file

@ -1,67 +0,0 @@
name: Update Contributors # Refresh contrib.rocks image cache
on:
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
refresh-contrib-cache:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Bump cacheBust in all README files
run: |
set -euo pipefail
TS="$(date +%s)"
# Target only the root README.md and localized READMEs under locales/*/README.md
mapfile -t FILES < <(git ls-files README.md 'locales/*/README.md' || true)
if [ "${#FILES[@]}" -eq 0 ]; then
echo "No target README files found." >&2
exit 1
fi
UPDATED=0
for f in "${FILES[@]}"; do
if grep -q 'cacheBust=' "$f"; then
# Use portable sed in GNU environment of ubuntu-latest
sed -i -E "s/cacheBust=[0-9]+/cacheBust=${TS}/g" "$f"
echo "Updated cacheBust in $f"
UPDATED=1
else
echo "Warning: cacheBust parameter not found in $f" >&2
fi
done
if [ "$UPDATED" -eq 0 ]; then
echo "No files were updated. Ensure READMEs embed contrib.rocks with cacheBust param." >&2
exit 1
fi
- name: Detect changes
id: changes
run: |
if git diff --quiet; then
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "changed=true" >> $GITHUB_OUTPUT
fi
- name: Create Pull Request
if: steps.changes.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "docs: update contributors list [skip ci]"
committer: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
branch: refresh-contrib-cache
delete-branch: true
title: "Refresh contrib.rocks image cache (all READMEs)"
body: |
Automated refresh of the contrib.rocks image cache by bumping the cacheBust parameter in README.md and locales/*/README.md.
base: main

View file

@ -1,59 +0,0 @@
name: Deploy roocode.com
on:
push:
branches:
- main
paths:
- 'apps/web-roo-code/**'
workflow_dispatch:
concurrency:
group: deploy-roocode-com
cancel-in-progress: true
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
jobs:
check-secrets:
runs-on: ubuntu-latest
outputs:
has-vercel-token: ${{ steps.check.outputs.has-vercel-token }}
steps:
- name: Check if VERCEL_TOKEN exists
id: check
run: |
if [ -n "${{ secrets.VERCEL_TOKEN }}" ]; then
echo "has-vercel-token=true" >> $GITHUB_OUTPUT
else
echo "has-vercel-token=false" >> $GITHUB_OUTPUT
fi
deploy:
runs-on: ubuntu-latest
needs: check-secrets
if: ${{ needs.check-secrets.outputs.has-vercel-token == 'true' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- name: Run lint
run: pnpm lint
working-directory: apps/web-roo-code
- name: Run type check
run: pnpm check-types
working-directory: apps/web-roo-code
- name: Run build
run: pnpm build
working-directory: apps/web-roo-code
- name: Install Vercel CLI
run: npm install --global vercel@latest
- name: Pull Vercel Environment Information
run: npx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project Artifacts
run: npx vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy Project Artifacts to Vercel
run: npx vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}

View file

@ -1,102 +0,0 @@
name: Preview roocode.com
on:
push:
branches-ignore:
- main
paths:
- "apps/web-roo-code/**"
pull_request:
paths:
- "apps/web-roo-code/**"
workflow_dispatch:
concurrency:
group: preview-roocode-com-${{ github.ref }}
cancel-in-progress: true
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
jobs:
check-secrets:
runs-on: ubuntu-latest
outputs:
has-vercel-token: ${{ steps.check.outputs.has-vercel-token }}
steps:
- name: Check if VERCEL_TOKEN exists
id: check
run: |
if [ -n "${{ secrets.VERCEL_TOKEN }}" ]; then
echo "has-vercel-token=true" >> $GITHUB_OUTPUT
else
echo "has-vercel-token=false" >> $GITHUB_OUTPUT
fi
preview:
runs-on: ubuntu-latest
needs: check-secrets
if: ${{ needs.check-secrets.outputs.has-vercel-token == 'true' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- name: Run lint
run: pnpm lint
working-directory: apps/web-roo-code
- name: Run type check
run: pnpm check-types
working-directory: apps/web-roo-code
- name: Run build
run: pnpm build
working-directory: apps/web-roo-code
- name: Install Vercel CLI
run: npm install --global vercel@latest
- name: Pull Vercel Environment Information
run: npx vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project Artifacts
run: npx vercel build --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy Project Artifacts to Vercel
id: deploy
run: |
DEPLOYMENT_URL=$(npx vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})
echo "deployment_url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
echo "Preview deployed to: $DEPLOYMENT_URL"
- name: Comment PR with preview link
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const deploymentUrl = '${{ steps.deploy.outputs.deployment_url }}';
const commentIdentifier = '<!-- roo-preview-comment -->';
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existingComment = comments.find(comment =>
comment.body.includes(commentIdentifier)
);
const comment = commentIdentifier + '\n🚀 **Preview deployed!**\n\nYour changes have been deployed to Vercel:\n\n**Preview URL:** ' + deploymentUrl + '\n\nThis preview will be updated automatically when you push new commits to this PR.';
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: comment
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}

1
.gitignore vendored
View file

@ -55,3 +55,4 @@ qdrant_storage/
plans/
roo-cli-*.tar.gz*
`nnul

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,3 @@
<div align="center">
<sub>
<b>English</b> • [Català](locales/ca/CODE_OF_CONDUCT.md) • [Deutsch](locales/de/CODE_OF_CONDUCT.md) • [Español](locales/es/CODE_OF_CONDUCT.md) • [Français](locales/fr/CODE_OF_CONDUCT.md) • [हिंदी](locales/hi/CODE_OF_CONDUCT.md) • [Bahasa Indonesia](locales/id/CODE_OF_CONDUCT.md) • [Italiano](locales/it/CODE_OF_CONDUCT.md) • [日本語](locales/ja/CODE_OF_CONDUCT.md)
</sub>
<sub>
[한국어](locales/ko/CODE_OF_CONDUCT.md) • [Nederlands](locales/nl/CODE_OF_CONDUCT.md) • [Polski](locales/pl/CODE_OF_CONDUCT.md) • [Português (BR)](locales/pt-BR/CODE_OF_CONDUCT.md) • [Русский](locales/ru/CODE_OF_CONDUCT.md) • [Türkçe](locales/tr/CODE_OF_CONDUCT.md) • [Tiếng Việt](locales/vi/CODE_OF_CONDUCT.md) • [简体中文](locales/zh-CN/CODE_OF_CONDUCT.md) • [繁體中文](locales/zh-TW/CODE_OF_CONDUCT.md)
</sub>
</div>
# Contributor Covenant Code of Conduct
## Our Pledge
@ -52,7 +39,7 @@ response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
that are not aligned with this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
@ -68,11 +55,11 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at support@roocode.com. All complaints
will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
reported by opening a GitHub Issue. All complaints will be reviewed and
investigated and will result in a response that is deemed necessary and
appropriate to the circumstances. The project team is obligated to maintain
confidentiality with regard to the reporter of an incident. Further details of
specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
@ -80,10 +67,9 @@ members of the project's leadership.
## Attribution
This Code of Conduct is adapted from [Cline's version][cline_coc] of the [Contributor Covenant][homepage], version 1.4,
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[cline_coc]: https://github.com/cline/cline/blob/main/CODE_OF_CONDUCT.md
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see

View file

@ -1,141 +1,15 @@
<div align="center">
<sub>
# Contributing to Moo Code
<b>English</b> • [Català](locales/ca/CONTRIBUTING.md) • [Deutsch](locales/de/CONTRIBUTING.md) • [Español](locales/es/CONTRIBUTING.md) • [Français](locales/fr/CONTRIBUTING.md) • [हिंदी](locales/hi/CONTRIBUTING.md) • [Bahasa Indonesia](locales/id/CONTRIBUTING.md) • [Italiano](locales/it/CONTRIBUTING.md) • [日本語](locales/ja/CONTRIBUTING.md)
This is a personal fork of [Roo Code](https://github.com/RooCodeInc/Roo-Code).
</sub>
<sub>
Contributions are welcome via pull requests. Please ensure:
[한국어](locales/ko/CONTRIBUTING.md) • [Nederlands](locales/nl/CONTRIBUTING.md) • [Polski](locales/pl/CONTRIBUTING.md) • [Português (BR)](locales/pt-BR/CONTRIBUTING.md) • [Русский](locales/ru/CONTRIBUTING.md) • [Türkçe](locales/tr/CONTRIBUTING.md) • [Tiếng Việt](locales/vi/CONTRIBUTING.md) • [简体中文](locales/zh-CN/CONTRIBUTING.md) • [繁體中文](locales/zh-TW/CONTRIBUTING.md)
1. Your changes are well-tested
2. You follow the existing code style
3. You include a clear description of your changes
</sub>
</div>
For bug reports and feature requests, please open a GitHub Issue.
# Contributing to Roo Code
## License
Roo Code is a community-driven project, and we deeply value every contribution. To streamline collaboration, we operate on an [Issue-First](#issue-first-approach) basis, meaning all [Pull Requests (PRs)](#submitting-a-pull-request) must first be linked to a GitHub Issue. Please review this guide carefully.
## Table of Contents
- [Before You Contribute](#before-you-contribute)
- [Finding & Planning Your Contribution](#finding--planning-your-contribution)
- [Development & Submission Process](#development--submission-process)
- [Legal](#legal)
## Before You Contribute
### 1. Code of Conduct
All contributors must adhere to our [Code of Conduct](./CODE_OF_CONDUCT.md).
### 2. Project Roadmap
Our roadmap guides the project's direction. Align your contributions with these key goals:
### Reliability First
- Ensure diff editing and command execution are consistently reliable.
- Reduce friction points that deter regular usage.
- Guarantee smooth operation across all locales and platforms.
- Expand robust support for a wide variety of AI providers and models.
### Enhanced User Experience
- Streamline the UI/UX for clarity and intuitiveness.
- Continuously improve the workflow to meet the high expectations developers have for daily-use tools.
### Leading on Agent Performance
- Establish comprehensive evaluation benchmarks (evals) to measure real-world productivity.
- Make it easy for everyone to easily run and interpret these evals.
- Ship improvements that demonstrate clear increases in eval scores.
Mention alignment with these areas in your PRs.
### 3. Join the Roo Code Community
- **Primary:** Join our [Discord](https://discord.gg/roocode) and DM **Hannes Rudolph (`hrudolph`)**.
- **Alternative:** Experienced contributors can engage directly via [GitHub Projects](https://github.com/orgs/RooCodeInc/projects/1).
## Finding & Planning Your Contribution
### Types of Contributions
- **Bug Fixes:** Addressing code issues.
- **New Features:** Adding functionality.
- **Documentation:** Improving guides and clarity.
### Issue-First Approach
All contributions start with a GitHub Issue using our skinny templates.
- **Check existing issues**: Search [GitHub Issues](https://github.com/RooCodeInc/Roo-Code/issues).
- **Create an issue** using:
- **Enhancements:** "Enhancement Request" template (plain language focused on user benefit).
- **Bugs:** "Bug Report" template (minimal repro + expected vs actual + version).
- **Want to work on it?** Comment "Claiming" on the issue and DM **Hannes Rudolph (`hrudolph`)** on [Discord](https://discord.gg/roocode) to get assigned. Assignment will be confirmed in the thread.
- **PRs must link to the issue.** Unlinked PRs may be closed.
### Deciding What to Work On
- Check the [GitHub Project](https://github.com/orgs/RooCodeInc/projects/1) for "Issue [Unassigned]" issues.
- For docs, visit [Roo Code Docs](https://github.com/RooCodeInc/Roo-Code-Docs).
### Reporting Bugs
- Check for existing reports first.
- Create a new bug using the ["Bug Report" template](https://github.com/RooCodeInc/Roo-Code/issues/new/choose) with:
- Clear, numbered reproduction steps
- Expected vs actual result
- Roo Code version (required); API provider/model if relevant
- **Security issues**: Report privately via [security advisories](https://github.com/RooCodeInc/Roo-Code/security/advisories/new).
## Development & Submission Process
### Development Setup
1. **Fork & Clone:**
```
git clone https://github.com/YOUR_USERNAME/Roo-Code.git
```
2. **Install Dependencies:**
```
pnpm install
```
3. **Debugging:** Open with VS Code (`F5`).
### Writing Code Guidelines
- One focused PR per feature or fix.
- Follow ESLint and TypeScript best practices.
- Write clear, descriptive commits referencing issues (e.g., `Fixes #123`).
- Provide thorough testing (`npm test`).
- Rebase onto the latest `main` branch before submission.
### Submitting a Pull Request
- Begin as a **Draft PR** if seeking early feedback.
- Clearly describe your changes following the Pull Request Template.
- Link the issue in the PR description/title (e.g., "Fixes #123").
- Provide screenshots/videos for UI changes.
- Indicate if documentation updates are necessary.
### Pull Request Policy
- Must reference an assigned GitHub Issue. To get assigned: comment "Claiming" on the issue and DM **Hannes Rudolph (`hrudolph`)** on [Discord](https://discord.gg/roocode). Assignment will be confirmed in the thread.
- Unlinked PRs may be closed.
- PRs should pass CI tests, align with the roadmap, and have clear documentation.
### Review Process
- **Daily Triage:** Quick checks by maintainers.
- **Weekly In-depth Review:** Comprehensive assessment.
- **Iterate promptly** based on feedback.
## Legal
By contributing, you agree your contributions will be licensed under the Apache 2.0 License, consistent with Roo Code's licensing.
By contributing, you agree that your contributions will be licensed under the Apache License 2.0.

View file

@ -1,38 +1,27 @@
# Roo Code Privacy Policy
# Moo Code Privacy Policy
**Last Updated: September 11th, 2025**
**Last Updated: April 2026**
Roo Code respects your privacy and is committed to transparency about how we handle your data. Below is a simple breakdown of where key pieces of data go—and, importantly, where they don’t.
## Privacy Statement
### **Where Your Data Goes (And Where It Doesn’t)**
Moo Code does **not** collect, transmit, or store any user data. All processing happens locally on your machine.
- **Code & Files**: Roo Code accesses files on your local machine when needed for AI-assisted features. When you send commands to Roo Code, relevant files may be transmitted to your chosen AI model provider (e.g., OpenAI, Anthropic, OpenRouter) to generate responses. If you select Roo Code Cloud as the model provider (proxy mode), your code may transit Roo Code servers only to forward it to the upstream provider. We do not store your code; it is deleted immediately after forwarding. Otherwise, your code is sent directly to the provider. AI providers may store data per their privacy policies.
- **Commands**: Any commands executed through Roo Code happen on your local environment. However, when you use AI-powered features, the relevant code and context from your commands may be transmitted to your chosen AI model provider (e.g., OpenAI, Anthropic, OpenRouter) to generate responses. We do not have access to or store this data, but AI providers may process it per their privacy policies.
- **Prompts & AI Requests**: When you use AI-powered features, your prompts and relevant project context are sent to your chosen AI model provider (e.g., OpenAI, Anthropic, OpenRouter) to generate responses. We do not store or process this data. These AI providers have their own privacy policies and may store data per their terms of service. If you choose Roo Code Cloud as the provider (proxy mode), prompts may transit Roo Code servers only to forward them to the upstream model and are not stored.
- **API Keys & Credentials**: If you enter an API key (e.g., to connect an AI model), it is stored locally on your device and never sent to us or any third party, except the provider you have chosen.
- **Telemetry (Usage Data)**: We collect anonymous feature usage and error data to help us improve Roo Code. This telemetry is powered by PostHog and includes your VS Code machine ID, feature usage patterns, and exception reports. This telemetry does **not** collect personally identifiable information, your code, or AI prompts. You can opt out of this telemetry at any time through the settings.
- **Marketplace Requests**: When you browse or search the Marketplace for Model Configuration Profiles (MCPs) or Custom Modes, Roo Code makes a secure API call to Roo Code's backend servers to retrieve listing information. These requests send only the query parameters (e.g., extension version, search term) necessary to fulfill the request and do not include your code, prompts, or personally identifiable information.
### Data Handling
### **How We Use Your Data (If Collected)**
- **Code & Files:** Moo Code accesses files on your local machine only. When you use AI-powered features, relevant files are sent **directly** to your chosen AI model provider (e.g., OpenAI, Anthropic, OpenRouter). Moo Code does not intercept, store, or forward this data.
- **API Keys & Credentials:** If you enter an API key, it is stored locally on your device and never sent to any third party other than the provider you have configured.
- **Telemetry:** Moo Code does **not** collect any telemetry, usage data, or analytics. There is no tracking, no error reporting to external servers, and no usage metrics collection.
- **Marketplace:** Moo Code does not connect to any marketplace or cloud services.
- We use telemetry to understand feature usage and improve Roo Code.
- We do **not** sell or share your data.
- We do **not** train any models on your data.
### Your Choices & Control
### **Your Choices & Control**
- You can run models locally to prevent data from being sent to third parties.
- No data is collected by Moo Code itself, so there is nothing to opt out of.
- You can run models locally to prevent data being sent to third-parties.
- Telemetry collection is enabled by default to help us improve Roo Code, but you can opt out at any time through the settings.
- You can delete Roo Code to stop all data collection.
### Contact
### **Security & Updates**
We take reasonable measures to secure your data, but no system is 100% secure. If our privacy policy changes, we will notify you within the extension.
### **Contact Us**
For any privacy-related questions, reach out to us at support@roocode.com.
For privacy-related questions, please open a GitHub Issue.
---
By using Roo Code, you agree to this Privacy Policy.
By using Moo Code, you agree to this Privacy Policy.

185
README.md
View file

@ -1,181 +1,60 @@
<p align="center">
<a href="https://marketplace.visualstudio.com/items?itemName=RooVeterinaryInc.roo-cline"><img src="https://img.shields.io/badge/VS_Code_Marketplace-007ACC?style=flat&logo=visualstudiocode&logoColor=white" alt="VS Code Marketplace"></a>
<a href="https://x.com/roocode"><img src="https://img.shields.io/badge/roocode-000000?style=flat&logo=x&logoColor=white" alt="X"></a>
<a href="https://youtube.com/@roocodeyt?feature=shared"><img src="https://img.shields.io/badge/YouTube-FF0000?style=flat&logo=youtube&logoColor=white" alt="YouTube"></a>
<a href="https://discord.gg/roocode"><img src="https://img.shields.io/badge/Join%20Discord-5865F2?style=flat&logo=discord&logoColor=white" alt="Join Discord"></a>
<a href="https://www.reddit.com/r/RooCode/"><img src="https://img.shields.io/badge/Join%20r%2FRooCode-FF4500?style=flat&logo=reddit&logoColor=white" alt="Join r/RooCode"></a>
</p>
<p align="center">
<em>Get help fast → <a href="https://discord.gg/roocode">Join Discord</a> • Prefer async? → <a href="https://www.reddit.com/r/RooCode/">Join r/RooCode</a></em>
</p>
# Moo Code
# Roo Code
A self-hosted fork of [Roo Code](https://github.com/RooCodeInc/Roo-Code) — an AI-powered coding assistant for VS Code.
> Your AI-Powered Dev Team, Right in Your Editor
> **Note:** This is an independent fork maintained separately from the upstream Roo Code project. It is not affiliated with or endorsed by Roo Code Veterinary Inc.
## What's New in v3.52.0
## Features
- Add Poe as an AI provider so you can access Poe models directly in Roo Code.
- Improve the xAI provider with a Responses API migration, reusable transform utilities, and updated Grok-4.20 defaults.
- Fix MiniMax model listings and context window handling for more reliable setup.
<details>
<summary>🌐 Available languages</summary>
- [English](README.md)
- [Català](locales/ca/README.md)
- [Deutsch](locales/de/README.md)
- [Español](locales/es/README.md)
- [Français](locales/fr/README.md)
- [हिंदी](locales/hi/README.md)
- [Bahasa Indonesia](locales/id/README.md)
- [Italiano](locales/it/README.md)
- [日本語](locales/ja/README.md)
- [한국어](locales/ko/README.md)
- [Nederlands](locales/nl/README.md)
- [Polski](locales/pl/README.md)
- [Português (BR)](locales/pt-BR/README.md)
- [Русский](locales/ru/README.md)
- [Türkçe](locales/tr/README.md)
- [Tiếng Việt](locales/vi/README.md)
- [简体中文](locales/zh-CN/README.md)
- [繁體中文](locales/zh-TW/README.md)
- ...
</details>
---
## What Can Roo Code Do For YOU?
- Generate Code from natural language descriptions and specs
- Generate code from natural language descriptions and specs
- Adapt with Modes: Code, Architect, Ask, Debug, and Custom Modes
- Refactor & Debug existing code
- Write & Update documentation
- Answer Questions about your codebase
- Refactor & debug existing code
- Write & update documentation
- Answer questions about your codebase
- Automate repetitive tasks
- Utilize MCP Servers
## Modes
## Build & Install
Roo Code adapts to how you work:
### Prerequisites
- Code Mode: everyday coding, edits, and file ops
- Architect Mode: plan systems, specs, and migrations
- Ask Mode: fast answers, explanations, and docs
- Debug Mode: trace issues, add logs, isolate root causes
- Custom Modes: build specialized modes for your team or workflow
- [Node.js](https://nodejs.org/) (see `.nvmrc` for version)
- [pnpm](https://pnpm.io/)
Learn more: [Using Modes](https://docs.roocode.com/basic-usage/using-modes) • [Custom Modes](https://docs.roocode.com/advanced-usage/custom-modes)
## Tutorial & Feature Videos
<div align="center">
| | | |
| :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| <a href="https://www.youtube.com/watch?v=Mcq3r1EPZ-4"><img src="https://img.youtube.com/vi/Mcq3r1EPZ-4/maxresdefault.jpg" width="100%"></a><br><b>Installing Roo Code</b> | <a href="https://www.youtube.com/watch?v=ZBML8h5cCgo"><img src="https://img.youtube.com/vi/ZBML8h5cCgo/maxresdefault.jpg" width="100%"></a><br><b>Configuring Profiles</b> | <a href="https://www.youtube.com/watch?v=r1bpod1VWhg"><img src="https://img.youtube.com/vi/r1bpod1VWhg/maxresdefault.jpg" width="100%"></a><br><b>Codebase Indexing</b> |
| <a href="https://www.youtube.com/watch?v=iiAv1eKOaxk"><img src="https://img.youtube.com/vi/iiAv1eKOaxk/maxresdefault.jpg" width="100%"></a><br><b>Custom Modes</b> | <a href="https://www.youtube.com/watch?v=Ho30nyY332E"><img src="https://img.youtube.com/vi/Ho30nyY332E/maxresdefault.jpg" width="100%"></a><br><b>Checkpoints</b> | <a href="https://www.youtube.com/watch?v=HmnNSasv7T8"><img src="https://img.youtube.com/vi/HmnNSasv7T8/maxresdefault.jpg" width="100%"></a><br><b>Context Management</b> |
</div>
<p align="center">
<a href="https://docs.roocode.com/tutorial-videos">More quick tutorial and feature videos...</a>
</p>
## Resources
- **[Documentation](https://docs.roocode.com):** The official guide to installing, configuring, and mastering Roo Code.
- **[YouTube Channel](https://youtube.com/@roocodeyt?feature=shared):** Watch tutorials and see features in action.
- **[Discord Server](https://discord.gg/roocode):** Join the community for real-time help and discussion.
- **[Reddit Community](https://www.reddit.com/r/RooCode):** Share your experiences and see what others are building.
- **[GitHub Issues](https://github.com/RooCodeInc/Roo-Code/issues):** Report bugs and track development.
- **[Feature Requests](https://github.com/RooCodeInc/Roo-Code/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop):** Have an idea? Share it with the developers.
---
## Local Setup & Development
1. **Clone** the repo:
```sh
git clone https://github.com/RooCodeInc/Roo-Code.git
```
2. **Install dependencies**:
### Build
```sh
pnpm install
pnpm build
```
3. **Run the extension**:
There are several ways to run the Roo Code extension:
### Development Mode (F5)
For active development, use VSCode's built-in debugging:
Press `F5` (or go to **Run** → **Start Debugging**) in VSCode. This will open a new VSCode window with the Roo Code extension running.
- Changes to the webview will appear immediately.
- Changes to the core extension will also hot reload automatically.
### Automated VSIX Installation
To build and install the extension as a VSIX package directly into VSCode:
### Create VSIX
```sh
pnpm install:vsix [-y] [--editor=<command>]
cd src && pnpm vsix
```
This command will:
### Install Extension
- Ask which editor command to use (code/cursor/code-insiders) - defaults to 'code'
- Uninstall any existing version of the extension.
- Build the latest VSIX package.
- Install the newly built VSIX.
- Prompt you to restart VS Code for changes to take effect.
```sh
code --install-extension ../bin/moo-code-{version}.vsix
```
Options:
## Development
- `-y`: Skip all confirmation prompts and use defaults
- `--editor=<command>`: Specify the editor command (e.g., `--editor=cursor` or `--editor=code-insiders`)
1. Clone the repo
2. Run `pnpm install`
3. Open in VS Code
4. Press `F5` to launch the extension development host
### Manual VSIX Installation
## Modes
If you prefer to install the VSIX package manually:
1. First, build the VSIX package:
```sh
pnpm vsix
```
2. A `.vsix` file will be generated in the `bin/` directory (e.g., `bin/roo-cline-<version>.vsix`).
3. Install it manually using the VSCode CLI:
```sh
code --install-extension bin/roo-cline-<version>.vsix
```
---
We use [changesets](https://github.com/changesets/changesets) for versioning and publishing. Check our `CHANGELOG.md` for release notes.
---
## Disclaimer
**Please note** that Roo Code, Inc does **not** make any representations or warranties regarding any code, models, or other tools provided or made available in connection with Roo Code, any associated third-party tools, or any resulting outputs. You assume **all risks** associated with the use of any such tools or outputs; such tools are provided on an **"AS IS"** and **"AS AVAILABLE"** basis. Such risks may include, without limitation, intellectual property infringement, cyber vulnerabilities or attacks, bias, inaccuracies, errors, defects, viruses, downtime, property loss or damage, and/or personal injury. You are solely responsible for your use of any such tools or outputs (including, without limitation, the legality, appropriateness, and results thereof).
---
## Contributing
We love community contributions! Get started by reading our [CONTRIBUTING.md](CONTRIBUTING.md).
---
- **Code Mode:** Everyday coding, edits, and file ops
- **Architect Mode:** Plan systems, specs, and migrations
- **Ask Mode:** Fast answers, explanations, and docs
- **Debug Mode:** Trace issues, add logs, isolate root causes
- **Custom Modes:** Build specialized modes for your workflow
## License
[Apache 2.0 © 2025 Roo Code, Inc.](./LICENSE)
---
**Enjoy Roo Code!** Whether you keep it on a short leash or let it roam autonomously, we can’t wait to see what you build. If you have questions or feature ideas, drop by our [Reddit community](https://www.reddit.com/r/RooCode/) or [Discord](https://discord.gg/roocode). Happy coding!
Licensed under the [Apache License 2.0](LICENSE).

View file

@ -2,16 +2,16 @@
## Supported Versions
We actively patch only the most recent minor release of Roo Code. Older versions receive fixes at our discretion.
We actively patch only the most recent release of Moo Code. Older versions receive fixes at our discretion.
## Reporting a Vulnerability
Email security@roocode.com with:
Please open a GitHub Issue or Security Advisory with:
- A short summary of the issue
- Steps to reproduce or a proof of concept
- Any logs, stack traces, or screenshots that might help us understand the problem
We acknowledge reports within 48 hours and aim to release a fix or mitigation within 30 days. While we work on a resolution, please keep the details private.
We acknowledge reports within 48 hours and aim to release a fix or mitigation within 30 days. While we work on a resolution, please keep the details private.
Thank you for helping us keep Roo Code users safe.
Thank you for helping us keep Moo Code users safe.

View file

@ -1,6 +1,6 @@
{
"name": "@roo-code/vscode-nightly",
"description": "Nightly build for the Roo Code VSCode extension.",
"description": "Nightly build for the Moo Code VSCode extension.",
"private": true,
"packageManager": "pnpm@10.8.1",
"scripts": {

View file

@ -1,5 +1,6 @@
{
"name": "roo-code-nightly",
"name": "moo-code-nightly",
"publisher": "moo-code",
"version": "0.0.1",
"icon": "assets/icons/icon-nightly.png",
"scripts": {}

View file

@ -1,7 +1,7 @@
{
"extension.displayName": "Roo Code Nightly",
"views.contextMenu.label": "Roo Code Nightly",
"views.terminalMenu.label": "Roo Code Nightly",
"views.activitybar.title": "Roo Code Nightly",
"configuration.title": "Roo Code Nightly"
"extension.displayName": "Moo Code Nightly",
"views.contextMenu.label": "Moo Code Nightly",
"views.terminalMenu.label": "Moo Code Nightly",
"views.activitybar.title": "Moo Code Nightly",
"configuration.title": "Moo Code Nightly"
}

View file

@ -1,22 +0,0 @@
version: 1.3
pr_review:
# Modify confidence_threshold to show fewer/more comments. Increase this to show fewer, but higher quality comments.
# If there’s too much noise, we suggest 0.9. The default value is 0.7.
confidence_threshold: 0.7
# If quiet mode is enabled, Ellipsis will only leave reviews when it has comments, so “Looks good to me” reviews
# will be skipped. This can reduce clutter.
quiet: true
# You can disable automatic code review using auto_review_enabled. This will override any global settings you
# have configured via the web UI.
auto_review_enabled: true
# You can enable auto-review on draft PRs using auto_review_draft. This will override any global settings you
# have configured via the web UI.
auto_review_draft: false
# You can allow Ellipsis to approve PRs using enable_approve_prs. Note: in common branch GitHub protection configurations,
# the Ellipsis approval will count towards the approval total and allow the PR to be merged when it otherwise may not be.
enable_approve_prs: false

View file

@ -22,126 +22,34 @@ export class CloudAPI {
this.baseUrl = getRooCodeApiUrl()
}
// Cloud features disabled — all HTTP methods are no-ops.
private async request<T>(
endpoint: string,
options: CloudAPIRequestOptions & {
_endpoint: string,
_options: CloudAPIRequestOptions & {
parseResponse?: (data: unknown) => T
} = {},
): Promise<T> {
const { timeout = 30_000, parseResponse, headers = {}, ...fetchOptions } = options
const sessionToken = this.authService.getSessionToken()
if (!sessionToken) {
throw new AuthenticationError()
}
const url = `${this.baseUrl}${endpoint}`
const requestHeaders = {
"Content-Type": "application/json",
Authorization: `Bearer ${sessionToken}`,
"User-Agent": getUserAgent(),
...headers,
}
try {
const response = await fetch(url, {
...fetchOptions,
headers: requestHeaders,
signal: AbortSignal.timeout(timeout),
})
if (!response.ok) {
await this.handleErrorResponse(response, endpoint)
}
const data = await response.json()
if (parseResponse) {
return parseResponse(data)
}
return data as T
} catch (error) {
if (error instanceof TypeError && error.message.includes("fetch")) {
throw new NetworkError(`Network error while calling ${endpoint}`)
}
if (error instanceof CloudAPIError) {
throw error
}
if (error instanceof Error && error.name === "AbortError") {
throw new CloudAPIError(`Request to ${endpoint} timed out`, undefined, undefined)
}
throw new CloudAPIError(
`Unexpected error while calling ${endpoint}: ${error instanceof Error ? error.message : String(error)}`,
)
}
// No-op: cloud features disabled
throw new CloudAPIError("Cloud features are disabled in this fork", 0, undefined)
}
private async handleErrorResponse(response: Response, endpoint: string): Promise<never> {
let responseBody: unknown
try {
responseBody = await response.json()
} catch {
responseBody = await response.text()
}
switch (response.status) {
case 401:
throw new AuthenticationError()
case 404:
if (endpoint.includes("/share")) {
throw new TaskNotFoundError()
}
throw new CloudAPIError(`Resource not found: ${endpoint}`, 404, responseBody)
default:
throw new CloudAPIError(
`HTTP ${response.status}: ${response.statusText}`,
response.status,
responseBody,
)
}
private async handleErrorResponse(_response: Response, _endpoint: string): Promise<never> {
throw new CloudAPIError("Cloud features are disabled in this fork", 0, undefined)
}
async shareTask(taskId: string, visibility: ShareVisibility = "organization"): Promise<ShareResponse> {
this.log(`[CloudAPI] Sharing task ${taskId} with visibility: ${visibility}`)
const response = await this.request("/api/extension/share", {
method: "POST",
body: JSON.stringify({ taskId, visibility }),
parseResponse: (data) => shareResponseSchema.parse(data),
})
this.log("[CloudAPI] Share response:", response)
return response
async shareTask(_taskId: string, _visibility: ShareVisibility = "organization"): Promise<ShareResponse> {
this.log("[CloudAPI] Cloud features disabled — shareTask is a no-op")
throw new CloudAPIError("Cloud features are disabled in this fork", 0, undefined)
}
async bridgeConfig() {
return this.request("/api/extension/bridge/config", {
method: "GET",
parseResponse: (data) =>
z
.object({
userId: z.string(),
socketBridgeUrl: z.string(),
token: z.string(),
})
.parse(data),
})
this.log("[CloudAPI] Cloud features disabled — bridgeConfig is a no-op")
throw new CloudAPIError("Cloud features are disabled in this fork", 0, undefined)
}
async creditBalance(): Promise<number> {
return this.request("/api/extension/credit-balance", {
method: "GET",
parseResponse: (data) => {
const result = z.object({ balance: z.number() }).parse(data)
return result.balance
},
})
this.log("[CloudAPI] Cloud features disabled — creditBalance is a no-op")
throw new CloudAPIError("Cloud features are disabled in this fork", 0, undefined)
}
}

View file

@ -2,296 +2,70 @@ import EventEmitter from "events"
import type { ExtensionContext } from "vscode"
import { z } from "zod"
import {
type SettingsService,
type SettingsServiceEvents,
type AuthService,
type AuthState,
type UserFeatures,
type UserSettingsConfig,
type UserSettingsData,
OrganizationAllowList,
OrganizationSettings,
organizationSettingsSchema,
userSettingsDataSchema,
ORGANIZATION_ALLOW_ALL,
} from "@roo-code/types"
import { getRooCodeApiUrl } from "./config.js"
import { RefreshTimer } from "./RefreshTimer.js"
const ORGANIZATION_SETTINGS_CACHE_KEY = "organization-settings"
const USER_SETTINGS_CACHE_KEY = "user-settings"
const parseExtensionSettingsResponse = (data: unknown) => {
const shapeResult = z.object({ organization: z.unknown(), user: z.unknown() }).safeParse(data)
if (!shapeResult.success) {
return { success: false, error: shapeResult.error } as const
}
const orgResult = organizationSettingsSchema.safeParse(shapeResult.data.organization)
if (!orgResult.success) {
return { success: false, error: orgResult.error } as const
}
const userResult = userSettingsDataSchema.safeParse(shapeResult.data.user)
if (!userResult.success) {
return { success: false, error: userResult.error } as const
}
return {
success: true,
data: { organization: orgResult.data, user: userResult.data },
} as const
}
/**
* CloudSettingsService — cloud features disabled.
*
* All methods that would make HTTP calls to app.roocode.com are no-ops.
* Returns empty/default settings.
*/
export class CloudSettingsService extends EventEmitter<SettingsServiceEvents> implements SettingsService {
private context: ExtensionContext
private authService: AuthService
private settings: OrganizationSettings | undefined = undefined
private userSettings: UserSettingsData | undefined = undefined
private timer: RefreshTimer
private log: (...args: unknown[]) => void
constructor(context: ExtensionContext, authService: AuthService, log?: (...args: unknown[]) => void) {
constructor(_context: ExtensionContext, _authService: AuthService, log?: (...args: unknown[]) => void) {
super()
this.context = context
this.authService = authService
this.log = log || console.log
this.timer = new RefreshTimer({
callback: async () => {
return await this.fetchSettings()
},
successInterval: 3_600_000,
initialBackoffMs: 1000,
maxBackoffMs: 3_600_000,
})
this.log("[cloud-settings] Cloud features disabled — CloudSettingsService is a no-op")
}
/**
* Initialize — cloud features disabled, no-op.
*/
public async initialize(): Promise<void> {
this.loadCachedSettings()
// Clear cached settings if we have missed a log out.
if (this.authService.getState() == "logged-out" && (this.settings || this.userSettings)) {
await this.removeSettings()
}
this.authService.on("auth-state-changed", async (data: { state: AuthState; previousState: AuthState }) => {
try {
if (data.state === "active-session") {
this.timer.start()
} else if (data.previousState === "active-session") {
this.timer.stop()
if (data.state === "logged-out") {
await this.removeSettings()
}
}
} catch (error) {
this.log(`[cloud-settings] error processing auth-state-changed: ${error}`, error)
}
})
if (this.authService.hasActiveSession()) {
this.timer.start()
}
}
private async fetchSettings(): Promise<boolean> {
const token = this.authService.getSessionToken()
if (!token) {
return false
}
try {
const response = await fetch(`${getRooCodeApiUrl()}/api/extension-settings`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
if (!response.ok) {
this.log("[cloud-settings] Failed to fetch extension settings:", response.status, response.statusText)
return false
}
const data = await response.json()
const result = parseExtensionSettingsResponse(data)
if (!result.success) {
this.log("[cloud-settings] Invalid extension settings format:", result.error)
return false
}
const { organization: newOrgSettings, user: newUserSettings } = result.data
let orgChanged = false
let userChanged = false
// Check for organization settings changes
if (!this.settings || this.settings.version !== newOrgSettings.version) {
this.settings = newOrgSettings
orgChanged = true
}
// Check for user settings changes
if (!this.userSettings || this.userSettings.version !== newUserSettings.version) {
this.userSettings = newUserSettings
userChanged = true
}
// Emit a single event if either settings changed
if (orgChanged || userChanged) {
this.emit("settings-updated", {} as Record<string, never>)
}
const hasChanges = orgChanged || userChanged
if (hasChanges) {
await this.cacheSettings()
}
return true
} catch (error) {
this.log("[cloud-settings] Error fetching extension settings:", error)
return false
}
}
private async cacheSettings(): Promise<void> {
// Store settings in separate globalState values
if (this.settings) {
await this.context.globalState.update(ORGANIZATION_SETTINGS_CACHE_KEY, this.settings)
}
if (this.userSettings) {
await this.context.globalState.update(USER_SETTINGS_CACHE_KEY, this.userSettings)
}
}
private loadCachedSettings(): void {
// Load settings from separate globalState values
this.settings = this.context.globalState.get<OrganizationSettings>(ORGANIZATION_SETTINGS_CACHE_KEY)
this.userSettings = this.context.globalState.get<UserSettingsData>(USER_SETTINGS_CACHE_KEY)
this.log("[cloud-settings] Cloud features disabled — initialize is a no-op")
}
public getAllowList(): OrganizationAllowList {
return this.settings?.allowList || ORGANIZATION_ALLOW_ALL
return ORGANIZATION_ALLOW_ALL
}
public getSettings(): OrganizationSettings | undefined {
return this.settings
return undefined
}
public getUserSettings(): UserSettingsData | undefined {
return this.userSettings
return undefined
}
public getUserFeatures(): UserFeatures {
return this.userSettings?.features || {}
return {}
}
public getUserSettingsConfig(): UserSettingsConfig {
return this.userSettings?.settings || {}
return {}
}
public async updateUserSettings(settings: Partial<UserSettingsConfig>): Promise<boolean> {
const token = this.authService.getSessionToken()
if (!token) {
this.log("[cloud-settings] No session token available for updating user settings")
return false
}
try {
const currentVersion = this.userSettings?.version
const requestBody: {
settings: Partial<UserSettingsConfig>
version?: number
} = {
settings,
}
// Include current version for optimistic locking if we have cached settings
if (currentVersion !== undefined) {
requestBody.version = currentVersion
}
const response = await fetch(`${getRooCodeApiUrl()}/api/user-settings`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(requestBody),
})
if (!response.ok) {
if (response.status === 409) {
this.log(
"[cloud-settings] Version conflict when updating user settings - settings may have been updated elsewhere",
)
} else {
this.log("[cloud-settings] Failed to update user settings:", response.status, response.statusText)
}
return false
}
const updatedUserSettings = await response.json()
const result = userSettingsDataSchema.safeParse(updatedUserSettings)
if (!result.success) {
this.log("[cloud-settings] Invalid user settings response format:", result.error)
return false
}
if (!this.userSettings || result.data.version > this.userSettings.version) {
this.userSettings = result.data
await this.cacheSettings()
this.emit("settings-updated", {} as Record<string, never>)
}
return true
} catch (error) {
this.log("[cloud-settings] Error updating user settings:", error)
return false
}
}
public isTaskSyncEnabled(): boolean {
// Org settings take precedence
if (this.authService.getStoredOrganizationId()) {
return this.settings?.cloudSettings?.recordTaskMessages ?? false
}
// User settings default to true if unspecified
const userSettings = this.userSettings
if (userSettings) {
return userSettings.settings.taskSyncEnabled ?? true
}
public async updateUserSettings(_settings: Partial<UserSettingsConfig>): Promise<boolean> {
this.log("[cloud-settings] Cloud features disabled — updateUserSettings is a no-op")
return false
}
private async removeSettings(): Promise<void> {
this.settings = undefined
this.userSettings = undefined
// Clear both cache keys
await this.context.globalState.update(ORGANIZATION_SETTINGS_CACHE_KEY, undefined)
await this.context.globalState.update(USER_SETTINGS_CACHE_KEY, undefined)
public isTaskSyncEnabled(): boolean {
return false
}
public dispose(): void {
this.removeAllListeners()
this.timer.stop()
}
}

View file

@ -1,6 +1,5 @@
import type { SettingsService, ShareResponse, ShareVisibility } from "@roo-code/types"
import { importVscode } from "./importVscode.js"
import type { CloudAPI } from "./CloudAPI.js"
export class CloudShareService {
@ -14,48 +13,20 @@ export class CloudShareService {
this.log = log || console.log
}
async shareTask(taskId: string, visibility: ShareVisibility = "organization"): Promise<ShareResponse> {
try {
const response = await this.cloudAPI.shareTask(taskId, visibility)
// Cloud features disabled — all methods are no-ops.
if (response.success && response.shareUrl) {
const vscode = await importVscode()
if (vscode?.env?.clipboard?.writeText) {
try {
await vscode.env.clipboard.writeText(response.shareUrl)
} catch (copyErr) {
this.log("[ShareService] Clipboard write failed (non-fatal):", copyErr)
}
} else {
this.log("[ShareService] VS Code clipboard unavailable; running outside extension host.")
}
}
return response
} catch (error) {
this.log("[ShareService] Error sharing task:", error)
throw error
}
async shareTask(_taskId: string, _visibility: ShareVisibility = "organization"): Promise<ShareResponse> {
this.log("[ShareService] Cloud features disabled — shareTask is a no-op")
throw new Error("Cloud features are disabled in this fork")
}
async canShareTask(): Promise<boolean> {
try {
return !!this.settingsService.getSettings()?.cloudSettings?.enableTaskSharing
} catch (error) {
this.log("[ShareService] Error checking if task can be shared:", error)
return false
}
// Cloud features disabled — sharing is not available
return false
}
async canSharePublicly(): Promise<boolean> {
try {
const cloudSettings = this.settingsService.getSettings()?.cloudSettings
// Public sharing requires both enableTaskSharing AND allowPublicTaskSharing to be true
return !!cloudSettings?.enableTaskSharing && cloudSettings?.allowPublicTaskSharing !== false
} catch (error) {
this.log("[ShareService] Error checking if task can be shared publicly:", error)
return false
}
// Cloud features disabled — public sharing is not available
return false
}
}

View file

@ -5,16 +5,11 @@ import {
type AuthService,
type SettingsService,
TelemetryEventName,
rooCodeTelemetryEventSchema,
TelemetryPropertiesProvider,
TelemetryEventSubscription,
} from "@roo-code/types"
import { getRooCodeApiUrl } from "./config.js"
import type { RetryQueue } from "./retry-queue/index.js"
abstract class BaseTelemetryClient implements TelemetryClient {
protected providerRef: WeakRef<TelemetryPropertiesProvider> | null = null
protected providerRef: WeakRef<any> | null = null
protected telemetryEnabled: boolean = false
constructor(
@ -71,7 +66,7 @@ abstract class BaseTelemetryClient implements TelemetryClient {
public async captureException(_error: Error, _additionalProperties?: Record<string, unknown>): Promise<void> {}
public setProvider(provider: TelemetryPropertiesProvider): void {
public setProvider(provider: any): void {
this.providerRef = new WeakRef(provider)
}
@ -85,206 +80,30 @@ abstract class BaseTelemetryClient implements TelemetryClient {
}
export class CloudTelemetryClient extends BaseTelemetryClient {
private retryQueue: RetryQueue | null = null
constructor(
private authService: AuthService,
private settingsService: SettingsService,
retryQueue?: RetryQueue,
) {
constructor(_authService: AuthService, _settingsService: SettingsService) {
super({
type: "exclude",
events: [TelemetryEventName.TASK_CONVERSATION_MESSAGE],
})
this.retryQueue = retryQueue || null
}
private async fetch(path: string, options: RequestInit, allowQueueing = true) {
if (!this.authService.isAuthenticated()) {
return
}
const token = this.authService.getSessionToken()
if (!token) {
console.error(`[TelemetryClient#fetch] Unauthorized: No session token available.`)
return
}
const url = `${getRooCodeApiUrl()}/api/${path}`
const fetchOptions: RequestInit = {
...options,
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
try {
const response = await fetch(url, fetchOptions)
if (!response.ok) {
console.error(
`[TelemetryClient#fetch] ${options.method} ${path} -> ${response.status} ${response.statusText}`,
)
// Queue for retry on server errors (5xx) or rate limiting (429)
// Do NOT retry on client errors (4xx) except 429 - they won't succeed
if (this.retryQueue && allowQueueing && (response.status >= 500 || response.status === 429)) {
await this.retryQueue.enqueue(url, fetchOptions, "telemetry")
}
}
return response
} catch (error) {
console.error(`[TelemetryClient#fetch] Network error for ${options.method} ${path}: ${error}`)
// Queue for retry on network failures (typically TypeError with "fetch failed" message)
// These are transient network issues that may succeed on retry
if (
this.retryQueue &&
allowQueueing &&
error instanceof TypeError &&
error.message.includes("fetch failed")
) {
await this.retryQueue.enqueue(url, fetchOptions, "telemetry")
}
throw error
}
public override async capture(_event: TelemetryEvent): Promise<void> {
// No-op: telemetry disabled
}
public override async capture(event: TelemetryEvent) {
if (!this.isTelemetryEnabled() || !this.isEventCapturable(event.event)) {
if (this.debug) {
console.info(`[TelemetryClient#capture] Skipping event: ${event.event}`)
}
return
}
const payload = {
type: event.event,
properties: await this.getEventProperties(event),
}
if (this.debug) {
console.info(`[TelemetryClient#capture] ${JSON.stringify(payload)}`)
}
const result = rooCodeTelemetryEventSchema.safeParse(payload)
if (!result.success) {
console.error(
`[TelemetryClient#capture] Invalid telemetry event: ${result.error.message} - ${JSON.stringify(payload)}`,
)
return
}
try {
await this.fetch(`events`, {
method: "POST",
body: JSON.stringify(result.data),
})
} catch (error) {
console.error(`[TelemetryClient#capture] Error sending telemetry event: ${error}`)
// Error is already queued for retry in the fetch method
}
public async backfillMessages(_messages: ClineMessage[], _taskId: string): Promise<void> {
// No-op: telemetry disabled
}
public async backfillMessages(messages: ClineMessage[], taskId: string): Promise<void> {
if (!this.isTelemetryEnabled()) {
return
}
if (!this.authService.isAuthenticated()) {
if (this.debug) {
console.info(`[TelemetryClient#backfillMessages] Skipping: Not authenticated`)
}
return
}
const token = this.authService.getSessionToken()
if (!token) {
console.error(`[TelemetryClient#backfillMessages] Unauthorized: No session token available.`)
return
}
try {
const mergedProperties = await this.getEventProperties({
event: TelemetryEventName.TASK_MESSAGE,
properties: { taskId },
})
const formData = new FormData()
formData.append("taskId", taskId)
formData.append("properties", JSON.stringify(mergedProperties))
formData.append(
"file",
new File([JSON.stringify(messages)], "task.json", {
type: "application/json",
}),
)
if (this.debug) {
console.info(
`[TelemetryClient#backfillMessages] Uploading ${messages.length} messages for task ${taskId}`,
)
}
const url = `${getRooCodeApiUrl()}/api/events/backfill`
const fetchOptions: RequestInit = {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
body: formData,
}
try {
const response = await fetch(url, fetchOptions)
if (!response.ok) {
console.error(
`[TelemetryClient#backfillMessages] POST events/backfill -> ${response.status} ${response.statusText}`,
)
}
} catch (fetchError) {
console.error(`[TelemetryClient#backfillMessages] Network error: ${fetchError}`)
throw fetchError
}
} catch (error) {
console.error(`[TelemetryClient#backfillMessages] Error uploading messages: ${error}`)
}
public override updateTelemetryState(_didUserOptIn: boolean): void {
// No-op: telemetry disabled
}
public override updateTelemetryState(_didUserOptIn: boolean) {}
public override isTelemetryEnabled(): boolean {
if (process.env.ROO_CODE_DISABLE_TELEMETRY === "1") {
return false
}
return true
return false
}
protected override isEventCapturable(eventName: TelemetryEventName): boolean {
// Ensure that this event type is supported by the telemetry client
if (!super.isEventCapturable(eventName)) {
return false
}
// Only record message telemetry if task sync is enabled
if (eventName === TelemetryEventName.TASK_MESSAGE) {
return this.settingsService.isTaskSyncEnabled()
}
// Other telemetry types are capturable at this point
return true
public override async shutdown(): Promise<void> {
// No-op: telemetry disabled
}
public override async shutdown() {}
}

View file

@ -1,8 +1,6 @@
import crypto from "crypto"
import EventEmitter from "events"
import type { ExtensionContext } from "vscode"
import { z } from "zod"
import type {
CloudUserInfo,
@ -12,185 +10,24 @@ import type {
AuthState,
} from "@roo-code/types"
import { getClerkBaseUrl, getRooCodeApiUrl, PRODUCTION_CLERK_BASE_URL } from "./config.js"
import { getUserAgent } from "./utils.js"
import { importVscode } from "./importVscode.js"
import { InvalidClientTokenError } from "./errors.js"
import { RefreshTimer } from "./RefreshTimer.js"
const AUTH_STATE_KEY = "clerk-auth-state"
/**
* AuthCredentials
* WebAuthService — cloud features disabled.
*
* All methods that would make HTTP calls to Clerk or Roo Code servers are no-ops.
* The service starts in "logged-out" state and never transitions to an active session.
*/
const authCredentialsSchema = z.object({
clientToken: z.string().min(1, "Client token cannot be empty"),
sessionId: z.string().min(1, "Session ID cannot be empty"),
organizationId: z.string().nullable().optional(),
})
type AuthCredentials = z.infer<typeof authCredentialsSchema>
/**
* Clerk Schemas
*/
const clerkSignInResponseSchema = z.object({
response: z.object({
created_session_id: z.string(),
}),
})
const clerkCreateSessionTokenResponseSchema = z.object({
jwt: z.string(),
})
const clerkMeResponseSchema = z.object({
response: z.object({
id: z.string().optional(),
first_name: z.string().nullish(),
last_name: z.string().nullish(),
image_url: z.string().optional(),
primary_email_address_id: z.string().optional(),
email_addresses: z
.array(
z.object({
id: z.string(),
email_address: z.string(),
}),
)
.optional(),
public_metadata: z.record(z.any()).optional(),
}),
})
const clerkOrganizationMembershipsSchema = z.object({
response: z.array(
z.object({
id: z.string(),
role: z.string(),
permissions: z.array(z.string()).optional(),
created_at: z.number().optional(),
updated_at: z.number().optional(),
organization: z.object({
id: z.string(),
name: z.string(),
slug: z.string().optional(),
image_url: z.string().optional(),
has_image: z.boolean().optional(),
created_at: z.number().optional(),
updated_at: z.number().optional(),
}),
}),
),
})
export class WebAuthService extends EventEmitter<AuthServiceEvents> implements AuthService {
private context: ExtensionContext
private timer: RefreshTimer
private state: AuthState = "initializing"
private log: (...args: unknown[]) => void
private readonly authCredentialsKey: string
private credentials: AuthCredentials | null = null
private sessionToken: string | null = null
private userInfo: CloudUserInfo | null = null
private isFirstRefreshAttempt: boolean = false
constructor(context: ExtensionContext, log?: (...args: unknown[]) => void) {
constructor(_context: ExtensionContext, log?: (...args: unknown[]) => void) {
super()
this.context = context
this.log = log || console.log
this.log("[auth] Using WebAuthService")
// Calculate auth credentials key based on Clerk base URL.
const clerkBaseUrl = getClerkBaseUrl()
if (clerkBaseUrl !== PRODUCTION_CLERK_BASE_URL) {
this.authCredentialsKey = `clerk-auth-credentials-${clerkBaseUrl}`
} else {
this.authCredentialsKey = "clerk-auth-credentials"
}
this.timer = new RefreshTimer({
callback: async () => {
await this.refreshSession()
return true
},
successInterval: 50_000,
initialBackoffMs: 1_000,
maxBackoffMs: 300_000,
})
}
private changeState(newState: AuthState): void {
const previousState = this.state
this.state = newState
this.log(`[auth] changeState: ${previousState} -> ${newState}`)
this.emit("auth-state-changed", { state: newState, previousState })
}
private async handleCredentialsChange(): Promise<void> {
try {
const credentials = await this.loadCredentials()
if (credentials) {
if (
this.credentials === null ||
this.credentials.clientToken !== credentials.clientToken ||
this.credentials.sessionId !== credentials.sessionId ||
this.credentials.organizationId !== credentials.organizationId
) {
this.transitionToAttemptingSession(credentials)
}
} else {
if (this.state !== "logged-out") {
this.transitionToLoggedOut()
}
}
} catch (error) {
this.log("[auth] Error handling credentials change:", error)
}
}
private transitionToLoggedOut(): void {
this.timer.stop()
this.credentials = null
this.sessionToken = null
this.userInfo = null
this.changeState("logged-out")
}
private transitionToAttemptingSession(credentials: AuthCredentials): void {
this.credentials = credentials
this.sessionToken = null
this.userInfo = null
this.isFirstRefreshAttempt = true
this.changeState("attempting-session")
this.timer.stop()
this.timer.start()
}
private transitionToInactiveSession(): void {
this.sessionToken = null
this.userInfo = null
this.changeState("inactive-session")
this.log("[auth] Using WebAuthService (cloud features disabled)")
}
/**
* Initialize the auth state
*
* This method loads tokens from storage and determines the current auth state.
* It also starts the refresh timer if we have an active session.
* Initialize the auth state — cloud features disabled, starts in logged-out state.
*/
public async initialize(): Promise<void> {
if (this.state !== "initializing") {
@ -198,196 +35,39 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
return
}
await this.handleCredentialsChange()
this.context.subscriptions.push(
this.context.secrets.onDidChange((e) => {
if (e.key === this.authCredentialsKey) {
this.handleCredentialsChange()
}
}),
)
// Start in logged-out state — no credentials loading, no timer, no HTTP calls.
this.state = "logged-out"
this.log("[auth] Cloud features disabled — initialized in logged-out state")
}
public broadcast(): void {}
private async storeCredentials(credentials: AuthCredentials): Promise<void> {
await this.context.secrets.store(this.authCredentialsKey, JSON.stringify(credentials))
}
private async loadCredentials(): Promise<AuthCredentials | null> {
const credentialsJson = await this.context.secrets.get(this.authCredentialsKey)
if (!credentialsJson) return null
try {
const parsedJson = JSON.parse(credentialsJson)
const credentials = authCredentialsSchema.parse(parsedJson)
// Migration: If no organizationId but we have userInfo, add it
if (credentials.organizationId === undefined && this.userInfo?.organizationId) {
credentials.organizationId = this.userInfo.organizationId
await this.storeCredentials(credentials)
this.log("[auth] Migrated credentials with organizationId")
}
return credentials
} catch (error) {
if (error instanceof z.ZodError) {
this.log("[auth] Invalid credentials format:", error.errors)
} else {
this.log("[auth] Failed to parse stored credentials:", error)
}
return null
}
}
private async clearCredentials(): Promise<void> {
await this.context.secrets.delete(this.authCredentialsKey)
public broadcast(): void {
// No-op: cloud features disabled
}
/**
* Start the login process
*
* This method initiates the authentication flow by generating a state parameter
* and opening the browser to the authorization URL.
*
* @param landingPageSlug Optional slug of a specific landing page (e.g., "supernova", "special-offer", etc.)
* @param useProviderSignup If true, uses provider signup flow (/extension/provider-sign-up). If false, uses standard sign-in (/extension/sign-in). Defaults to false.
* Login — cloud features disabled, no-op.
*/
public async login(landingPageSlug?: string, useProviderSignup: boolean = false): Promise<void> {
try {
const vscode = await importVscode()
if (!vscode) {
throw new Error("VS Code API not available")
}
// Generate a cryptographically random state parameter.
const state = crypto.randomBytes(16).toString("hex")
await this.context.globalState.update(AUTH_STATE_KEY, state)
const packageJSON = this.context.extension?.packageJSON
const publisher = packageJSON?.publisher ?? "RooVeterinaryInc"
const name = packageJSON?.name ?? "roo-cline"
const params = new URLSearchParams({
state,
auth_redirect: `${vscode.env.uriScheme}://${publisher}.${name}`,
})
// Use landing page URL if slug is provided, otherwise use provider sign-up or sign-in URL based on parameter
const url = landingPageSlug
? `${getRooCodeApiUrl()}/l/${landingPageSlug}?${params.toString()}`
: useProviderSignup
? `${getRooCodeApiUrl()}/extension/provider-sign-up?${params.toString()}`
: `${getRooCodeApiUrl()}/extension/sign-in?${params.toString()}`
await vscode.env.openExternal(vscode.Uri.parse(url))
} catch (error) {
const context = landingPageSlug ? ` (landing page: ${landingPageSlug})` : ""
this.log(`[auth] Error initiating Roo Code Cloud auth${context}: ${error}`)
throw new Error(`Failed to initiate Roo Code Cloud authentication${context}: ${error}`)
}
public async login(_landingPageSlug?: string, _useProviderSignup: boolean = false): Promise<void> {
this.log("[auth] Cloud features disabled — login is a no-op")
}
/**
* Handle the callback from Roo Code Cloud
*
* This method is called when the user is redirected back to the extension
* after authenticating with Roo Code Cloud.
*
* @param code The authorization code from the callback
* @param state The state parameter from the callback
* @param organizationId The organization ID from the callback (null for personal accounts)
* @param providerModel The model ID selected during signup (optional)
* Handle auth callback — cloud features disabled, no-op.
*/
public async handleCallback(
code: string | null,
state: string | null,
organizationId?: string | null,
providerModel?: string | null,
_code: string | null,
_state: string | null,
_organizationId?: string | null,
_providerModel?: string | null,
): Promise<void> {
if (!code || !state) {
const vscode = await importVscode()
if (vscode) {
vscode.window.showInformationMessage("Invalid Roo Code Cloud sign in url")
}
return
}
try {
// Validate state parameter to prevent CSRF attacks.
const storedState = this.context.globalState.get(AUTH_STATE_KEY)
if (state !== storedState) {
this.log("[auth] State mismatch in callback")
throw new Error("Invalid state parameter. Authentication request may have been tampered with.")
}
const credentials = await this.clerkSignIn(code)
// Set organizationId (null for personal accounts)
credentials.organizationId = organizationId || null
await this.storeCredentials(credentials)
// Store the provider model if provided, or flag that no model was selected
if (providerModel) {
await this.context.globalState.update("roo-provider-model", providerModel)
await this.context.globalState.update("roo-auth-skip-model", undefined)
this.log(`[auth] Stored provider model: ${providerModel}`)
} else {
// No model was selected during signup - flag this for the webview
await this.context.globalState.update("roo-auth-skip-model", true)
this.log(`[auth] No provider model selected during signup`)
}
const vscode = await importVscode()
if (vscode) {
vscode.window.showInformationMessage("Successfully authenticated with Roo Code Cloud")
}
this.log("[auth] Successfully authenticated with Roo Code Cloud")
} catch (error) {
this.log(`[auth] Error handling Roo Code Cloud callback: ${error}`)
this.changeState("logged-out")
throw new Error(`Failed to handle Roo Code Cloud callback: ${error}`)
}
this.log("[auth] Cloud features disabled — handleCallback is a no-op")
}
/**
* Log out
*
* This method removes all stored tokens and stops the refresh timer.
* Logout — cloud features disabled, no-op.
*/
public async logout(): Promise<void> {
const oldCredentials = this.credentials
try {
// Clear credentials from storage - onDidChange will handle state transitions
await this.clearCredentials()
await this.context.globalState.update(AUTH_STATE_KEY, undefined)
if (oldCredentials) {
try {
await this.clerkLogout(oldCredentials)
} catch (error) {
this.log("[auth] Error calling clerkLogout:", error)
}
}
const vscode = await importVscode()
if (vscode) {
vscode.window.showInformationMessage("Logged out from Roo Code Cloud")
}
this.log("[auth] Logged out from Roo Code Cloud")
} catch (error) {
this.log(`[auth] Error logging out from Roo Code Cloud: ${error}`)
throw new Error(`Failed to log out from Roo Code Cloud: ${error}`)
}
this.log("[auth] Cloud features disabled — logout is a no-op")
}
public getState(): AuthState {
@ -395,350 +75,39 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
}
public getSessionToken(): string | undefined {
if (this.state === "active-session" && this.sessionToken) {
return this.sessionToken
}
return
// Never have a session token when cloud features are disabled
return undefined
}
/**
* Check if the user is authenticated
*
* @returns True if the user is authenticated (has an active, attempting, or inactive session)
*/
public isAuthenticated(): boolean {
return (
this.state === "active-session" || this.state === "attempting-session" || this.state === "inactive-session"
)
return false
}
public hasActiveSession(): boolean {
return this.state === "active-session"
return false
}
/**
* Check if the user has an active session or is currently attempting to acquire one
*
* @returns True if the user has an active session or is attempting to get one
*/
public hasOrIsAcquiringActiveSession(): boolean {
return this.state === "active-session" || this.state === "attempting-session"
return false
}
/**
* Refresh the session
*
* This method refreshes the session token using the client token.
*/
private async refreshSession(): Promise<void> {
if (!this.credentials) {
this.log("[auth] Cannot refresh session: missing credentials")
return
}
try {
const previousState = this.state
this.sessionToken = await this.clerkCreateSessionToken()
if (previousState !== "active-session") {
this.changeState("active-session")
this.fetchUserInfo()
} else {
this.state = "active-session"
}
} catch (error) {
if (error instanceof InvalidClientTokenError) {
this.log("[auth] Invalid/Expired client token: clearing credentials")
this.clearCredentials()
} else if (this.isFirstRefreshAttempt && this.state === "attempting-session") {
this.isFirstRefreshAttempt = false
this.transitionToInactiveSession()
}
this.log("[auth] Failed to refresh session", error)
throw error
}
}
private async fetchUserInfo(): Promise<void> {
if (!this.credentials) {
return
}
this.userInfo = await this.clerkMe()
this.emit("user-info", { userInfo: this.userInfo })
}
/**
* Extract user information from the ID token
*
* @returns User information from ID token claims or null if no ID token available
*/
public getUserInfo(): CloudUserInfo | null {
return this.userInfo
return null
}
/**
* Get the stored organization ID from credentials
*
* @returns The stored organization ID, null for personal accounts or if no credentials exist
*/
public getStoredOrganizationId(): string | null {
return this.credentials?.organizationId || null
return null
}
/**
* Switch to a different organization context
* @param organizationId The organization ID to switch to, or null for personal account
*/
public async switchOrganization(organizationId: string | null): Promise<void> {
if (!this.credentials) {
throw new Error("Cannot switch organization: not authenticated")
}
// Update the stored credentials with the new organization ID
const updatedCredentials: AuthCredentials = {
...this.credentials,
organizationId: organizationId,
}
// Store the updated credentials, handleCredentialsChange will handle the update
await this.storeCredentials(updatedCredentials)
public async switchOrganization(_organizationId: string | null): Promise<void> {
this.log("[auth] Cloud features disabled — switchOrganization is a no-op")
}
/**
* Get all organization memberships for the current user
* @returns Array of organization memberships
*/
public async getOrganizationMemberships(): Promise<CloudOrganizationMembership[]> {
if (!this.credentials) {
return []
}
try {
return await this.clerkGetOrganizationMemberships()
} catch (error) {
this.log(`[auth] Failed to get organization memberships: ${error}`)
return []
}
return []
}
private async clerkSignIn(ticket: string): Promise<AuthCredentials> {
const formData = new URLSearchParams()
formData.append("strategy", "ticket")
formData.append("ticket", ticket)
const response = await fetch(`${getClerkBaseUrl()}/v1/client/sign_ins`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": this.userAgent(),
},
body: formData.toString(),
signal: AbortSignal.timeout(10000),
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const {
response: { created_session_id: sessionId },
} = clerkSignInResponseSchema.parse(await response.json())
// 3. Extract the client token from the Authorization header.
const clientToken = response.headers.get("authorization")
if (!clientToken) {
throw new Error("No authorization header found in the response")
}
return authCredentialsSchema.parse({ clientToken, sessionId })
}
private async clerkCreateSessionToken(): Promise<string> {
const formData = new URLSearchParams()
formData.append("_is_native", "1")
// Handle 3 cases for organization_id:
// 1. Have an org id: organization_id=THE_ORG_ID
// 2. Have a personal account: organization_id= (empty string)
// 3. Don't know if you have an org id (old style credentials): don't send organization_id param at all
const organizationId = this.getStoredOrganizationId()
if (this.credentials?.organizationId !== undefined) {
// We have organization context info (either org id or personal account)
formData.append("organization_id", organizationId || "")
}
// If organizationId is undefined, don't send the param at all (old credentials)
const response = await fetch(`${getClerkBaseUrl()}/v1/client/sessions/${this.credentials!.sessionId}/tokens`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Bearer ${this.credentials!.clientToken}`,
"User-Agent": this.userAgent(),
},
body: formData.toString(),
signal: AbortSignal.timeout(10000),
})
if (response.status === 401 || response.status === 404) {
throw new InvalidClientTokenError()
} else if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const data = clerkCreateSessionTokenResponseSchema.parse(await response.json())
return data.jwt
}
private async clerkMe(): Promise<CloudUserInfo> {
const response = await fetch(`${getClerkBaseUrl()}/v1/me`, {
headers: {
Authorization: `Bearer ${this.credentials!.clientToken}`,
"User-Agent": this.userAgent(),
},
signal: AbortSignal.timeout(10000),
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const payload = await response.json()
const { response: userData } = clerkMeResponseSchema.parse(payload)
const userInfo: CloudUserInfo = {
id: userData.id,
picture: userData.image_url,
}
const names = [userData.first_name, userData.last_name].filter((name) => !!name)
userInfo.name = names.length > 0 ? names.join(" ") : undefined
const primaryEmailAddressId = userData.primary_email_address_id
const emailAddresses = userData.email_addresses
if (primaryEmailAddressId && emailAddresses) {
userInfo.email = emailAddresses.find(
(email: { id: string }) => primaryEmailAddressId === email.id,
)?.email_address
}
// Fetch organization info if user is in organization context
try {
const storedOrgId = this.getStoredOrganizationId()
if (this.credentials?.organizationId !== undefined) {
// We have organization context info
if (storedOrgId !== null) {
// User is in organization context - fetch user's memberships and filter
const orgMemberships = await this.clerkGetOrganizationMemberships()
const userMembership = this.findOrganizationMembership(orgMemberships, storedOrgId)
if (userMembership) {
this.setUserOrganizationInfo(userInfo, userMembership)
this.log("[auth] User in organization context:", {
id: userMembership.organization.id,
name: userMembership.organization.name,
role: userMembership.role,
})
} else {
this.log("[auth] Warning: User not found in stored organization:", storedOrgId)
}
} else {
this.log("[auth] User in personal account context - not setting organization info")
}
} else {
// Old credentials without organization context - fetch organization info to determine context
const orgMemberships = await this.clerkGetOrganizationMemberships()
const primaryOrgMembership = this.findPrimaryOrganizationMembership(orgMemberships)
if (primaryOrgMembership) {
this.setUserOrganizationInfo(userInfo, primaryOrgMembership)
this.log("[auth] Legacy credentials: Found organization membership:", {
id: primaryOrgMembership.organization.id,
name: primaryOrgMembership.organization.name,
role: primaryOrgMembership.role,
})
} else {
this.log("[auth] Legacy credentials: No organization memberships found")
}
}
} catch (error) {
this.log("[auth] Failed to fetch organization info:", error)
// Don't throw - organization info is optional
}
return userInfo
}
private findOrganizationMembership(
memberships: CloudOrganizationMembership[],
organizationId: string,
): CloudOrganizationMembership | undefined {
return memberships?.find((membership) => membership.organization.id === organizationId)
}
private findPrimaryOrganizationMembership(
memberships: CloudOrganizationMembership[],
): CloudOrganizationMembership | undefined {
return memberships && memberships.length > 0 ? memberships[0] : undefined
}
private setUserOrganizationInfo(userInfo: CloudUserInfo, membership: CloudOrganizationMembership): void {
userInfo.organizationId = membership.organization.id
userInfo.organizationName = membership.organization.name
userInfo.organizationRole = membership.role
userInfo.organizationImageUrl = membership.organization.image_url
}
private async clerkGetOrganizationMemberships(): Promise<CloudOrganizationMembership[]> {
if (!this.credentials) {
this.log("[auth] Cannot get organization memberships: missing credentials")
return []
}
const response = await fetch(`${getClerkBaseUrl()}/v1/me/organization_memberships`, {
headers: {
Authorization: `Bearer ${this.credentials.clientToken}`,
"User-Agent": this.userAgent(),
},
signal: AbortSignal.timeout(10000),
})
if (response.ok) {
return clerkOrganizationMembershipsSchema.parse(await response.json()).response
}
const errorMessage = `Failed to get organization memberships: ${response.status} ${response.statusText}`
this.log(`[auth] ${errorMessage}`)
throw new Error(errorMessage)
}
private async clerkLogout(credentials: AuthCredentials): Promise<void> {
const formData = new URLSearchParams()
formData.append("_is_native", "1")
const response = await fetch(`${getClerkBaseUrl()}/v1/client/sessions/${credentials.sessionId}/remove`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Bearer ${credentials.clientToken}`,
"User-Agent": this.userAgent(),
},
body: formData.toString(),
signal: AbortSignal.timeout(10000),
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
}
private userAgent(): string {
return getUserAgent(this.context)
public dispose(): void {
this.removeAllListeners()
}
}

View file

@ -1,5 +1,6 @@
export const PRODUCTION_CLERK_BASE_URL = "https://clerk.roocode.com"
export const PRODUCTION_ROO_CODE_API_URL = "https://app.roocode.com"
// Cloud features disabled — production URLs neutralized for standalone fork.
export const PRODUCTION_CLERK_BASE_URL = ""
export const PRODUCTION_ROO_CODE_API_URL = ""
export const getClerkBaseUrl = () => process.env.CLERK_BASE_URL || PRODUCTION_CLERK_BASE_URL

View file

@ -70,34 +70,15 @@ export class RetryQueue extends EventEmitter<RetryQueueEvents> {
}
}
// Cloud features disabled — enqueue is a no-op to prevent any HTTP calls
public async enqueue(
url: string,
options: RequestInit,
type: QueuedRequest["type"] = "other",
operation?: string,
_url: string,
_options: RequestInit,
_type: QueuedRequest["type"] = "other",
_operation?: string,
): Promise<void> {
if (this.queue.size >= this.config.maxQueueSize) {
const oldestId = Array.from(this.queue.keys())[0]
if (oldestId) {
this.queue.delete(oldestId)
}
}
const request: QueuedRequest = {
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
url,
options,
timestamp: Date.now(),
retryCount: 0,
type,
operation,
}
this.queue.set(request.id, request)
await this.persistQueue()
this.emit("request-queued", request)
this.log(`[RetryQueue] Queued request: ${url}`)
// No-op: cloud features disabled
this.log("[RetryQueue] Cloud features disabled - enqueue is a no-op")
}
public async retryAll(): Promise<void> {
@ -206,17 +187,18 @@ export class RetryQueue extends EventEmitter<RetryQueueEvents> {
}
}
// Cloud features disabled — skip actual HTTP fetch, return fake success
this.log(`[RetryQueue] Cloud features disabled - skipping request to ${request.url}`)
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), this.config.requestTimeout)
try {
const response = await fetch(request.url, {
...request.options,
signal: controller.signal,
headers: {
...headers,
"X-Retry-Queue": "true",
},
// Return a fake successful response instead of making a real HTTP call
const response = new Response(JSON.stringify({ success: true }), {
status: 200,
statusText: "OK",
headers: { "Content-Type": "application/json" },
})
clearTimeout(timeoutId)

View file

@ -1,42 +1,23 @@
import { PostHog } from "posthog-node"
import * as vscode from "vscode"
import {
type TelemetryProperties,
type TelemetryEvent,
TelemetryEventName,
getErrorStatusCode,
getErrorMessage,
shouldReportApiErrorToTelemetry,
isApiProviderError,
extractApiProviderErrorProperties,
isConsecutiveMistakeError,
extractConsecutiveMistakeErrorProperties,
} from "@roo-code/types"
import { type TelemetryEvent, TelemetryEventName } from "@roo-code/types"
import { BaseTelemetryClient } from "./BaseTelemetryClient"
/**
* PostHogTelemetryClient handles telemetry event tracking for the Roo Code extension.
* Uses PostHog analytics to track user interactions and system events.
* Respects user privacy settings and VSCode's global telemetry configuration.
* All methods are no-ops — telemetry has been disabled in this fork.
*/
export class PostHogTelemetryClient extends BaseTelemetryClient {
private client: PostHog
private distinctId: string = vscode.env.machineId
// Git repository properties that should be filtered out
private readonly gitPropertyNames = ["repositoryUrl", "repositoryName", "defaultBranch"]
constructor(debug = false) {
constructor(_debug = false) {
super(
{
type: "exclude",
events: [TelemetryEventName.TASK_MESSAGE, TelemetryEventName.LLM_COMPLETION],
},
debug,
_debug,
)
this.client = new PostHog(process.env.POSTHOG_API_KEY || "", { host: "https://ph.roocode.com" })
}
/**
@ -45,126 +26,28 @@ export class PostHogTelemetryClient extends BaseTelemetryClient {
* @returns Whether the property should be included in telemetry events
*/
protected override isPropertyCapturable(propertyName: string): boolean {
// Filter out git repository properties
if (this.gitPropertyNames.includes(propertyName)) {
return false
}
return true
}
public override async capture(event: TelemetryEvent): Promise<void> {
if (!this.isTelemetryEnabled() || !this.isEventCapturable(event.event)) {
if (this.debug) {
console.info(`[PostHogTelemetryClient#capture] Skipping event: ${event.event}`)
}
return
}
if (this.debug) {
console.info(`[PostHogTelemetryClient#capture] ${event.event}`)
}
const properties = await this.getEventProperties(event)
this.client.capture({
distinctId: this.distinctId,
event: event.event,
properties,
})
public override async capture(_event: TelemetryEvent): Promise<void> {
// No-op: telemetry disabled
}
public override async captureException(
error: Error,
additionalProperties?: Record<string, unknown>,
_error: Error,
_additionalProperties?: Record<string, unknown>,
): Promise<void> {
if (!this.isTelemetryEnabled()) {
if (this.debug) {
console.info(`[PostHogTelemetryClient#captureException] Skipping exception: ${error.message}`)
}
return
}
// Extract error status code and message for filtering.
const errorCode = getErrorStatusCode(error)
const errorMessage = getErrorMessage(error) ?? error.message
// Filter out expected errors (e.g., 402 billing, 429 rate limits)
if (!shouldReportApiErrorToTelemetry(errorCode, errorMessage)) {
if (this.debug) {
console.info(
`[PostHogTelemetryClient#captureException] Filtering out expected error: ${errorCode} - ${errorMessage}`,
)
}
return
}
if (this.debug) {
console.info(`[PostHogTelemetryClient#captureException] ${error.message}`)
}
// Auto-extract properties from known error types and merge with additionalProperties.
// Explicit additionalProperties take precedence over auto-extracted properties.
let mergedProperties = additionalProperties
if (isApiProviderError(error)) {
const extractedProperties = extractApiProviderErrorProperties(error)
mergedProperties = { ...extractedProperties, ...additionalProperties }
} else if (isConsecutiveMistakeError(error)) {
const extractedProperties = extractConsecutiveMistakeErrorProperties(error)
mergedProperties = { ...extractedProperties, ...additionalProperties }
}
// Override the error message with the extracted error message.
error.message = errorMessage
const provider = this.providerRef?.deref()
let telemetryProperties: TelemetryProperties | undefined = undefined
if (provider) {
try {
telemetryProperties = await provider.getTelemetryProperties()
} catch (_error) {
// Ignore.
}
}
const exceptionProperties = {
...mergedProperties,
$app_version: telemetryProperties?.appVersion,
}
this.client.captureException(error, this.distinctId, exceptionProperties)
// No-op: telemetry disabled
}
/**
* Updates the telemetry state based on user preferences and VSCode settings.
* Only enables telemetry if both VSCode global telemetry is enabled and
* user has opted in.
* @param didUserOptIn Whether the user has explicitly opted into telemetry
*/
public override updateTelemetryState(didUserOptIn: boolean): void {
this.telemetryEnabled = false
// First check global telemetry level - telemetry should only be enabled when level is "all".
const telemetryLevel = vscode.workspace.getConfiguration("telemetry").get<string>("telemetryLevel", "all")
const globalTelemetryEnabled = telemetryLevel === "all"
// We only enable telemetry if global vscode telemetry is enabled.
if (globalTelemetryEnabled) {
this.telemetryEnabled = didUserOptIn
}
// Update PostHog client state based on telemetry preference.
if (this.telemetryEnabled) {
this.client.optIn()
} else {
this.client.optOut()
}
public override updateTelemetryState(_didUserOptIn: boolean): void {
// No-op: telemetry disabled
}
public override async shutdown(): Promise<void> {
await this.client.shutdown()
// No-op: telemetry disabled
}
}

View file

@ -1,59 +0,0 @@
# Reapplication Progress — rc6 branch cleanup
# Updated: 2026-02-15
## Completed Batches
### Batch 1 — Clean cherry-picks (PR #11473)
- 22 PRs merged cleanly
- Status: MERGED to main
### Batch 2 — Minor conflicts (PR #11474)
- 9 PRs with minor conflicts resolved
- Status: MERGED to main
### Batch 3 — Skills Infrastructure & Browser Use Removal (4 PRs)
- PR #11102: skill mode dropdown (44 conflicts resolved)
- PR #11157: improve Skills/Slash Commands UI (6 conflicts resolved)
- PR #11414: remove built-in skills mechanism (4 conflicts resolved)
- PR #11392: remove browser use entirely (5 conflicts resolved)
- Status: ON BRANCH reapply/batch-3-4-5-major-conflicts
### Batch 4 — Provider Removals (2 PRs)
- PR #11253: remove URL context/Grounding checkboxes (4 conflicts resolved)
- PR #11297: remove 9 low-usage providers + retired UX (14 conflicts resolved)
- Status: ON BRANCH reapply/batch-3-4-5-major-conflicts
### Batch 5 — Azure Foundry
- PR #11315 and #11374: EXCLUDED — depends on AI-SDK (@ai-sdk/azure, from "ai")
- These PRs are AI-SDK-entangled and cannot be cherry-picked to the pre-AI-SDK codebase
- Status: DEFERRED (AI-SDK dependent)
## Post-cherry-pick Fixes Applied
1. Restored gemini.ts + vertex.ts to pre-AI-SDK state (cherry-picks brought AI-SDK versions)
2. Restored ai-sdk.spec.ts, gemini-handler.spec.ts, vertex.spec.ts to pre-AI-SDK versions
3. Fixed processUserContentMentions.ts ghost import (rooMessage.ts doesn't exist)
4. Added missing skills type exports to @roo-code/types (SkillMetadata, validateSkillName, etc.)
5. Added SkillsSettings import to SettingsView.tsx
6. Added Dialog/Select/Collapsible mocks to SettingsView test files
7. Fixed Task.ts type mismatches (replaced local types with Anthropic SDK types)
8. Added skills state to ExtensionStateContext
## Deferred PRs (AI-SDK Entangled)
- #11379: delegation (AI-SDK)
- #11418: delegation (AI-SDK)
- #11422: delegation (AI-SDK)
- #11315: Azure Foundry provider (AI-SDK)
- #11374: Azure Foundry fix (AI-SDK)
## Validation Results
- Backend tests: ALL PASSED (5224 tests)
- UI tests: ALL PASSED (1267 tests)
- Type checks: ALL PASSED (14/14 packages)
- AI-SDK contamination: CLEAN (0 matches)
## Notes
- Pre-push hook fails on `roo-cline:bundle` because `generate-built-in-skills.ts` was removed
by PR #11414 but `package.json` still references it in `prebundle`. This is expected and
will be resolved when the PR is merged to main and the script reference is cleaned up.
- Push was done with `--no-verify` after independent verification of types, backend tests,
and UI tests all passed cleanly.

View file

@ -7,11 +7,11 @@ import { getCodeActionCommand } from "../utils/commands"
import { EditorUtils } from "../integrations/editor/EditorUtils"
export const TITLES: Record<CodeActionName, string> = {
EXPLAIN: "Explain with Roo Code",
FIX: "Fix with Roo Code",
IMPROVE: "Improve with Roo Code",
ADD_TO_CONTEXT: "Add to Roo Code",
NEW_TASK: "New Roo Code Task",
EXPLAIN: "Explain with Moo Code",
FIX: "Fix with Moo Code",
IMPROVE: "Improve with Moo Code",
ADD_TO_CONTEXT: "Add to Moo Code",
NEW_TASK: "New Moo Code Task",
} as const
export class CodeActionProvider implements vscode.CodeActionProvider {

View file

@ -21,7 +21,7 @@ import { t } from "../i18n"
export function getVisibleProviderOrLog(outputChannel: vscode.OutputChannel): ClineProvider | undefined {
const visibleProvider = ClineProvider.getVisibleInstance()
if (!visibleProvider) {
outputChannel.appendLine("Cannot find any visible Roo Code instances.")
outputChannel.appendLine("Cannot find any visible Moo Code instances.")
return undefined
}
return visibleProvider
@ -227,7 +227,7 @@ export const openClineInNewTab = async ({ context, outputChannel }: Omit<Registe
const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two
const newPanel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Roo Code", targetCol, {
const newPanel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Moo Code", targetCol, {
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [context.extensionUri],

View file

@ -44,7 +44,7 @@ export interface SingleCompletionHandler {
export interface ApiHandlerCreateMessageMetadata {
/**
* Task ID used for tracking and provider-specific features:
* - Roo: Sent as X-Roo-Task-ID header
* - Roo: Sent as X-Moo-Task-ID header
* - Requesty: Sent as trace_id
*/
taskId: string

View file

@ -252,7 +252,7 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
this.costModelConfig = this.getModel()
const clientConfig: BedrockRuntimeClientConfig = {
userAgentAppId: `RooCode#${Package.version}`,
userAgentAppId: `MooCode#${Package.version}`,
region: this.options.awsRegion,
// Add the endpoint configuration when specified and enabled
...(this.options.awsBedrockEndpoint &&

View file

@ -1,7 +1,7 @@
import { Package } from "../../shared/package"
export const DEFAULT_HEADERS = {
"HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline",
"X-Title": "Roo Code",
"User-Agent": `RooCode/${Package.version}`,
"HTTP-Referer": "https://github.com/moo-code/Moo-Code",
"X-Title": "Moo Code",
"User-Agent": `MooCode/${Package.version}`,
}

View file

@ -87,8 +87,8 @@ async function fetchModelsFromProvider(options: GetModelsOptions): Promise<Model
models = await getVercelAiGatewayModels()
break
case "roo": {
// Roo Code Cloud provider requires baseUrl and optional apiKey
const rooBaseUrl = options.baseUrl ?? process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy"
// Moo Code Cloud provider requires baseUrl and optional apiKey
const rooBaseUrl = options.baseUrl ?? process.env.ROO_CODE_PROVIDER_URL ?? ""
models = await getRooModels(rooBaseUrl, options.apiKey)
break
}

View file

@ -6,14 +6,18 @@ import { DEFAULT_HEADERS } from "../constants"
import { resolveVersionedSettings, type VersionedSettings } from "./versionedSettings"
/**
* Fetches available models from the Roo Code Cloud provider
* Fetches available models from the Moo Code Cloud provider
*
* @param baseUrl The base URL of the Roo Code Cloud provider
* @param apiKey The API key (session token) for the Roo Code Cloud provider
* @param baseUrl The base URL of the Moo Code Cloud provider
* @param apiKey The API key (session token) for the Moo Code Cloud provider
* @returns A promise that resolves to a record of model IDs to model info
* @throws Will throw an error if the request fails or the response is not as expected.
*/
export async function getRooModels(baseUrl: string, apiKey?: string): Promise<ModelRecord> {
// Cloud features disabled — return empty models if no base URL configured
if (!baseUrl) {
return {}
}
// Construct the models endpoint URL early so it's available in catch block for logging
// Strip trailing /v1 or /v1/ to avoid /v1/v1/models
const normalizedBase = baseUrl.replace(/\/?v1\/?$/, "")
@ -65,9 +69,9 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise<Mo
const parsed = RooModelsResponseSchema.safeParse(data)
if (!parsed.success) {
console.error("Error fetching Roo Code Cloud models: Unexpected response format", data)
console.error("Error fetching Moo Code Cloud models: Unexpected response format", data)
console.error("Validation errors:", parsed.error.format())
throw new Error("Failed to fetch Roo Code Cloud models: Unexpected response format.")
throw new Error("Failed to fetch Moo Code Cloud models: Unexpected response format.")
}
// Process the validated model data
@ -162,7 +166,7 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise<Mo
}
} catch (error: any) {
// Enhanced error logging
console.error("[getRooModels] Error fetching Roo Code Cloud models:", {
console.error("[getRooModels] Error fetching Moo Code Cloud models:", {
message: error.message || String(error),
name: error.name,
stack: error.stack,
@ -172,21 +176,21 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise<Mo
// Handle abort/timeout
if (error.name === "AbortError") {
throw new Error("Failed to fetch Roo Code Cloud models: Request timed out after 10 seconds.")
throw new Error("Failed to fetch Moo Code Cloud models: Request timed out after 10 seconds.")
}
// Handle fetch errors
if (error.message?.includes("HTTP")) {
throw new Error(`Failed to fetch Roo Code Cloud models: ${error.message}. Check base URL and API key.`)
throw new Error(`Failed to fetch Moo Code Cloud models: ${error.message}. Check base URL and API key.`)
}
// Handle network errors
if (error instanceof TypeError) {
throw new Error(
"Failed to fetch Roo Code Cloud models: No response from server. Check Roo Code Cloud server status and base URL.",
"Failed to fetch Moo Code Cloud models: No response from server. Check Moo Code Cloud server status and base URL.",
)
}
throw new Error(`Failed to fetch Roo Code Cloud models: ${error.message || "An unknown error occurred."}`)
throw new Error(`Failed to fetch Moo Code Cloud models: ${error.message || "An unknown error occurred."}`)
}
}

View file

@ -165,7 +165,7 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan
} as const
} catch (error) {
throw new Error(
"Please check the LM Studio developer logs to debug what went wrong. You may need to load the model with a larger context length to work with Roo Code's prompts.",
"Please check the LM Studio developer logs to debug what went wrong. You may need to load the model with a larger context length to work with Moo Code's prompts.",
)
}
}
@ -209,7 +209,7 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan
return response.choices[0]?.message.content || ""
} catch (error) {
throw new Error(
"Please check the LM Studio developer logs to debug what went wrong. You may need to load the model with a larger context length to work with Roo Code's prompts.",
"Please check the LM Studio developer logs to debug what went wrong. You may need to load the model with a larger context length to work with Moo Code's prompts.",
)
}
}

View file

@ -360,7 +360,7 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
const codexHeaders: Record<string, string> = {
originator: "roo-code",
session_id: taskId || this.sessionId,
"User-Agent": `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`,
"User-Agent": `moo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`,
...(accountId ? { "ChatGPT-Account-Id": accountId } : {}),
}
@ -505,7 +505,7 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
Authorization: `Bearer ${accessToken}`,
originator: "roo-code",
session_id: taskId || this.sessionId,
"User-Agent": `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`,
"User-Agent": `moo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`,
}
// Add ChatGPT-Account-Id if available (required for organization subscriptions)
@ -1201,7 +1201,7 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
Authorization: `Bearer ${accessToken}`,
originator: "roo-code",
session_id: this.sessionId,
"User-Agent": `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`,
"User-Agent": `moo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`,
}
// Add ChatGPT-Account-Id if available

View file

@ -95,7 +95,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
}
const apiKey = this.options.openAiNativeApiKey ?? "not-provided"
// Include originator, session_id, and User-Agent headers for API tracking and debugging
const userAgent = `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`
const userAgent = `moo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`
this.client = new OpenAI({
baseURL: this.options.openAiNativeBaseUrl || undefined,
apiKey,
@ -415,7 +415,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
// Build per-request headers using taskId when available, falling back to sessionId
const taskId = metadata?.taskId
const userAgent = `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`
const userAgent = `moo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`
const requestHeaders: Record<string, string> = {
originator: "roo-code",
session_id: taskId || this.sessionId,
@ -563,7 +563,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
// Build per-request headers using taskId when available, falling back to sessionId
const taskId = metadata?.taskId
const userAgent = `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`
const userAgent = `moo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`
try {
const response = await fetch(url, {

View file

@ -44,10 +44,10 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
constructor(options: ApiHandlerOptions) {
const sessionToken = options.rooApiKey ?? getSessionToken()
let baseURL = process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy"
let baseURL = process.env.ROO_CODE_PROVIDER_URL ?? ""
// Ensure baseURL ends with /v1 for OpenAI client, but don't duplicate it
if (!baseURL.endsWith("/v1")) {
if (baseURL && !baseURL.endsWith("/v1")) {
baseURL = `${baseURL}/v1`
}
@ -55,7 +55,7 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
// The provider-proxy server will return 401 if authentication fails.
super({
...options,
providerName: "Roo Code Cloud",
providerName: "Moo Code Cloud",
baseURL, // Already has /v1 suffix
apiKey: sessionToken,
defaultProviderModelId: rooDefaultModelId,
@ -65,9 +65,10 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
// Load dynamic models asynchronously - strip /v1 from baseURL for fetcher
this.fetcherBaseURL = baseURL.endsWith("/v1") ? baseURL.slice(0, -3) : baseURL
this.loadDynamicModels(this.fetcherBaseURL, sessionToken).catch((error) => {
console.error("[RooHandler] Failed to load dynamic models:", error)
})
// Cloud features disabled — skip loading dynamic models from upstream
// this.loadDynamicModels(this.fetcherBaseURL, sessionToken).catch((error) => {
// console.error("[RooHandler] Failed to load dynamic models:", error)
// })
}
protected override createStream(
@ -127,215 +128,12 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
messages: Anthropic.Messages.MessageParam[],
metadata?: ApiHandlerCreateMessageMetadata,
): ApiStream {
try {
// Reset reasoning_details accumulator for this request
this.currentReasoningDetails = []
const headers: Record<string, string> = {
"X-Roo-App-Version": Package.version,
}
if (metadata?.taskId) {
headers["X-Roo-Task-ID"] = metadata.taskId
}
const stream = await this.createStream(systemPrompt, messages, metadata, { headers })
let lastUsage: RooUsage | undefined = undefined
// Accumulator for reasoning_details FROM the API.
// We preserve the original shape of reasoning_details to prevent malformed responses.
const reasoningDetailsAccumulator = new Map<
string,
{
type: string
text?: string
summary?: string
data?: string
id?: string | null
format?: string
signature?: string
index: number
}
>()
// Track whether we've yielded displayable text from reasoning_details.
// When reasoning_details has displayable content (reasoning.text or reasoning.summary),
// we skip yielding the top-level reasoning field to avoid duplicate display.
let hasYieldedReasoningFromDetails = false
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta
const finishReason = chunk.choices[0]?.finish_reason
if (delta) {
// Handle reasoning_details array format (used by Gemini 3, Claude, OpenAI o-series, etc.)
// See: https://openrouter.ai/docs/use-cases/reasoning-tokens#preserving-reasoning-blocks
// Priority: Check for reasoning_details first, as it's the newer format
const deltaWithReasoning = delta as typeof delta & {
reasoning_details?: Array<{
type: string
text?: string
summary?: string
data?: string
id?: string | null
format?: string
signature?: string
index?: number
}>
}
if (deltaWithReasoning.reasoning_details && Array.isArray(deltaWithReasoning.reasoning_details)) {
for (const detail of deltaWithReasoning.reasoning_details) {
const index = detail.index ?? 0
// Use id as key when available to merge chunks that share the same reasoning block id
// This ensures that reasoning.summary and reasoning.encrypted chunks with the same id
// are merged into a single object, matching the provider's expected format
const key = detail.id ?? `${detail.type}-${index}`
const existing = reasoningDetailsAccumulator.get(key)
if (existing) {
// Accumulate text/summary/data for existing reasoning detail
if (detail.text !== undefined) {
existing.text = (existing.text || "") + detail.text
}
if (detail.summary !== undefined) {
existing.summary = (existing.summary || "") + detail.summary
}
if (detail.data !== undefined) {
existing.data = (existing.data || "") + detail.data
}
// Update other fields if provided
// Note: Don't update type - keep original type (e.g., reasoning.summary)
// even when encrypted data chunks arrive with type reasoning.encrypted
if (detail.id !== undefined) existing.id = detail.id
if (detail.format !== undefined) existing.format = detail.format
if (detail.signature !== undefined) existing.signature = detail.signature
} else {
// Start new reasoning detail accumulation
reasoningDetailsAccumulator.set(key, {
type: detail.type,
text: detail.text,
summary: detail.summary,
data: detail.data,
id: detail.id,
format: detail.format,
signature: detail.signature,
index,
})
}
// Yield text for display (still fragmented for live streaming)
// Only reasoning.text and reasoning.summary have displayable content
// reasoning.encrypted is intentionally skipped as it contains redacted content
let reasoningText: string | undefined
if (detail.type === "reasoning.text" && typeof detail.text === "string") {
reasoningText = detail.text
} else if (detail.type === "reasoning.summary" && typeof detail.summary === "string") {
reasoningText = detail.summary
}
if (reasoningText) {
hasYieldedReasoningFromDetails = true
yield { type: "reasoning", text: reasoningText }
}
}
}
// Handle top-level reasoning field for UI display.
// Skip if we've already yielded from reasoning_details to avoid duplicate display.
if ("reasoning" in delta && delta.reasoning && typeof delta.reasoning === "string") {
if (!hasYieldedReasoningFromDetails) {
yield { type: "reasoning", text: delta.reasoning }
}
} else if ("reasoning_content" in delta && typeof delta.reasoning_content === "string") {
// Also check for reasoning_content for backward compatibility
if (!hasYieldedReasoningFromDetails) {
yield { type: "reasoning", text: delta.reasoning_content }
}
}
// Emit raw tool call chunks - NativeToolCallParser handles state management
if ("tool_calls" in delta && Array.isArray(delta.tool_calls)) {
for (const toolCall of delta.tool_calls) {
yield {
type: "tool_call_partial",
index: toolCall.index,
id: toolCall.id,
name: toolCall.function?.name,
arguments: toolCall.function?.arguments,
}
}
}
if (delta.content) {
yield {
type: "text",
text: delta.content,
}
}
}
if (finishReason) {
const endEvents = NativeToolCallParser.processFinishReason(finishReason)
for (const event of endEvents) {
yield event
}
}
if (chunk.usage) {
lastUsage = chunk.usage as RooUsage
}
}
// After streaming completes, store ONLY the reasoning_details we received from the API.
if (reasoningDetailsAccumulator.size > 0) {
this.currentReasoningDetails = Array.from(reasoningDetailsAccumulator.values())
}
if (lastUsage) {
// Check if the current model is marked as free
const model = this.getModel()
const isFreeModel = model.info.isFree ?? false
// Normalize input tokens based on protocol expectations:
// - OpenAI protocol expects TOTAL input tokens (cached + non-cached)
// - Anthropic protocol expects NON-CACHED input tokens (caches passed separately)
const modelId = model.id
const apiProtocol = getApiProtocol("roo", modelId)
const promptTokens = lastUsage.prompt_tokens || 0
const cacheWrite = lastUsage.cache_creation_input_tokens || 0
const cacheRead = lastUsage.prompt_tokens_details?.cached_tokens || 0
const nonCached = Math.max(0, promptTokens - cacheWrite - cacheRead)
const inputTokensForDownstream = apiProtocol === "anthropic" ? nonCached : promptTokens
yield {
type: "usage",
inputTokens: inputTokensForDownstream,
outputTokens: lastUsage.completion_tokens || 0,
cacheWriteTokens: cacheWrite,
cacheReadTokens: cacheRead,
totalCost: isFreeModel ? 0 : (lastUsage.cost ?? 0),
}
}
} catch (error) {
const errorContext = {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
modelId: this.options.apiModelId,
hasTaskId: Boolean(metadata?.taskId),
}
console.error(`[RooHandler] Error during message streaming: ${JSON.stringify(errorContext)}`)
throw error
}
// Cloud features disabled — Moo Code provider is not available in this fork
throw new Error("Moo Code provider is not available in this fork. Cloud features have been disabled.")
}
override async completePrompt(prompt: string): Promise<string> {
// Update API key before making request to ensure we use the latest session token
this.client.apiKey = this.options.rooApiKey ?? getSessionToken()
return super.completePrompt(prompt)
throw new Error("Moo Code provider is not available in this fork. Cloud features have been disabled.")
}
private async loadDynamicModels(baseURL: string, apiKey?: string): Promise<void> {
@ -387,7 +185,7 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
}
/**
* Generate an image using Roo Code Cloud's image generation API
* Generate an image using Moo Code Cloud's image generation API
* @param prompt The text prompt for image generation
* @param model The model to use for generation
* @param inputImage Optional base64 encoded input image data URL
@ -400,36 +198,9 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
inputImage?: string,
apiMethod?: ImageGenerationApiMethod,
): Promise<ImageGenerationResult> {
const sessionToken = this.options.rooApiKey ?? getSessionToken()
if (!sessionToken || sessionToken === "unauthenticated") {
return {
success: false,
error: t("tools:generateImage.roo.authRequired"),
}
return {
success: false,
error: "Moo Code provider is not available in this fork. Cloud features have been disabled.",
}
const baseURL = `${this.fetcherBaseURL}/v1`
// Use the specified API method, defaulting to chat_completions for backward compatibility
if (apiMethod === "images_api") {
return generateImageWithImagesApi({
baseURL,
authToken: sessionToken,
model,
prompt,
inputImage,
outputFormat: "png",
})
}
// Default to chat completions approach
return generateImageWithProvider({
baseURL,
authToken: sessionToken,
model,
prompt,
inputImage,
})
}
}

View file

@ -70,8 +70,8 @@ export async function generateImageWithProvider(options: ImageGenerationOptions)
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"HTTP-Referer": "https://github.com/RooVetGit/Roo-Code",
"X-Title": "Roo Code",
"HTTP-Referer": "https://github.com/moo-code/Moo-Code",
"X-Title": "Moo Code",
},
body: JSON.stringify({
model,
@ -216,8 +216,8 @@ export async function generateImageWithImagesApi(options: ImagesApiOptions): Pro
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"HTTP-Referer": "https://github.com/RooVetGit/Roo-Code",
"X-Title": "Roo Code",
"HTTP-Referer": "https://github.com/moo-code/Moo-Code",
"X-Title": "Moo Code",
},
body: JSON.stringify(requestBody),
}

View file

@ -91,7 +91,7 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
this.dispose()
throw new Error(
`Roo Code <Language Model API>: Failed to initialize handler: ${error instanceof Error ? error.message : "Unknown error"}`,
`Moo Code <Language Model API>: Failed to initialize handler: ${error instanceof Error ? error.message : "Unknown error"}`,
)
}
}
@ -106,17 +106,17 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
try {
// Check if the client is already initialized
if (this.client) {
console.debug("Roo Code <Language Model API>: Client already initialized")
console.debug("Moo Code <Language Model API>: Client already initialized")
return
}
// Create a new client instance
this.client = await this.createClient(this.options.vsCodeLmModelSelector || {})
console.debug("Roo Code <Language Model API>: Client initialized successfully")
console.debug("Moo Code <Language Model API>: Client initialized successfully")
} catch (error) {
// Handle errors during client initialization
const errorMessage = error instanceof Error ? error.message : "Unknown error"
console.error("Roo Code <Language Model API>: Client initialization failed:", errorMessage)
throw new Error(`Roo Code <Language Model API>: Failed to initialize client: ${errorMessage}`)
console.error("Moo Code <Language Model API>: Client initialization failed:", errorMessage)
throw new Error(`Moo Code <Language Model API>: Failed to initialize client: ${errorMessage}`)
}
}
/**
@ -164,7 +164,7 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error"
throw new Error(`Roo Code <Language Model API>: Failed to select model: ${errorMessage}`)
throw new Error(`Moo Code <Language Model API>: Failed to select model: ${errorMessage}`)
}
}
@ -225,13 +225,13 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
private async internalCountTokens(text: string | vscode.LanguageModelChatMessage): Promise<number> {
// Check for required dependencies
if (!this.client) {
console.warn("Roo Code <Language Model API>: No client available for token counting")
console.warn("Moo Code <Language Model API>: No client available for token counting")
return 0
}
// Validate input
if (!text) {
console.debug("Roo Code <Language Model API>: Empty text provided for token counting")
console.debug("Moo Code <Language Model API>: Empty text provided for token counting")
return 0
}
@ -255,24 +255,24 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
} else if (text instanceof vscode.LanguageModelChatMessage) {
// For chat messages, ensure we have content
if (!text.content || (Array.isArray(text.content) && text.content.length === 0)) {
console.debug("Roo Code <Language Model API>: Empty chat message content")
console.debug("Moo Code <Language Model API>: Empty chat message content")
return 0
}
const countMessage = extractTextCountFromMessage(text)
tokenCount = await this.client.countTokens(countMessage, cancellationToken)
} else {
console.warn("Roo Code <Language Model API>: Invalid input type for token counting")
console.warn("Moo Code <Language Model API>: Invalid input type for token counting")
return 0
}
// Validate the result
if (typeof tokenCount !== "number") {
console.warn("Roo Code <Language Model API>: Non-numeric token count received:", tokenCount)
console.warn("Moo Code <Language Model API>: Non-numeric token count received:", tokenCount)
return 0
}
if (tokenCount < 0) {
console.warn("Roo Code <Language Model API>: Negative token count received:", tokenCount)
console.warn("Moo Code <Language Model API>: Negative token count received:", tokenCount)
return 0
}
@ -280,12 +280,12 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
} catch (error) {
// Handle specific error types
if (error instanceof vscode.CancellationError) {
console.debug("Roo Code <Language Model API>: Token counting cancelled by user")
console.debug("Moo Code <Language Model API>: Token counting cancelled by user")
return 0
}
const errorMessage = error instanceof Error ? error.message : "Unknown error"
console.warn("Roo Code <Language Model API>: Token counting failed:", errorMessage)
console.warn("Moo Code <Language Model API>: Token counting failed:", errorMessage)
// Log additional error details if available
if (error instanceof Error && error.stack) {
@ -317,7 +317,7 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
private async getClient(): Promise<vscode.LanguageModelChat> {
if (!this.client) {
console.debug("Roo Code <Language Model API>: Getting client with options:", {
console.debug("Moo Code <Language Model API>: Getting client with options:", {
vsCodeLmModelSelector: this.options.vsCodeLmModelSelector,
hasOptions: !!this.options,
selectorKeys: this.options.vsCodeLmModelSelector ? Object.keys(this.options.vsCodeLmModelSelector) : [],
@ -326,12 +326,12 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
try {
// Use default empty selector if none provided to get all available models
const selector = this.options?.vsCodeLmModelSelector || {}
console.debug("Roo Code <Language Model API>: Creating client with selector:", selector)
console.debug("Moo Code <Language Model API>: Creating client with selector:", selector)
this.client = await this.createClient(selector)
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error"
console.error("Roo Code <Language Model API>: Client creation failed:", message)
throw new Error(`Roo Code <Language Model API>: Failed to create client: ${message}`)
console.error("Moo Code <Language Model API>: Client creation failed:", message)
throw new Error(`Moo Code <Language Model API>: Failed to create client: ${message}`)
}
}
@ -395,7 +395,7 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
try {
// Create the response stream with required options
const requestOptions: vscode.LanguageModelChatRequestOptions = {
justification: `Roo Code would like to use '${client.name}' from '${client.vendor}', Click 'Allow' to proceed.`,
justification: `Moo Code would like to use '${client.name}' from '${client.vendor}', Click 'Allow' to proceed.`,
tools: convertToVsCodeLmTools(metadata?.tools ?? []),
}
@ -410,7 +410,7 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
if (chunk instanceof vscode.LanguageModelTextPart) {
// Validate text part value
if (typeof chunk.value !== "string") {
console.warn("Roo Code <Language Model API>: Invalid text part value received:", chunk.value)
console.warn("Moo Code <Language Model API>: Invalid text part value received:", chunk.value)
continue
}
@ -423,23 +423,23 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
try {
// Validate tool call parameters
if (!chunk.name || typeof chunk.name !== "string") {
console.warn("Roo Code <Language Model API>: Invalid tool name received:", chunk.name)
console.warn("Moo Code <Language Model API>: Invalid tool name received:", chunk.name)
continue
}
if (!chunk.callId || typeof chunk.callId !== "string") {
console.warn("Roo Code <Language Model API>: Invalid tool callId received:", chunk.callId)
console.warn("Moo Code <Language Model API>: Invalid tool callId received:", chunk.callId)
continue
}
// Ensure input is a valid object
if (!chunk.input || typeof chunk.input !== "object") {
console.warn("Roo Code <Language Model API>: Invalid tool input received:", chunk.input)
console.warn("Moo Code <Language Model API>: Invalid tool input received:", chunk.input)
continue
}
// Log tool call for debugging
console.debug("Roo Code <Language Model API>: Processing tool call:", {
console.debug("Moo Code <Language Model API>: Processing tool call:", {
name: chunk.name,
callId: chunk.callId,
inputSize: JSON.stringify(chunk.input).length,
@ -457,12 +457,12 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
}
}
} catch (error) {
console.error("Roo Code <Language Model API>: Failed to process tool call:", error)
console.error("Moo Code <Language Model API>: Failed to process tool call:", error)
// Continue processing other chunks even if one fails
continue
}
} else {
console.warn("Roo Code <Language Model API>: Unknown chunk type received:", chunk)
console.warn("Moo Code <Language Model API>: Unknown chunk type received:", chunk)
}
}
@ -479,11 +479,11 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
this.ensureCleanState()
if (error instanceof vscode.CancellationError) {
throw new Error("Roo Code <Language Model API>: Request cancelled by user")
throw new Error("Moo Code <Language Model API>: Request cancelled by user")
}
if (error instanceof Error) {
console.error("Roo Code <Language Model API>: Stream error details:", {
console.error("Moo Code <Language Model API>: Stream error details:", {
message: error.message,
stack: error.stack,
name: error.name,
@ -494,13 +494,13 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
} else if (typeof error === "object" && error !== null) {
// Handle error-like objects
const errorDetails = JSON.stringify(error, null, 2)
console.error("Roo Code <Language Model API>: Stream error object:", errorDetails)
throw new Error(`Roo Code <Language Model API>: Response stream error: ${errorDetails}`)
console.error("Moo Code <Language Model API>: Stream error object:", errorDetails)
throw new Error(`Moo Code <Language Model API>: Response stream error: ${errorDetails}`)
} else {
// Fallback for unknown error types
const errorMessage = String(error)
console.error("Roo Code <Language Model API>: Unknown stream error:", errorMessage)
throw new Error(`Roo Code <Language Model API>: Response stream error: ${errorMessage}`)
console.error("Moo Code <Language Model API>: Unknown stream error:", errorMessage)
throw new Error(`Moo Code <Language Model API>: Response stream error: ${errorMessage}`)
}
}
}
@ -520,7 +520,7 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
// Log any missing properties for debugging
for (const [prop, value] of Object.entries(requiredProps)) {
if (!value && value !== 0) {
console.warn(`Roo Code <Language Model API>: Client missing ${prop} property`)
console.warn(`Moo Code <Language Model API>: Client missing ${prop} property`)
}
}
@ -551,7 +551,7 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
? stringifyVsCodeLmModelSelector(this.options.vsCodeLmModelSelector)
: "vscode-lm"
console.debug("Roo Code <Language Model API>: No client available, using fallback model info")
console.debug("Moo Code <Language Model API>: No client available, using fallback model info")
return {
id: fallbackId,

View file

@ -23,7 +23,7 @@ function asObjectSafe(value: any): object {
return {}
} catch (error) {
console.warn("Roo Code <Language Model API>: Failed to parse object:", error)
console.warn("Moo Code <Language Model API>: Failed to parse object:", error)
return {}
}
}
@ -184,7 +184,7 @@ export function extractTextCountFromMessage(message: vscode.LanguageModelChatMes
try {
text += JSON.stringify(item.input)
} catch (error) {
console.error("Roo Code <Language Model API>: Failed to stringify tool call input:", error)
console.error("Moo Code <Language Model API>: Failed to stringify tool call input:", error)
}
}
}

View file

@ -191,7 +191,7 @@ export class GenerateImageTool extends BaseTool<"generate_image"> {
let result
if (modelProvider === "roo") {
// Use Roo Code Cloud provider (supports both chat completions and images API)
// Use Moo Code Cloud provider (supports both chat completions and images API)
const rooHandler = new RooHandler({} as any)
result = await rooHandler.generateImage(prompt, selectedModel, inputImageData, apiMethod)
} else {

View file

@ -1265,8 +1265,8 @@ export class ClineProvider
`style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`,
`img-src ${webview.cspSource} https://storage.googleapis.com https://img.clerk.com data:`,
`media-src ${webview.cspSource}`,
`script-src 'unsafe-eval' ${webview.cspSource} https://* https://*.posthog.com http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
`connect-src ${webview.cspSource} ${openRouterDomain} https://* https://*.posthog.com ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
`script-src 'unsafe-eval' ${webview.cspSource} https://* http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
`connect-src ${webview.cspSource} ${openRouterDomain} https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
]
return /*html*/ `
@ -1283,7 +1283,7 @@ export class ClineProvider
window.AUDIO_BASE_URI = "${audioUri}"
window.MATERIAL_ICONS_BASE_URI = "${materialIconsUri}"
</script>
<title>Roo Code</title>
<title>Moo Code</title>
</head>
<body>
<div id="root"></div>
@ -1362,7 +1362,7 @@ export class ClineProvider
window.AUDIO_BASE_URI = "${audioUri}"
window.MATERIAL_ICONS_BASE_URI = "${materialIconsUri}"
</script>
<title>Roo Code</title>
<title>Moo Code</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@ -2232,7 +2232,7 @@ export class ClineProvider
// Ignore this error.
}
const telemetryKey = process.env.POSTHOG_API_KEY
const telemetryKey = "" // Telemetry disabled — PostHog key injection removed
const machineId = vscode.env.machineId
const mergedAllowedCommands = this.mergeAllowedCommands(allowedCommands)
const mergedDeniedCommands = this.mergeDeniedCommands(deniedCommands)

View file

@ -65,7 +65,7 @@ export async function generateErrorDiagnostics(params: GenerateDiagnosticsParams
// Prepend human-readable guidance comments before the JSON payload
const headerComment =
"// Please share this file with Roo Code Support (support@roocode.com) to diagnose the issue faster\n" +
"// Please share this file with Moo Code Support (support@moo-code.dev) to diagnose the issue faster\n" +
"// Just make sure you're OK sharing the contents of the conversation below.\n\n"
const jsonContent = JSON.stringify(diagnostics, null, 2)
const fullContent = headerComment + jsonContent

View file

@ -994,7 +994,7 @@ export const webviewMessageHandler = async (
key: "roo",
options: {
provider: "roo",
baseUrl: process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy",
baseUrl: process.env.ROO_CODE_PROVIDER_URL ?? "",
apiKey: CloudService.hasInstance()
? CloudService.instance.authService?.getSessionToken()
: undefined,
@ -1134,7 +1134,7 @@ export const webviewMessageHandler = async (
try {
const rooOptions = {
provider: "roo" as const,
baseUrl: process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy",
baseUrl: process.env.ROO_CODE_PROVIDER_URL ?? "",
apiKey: CloudService.hasInstance()
? CloudService.instance.authService?.getSessionToken()
: undefined,

View file

@ -100,7 +100,7 @@ async function checkWorktreeAutoOpen(
// Open the Roo Code sidebar with a slight delay to ensure UI is ready
setTimeout(async () => {
try {
await vscode.commands.executeCommand("roo-cline.plusButtonClicked")
await vscode.commands.executeCommand("moo-code.plusButtonClicked")
} catch (error) {
outputChannel.appendLine(
`[Worktree] Error auto-opening sidebar: ${error instanceof Error ? error.message : String(error)}`,
@ -137,11 +137,12 @@ export async function activate(context: vscode.ExtensionContext) {
// Initialize telemetry service.
const telemetryService = TelemetryService.createInstance()
try {
telemetryService.register(new PostHogTelemetryClient())
} catch (error) {
console.warn("Failed to register PostHogTelemetryClient:", error)
}
// Telemetry disabled — PostHogTelemetryClient registration removed
// try {
// telemetryService.register(new PostHogTelemetryClient())
// } catch (error) {
// console.warn("Failed to register PostHogTelemetryClient:", error)
// }
// Create logger for cloud services.
const cloudLogger = createDualLogger(createOutputChannelLogger(outputChannel))
@ -210,7 +211,7 @@ export async function activate(context: vscode.ExtensionContext) {
: undefined
await refreshModels({
provider: "roo",
baseUrl: process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy",
baseUrl: process.env.ROO_CODE_PROVIDER_URL ?? "",
apiKey: sessionToken,
})
} else {
@ -268,15 +269,16 @@ export async function activate(context: vscode.ExtensionContext) {
"user-info": userInfoHandler,
})
try {
if (cloudService.telemetryClient) {
TelemetryService.instance.register(cloudService.telemetryClient)
}
} catch (error) {
outputChannel.appendLine(
`[CloudService] Failed to register TelemetryClient: ${error instanceof Error ? error.message : String(error)}`,
)
}
// Telemetry disabled — cloud telemetry registration removed
// try {
// if (cloudService.telemetryClient) {
// TelemetryService.instance.register(cloudService.telemetryClient)
// }
// } catch (error) {
// outputChannel.appendLine(
// `[CloudService] Failed to register TelemetryClient: ${error instanceof Error ? error.message : String(error)}`,
// )
// }
// Add to subscriptions for proper cleanup on deactivate.
context.subscriptions.push(cloudService)

View file

@ -139,7 +139,7 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
try {
const models = await getModels({
provider: "roo" as const,
baseUrl: process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy",
baseUrl: process.env.ROO_CODE_PROVIDER_URL ?? "",
apiKey: CloudService.hasInstance()
? CloudService.instance.authService?.getSessionToken()
: undefined,

View file

@ -17,7 +17,7 @@ export class Terminal extends BaseTerminal {
const env = Terminal.getEnv()
const iconPath = new vscode.ThemeIcon("rocket")
this.terminal = terminal ?? vscode.window.createTerminal({ cwd, name: "Roo Code", iconPath, env })
this.terminal = terminal ?? vscode.window.createTerminal({ cwd, name: "Moo Code", iconPath, env })
if (Terminal.getTerminalZdotdir()) {
ShellIntegrationManager.terminalTmpDirs.set(id, env.ZDOTDIR)

View file

@ -1,8 +1,8 @@
{
"name": "roo-cline",
"name": "moo-code",
"displayName": "%extension.displayName%",
"description": "%extension.description%",
"publisher": "RooVeterinaryInc",
"publisher": "moo-code",
"version": "3.52.1",
"icon": "assets/icons/icon.png",
"galleryBanner": {
@ -14,13 +14,13 @@
"node": "20.19.2"
},
"author": {
"name": "Roo Code"
"name": "Moo Code"
},
"repository": {
"type": "git",
"url": "https://github.com/RooCodeInc/Roo-Code"
"url": "https://github.com/moo-code/Moo-Code"
},
"homepage": "https://roocode.com",
"homepage": "https://github.com/moo-code/Moo-Code",
"categories": [
"AI",
"Chat",
@ -42,8 +42,8 @@
"sonnet",
"ai",
"llama",
"roo code",
"roocode"
"moo code",
"moocode"
],
"activationEvents": [
"onLanguage",
@ -54,119 +54,119 @@
"viewsContainers": {
"activitybar": [
{
"id": "roo-cline-ActivityBar",
"id": "moo-code-ActivityBar",
"title": "%views.activitybar.title%",
"icon": "assets/icons/icon.svg"
}
]
},
"views": {
"roo-cline-ActivityBar": [
"moo-code-ActivityBar": [
{
"type": "webview",
"id": "roo-cline.SidebarProvider",
"id": "moo-code.SidebarProvider",
"name": "%views.sidebar.name%"
}
]
},
"commands": [
{
"command": "roo-cline.plusButtonClicked",
"command": "moo-code.plusButtonClicked",
"title": "%command.newTask.title%",
"icon": "$(edit)"
},
{
"command": "roo-cline.historyButtonClicked",
"command": "moo-code.historyButtonClicked",
"title": "%command.history.title%",
"icon": "$(history)"
},
{
"command": "roo-cline.marketplaceButtonClicked",
"command": "moo-code.marketplaceButtonClicked",
"title": "%command.marketplace.title%",
"icon": "$(extensions)"
},
{
"command": "roo-cline.popoutButtonClicked",
"command": "moo-code.popoutButtonClicked",
"title": "%command.openInEditor.title%",
"icon": "$(link-external)"
},
{
"command": "roo-cline.cloudButtonClicked",
"command": "moo-code.cloudButtonClicked",
"title": "%command.cloud.title%",
"icon": "$(cloud)"
},
{
"command": "roo-cline.settingsButtonClicked",
"command": "moo-code.settingsButtonClicked",
"title": "%command.settings.title%",
"icon": "$(settings-gear)"
},
{
"command": "roo-cline.openInNewTab",
"command": "moo-code.openInNewTab",
"title": "%command.openInNewTab.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.explainCode",
"command": "moo-code.explainCode",
"title": "%command.explainCode.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.fixCode",
"command": "moo-code.fixCode",
"title": "%command.fixCode.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.improveCode",
"command": "moo-code.improveCode",
"title": "%command.improveCode.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.addToContext",
"command": "moo-code.addToContext",
"title": "%command.addToContext.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.newTask",
"command": "moo-code.newTask",
"title": "%command.newTask.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.terminalAddToContext",
"command": "moo-code.terminalAddToContext",
"title": "%command.terminal.addToContext.title%",
"category": "Terminal"
},
{
"command": "roo-cline.terminalFixCommand",
"command": "moo-code.terminalFixCommand",
"title": "%command.terminal.fixCommand.title%",
"category": "Terminal"
},
{
"command": "roo-cline.terminalExplainCommand",
"command": "moo-code.terminalExplainCommand",
"title": "%command.terminal.explainCommand.title%",
"category": "Terminal"
},
{
"command": "roo-cline.setCustomStoragePath",
"command": "moo-code.setCustomStoragePath",
"title": "%command.setCustomStoragePath.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.importSettings",
"command": "moo-code.importSettings",
"title": "%command.importSettings.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.focusInput",
"command": "moo-code.focusInput",
"title": "%command.focusInput.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.acceptInput",
"command": "moo-code.acceptInput",
"title": "%command.acceptInput.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.toggleAutoApprove",
"command": "moo-code.toggleAutoApprove",
"title": "%command.toggleAutoApprove.title%",
"category": "%configuration.title%"
}
@ -174,112 +174,112 @@
"menus": {
"editor/context": [
{
"submenu": "roo-cline.contextMenu",
"submenu": "moo-code.contextMenu",
"group": "1"
}
],
"roo-cline.contextMenu": [
"moo-code.contextMenu": [
{
"command": "roo-cline.addToContext",
"command": "moo-code.addToContext",
"group": "1_actions@1"
},
{
"command": "roo-cline.explainCode",
"command": "moo-code.explainCode",
"group": "1_actions@2"
},
{
"command": "roo-cline.improveCode",
"command": "moo-code.improveCode",
"group": "1_actions@3"
}
],
"terminal/context": [
{
"submenu": "roo-cline.terminalMenu",
"submenu": "moo-code.terminalMenu",
"group": "2"
}
],
"roo-cline.terminalMenu": [
"moo-code.terminalMenu": [
{
"command": "roo-cline.terminalAddToContext",
"command": "moo-code.terminalAddToContext",
"group": "1_actions@1"
},
{
"command": "roo-cline.terminalFixCommand",
"command": "moo-code.terminalFixCommand",
"group": "1_actions@2"
},
{
"command": "roo-cline.terminalExplainCommand",
"command": "moo-code.terminalExplainCommand",
"group": "1_actions@3"
}
],
"view/title": [
{
"command": "roo-cline.plusButtonClicked",
"command": "moo-code.plusButtonClicked",
"group": "navigation@1",
"when": "view == roo-cline.SidebarProvider"
"when": "view == moo-code.SidebarProvider"
},
{
"command": "roo-cline.settingsButtonClicked",
"command": "moo-code.settingsButtonClicked",
"group": "navigation@2",
"when": "view == roo-cline.SidebarProvider"
"when": "view == moo-code.SidebarProvider"
},
{
"command": "roo-cline.cloudButtonClicked",
"command": "moo-code.cloudButtonClicked",
"group": "navigation@3",
"when": "view == roo-cline.SidebarProvider"
"when": "view == moo-code.SidebarProvider"
},
{
"command": "roo-cline.marketplaceButtonClicked",
"command": "moo-code.marketplaceButtonClicked",
"group": "navigation@4",
"when": "view == roo-cline.SidebarProvider"
"when": "view == moo-code.SidebarProvider"
},
{
"command": "roo-cline.historyButtonClicked",
"command": "moo-code.historyButtonClicked",
"group": "overflow@1",
"when": "view == roo-cline.SidebarProvider"
"when": "view == moo-code.SidebarProvider"
},
{
"command": "roo-cline.popoutButtonClicked",
"command": "moo-code.popoutButtonClicked",
"group": "overflow@2",
"when": "view == roo-cline.SidebarProvider"
"when": "view == moo-code.SidebarProvider"
}
],
"editor/title": [
{
"command": "roo-cline.plusButtonClicked",
"command": "moo-code.plusButtonClicked",
"group": "navigation@1",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
"when": "activeWebviewPanelId == moo-code.TabPanelProvider"
},
{
"command": "roo-cline.settingsButtonClicked",
"command": "moo-code.settingsButtonClicked",
"group": "navigation@2",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
"when": "activeWebviewPanelId == moo-code.TabPanelProvider"
},
{
"command": "roo-cline.cloudButtonClicked",
"command": "moo-code.cloudButtonClicked",
"group": "navigation@3",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
"when": "activeWebviewPanelId == moo-code.TabPanelProvider"
},
{
"command": "roo-cline.marketplaceButtonClicked",
"command": "moo-code.marketplaceButtonClicked",
"group": "navigation@4",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
"when": "activeWebviewPanelId == moo-code.TabPanelProvider"
},
{
"command": "roo-cline.historyButtonClicked",
"command": "moo-code.historyButtonClicked",
"group": "overflow@1",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
"when": "activeWebviewPanelId == moo-code.TabPanelProvider"
},
{
"command": "roo-cline.popoutButtonClicked",
"command": "moo-code.popoutButtonClicked",
"group": "overflow@2",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
"when": "activeWebviewPanelId == moo-code.TabPanelProvider"
}
]
},
"keybindings": [
{
"command": "roo-cline.addToContext",
"command": "moo-code.addToContext",
"key": "cmd+k cmd+a",
"mac": "cmd+k cmd+a",
"win": "ctrl+k ctrl+a",
@ -287,7 +287,7 @@
"when": "editorTextFocus && editorHasSelection"
},
{
"command": "roo-cline.toggleAutoApprove",
"command": "moo-code.toggleAutoApprove",
"key": "cmd+alt+a",
"mac": "cmd+alt+a",
"win": "ctrl+alt+a",
@ -296,18 +296,18 @@
],
"submenus": [
{
"id": "roo-cline.contextMenu",
"id": "moo-code.contextMenu",
"label": "%views.contextMenu.label%"
},
{
"id": "roo-cline.terminalMenu",
"id": "moo-code.terminalMenu",
"label": "%views.terminalMenu.label%"
}
],
"configuration": {
"title": "%configuration.title%",
"properties": {
"roo-cline.allowedCommands": {
"moo-code.allowedCommands": {
"type": "array",
"items": {
"type": "string"
@ -319,7 +319,7 @@
],
"description": "%commands.allowedCommands.description%"
},
"roo-cline.deniedCommands": {
"moo-code.deniedCommands": {
"type": "array",
"items": {
"type": "string"
@ -327,14 +327,14 @@
"default": [],
"description": "%commands.deniedCommands.description%"
},
"roo-cline.commandExecutionTimeout": {
"moo-code.commandExecutionTimeout": {
"type": "number",
"default": 0,
"minimum": 0,
"maximum": 600,
"description": "%commands.commandExecutionTimeout.description%"
},
"roo-cline.commandTimeoutAllowlist": {
"moo-code.commandTimeoutAllowlist": {
"type": "array",
"items": {
"type": "string"
@ -342,12 +342,12 @@
"default": [],
"description": "%commands.commandTimeoutAllowlist.description%"
},
"roo-cline.preventCompletionWithOpenTodos": {
"moo-code.preventCompletionWithOpenTodos": {
"type": "boolean",
"default": false,
"description": "%commands.preventCompletionWithOpenTodos.description%"
},
"roo-cline.vsCodeLmModelSelector": {
"moo-code.vsCodeLmModelSelector": {
"type": "object",
"properties": {
"vendor": {
@ -361,70 +361,70 @@
},
"description": "%settings.vsCodeLmModelSelector.description%"
},
"roo-cline.customStoragePath": {
"moo-code.customStoragePath": {
"type": "string",
"default": "",
"description": "%settings.customStoragePath.description%"
},
"roo-cline.enableCodeActions": {
"moo-code.enableCodeActions": {
"type": "boolean",
"default": true,
"description": "%settings.enableCodeActions.description%"
},
"roo-cline.autoImportSettingsPath": {
"moo-code.autoImportSettingsPath": {
"type": "string",
"default": "",
"description": "%settings.autoImportSettingsPath.description%"
},
"roo-cline.maximumIndexedFilesForFileSearch": {
"moo-code.maximumIndexedFilesForFileSearch": {
"type": "number",
"default": 10000,
"minimum": 5000,
"maximum": 500000,
"description": "%settings.maximumIndexedFilesForFileSearch.description%"
},
"roo-cline.useAgentRules": {
"moo-code.useAgentRules": {
"type": "boolean",
"default": true,
"description": "%settings.useAgentRules.description%"
},
"roo-cline.apiRequestTimeout": {
"moo-code.apiRequestTimeout": {
"type": "number",
"default": 600,
"minimum": 0,
"maximum": 3600,
"description": "%settings.apiRequestTimeout.description%"
},
"roo-cline.newTaskRequireTodos": {
"moo-code.newTaskRequireTodos": {
"type": "boolean",
"default": false,
"description": "%settings.newTaskRequireTodos.description%"
},
"roo-cline.codeIndex.embeddingBatchSize": {
"moo-code.codeIndex.embeddingBatchSize": {
"type": "number",
"default": 60,
"minimum": 1,
"maximum": 200,
"description": "%settings.codeIndex.embeddingBatchSize.description%"
},
"roo-cline.debug": {
"moo-code.debug": {
"type": "boolean",
"default": false,
"description": "%settings.debug.description%"
},
"roo-cline.debugProxy.enabled": {
"moo-code.debugProxy.enabled": {
"type": "boolean",
"default": false,
"description": "%settings.debugProxy.enabled.description%",
"markdownDescription": "%settings.debugProxy.enabled.description%"
},
"roo-cline.debugProxy.serverUrl": {
"moo-code.debugProxy.serverUrl": {
"type": "string",
"default": "http://127.0.0.1:8888",
"description": "%settings.debugProxy.serverUrl.description%",
"markdownDescription": "%settings.debugProxy.serverUrl.description%"
},
"roo-cline.debugProxy.tlsInsecure": {
"moo-code.debugProxy.tlsInsecure": {
"type": "boolean",
"default": false,
"description": "%settings.debugProxy.tlsInsecure.description%",

View file

@ -1,10 +1,10 @@
{
"extension.displayName": "Roo Code",
"extension.displayName": "Moo Code",
"extension.description": "A whole dev team of AI agents in your editor.",
"views.contextMenu.label": "Roo Code",
"views.terminalMenu.label": "Roo Code",
"views.activitybar.title": "Roo Code",
"views.sidebar.name": "Roo Code",
"views.contextMenu.label": "Moo Code",
"views.terminalMenu.label": "Moo Code",
"views.activitybar.title": "Moo Code",
"views.sidebar.name": "Moo Code",
"command.newTask.title": "New Task",
"command.history.title": "Task History",
"command.marketplace.title": "Marketplace",
@ -25,7 +25,7 @@
"command.terminal.explainCommand.title": "Explain This Command",
"command.acceptInput.title": "Accept Input/Suggestion",
"command.toggleAutoApprove.title": "Toggle Auto-Approve",
"configuration.title": "Roo Code",
"configuration.title": "Moo Code",
"commands.allowedCommands.description": "Commands that can be auto-executed when 'Always approve execute operations' is enabled",
"commands.deniedCommands.description": "Command prefixes that will be automatically denied without asking for approval. In case of conflicts with allowed commands, the longest prefix match takes precedence. Add * to deny all commands.",
"commands.commandExecutionTimeout.description": "Maximum time in seconds to wait for command execution to complete before timing out (0 = no timeout, 1-600s, default: 0s)",
@ -35,8 +35,8 @@
"settings.vsCodeLmModelSelector.vendor.description": "The vendor of the language model (e.g. copilot)",
"settings.vsCodeLmModelSelector.family.description": "The family of the language model (e.g. gpt-4)",
"settings.customStoragePath.description": "Custom storage path. Leave empty to use the default location. Supports absolute paths (e.g. 'D:\\RooCodeStorage')",
"settings.enableCodeActions.description": "Enable Roo Code quick fixes",
"settings.autoImportSettingsPath.description": "Path to a RooCode configuration file to automatically import on extension startup. Supports absolute paths and paths relative to the home directory (e.g. '~/Documents/roo-code-settings.json'). Leave empty to disable auto-import.",
"settings.enableCodeActions.description": "Enable Moo Code quick fixes",
"settings.autoImportSettingsPath.description": "Path to a MooCode configuration file to automatically import on extension startup. Supports absolute paths and paths relative to the home directory (e.g. '~/Documents/moo-code-settings.json'). Leave empty to disable auto-import.",
"settings.maximumIndexedFilesForFileSearch.description": "Maximum number of files to index for the @ file search feature. Higher values provide better search results in large projects but may use more memory. Default: 10,000.",
"settings.useAgentRules.description": "Enable loading of AGENTS.md files for agent-specific rules (see https://agent-rules.org/)",
"settings.apiRequestTimeout.description": "Maximum time in seconds to wait for API responses (0 = no timeout, 1-3600s, default: 600s). Higher values are recommended for local providers like LM Studio and Ollama that may need more processing time.",

View file

@ -176,7 +176,7 @@ export abstract class ShadowCheckpointService extends EventEmitter {
await git.init({ "--template": "" })
await git.addConfig("core.worktree", this.workspaceDir) // Sets the working tree to the current workspace.
await git.addConfig("commit.gpgSign", "false") // Disable commit signing for shadow repo.
await git.addConfig("user.name", "Roo Code")
await git.addConfig("user.name", "Moo Code")
await git.addConfig("user.email", "noreply@example.com")
await this.writeExcludeFile()
await this.stageAll(git)

View file

@ -41,7 +41,7 @@ export class BedrockEmbedder implements IEmbedder {
const credentials = this.profile ? fromIni({ profile: this.profile }) : fromNodeProviderChain()
this.bedrockClient = new BedrockRuntimeClient({
userAgentAppId: `RooCode#${Package.version}`,
userAgentAppId: `MooCode#${Package.version}`,
region: this.region,
credentials,
})

View file

@ -76,8 +76,8 @@ export class OpenRouterEmbedder implements IEmbedder {
baseURL: this.baseUrl,
apiKey: apiKey,
defaultHeaders: {
"HTTP-Referer": "https://github.com/RooCodeInc/Roo-Code",
"X-Title": "Roo Code",
"HTTP-Referer": "https://github.com/moo-code/Moo-Code",
"X-Title": "Moo Code",
},
})
} catch (error) {

View file

@ -1,117 +1,37 @@
import axios from "axios"
import * as yaml from "yaml"
import { z } from "zod"
import {
type MarketplaceItem,
type MarketplaceItemType,
modeMarketplaceItemSchema,
mcpMarketplaceItemSchema,
} from "@roo-code/types"
import { getRooCodeApiUrl } from "@roo-code/cloud"
const modeMarketplaceResponse = z.object({
items: z.array(modeMarketplaceItemSchema),
})
const mcpMarketplaceResponse = z.object({
items: z.array(mcpMarketplaceItemSchema),
})
import type { MarketplaceItem, MarketplaceItemType } from "@roo-code/types"
/**
* RemoteConfigLoader — cloud features disabled.
*
* All methods that would fetch from app.roocode.com return empty arrays.
* No HTTP requests are made to upstream servers.
*/
export class RemoteConfigLoader {
private apiBaseUrl: string
private cache: Map<string, { data: MarketplaceItem[]; timestamp: number }> = new Map()
private cacheDuration = 5 * 60 * 1000 // 5 minutes
constructor() {
this.apiBaseUrl = getRooCodeApiUrl()
// Cloud features disabled — no API base URL needed
}
async loadAllItems(hideMarketplaceMcps = false): Promise<MarketplaceItem[]> {
const items: MarketplaceItem[] = []
const modesPromise = this.fetchModes()
const mcpsPromise = hideMarketplaceMcps ? Promise.resolve([]) : this.fetchMcps()
const [modes, mcps] = await Promise.all([modesPromise, mcpsPromise])
items.push(...modes, ...mcps)
return items
async loadAllItems(_hideMarketplaceMcps = false): Promise<MarketplaceItem[]> {
// Cloud features disabled — return empty array, no HTTP calls
return []
}
private async fetchModes(): Promise<MarketplaceItem[]> {
const cacheKey = "modes"
const cached = this.getFromCache(cacheKey)
if (cached) {
return cached
}
const data = await this.fetchWithRetry<string>(`${this.apiBaseUrl}/api/marketplace/modes`)
const yamlData = yaml.parse(data)
const validated = modeMarketplaceResponse.parse(yamlData)
const items: MarketplaceItem[] = validated.items.map((item) => ({
type: "mode" as const,
...item,
}))
this.setCache(cacheKey, items)
return items
// Cloud features disabled — return empty array
return []
}
private async fetchMcps(): Promise<MarketplaceItem[]> {
const cacheKey = "mcps"
const cached = this.getFromCache(cacheKey)
if (cached) {
return cached
}
const data = await this.fetchWithRetry<string>(`${this.apiBaseUrl}/api/marketplace/mcps`)
const yamlData = yaml.parse(data)
const validated = mcpMarketplaceResponse.parse(yamlData)
const items: MarketplaceItem[] = validated.items.map((item) => ({
type: "mcp" as const,
...item,
}))
this.setCache(cacheKey, items)
return items
}
private async fetchWithRetry<T>(url: string, maxRetries = 3): Promise<T> {
let lastError: Error
for (let i = 0; i < maxRetries; i++) {
try {
const response = await axios.get(url, {
timeout: 10000, // 10 second timeout
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
})
return response.data as T
} catch (error) {
lastError = error as Error
if (i < maxRetries - 1) {
// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, i) * 1000
await new Promise((resolve) => setTimeout(resolve, delay))
}
}
}
throw lastError!
// Cloud features disabled — return empty array
return []
}
async getItem(id: string, type: MarketplaceItemType): Promise<MarketplaceItem | null> {
const items = await this.loadAllItems()
return items.find((item) => item.id === id && item.type === type) || null
// Cloud features disabled — no items available
return null
}
private getFromCache(key: string): MarketplaceItem[] | null {

View file

@ -687,7 +687,7 @@ export class McpHub {
try {
const client = new Client(
{
name: "Roo Code",
name: "Moo Code",
version: this.providerRef.deref()?.context.extension?.packageJSON?.version ?? "1.0.0",
},
{

View file

@ -147,19 +147,19 @@ export class MdmService {
switch (platform) {
case "win32": {
// Windows: %ProgramData%\RooCode\mdm.json or mdm.dev.json
// Windows: %ProgramData%\MooCode\mdm.json or mdm.dev.json
const programData = process.env.PROGRAMDATA || "C:\\ProgramData"
return path.join(programData, "RooCode", configFileName)
return path.join(programData, "MooCode", configFileName)
}
case "darwin":
// macOS: /Library/Application Support/RooCode/mdm.json or mdm.dev.json
return `/Library/Application Support/RooCode/${configFileName}`
// macOS: /Library/Application Support/MooCode/mdm.json or mdm.dev.json
return `/Library/Application Support/MooCode/${configFileName}`
case "linux":
default:
// Linux: /etc/roo-code/mdm.json or mdm.dev.json
return `/etc/roo-code/${configFileName}`
// Linux: /etc/moo-code/mdm.json or mdm.dev.json
return `/etc/moo-code/${configFileName}`
}
}

View file

@ -1,29 +1,11 @@
import posthog from "posthog-js"
import type { TelemetrySetting } from "@roo-code/types"
class TelemetryClient {
private static instance: TelemetryClient
private static telemetryEnabled: boolean = false
public updateTelemetryState(telemetrySetting: TelemetrySetting, apiKey?: string, distinctId?: string) {
posthog.reset()
if (telemetrySetting !== "disabled" && apiKey && distinctId) {
TelemetryClient.telemetryEnabled = true
posthog.init(apiKey, {
api_host: "https://ph.roocode.com",
ui_host: "https://us.posthog.com",
persistence: "localStorage",
loaded: () => posthog.identify(distinctId),
capture_pageview: false,
capture_pageleave: false,
autocapture: false,
})
} else {
TelemetryClient.telemetryEnabled = false
}
public updateTelemetryState(_telemetrySetting: TelemetrySetting, _apiKey?: string, _distinctId?: string) {
// No-op: telemetry disabled
}
public static getInstance(): TelemetryClient {
@ -34,14 +16,8 @@ class TelemetryClient {
return TelemetryClient.instance
}
public capture(eventName: string, properties?: Record<string, any>) {
if (TelemetryClient.telemetryEnabled) {
try {
posthog.capture(eventName, properties)
} catch (_error) {
// Silently fail if there's an error capturing an event.
}
}
public capture(_eventName: string, _properties?: Record<string, any>) {
// No-op: telemetry disabled
}
}

View file

@ -9,6 +9,6 @@ export function buildDocLink(path: string, campaign: string): string {
// Remove any leading slash from path
const cleanPath = path.replace(/^\//, "")
const [basePath, hash] = cleanPath.split("#")
const baseUrl = `https://docs.roocode.com/${basePath}?utm_source=extension&utm_medium=ide&utm_campaign=${encodeURIComponent(campaign)}`
const baseUrl = `https://docs.moo-code.dev/${basePath}?utm_source=extension&utm_medium=ide&utm_campaign=${encodeURIComponent(campaign)}`
return hash ? `${baseUrl}#${hash}` : baseUrl
}