mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-25 05:53:57 +00:00
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:
parent
c4547d25c5
commit
2fc7798424
57 changed files with 449 additions and 6220 deletions
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
|
@ -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
|
||||
|
|
|
|||
92
.github/workflows/marketplace-publish.yml
vendored
92
.github/workflows/marketplace-publish.yml
vendored
|
|
@ -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}"
|
||||
52
.github/workflows/nightly-publish.yml
vendored
52
.github/workflows/nightly-publish.yml
vendored
|
|
@ -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)"
|
||||
67
.github/workflows/update-contributors.yml
vendored
67
.github/workflows/update-contributors.yml
vendored
|
|
@ -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
|
||||
59
.github/workflows/website-deploy.yml
vendored
59
.github/workflows/website-deploy.yml
vendored
|
|
@ -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 }}
|
||||
102
.github/workflows/website-preview.yml
vendored
102
.github/workflows/website-preview.yml
vendored
|
|
@ -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
1
.gitignore
vendored
|
|
@ -55,3 +55,4 @@ qdrant_storage/
|
|||
plans/
|
||||
|
||||
roo-cli-*.tar.gz*
|
||||
`nnul
|
||||
3435
CHANGELOG.md
3435
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
|
|
|||
144
CONTRIBUTING.md
144
CONTRIBUTING.md
|
|
@ -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.
|
||||
|
|
|
|||
41
PRIVACY.md
41
PRIVACY.md
|
|
@ -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
185
README.md
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
59
progress.txt
59
progress.txt
|
|
@ -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.
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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}`,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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."}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
172
src/package.json
172
src/package.json
|
|
@ -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%",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue